diff --git a/CMakeLists.txt b/CMakeLists.txt index 4cf8857a..350fb149 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,9 +79,9 @@ ELSEIF (CMAKE_COMPILER_IS_GNUCXX) include(CheckCXXCompilerFlag) set(CMAKE_CXX_FLAGS_RELEASE "-s -O2") - set(CMAKE_CXX_FLAGS_DEBUG "-ggdb -O0 -Wall -Wextra -pedantic -Werror") + set(CMAKE_CXX_FLAGS_DEBUG "-ggdb -O0 -Wall -Wextra -pedantic") - set(ADDITIONAL_DEBUG_FLAGS -Wcast-align -Wmissing-declarations -Winline -Wno-long-long -Wno-error=extra -Wno-error=unused-parameter -Wno-inline -Wno-error=delete-non-virtual-dtor -Wno-error=sign-compare -Wno-error=reorder -Wno-error=missing-declarations) + set(ADDITIONAL_DEBUG_FLAGS -Wcast-align -Wmissing-declarations -Winline -Wno-long-long -Wno-inline) FOREACH(FLAG ${ADDITIONAL_DEBUG_FLAGS}) CHECK_CXX_COMPILER_FLAG("${FLAG}" CXX_HAS_WARNING_${FLAG}) 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 25bf2ebd..f858a129 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..17900834 100644 --- a/cockatrice/src/gamesmodel.cpp +++ b/cockatrice/src/gamesmodel.cpp @@ -1,6 +1,52 @@ #include "gamesmodel.h" #include "pb/serverinfo_game.pb.h" #include +#include +#include + +namespace { + const unsigned SECS_PER_HALF_MIN = 30; + const unsigned SECS_PER_MIN = 60; + const unsigned SECS_PER_HALF_HOUR = 1600; // 60 * 30 + const unsigned SECS_PER_HOUR = 3600; // 60 * 60 + const unsigned SECS_PER_DAY = 86400; // 60 * 60 * 24 + + /** + * Pretty print an integer number of seconds ago. Accurate to only one unit, + * rounded. + * + * For example... + * 0-59 seconds will return "X seconds ago" + * 1-59 minutes will return "X minutes ago"; 90 seconds will return "2 minutes ago" + * 1-23 hours will return "X hours ago"; 90 minutes will return "2 hours ago" + * 24+ hours will return "1+ days ago", because it seems unlikely that we care about + * an accurate timestamp of day old games. + */ + std::string prettyPrintSecsAgo(uint32_t secs) { + std::ostringstream str_stream; + + if (secs < SECS_PER_MIN) { + str_stream << secs; + str_stream << "s ago"; + } else if (secs < SECS_PER_HOUR) { + uint32_t mins = secs / SECS_PER_MIN; + if (secs % SECS_PER_MIN >= SECS_PER_HALF_MIN) + mins++; + str_stream << mins; + str_stream << "m ago"; + } else if (secs < SECS_PER_DAY) { + uint32_t hours = secs / SECS_PER_HOUR; + if (secs % SECS_PER_HOUR >= SECS_PER_HALF_HOUR) + hours++; + str_stream << hours; + str_stream << "h ago"; + } else { + str_stream << "a long time ago"; + } + + return str_stream.str(); + } +} GamesModel::GamesModel(const QMap &_rooms, const QMap &_gameTypes, QObject *parent) : QAbstractTableModel(parent), rooms(_rooms), gameTypes(_gameTypes) @@ -13,25 +59,36 @@ 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 QString::fromStdString(prettyPrintSecsAgo(secs)); + case SORT_ROLE: return QVariant(secs); + default: 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 +96,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 +107,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("Start time"); + 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 +128,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 +136,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 +156,7 @@ GamesProxyModel::GamesProxyModel(QObject *parent, ServerInfo_User *_ownUser) maxPlayersFilterMin(-1), maxPlayersFilterMax(-1) { + setSortRole(GamesModel::SORT_ROLE); setDynamicSortFilter(true); } @@ -146,7 +206,7 @@ void GamesProxyModel::resetFilterParameters() gameTypeFilter.clear(); maxPlayersFilterMin = -1; maxPlayersFilterMax = -1; - + invalidateFilter(); } @@ -155,7 +215,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 +234,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; }