diff --git a/cockatrice/src/dlg_filter_games.cpp b/cockatrice/src/dlg_filter_games.cpp index c7649705..110d5bff 100644 --- a/cockatrice/src/dlg_filter_games.cpp +++ b/cockatrice/src/dlg_filter_games.cpp @@ -1,6 +1,7 @@ #include "dlg_filter_games.h" #include +#include #include #include #include @@ -15,7 +16,13 @@ DlgFilterGames::DlgFilterGames(const QMap &_allGameTypes, const GamesProxyModel *_gamesProxyModel, QWidget *parent) - : QDialog(parent), allGameTypes(_allGameTypes), gamesProxyModel(_gamesProxyModel) + : QDialog(parent), allGameTypes(_allGameTypes), gamesProxyModel(_gamesProxyModel), + gameAgeMap({{QTime(), tr("no limit")}, + {QTime(0, 5), tr("5 minutes")}, + {QTime(0, 10), tr("10 minutes")}, + {QTime(0, 30), tr("30 minutes")}, + {QTime(1, 0), tr("1 hour")}, + {QTime(2, 0), tr("2 hours")}}) { showBuddiesOnlyGames = new QCheckBox(tr("Show '&buddies only' games")); showBuddiesOnlyGames->setChecked(gamesProxyModel->getShowBuddiesOnlyGames()); @@ -29,6 +36,14 @@ DlgFilterGames::DlgFilterGames(const QMap &_allGameTypes, hideIgnoredUserGames = new QCheckBox(tr("Hide '&ignored user' games")); hideIgnoredUserGames->setChecked(gamesProxyModel->getHideIgnoredUserGames()); + maxGameAgeComboBox = new QComboBox(); + maxGameAgeComboBox->setEditable(false); + maxGameAgeComboBox->addItems(gameAgeMap.values()); + QTime gameAge = gamesProxyModel->getMaxGameAge(); + maxGameAgeComboBox->setCurrentIndex(gameAgeMap.keys().indexOf(gameAge)); // index is -1 if unknown + QLabel *maxGameAgeLabel = new QLabel(tr("&Newer than:")); + maxGameAgeLabel->setBuddy(maxGameAgeComboBox); + gameNameFilterEdit = new QLineEdit; gameNameFilterEdit->setText(gamesProxyModel->getGameNameFilter()); QLabel *gameNameFilterLabel = new QLabel(tr("Game &description:")); @@ -43,6 +58,8 @@ DlgFilterGames::DlgFilterGames(const QMap &_allGameTypes, generalGrid->addWidget(gameNameFilterEdit, 0, 1); generalGrid->addWidget(creatorNameFilterLabel, 1, 0); generalGrid->addWidget(creatorNameFilterEdit, 1, 1); + generalGrid->addWidget(maxGameAgeLabel, 2, 0); + generalGrid->addWidget(maxGameAgeComboBox, 2, 1); generalGroupBox = new QGroupBox(tr("General")); generalGroupBox->setLayout(generalGrid); @@ -220,6 +237,15 @@ int DlgFilterGames::getMaxPlayersFilterMax() const return maxPlayersFilterMaxSpinBox->value(); } +const QTime &DlgFilterGames::getMaxGameAge() const +{ + int index = maxGameAgeComboBox->currentIndex(); + if (index < 0 || index >= gameAgeMap.size()) { // index is out of bounds + return gamesProxyModel->getMaxGameAge(); // leave the setting unchanged + } + return gameAgeMap.keys().at(index); +} + void DlgFilterGames::setMaxPlayersFilter(int _maxPlayersFilterMin, int _maxPlayersFilterMax) { maxPlayersFilterMinSpinBox->setValue(_maxPlayersFilterMin); diff --git a/cockatrice/src/dlg_filter_games.h b/cockatrice/src/dlg_filter_games.h index 1f37bf31..8c2301ad 100644 --- a/cockatrice/src/dlg_filter_games.h +++ b/cockatrice/src/dlg_filter_games.h @@ -4,11 +4,14 @@ #include "gamesmodel.h" #include +#include #include #include #include +#include class QCheckBox; +class QComboBox; class QGroupBox; class QLineEdit; class QSpinBox; @@ -27,6 +30,7 @@ private: QMap gameTypeFilterCheckBoxes; QSpinBox *maxPlayersFilterMinSpinBox; QSpinBox *maxPlayersFilterMaxSpinBox; + QComboBox *maxGameAgeComboBox; const QMap &allGameTypes; const GamesProxyModel *gamesProxyModel; @@ -56,6 +60,8 @@ public: int getMaxPlayersFilterMin() const; int getMaxPlayersFilterMax() const; void setMaxPlayersFilter(int _maxPlayersFilterMin, int _maxPlayersFilterMax); + const QTime &getMaxGameAge() const; + const QMap gameAgeMap; }; #endif diff --git a/cockatrice/src/gameselector.cpp b/cockatrice/src/gameselector.cpp index 8cc57045..a41c8128 100644 --- a/cockatrice/src/gameselector.cpp +++ b/cockatrice/src/gameselector.cpp @@ -74,18 +74,16 @@ GameSelector::GameSelector(AbstractClient *_client, connect(filterButton, SIGNAL(clicked()), this, SLOT(actSetFilter())); clearFilterButton = new QPushButton; clearFilterButton->setIcon(QPixmap("theme:icons/clearsearch")); - if (showFilters && gameListProxyModel->areFilterParametersSetToDefaults()) { - clearFilterButton->setEnabled(false); - } else { - clearFilterButton->setEnabled(true); - } + bool filtersSetToDefault = showFilters && gameListProxyModel->areFilterParametersSetToDefaults(); + clearFilterButton->setEnabled(!filtersSetToDefault); connect(clearFilterButton, SIGNAL(clicked()), this, SLOT(actClearFilter())); if (room) { createButton = new QPushButton; connect(createButton, SIGNAL(clicked()), this, SLOT(actCreate())); - } else - createButton = 0; + } else { + createButton = nullptr; + } joinButton = new QPushButton; spectateButton = new QPushButton; @@ -161,6 +159,7 @@ void GameSelector::actSetFilter() gameListProxyModel->setCreatorNameFilter(dlg.getCreatorNameFilter()); gameListProxyModel->setGameTypeFilter(dlg.getGameTypeFilter()); gameListProxyModel->setMaxPlayersFilter(dlg.getMaxPlayersFilterMin(), dlg.getMaxPlayersFilterMax()); + gameListProxyModel->setMaxGameAge(dlg.getMaxGameAge()); gameListProxyModel->saveFilterParameters(gameTypeMap); clearFilterButton->setEnabled(!gameListProxyModel->areFilterParametersSetToDefaults()); diff --git a/cockatrice/src/gamesmodel.cpp b/cockatrice/src/gamesmodel.cpp index 3a0441bb..e34b02e1 100644 --- a/cockatrice/src/gamesmodel.cpp +++ b/cockatrice/src/gamesmodel.cpp @@ -23,7 +23,15 @@ enum GameListColumn SPECTATORS }; -const QString GamesModel::getGameCreatedString(const int secs) const +const bool DEFAULT_UNAVAILABLE_GAMES_VISIBLE = false; +const bool DEFAULT_SHOW_PASSWORD_PROTECTED_GAMES = true; +const bool DEFAULT_SHOW_BUDDIES_ONLY_GAMES = true; +const bool DEFAULT_HIDE_IGNORED_USER_GAMES = false; +const int DEFAULT_MAX_PLAYERS_MIN = 1; +const int DEFAULT_MAX_PLAYERS_MAX = 99; +constexpr QTime DEFAULT_MAX_GAME_AGE = QTime(); + +const QString GamesModel::getGameCreatedString(const int secs) { QString ret; @@ -60,20 +68,23 @@ QVariant GamesModel::data(const QModelIndex &index, int role) const if ((index.row() >= gameList.size()) || (index.column() >= columnCount())) return QVariant(); - const ServerInfo_Game &g = gameList[index.row()]; + const ServerInfo_Game &gameentry = gameList[index.row()]; switch (index.column()) { case ROOM: - return rooms.value(g.room_id()); + return rooms.value(gameentry.room_id()); case CREATED: { - QDateTime then; - then.setTime_t(g.start_time()); - int secs = then.secsTo(QDateTime::currentDateTime()); - switch (role) { - case Qt::DisplayRole: + case Qt::DisplayRole: { +#if (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)) + QDateTime then = QDateTime::fromSecsSinceEpoch(gameentry.start_time(), Qt::UTC); +#else + QDateTime then = QDateTime::fromTime_t(gameentry.start_time(), Qt::UTC); +#endif + int secs = then.secsTo(QDateTime::currentDateTimeUtc()); return getGameCreatedString(secs); + } case SORT_ROLE: - return QVariant(secs); + return QVariant(-static_cast(gameentry.start_time())); case Qt::TextAlignmentRole: return Qt::AlignCenter; default: @@ -84,7 +95,7 @@ QVariant GamesModel::data(const QModelIndex &index, int role) const switch (role) { case SORT_ROLE: case Qt::DisplayRole: - return QString::fromStdString(g.description()); + return QString::fromStdString(gameentry.description()); case Qt::TextAlignmentRole: return Qt::AlignLeft; default: @@ -94,11 +105,11 @@ QVariant GamesModel::data(const QModelIndex &index, int role) const switch (role) { case SORT_ROLE: case Qt::DisplayRole: - return QString::fromStdString(g.creator_info().name()); + return QString::fromStdString(gameentry.creator_info().name()); case Qt::DecorationRole: { QPixmap avatarPixmap = UserLevelPixmapGenerator::generatePixmap( - 13, (UserLevelFlags)g.creator_info().user_level(), false, - QString::fromStdString(g.creator_info().privlevel())); + 13, (UserLevelFlags)gameentry.creator_info().user_level(), false, + QString::fromStdString(gameentry.creator_info().privlevel())); return QIcon(avatarPixmap); } default: @@ -110,9 +121,9 @@ QVariant GamesModel::data(const QModelIndex &index, int role) const case SORT_ROLE: case Qt::DisplayRole: { QStringList result; - GameTypeMap gameTypeMap = gameTypes.value(g.room_id()); - for (int i = g.game_types_size() - 1; i >= 0; --i) - result.append(gameTypeMap.value(g.game_types(i))); + GameTypeMap gameTypeMap = gameTypes.value(gameentry.room_id()); + for (int i = gameentry.game_types_size() - 1; i >= 0; --i) + result.append(gameTypeMap.value(gameentry.game_types(i))); return result.join(", "); } case Qt::TextAlignmentRole: @@ -125,16 +136,16 @@ QVariant GamesModel::data(const QModelIndex &index, int role) const case SORT_ROLE: case Qt::DisplayRole: { QStringList result; - if (g.with_password()) + if (gameentry.with_password()) result.append(tr("password")); - if (g.only_buddies()) + if (gameentry.only_buddies()) result.append(tr("buddies only")); - if (g.only_registered()) + if (gameentry.only_registered()) result.append(tr("reg. users only")); return result.join(", "); } case Qt::DecorationRole: { - return g.with_password() ? QIcon(LockPixmapGenerator::generatePixmap(13)) : QVariant(); + return gameentry.with_password() ? QIcon(LockPixmapGenerator::generatePixmap(13)) : QVariant(); case Qt::TextAlignmentRole: return Qt::AlignLeft; default: @@ -145,7 +156,7 @@ QVariant GamesModel::data(const QModelIndex &index, int role) const switch (role) { case SORT_ROLE: case Qt::DisplayRole: - return QString("%1/%2").arg(g.player_count()).arg(g.max_players()); + return QString("%1/%2").arg(gameentry.player_count()).arg(gameentry.max_players()); case Qt::TextAlignmentRole: return Qt::AlignCenter; default: @@ -156,19 +167,19 @@ QVariant GamesModel::data(const QModelIndex &index, int role) const switch (role) { case SORT_ROLE: case Qt::DisplayRole: { - if (g.spectators_allowed()) { + if (gameentry.spectators_allowed()) { QString result; - result.append(QString::number(g.spectators_count())); + result.append(QString::number(gameentry.spectators_count())); - if (g.spectators_can_chat() && g.spectators_omniscient()) { + if (gameentry.spectators_can_chat() && gameentry.spectators_omniscient()) { result.append(" (") .append(tr("can chat")) .append(" & ") .append(tr("see hands")) .append(")"); - } else if (g.spectators_can_chat()) { + } else if (gameentry.spectators_can_chat()) { result.append(" (").append(tr("can chat")).append(")"); - } else if (g.spectators_omniscient()) { + } else if (gameentry.spectators_omniscient()) { result.append(" (").append(tr("can see hands")).append(")"); } @@ -314,6 +325,12 @@ void GamesProxyModel::setMaxPlayersFilter(int _maxPlayersFilterMin, int _maxPlay invalidateFilter(); } +void GamesProxyModel::setMaxGameAge(const QTime &_maxGameAge) +{ + maxGameAge = _maxGameAge; + invalidateFilter(); +} + int GamesProxyModel::getNumFilteredGames() const { GamesModel *model = qobject_cast(sourceModel()); @@ -340,6 +357,7 @@ void GamesProxyModel::resetFilterParameters() gameTypeFilter.clear(); maxPlayersFilterMin = DEFAULT_MAX_PLAYERS_MIN; maxPlayersFilterMax = DEFAULT_MAX_PLAYERS_MAX; + maxGameAge = DEFAULT_MAX_GAME_AGE; invalidateFilter(); } @@ -351,7 +369,7 @@ bool GamesProxyModel::areFilterParametersSetToDefaults() const showBuddiesOnlyGames == DEFAULT_SHOW_BUDDIES_ONLY_GAMES && hideIgnoredUserGames == DEFAULT_HIDE_IGNORED_USER_GAMES && gameNameFilter.isEmpty() && creatorNameFilter.isEmpty() && gameTypeFilter.isEmpty() && maxPlayersFilterMin == DEFAULT_MAX_PLAYERS_MIN && - maxPlayersFilterMax == DEFAULT_MAX_PLAYERS_MAX; + maxPlayersFilterMax == DEFAULT_MAX_PLAYERS_MAX && maxGameAge == DEFAULT_MAX_GAME_AGE; } void GamesProxyModel::loadFilterParameters(const QMap &allGameTypes) @@ -365,6 +383,7 @@ void GamesProxyModel::loadFilterParameters(const QMap &allGameType creatorNameFilter = gameFilters.getCreatorNameFilter(); maxPlayersFilterMin = gameFilters.getMinPlayers(); maxPlayersFilterMax = gameFilters.getMaxPlayers(); + maxGameAge = gameFilters.getMaxGameAge(); QMapIterator gameTypesIterator(allGameTypes); while (gameTypesIterator.hasNext()) { @@ -396,6 +415,7 @@ void GamesProxyModel::saveFilterParameters(const QMap &allGameType gameFilters.setMinPlayers(maxPlayersFilterMin); gameFilters.setMaxPlayers(maxPlayersFilterMax); + gameFilters.setMaxGameAge(maxGameAge); } bool GamesProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex & /*sourceParent*/) const @@ -405,6 +425,11 @@ bool GamesProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex & /*sour bool GamesProxyModel::filterAcceptsRow(int sourceRow) const { +#if (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)) + static const QDate epochDate = QDateTime::fromSecsSinceEpoch(0, Qt::UTC).date(); +#else + static const QDate epochDate = QDateTime::fromTime_t(0, Qt::UTC).date(); +#endif GamesModel *model = qobject_cast(sourceModel()); if (!model) return false; @@ -442,11 +467,21 @@ bool GamesProxyModel::filterAcceptsRow(int sourceRow) const if (!gameTypeFilter.isEmpty() && gameTypes.intersect(gameTypeFilter).isEmpty()) return false; - if ((int)game.max_players() < maxPlayersFilterMin) + if (game.max_players() < maxPlayersFilterMin) return false; - if ((int)game.max_players() > maxPlayersFilterMax) + if (game.max_players() > maxPlayersFilterMax) return false; + if (maxGameAge.isValid()) { + QDateTime now = QDateTime::currentDateTimeUtc(); + qint64 signed_start_time = game.start_time(); // cast to 64 bit value to allow signed value + QDateTime total = now.addSecs(-signed_start_time); // a 32 bit value would wrap at 2038-1-19 + // games shouldn't have negative ages but we'll not filter them + // because qtime wraps after a day we consider all games older than a day to be too old + if (total.isValid() && total.date() >= epochDate && (total.date() > epochDate || total.time() > maxGameAge)) { + return false; + } + } return true; } diff --git a/cockatrice/src/gamesmodel.h b/cockatrice/src/gamesmodel.h index e114a110..7cc3891f 100644 --- a/cockatrice/src/gamesmodel.h +++ b/cockatrice/src/gamesmodel.h @@ -9,6 +9,8 @@ #include #include #include +#include +#include class GamesModel : public QAbstractTableModel { @@ -37,7 +39,7 @@ public: } QVariant data(const QModelIndex &index, int role) const; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; - const QString getGameCreatedString(const int secs) const; + static const QString getGameCreatedString(const int secs); const ServerInfo_Game &getGame(int row); /** @@ -68,20 +70,22 @@ class GamesProxyModel : public QSortFilterProxyModel private: bool ownUserIsRegistered; const TabSupervisor *tabSupervisor; + + // If adding any additional filters, make sure to update: + // - GamesProxyModel() + // - resetFilterParameters() + // - areFilterParametersSetToDefaults() + // - loadFilterParameters() + // - saveFilterParameters() + // - filterAcceptsRow() bool showBuddiesOnlyGames; bool hideIgnoredUserGames; bool unavailableGamesVisible; bool showPasswordProtectedGames; QString gameNameFilter, creatorNameFilter; QSet gameTypeFilter; - int maxPlayersFilterMin, maxPlayersFilterMax; - - static const bool DEFAULT_UNAVAILABLE_GAMES_VISIBLE = false; - static const bool DEFAULT_SHOW_PASSWORD_PROTECTED_GAMES = true; - static const bool DEFAULT_SHOW_BUDDIES_ONLY_GAMES = true; - static const bool DEFAULT_HIDE_IGNORED_USER_GAMES = false; - static const int DEFAULT_MAX_PLAYERS_MIN = 1; - static const int DEFAULT_MAX_PLAYERS_MAX = 99; + quint32 maxPlayersFilterMin, maxPlayersFilterMax; + QTime maxGameAge; public: GamesProxyModel(QObject *parent = nullptr, const TabSupervisor *_tabSupervisor = nullptr); @@ -130,6 +134,12 @@ public: return maxPlayersFilterMax; } void setMaxPlayersFilter(int _maxPlayersFilterMin, int _maxPlayersFilterMax); + const QTime &getMaxGameAge() const + { + return maxGameAge; + } + void setMaxGameAge(const QTime &_maxGameAge); + int getNumFilteredGames() const; void resetFilterParameters(); bool areFilterParametersSetToDefaults() const; diff --git a/cockatrice/src/settings/gamefilterssettings.cpp b/cockatrice/src/settings/gamefilterssettings.cpp index 58d9fab2..5a6c49c4 100644 --- a/cockatrice/src/settings/gamefilterssettings.cpp +++ b/cockatrice/src/settings/gamefilterssettings.cpp @@ -1,6 +1,7 @@ #include "gamefilterssettings.h" #include +#include GameFiltersSettings::GameFiltersSettings(QString settingPath, QObject *parent) : SettingsManager(settingPath + "gamefilters.ini", parent) @@ -102,6 +103,17 @@ int GameFiltersSettings::getMaxPlayers() return previous == QVariant() ? 99 : previous.toInt(); } +void GameFiltersSettings::setMaxGameAge(const QTime &maxGameAge) +{ + setValue(maxGameAge, "max_game_age_time", "filter_games"); +} + +QTime GameFiltersSettings::getMaxGameAge() +{ + QVariant previous = getValue("max_game_age_time", "filter_games"); + return previous.toTime(); +} + void GameFiltersSettings::setGameTypeEnabled(QString gametype, bool enabled) { setValue(enabled, "game_type/" + hashGameType(gametype), "filter_games"); diff --git a/cockatrice/src/settings/gamefilterssettings.h b/cockatrice/src/settings/gamefilterssettings.h index a24d423f..e6bc40e5 100644 --- a/cockatrice/src/settings/gamefilterssettings.h +++ b/cockatrice/src/settings/gamefilterssettings.h @@ -17,6 +17,7 @@ public: QString getCreatorNameFilter(); int getMinPlayers(); int getMaxPlayers(); + QTime getMaxGameAge(); bool isGameTypeEnabled(QString gametype); void setShowBuddiesOnlyGames(bool show); @@ -27,6 +28,7 @@ public: void setCreatorNameFilter(QString creatorName); void setMinPlayers(int min); void setMaxPlayers(int max); + void setMaxGameAge(const QTime &maxGameAge); void setGameTypeEnabled(QString gametype, bool enabled); void setGameHashedTypeEnabled(QString gametypeHASHED, bool enabled); signals: