diff --git a/cockatrice/src/carddatabase.cpp b/cockatrice/src/carddatabase.cpp index f34a0725..5336b5b3 100644 --- a/cockatrice/src/carddatabase.cpp +++ b/cockatrice/src/carddatabase.cpp @@ -225,7 +225,7 @@ QString PictureLoader::getPicUrl() } // if a card has a muid, use the default url; if not, use the fallback - int muid = set ? muid = card->getMuId(set->getShortName()) : 0; + int muid = set ? card->getMuId(set->getShortName()) : 0; if(muid) picUrl = picDownloadHq ? settingsCache->getPicUrlHq() : settingsCache->getPicUrl(); else diff --git a/cockatrice/src/gameselector.cpp b/cockatrice/src/gameselector.cpp index eef42706..9b0dc709 100644 --- a/cockatrice/src/gameselector.cpp +++ b/cockatrice/src/gameselector.cpp @@ -27,16 +27,17 @@ GameSelector::GameSelector(AbstractClient *_client, const TabSupervisor *_tabSup gameListProxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); gameListView->setModel(gameListProxyModel); gameListView->setSortingEnabled(true); + gameListView->sortByColumn(gameListModel->startTimeColIndex(), Qt::AscendingOrder); gameListView->setAlternatingRowColors(true); gameListView->setRootIsDecorated(true); if (_room) - gameListView->header()->hideSection(1); + gameListView->header()->hideSection(gameListModel->roomColIndex()); else gameListProxyModel->setUnavailableGamesVisible(true); #if QT_VERSION < 0x050000 - gameListView->header()->setResizeMode(1, QHeaderView::ResizeToContents); + gameListView->header()->setResizeMode(0, QHeaderView::ResizeToContents); #else - gameListView->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + gameListView->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); #endif filterButton = new QPushButton; filterButton->setIcon(QIcon(":/resources/icon_search.svg")); @@ -53,7 +54,7 @@ GameSelector::GameSelector(AbstractClient *_client, const TabSupervisor *_tabSup createButton = 0; joinButton = new QPushButton; spectateButton = new QPushButton; - + QHBoxLayout *buttonLayout = new QHBoxLayout; buttonLayout->addWidget(filterButton); buttonLayout->addWidget(clearFilterButton); @@ -63,7 +64,7 @@ GameSelector::GameSelector(AbstractClient *_client, const TabSupervisor *_tabSup buttonLayout->addWidget(joinButton); buttonLayout->addWidget(spectateButton); buttonLayout->setAlignment(Qt::AlignTop); - + QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addWidget(gameListView); mainLayout->addLayout(buttonLayout); diff --git a/cockatrice/src/gamesmodel.cpp b/cockatrice/src/gamesmodel.cpp index df0ec880..77e2f7f5 100644 --- a/cockatrice/src/gamesmodel.cpp +++ b/cockatrice/src/gamesmodel.cpp @@ -1,6 +1,67 @@ #include "gamesmodel.h" #include "pb/serverinfo_game.pb.h" +#include #include +#include +#include + +namespace { + const unsigned SECS_PER_MIN = 60; + const unsigned SECS_PER_HOUR = 60 * 60; + + /** + * Pretty print an integer number of seconds ago. Accurate to only one unit, + * rounded; <5 minutes and >5 hours are displayed as such. As a special case, + * time between 60 and 90 minutes will display both the hours and minutes. + * + * For example... + * 0-300 seconds will return "<5m ago" + * 5-59 minutes will return "Xm ago" + * 60-90 minutes will return "Xhr Ym ago" + * 91-300 minutes will return "Xhr ago" + * 300+ minutes will return "5+ hr ago" + */ + QString prettyPrintSecsAgo(uint32_t secs) { + if (secs < SECS_PER_MIN) { + return QObject::tr("<1m ago"); + } + if (secs < SECS_PER_MIN * 5) { + return QObject::tr("<5m ago"); + } + if (secs < SECS_PER_HOUR) { + uint32_t mins = secs / SECS_PER_MIN; + if (secs % SECS_PER_MIN >= 30) + mins++; + //: This will have a number prepended, like "10m ago" + return QString::number(mins).append(QObject::tr("m ago")); + } + // Here, we want to display both the hours and minutes. + // + // There are two small "corner" cases which could be rectified with + // some more knotty iffy-elsey code: + // Between 1:00:00 and 1:00:29 will display "1hr 0m ago" + // Between 1:29:30 and 1:29:59 will display "1hr 31m ago" + // + // Personally, I prefer to keep the code cleaner, and allow these. + if (secs < SECS_PER_MIN * 90) { + uint32_t mins = secs / SECS_PER_MIN - 60; + if (secs % SECS_PER_MIN >= 30) + mins++; + return QObject::tr("1hr ") + .append(QString::number(mins)) + //: This will have a number prepended, like "5m ago" + .append(QObject::tr("m ago")); + } + if (secs < SECS_PER_HOUR * 5) { + uint32_t hours = secs / SECS_PER_HOUR; + if (secs % SECS_PER_HOUR >= SECS_PER_MIN * 30) + hours++; + //: This will have a number prepended, like "2h ago" + return QString::number(hours).append(QObject::tr("hr ago")); + } + return QObject::tr("5+ hrs ago"); + } +} GamesModel::GamesModel(const QMap &_rooms, const QMap &_gameTypes, QObject *parent) : QAbstractTableModel(parent), rooms(_rooms), gameTypes(_gameTypes) @@ -13,25 +74,39 @@ QVariant GamesModel::data(const QModelIndex &index, int role) const return QVariant(); if (role == Qt::UserRole) return index.row(); - if (role != Qt::DisplayRole) + if (role != Qt::DisplayRole && role != SORT_ROLE) return QVariant(); if ((index.row() >= gameList.size()) || (index.column() >= columnCount())) return QVariant(); - + const ServerInfo_Game &g = gameList[index.row()]; switch (index.column()) { - case 0: return QString::fromStdString(g.description()); - case 1: return rooms.value(g.room_id()); - case 2: return QString::fromStdString(g.creator_info().name()); - case 3: { + case 0: return rooms.value(g.room_id()); + case 1: { + uint32_t now = time(NULL); + uint32_t then = g.start_time(); + int secs = now - then; + + switch (role) { + case Qt::DisplayRole: return prettyPrintSecsAgo(secs); + case SORT_ROLE: return QVariant(secs); + default: { + qDebug() << "Returning data for col 1 of games model when role != display, role != sort"; + return QVariant(); // Shouldn't ever be reached. + } + } + } + case 2: return QString::fromStdString(g.description()); + case 3: return QString::fromStdString(g.creator_info().name()); + case 4: { 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))); return result.join(", "); } - case 4: return g.with_password() ? ((g.spectators_need_password() || !g.spectators_allowed()) ? tr("yes") : tr("yes, free for spectators")) : tr("no"); - case 5: { + case 5: return g.with_password() ? ((g.spectators_need_password() || !g.spectators_allowed()) ? tr("yes") : tr("yes, free for spectators")) : tr("no"); + case 6: { QStringList result; if (g.only_buddies()) result.append(tr("buddies only")); @@ -39,8 +114,8 @@ QVariant GamesModel::data(const QModelIndex &index, int role) const result.append(tr("reg. users only")); return result.join(", "); } - case 6: return QString("%1/%2").arg(g.player_count()).arg(g.max_players()); - case 7: return g.spectators_allowed() ? QVariant(g.spectators_count()) : QVariant(tr("not allowed")); + case 7: return QString("%1/%2").arg(g.player_count()).arg(g.max_players()); + case 8: return g.spectators_allowed() ? QVariant(g.spectators_count()) : QVariant(tr("not allowed")); default: return QVariant(); } } @@ -50,14 +125,15 @@ QVariant GamesModel::headerData(int section, Qt::Orientation orientation, int ro if ((role != Qt::DisplayRole) || (orientation != Qt::Horizontal)) return QVariant(); switch (section) { - case 0: return tr("Description"); - case 1: return tr("Room"); - case 2: return tr("Creator"); - case 3: return tr("Game type"); - case 4: return tr("Password"); - case 5: return tr("Restrictions"); - case 6: return tr("Players"); - case 7: return tr("Spectators"); + case 0: return tr("Room"); + case 1: return tr("Game Created"); + case 2: return tr("Description"); + case 3: return tr("Creator"); + case 4: return tr("Game Type"); + case 5: return tr("Password"); + case 6: return tr("Restrictions"); + case 7: return tr("Players"); + case 8: return tr("Spectators"); default: return QVariant(); } } @@ -70,7 +146,7 @@ const ServerInfo_Game &GamesModel::getGame(int row) void GamesModel::updateGameList(const ServerInfo_Game &game) { - for (int i = 0; i < gameList.size(); i++) + for (int i = 0; i < gameList.size(); i++) { if (gameList[i].game_id() == game.game_id()) { if (game.closed()) { beginRemoveRows(QModelIndex(), i, i); @@ -78,10 +154,11 @@ void GamesModel::updateGameList(const ServerInfo_Game &game) endRemoveRows(); } else { gameList[i].MergeFrom(game); - emit dataChanged(index(i, 0), index(i, 7)); + emit dataChanged(index(i, 0), index(i, NUM_COLS-1)); } return; } + } if (game.player_count() <= 0) return; beginInsertRows(QModelIndex(), gameList.size(), gameList.size()); @@ -97,6 +174,7 @@ GamesProxyModel::GamesProxyModel(QObject *parent, ServerInfo_User *_ownUser) maxPlayersFilterMin(-1), maxPlayersFilterMax(-1) { + setSortRole(GamesModel::SORT_ROLE); setDynamicSortFilter(true); } @@ -146,7 +224,7 @@ void GamesProxyModel::resetFilterParameters() gameTypeFilter.clear(); maxPlayersFilterMin = -1; maxPlayersFilterMax = -1; - + invalidateFilter(); } @@ -155,7 +233,7 @@ bool GamesProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &/*sourc GamesModel *model = qobject_cast(sourceModel()); if (!model) return false; - + const ServerInfo_Game &game = model->getGame(sourceRow); if (!unavailableGamesVisible) { if (game.player_count() == game.max_players()) @@ -174,17 +252,17 @@ bool GamesProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &/*sourc if (!creatorNameFilter.isEmpty()) if (!QString::fromStdString(game.creator_info().name()).contains(creatorNameFilter, Qt::CaseInsensitive)) return false; - + QSet gameTypes; for (int i = 0; i < game.game_types_size(); ++i) gameTypes.insert(game.game_types(i)); if (!gameTypeFilter.isEmpty() && gameTypes.intersect(gameTypeFilter).isEmpty()) return false; - + if ((maxPlayersFilterMin != -1) && ((int)game.max_players() < maxPlayersFilterMin)) return false; if ((maxPlayersFilterMax != -1) && ((int)game.max_players() > maxPlayersFilterMax)) return false; - + return true; } diff --git a/cockatrice/src/gamesmodel.h b/cockatrice/src/gamesmodel.h index aeb37e95..85432cf1 100644 --- a/cockatrice/src/gamesmodel.h +++ b/cockatrice/src/gamesmodel.h @@ -16,15 +16,27 @@ private: QList gameList; QMap rooms; QMap gameTypes; + + static const int NUM_COLS = 9; public: + static const int SORT_ROLE = Qt::UserRole+1; + GamesModel(const QMap &_rooms, const QMap &_gameTypes, QObject *parent = 0); int rowCount(const QModelIndex &parent = QModelIndex()) const { return parent.isValid() ? 0 : gameList.size(); } - int columnCount(const QModelIndex &/*parent*/ = QModelIndex()) const { return 8; } + int columnCount(const QModelIndex &/*parent*/ = QModelIndex()) const { return NUM_COLS; } QVariant data(const QModelIndex &index, int role) const; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; - + const ServerInfo_Game &getGame(int row); + + /** + * Update game list with a (possibly new) game. + */ void updateGameList(const ServerInfo_Game &game); + + int roomColIndex() { return 0; } + int startTimeColIndex() { return 1; } + const QMap &getGameTypes() { return gameTypes; } }; @@ -39,7 +51,7 @@ private: int maxPlayersFilterMin, maxPlayersFilterMax; public: GamesProxyModel(QObject *parent = 0, ServerInfo_User *_ownUser = 0); - + bool getUnavailableGamesVisible() const { return unavailableGamesVisible; } void setUnavailableGamesVisible(bool _unavailableGamesVisible); bool getPasswordProtectedGamesVisible() const { return passwordProtectedGamesVisible; }