qmlfrontend/players_model.cpp
author Wuzzy <Wuzzy2@mail.ru>
Sat, 06 Jun 2020 15:40:51 +0200
changeset 15597 6e72bd61002e
parent 15047 773beead236f
child 15891 d52f5d8e75e6
permissions -rw-r--r--
Disable gfMoreWind for land objects on turn end only after a fixed-time delay 15s sounds much, but it's the average amount for gfMineStrike mines to settle naturally. And it would be very confusing to see falling mines suddenly not caring about gfMoreWind for no apparent reason. Note this whole thing is a giant hack anyway, to prevent a turn being blocked by infinitely bouncing mines. The better solution would be to help gfMoreWind-affected land objects settle naturally more reliably even under extreme wind. But this commit is "good enough" for now. If you don't like the delay, you can always tweak the constant.

#include <QDebug>
#include <QFile>
#include <QModelIndex>
#include <QModelIndexList>
#include <QPainter>
#include <QTextStream>

#include "players_model.h"

PlayersListModel::PlayersListModel(QObject *parent)
    : QAbstractListModel(parent) {
  m_fontInRoom = QFont();
  m_fontInRoom.setItalic(true);
}

int PlayersListModel::rowCount(const QModelIndex &parent) const {
  if (parent.isValid())
    return 0;
  else
    return m_data.size();
}

QVariant PlayersListModel::data(const QModelIndex &index, int role) const {
  if (!index.isValid() || index.row() < 0 || index.row() >= rowCount() ||
      index.column() != 0)
    return QVariant(QVariant::Invalid);

  return m_data.at(index.row()).value(role);
}

bool PlayersListModel::setData(const QModelIndex &index, const QVariant &value,
                               int role) {
  if (!index.isValid() || index.row() < 0 || index.row() >= rowCount() ||
      index.column() != 0)
    return false;

  m_data[index.row()].insert(role, value);

  emit dataChanged(index, index);

  return true;
}

bool PlayersListModel::insertRows(int row, int count,
                                  const QModelIndex &parent) {
  if (parent.isValid() || row > rowCount() || row < 0 || count < 1)
    return false;

  beginInsertRows(parent, row, row + count - 1);

  for (int i = 0; i < count; ++i) m_data.insert(row, DataEntry());

  endInsertRows();

  return true;
}

bool PlayersListModel::removeRows(int row, int count,
                                  const QModelIndex &parent) {
  if (parent.isValid() || row + count > rowCount() || row < 0 || count < 1)
    return false;

  beginRemoveRows(parent, row, row + count - 1);

  for (int i = 0; i < count; ++i) m_data.removeAt(row);

  endRemoveRows();

  return true;
}

QModelIndex PlayersListModel::nicknameIndex(const QString &nickname) {
  QModelIndexList mil =
      match(index(0), Qt::DisplayRole, nickname, 1, Qt::MatchExactly);

  if (mil.size() > 0)
    return mil[0];
  else
    return QModelIndex();
}

void PlayersListModel::addPlayer(const QString &nickname, bool notify) {
  insertRow(rowCount());

  QModelIndex mi = index(rowCount() - 1);
  setData(mi, nickname);

  checkFriendIgnore(mi);

  emit nickAddedLobby(nickname, notify);
}

void PlayersListModel::removePlayer(const QString &nickname,
                                    const QString &msg) {
  if (msg.isEmpty())
    emit nickRemovedLobby(nickname, QString());
  else
    emit nickRemovedLobby(nickname, msg);

  QModelIndex mi = nicknameIndex(nickname);

  if (mi.isValid()) removeRow(mi.row());
}

void PlayersListModel::playerJoinedRoom(const QString &nickname, bool notify) {
  QModelIndex mi = nicknameIndex(nickname);

  if (mi.isValid()) {
    setData(mi, true, RoomFilterRole);
    updateIcon(mi);
    updateSortData(mi);
  }

  emit nickAdded(nickname, notify);
}

void PlayersListModel::playerLeftRoom(const QString &nickname) {
  emit nickRemoved(nickname);

  QModelIndex mi = nicknameIndex(nickname);

  if (mi.isValid()) {
    setData(mi, false, RoomFilterRole);
    setData(mi, false, RoomAdmin);
    setData(mi, false, Ready);
    setData(mi, false, InGame);
    updateIcon(mi);
  }
}

void PlayersListModel::setFlag(const QString &nickname, StateFlag flagType,
                               bool isSet) {
  if (flagType == Friend) {
    if (isSet)
      m_friendsSet.insert(nickname.toLower());
    else
      m_friendsSet.remove(nickname.toLower());

    // FIXME: set proper file name
    // saveSet(m_friendsSet, "friends");
  } else if (flagType == Ignore) {
    if (isSet)
      m_ignoredSet.insert(nickname.toLower());
    else
      m_ignoredSet.remove(nickname.toLower());

    // FIXME: set proper file name
    // saveSet(m_ignoredSet, "ignore");
  }

  QModelIndex mi = nicknameIndex(nickname);

  if (mi.isValid()) {
    setData(mi, isSet, flagType);

    if (flagType == Friend || flagType == ServerAdmin || flagType == Ignore ||
        flagType == RoomAdmin)
      updateSortData(mi);

    updateIcon(mi);
  }
}

bool PlayersListModel::isFlagSet(const QString &nickname, StateFlag flagType) {
  QModelIndex mi = nicknameIndex(nickname);

  if (mi.isValid())
    return mi.data(flagType).toBool();
  else if (flagType == Friend)
    return isFriend(nickname);
  else if (flagType == Ignore)
    return isIgnored(nickname);
  else
    return false;
}

void PlayersListModel::resetRoomFlags() {
  for (int i = rowCount() - 1; i >= 0; --i) {
    QModelIndex mi = index(i);

    if (mi.data(RoomFilterRole).toBool()) {
      setData(mi, false, RoomFilterRole);
      setData(mi, false, RoomAdmin);
      setData(mi, false, Ready);
      setData(mi, false, InGame);

      updateSortData(mi);
      updateIcon(mi);
    }
  }
}

void PlayersListModel::updateIcon(const QModelIndex &index) {
  quint32 iconNum = 0;

  QList<bool> flags;
  flags << index.data(Ready).toBool() << index.data(ServerAdmin).toBool()
        << index.data(RoomAdmin).toBool() << index.data(Registered).toBool()
        << index.data(Friend).toBool() << index.data(Ignore).toBool()
        << index.data(InGame).toBool() << index.data(RoomFilterRole).toBool()
        << index.data(InRoom).toBool() << index.data(Contributor).toBool();

  for (int i = flags.size() - 1; i >= 0; --i)
    if (flags[i]) iconNum |= 1 << i;

  if (m_icons().contains(iconNum)) {
    setData(index, m_icons().value(iconNum), Qt::DecorationRole);
  } else {
    QPixmap result(24, 16);
    result.fill(Qt::transparent);

    QPainter painter(&result);

    if (index.data(RoomFilterRole).toBool()) {
      if (index.data(InGame).toBool()) {
        painter.drawPixmap(0, 0, 16, 16, QPixmap(":/res/chat/ingame.png"));
      } else {
        if (index.data(Ready).toBool())
          painter.drawPixmap(0, 0, 16, 16, QPixmap(":/res/chat/lamp.png"));
        else
          painter.drawPixmap(0, 0, 16, 16, QPixmap(":/res/chat/lamp_off.png"));
      }
    } else {  // we're in lobby
      if (!index.data(InRoom).toBool())
        painter.drawPixmap(0, 0, 16, 16, QPixmap(":/res/Flake.png"));
    }

    QString mainIconName(":/res/chat/");

    if (index.data(ServerAdmin).toBool())
      mainIconName += "serveradmin";
    else {
      if (index.data(RoomAdmin).toBool())
        mainIconName += "roomadmin";
      else
        mainIconName += "hedgehog";

      if (index.data(Contributor).toBool()) mainIconName += "contributor";
    }

    if (!index.data(Registered).toBool()) mainIconName += "_gray";

    painter.drawPixmap(8, 0, 16, 16, QPixmap(mainIconName + ".png"));

    if (index.data(Ignore).toBool())
      painter.drawPixmap(8, 0, 16, 16, QPixmap(":/res/chat/ignore.png"));
    else if (index.data(Friend).toBool())
      painter.drawPixmap(8, 0, 16, 16, QPixmap(":/res/chat/friend.png"));

    painter.end();

    QIcon icon(result);

    setData(index, icon, Qt::DecorationRole);
    m_icons().insert(iconNum, icon);
  }

  if (index.data(Ignore).toBool())
    setData(index, QColor(Qt::gray), Qt::ForegroundRole);
  else if (index.data(Friend).toBool())
    setData(index, QColor(Qt::green), Qt::ForegroundRole);
  else
    setData(index, QBrush(QColor(0xff, 0xcc, 0x00)), Qt::ForegroundRole);
}

QHash<quint32, QIcon> &PlayersListModel::m_icons() {
  static QHash<quint32, QIcon> iconsCache;

  return iconsCache;
}

void PlayersListModel::updateSortData(const QModelIndex &index) {
  QString result =
      QString("%1%2%3%4%5%6")
          // room admins go first, then server admins, then friends
          .arg(1 - index.data(RoomAdmin).toInt())
          .arg(1 - index.data(ServerAdmin).toInt())
          .arg(1 - index.data(Friend).toInt())
          // ignored at bottom
          .arg(index.data(Ignore).toInt())
          // keep nicknames starting from non-letter character at bottom within
          // group assume there are no empty nicks in list
          .arg(index.data(Qt::DisplayRole).toString().at(0).isLetter() ? 0 : 1)
          // sort ignoring case
          .arg(index.data(Qt::DisplayRole).toString().toLower());

  setData(index, result, SortRole);
}

void PlayersListModel::setNickname(const QString &nickname) {
  m_nickname = nickname;

  // FIXME: set proper file names
  // loadSet(m_friendsSet, "friends");
  // loadSet(m_ignoredSet, "ignore");

  for (int i = rowCount() - 1; i >= 0; --i) checkFriendIgnore(index(i));
}

bool PlayersListModel::isFriend(const QString &nickname) {
  return m_friendsSet.contains(nickname.toLower());
}

bool PlayersListModel::isIgnored(const QString &nickname) {
  return m_ignoredSet.contains(nickname.toLower());
}

void PlayersListModel::checkFriendIgnore(const QModelIndex &mi) {
  setData(mi, isFriend(mi.data().toString()), Friend);
  setData(mi, isIgnored(mi.data().toString()), Ignore);

  updateIcon(mi);
  updateSortData(mi);
}

void PlayersListModel::loadSet(QSet<QString> &set, const QString &fileName) {
  set.clear();

  QFile txt(fileName);
  if (!txt.open(QIODevice::ReadOnly)) return;

  QTextStream stream(&txt);
  stream.setCodec("UTF-8");

  while (!stream.atEnd()) {
    QString str = stream.readLine();
    if (str.startsWith(";") || str.isEmpty()) continue;

    set.insert(str.trimmed());
  }

  txt.close();
}

void PlayersListModel::saveSet(const QSet<QString> &set,
                               const QString &fileName) {
  qDebug("saving set");

  QFile txt(fileName);

  // list empty? => rather have no file for the list than an empty one
  if (set.isEmpty()) {
    if (txt.exists()) {
      // try to remove file, if successful we're done here.
      if (txt.remove()) return;
    } else
      // there is no file
      return;
  }

  if (!txt.open(QIODevice::WriteOnly | QIODevice::Truncate)) return;

  QTextStream stream(&txt);
  stream.setCodec("UTF-8");

  stream << "; this list is used by Hedgewars - do not edit it unless you know "
            "what you're doing!"
         << endl;

  foreach (const QString &nick, set.values())
    stream << nick << endl;

  txt.close();
}