From 58d024d0674967b0fec2e18d84ca8019c05e42c7 Mon Sep 17 00:00:00 2001 From: tooomm Date: Wed, 30 Sep 2020 04:00:09 +0200 Subject: [PATCH 001/126] fix update message (#4116) --- cockatrice/src/spoilerbackgroundupdater.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cockatrice/src/spoilerbackgroundupdater.cpp b/cockatrice/src/spoilerbackgroundupdater.cpp index 22d6e970c..7521b9ee9 100644 --- a/cockatrice/src/spoilerbackgroundupdater.cpp +++ b/cockatrice/src/spoilerbackgroundupdater.cpp @@ -167,8 +167,8 @@ bool SpoilerBackgroundUpdater::saveDownloadedFile(QByteArray data) QList lines = data.split('\n'); foreach (QByteArray line, lines) { - if (line.contains("created:")) { - QString timeStamp = QString(line).replace("created:", "").trimmed(); + if (line.contains("Created At:")) { + QString timeStamp = QString(line).replace("Created At:", "").trimmed(); timeStamp.chop(6); // Remove " (UTC)" auto utcTime = QLocale().toDateTime(timeStamp, "ddd, MMM dd yyyy, hh:mm:ss"); From eba9c097f69e29e2aaf42795c7b972b03be6ba44 Mon Sep 17 00:00:00 2001 From: Kaitlin <63179146+knitknit@users.noreply.github.com> Date: Wed, 30 Sep 2020 23:46:10 -0400 Subject: [PATCH 002/126] Add dropdown for game age filtering (#4092) * Part 1 for #3067: Basic combo box (dropdown) filtering mechanism for game age. * Apply suggestions from draft review # Conflicts: # cockatrice/src/gamesmodel.cpp # cockatrice/src/gamesmodel.h * switch to using QTime * check for games older than a day * formatting for casts and more unnecessary cosmetic changes * ebbit1q fixes Co-authored-by: ebbit1q --- cockatrice/src/dlg_filter_games.cpp | 28 +++++- cockatrice/src/dlg_filter_games.h | 6 ++ cockatrice/src/gameselector.cpp | 13 ++- cockatrice/src/gamesmodel.cpp | 93 +++++++++++++------ cockatrice/src/gamesmodel.h | 28 ++++-- .../src/settings/gamefilterssettings.cpp | 12 +++ cockatrice/src/settings/gamefilterssettings.h | 2 + 7 files changed, 136 insertions(+), 46 deletions(-) diff --git a/cockatrice/src/dlg_filter_games.cpp b/cockatrice/src/dlg_filter_games.cpp index c76497053..110d5bff3 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 1f37bf31c..8c2301ad6 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 8cc570456..a41c81283 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 3a0441bbc..e34b02e17 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 e114a1106..7cc3891f1 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 58d9fab27..5a6c49c4d 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 a24d423f0..e6bc40e59 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: From a5511190a33ff556f02e9956ad5e2cd9f2e699ea Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Fri, 2 Oct 2020 18:12:13 +0200 Subject: [PATCH 003/126] add missing cardlink for the flip messages in the message log (#4122) --- cockatrice/src/messagelogwidget.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cockatrice/src/messagelogwidget.cpp b/cockatrice/src/messagelogwidget.cpp index 65ed5e120..1caecf241 100644 --- a/cockatrice/src/messagelogwidget.cpp +++ b/cockatrice/src/messagelogwidget.cpp @@ -380,9 +380,11 @@ void MessageLogWidget::logDumpZone(Player *player, CardZone *zone, int numberCar void MessageLogWidget::logFlipCard(Player *player, QString cardName, bool faceDown) { if (faceDown) { - appendHtmlServerMessage(tr("%1 turns %2 face-down.").arg(sanitizeHtml(player->getName())).arg(cardName)); + appendHtmlServerMessage( + tr("%1 turns %2 face-down.").arg(sanitizeHtml(player->getName())).arg(cardLink(cardName))); } else { - appendHtmlServerMessage(tr("%1 turns %2 face-up.").arg(sanitizeHtml(player->getName())).arg(cardName)); + appendHtmlServerMessage( + tr("%1 turns %2 face-up.").arg(sanitizeHtml(player->getName())).arg(cardLink(cardName))); } } From e2251fe06b45dc0bbac7a62985153203f29fad24 Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Fri, 2 Oct 2020 18:13:12 +0200 Subject: [PATCH 004/126] update sfmt to version 1.5.1 from 1.4.1 (#4124) --- common/sfmt/SFMT-common.h | 26 +++++++++++++------------- common/sfmt/SFMT-params.h | 12 ++++++------ common/sfmt/SFMT.c | 24 ++++++++++++++---------- common/sfmt/SFMT.h | 11 ++++++++++- 4 files changed, 43 insertions(+), 30 deletions(-) diff --git a/common/sfmt/SFMT-common.h b/common/sfmt/SFMT-common.h index c7d8aa9fb..a5a9b0504 100644 --- a/common/sfmt/SFMT-common.h +++ b/common/sfmt/SFMT-common.h @@ -28,7 +28,7 @@ extern "C" { #include "SFMT.h" inline static void do_recursion(w128_t * r, w128_t * a, w128_t * b, - w128_t * c, w128_t * d); + w128_t * c, w128_t * d); inline static void rshift128(w128_t *out, w128_t const *in, int shift); inline static void lshift128(w128_t *out, w128_t const *in, int shift); @@ -123,24 +123,24 @@ inline static void lshift128(w128_t *out, w128_t const *in, int shift) */ #ifdef ONLY64 inline static void do_recursion(w128_t *r, w128_t *a, w128_t *b, w128_t *c, - w128_t *d) { + w128_t *d) { w128_t x; w128_t y; lshift128(&x, a, SFMT_SL2); rshift128(&y, c, SFMT_SR2); r->u[0] = a->u[0] ^ x.u[0] ^ ((b->u[0] >> SFMT_SR1) & SFMT_MSK2) ^ y.u[0] - ^ (d->u[0] << SFMT_SL1); + ^ (d->u[0] << SFMT_SL1); r->u[1] = a->u[1] ^ x.u[1] ^ ((b->u[1] >> SFMT_SR1) & SFMT_MSK1) ^ y.u[1] - ^ (d->u[1] << SFMT_SL1); + ^ (d->u[1] << SFMT_SL1); r->u[2] = a->u[2] ^ x.u[2] ^ ((b->u[2] >> SFMT_SR1) & SFMT_MSK4) ^ y.u[2] - ^ (d->u[2] << SFMT_SL1); + ^ (d->u[2] << SFMT_SL1); r->u[3] = a->u[3] ^ x.u[3] ^ ((b->u[3] >> SFMT_SR1) & SFMT_MSK3) ^ y.u[3] - ^ (d->u[3] << SFMT_SL1); + ^ (d->u[3] << SFMT_SL1); } #else inline static void do_recursion(w128_t *r, w128_t *a, w128_t *b, - w128_t *c, w128_t *d) + w128_t *c, w128_t *d) { w128_t x; w128_t y; @@ -148,17 +148,17 @@ inline static void do_recursion(w128_t *r, w128_t *a, w128_t *b, lshift128(&x, a, SFMT_SL2); rshift128(&y, c, SFMT_SR2); r->u[0] = a->u[0] ^ x.u[0] ^ ((b->u[0] >> SFMT_SR1) & SFMT_MSK1) - ^ y.u[0] ^ (d->u[0] << SFMT_SL1); + ^ y.u[0] ^ (d->u[0] << SFMT_SL1); r->u[1] = a->u[1] ^ x.u[1] ^ ((b->u[1] >> SFMT_SR1) & SFMT_MSK2) - ^ y.u[1] ^ (d->u[1] << SFMT_SL1); + ^ y.u[1] ^ (d->u[1] << SFMT_SL1); r->u[2] = a->u[2] ^ x.u[2] ^ ((b->u[2] >> SFMT_SR1) & SFMT_MSK3) - ^ y.u[2] ^ (d->u[2] << SFMT_SL1); + ^ y.u[2] ^ (d->u[2] << SFMT_SL1); r->u[3] = a->u[3] ^ x.u[3] ^ ((b->u[3] >> SFMT_SR1) & SFMT_MSK4) - ^ y.u[3] ^ (d->u[3] << SFMT_SL1); + ^ y.u[3] ^ (d->u[3] << SFMT_SL1); } #endif -#endif - #if defined(__cplusplus) } #endif + +#endif // SFMT_COMMON_H diff --git a/common/sfmt/SFMT-params.h b/common/sfmt/SFMT-params.h index 372e6f11a..2fe663ab6 100644 --- a/common/sfmt/SFMT-params.h +++ b/common/sfmt/SFMT-params.h @@ -46,8 +46,8 @@ */ /** the parameter of shift right as one 128-bit register. - * The 128-bit integer is shifted by (SFMT_SL2 * 8) bits. -#define SFMT_SR21 1 + * The 128-bit integer is shifted by (SFMT_SR2 * 8) bits. +#define SFMT_SR2 1 */ /** A bitmask, used in the recursion. These parameters are introduced @@ -59,10 +59,10 @@ */ /** These definitions are part of a 128-bit period certification vector. -#define SFMT_PARITY1 0x00000001U -#define SFMT_PARITY2 0x00000000U -#define SFMT_PARITY3 0x00000000U -#define SFMT_PARITY4 0xc98e126aU +#define SFMT_PARITY1 0x00000001U +#define SFMT_PARITY2 0x00000000U +#define SFMT_PARITY3 0x00000000U +#define SFMT_PARITY4 0xc98e126aU */ #if SFMT_MEXP == 607 diff --git a/common/sfmt/SFMT.c b/common/sfmt/SFMT.c index 2652df7de..b4ac9308b 100644 --- a/common/sfmt/SFMT.c +++ b/common/sfmt/SFMT.c @@ -40,11 +40,6 @@ extern "C" { #undef ONLY64 #endif -/** - * parameters used by sse2. - */ -static const w128_t sse2_param_mask = {{SFMT_MSK1, SFMT_MSK2, - SFMT_MSK3, SFMT_MSK4}}; /*---------------- STATIC FUNCTIONS ----------------*/ @@ -60,11 +55,18 @@ inline static void swap(w128_t *array, int size); #if defined(HAVE_ALTIVEC) #include "SFMT-alti.h" #elif defined(HAVE_SSE2) +/** + * parameters used by sse2. + */ + static const w128_t sse2_param_mask = {{SFMT_MSK1, SFMT_MSK2, + SFMT_MSK3, SFMT_MSK4}}; #if defined(_MSC_VER) #include "SFMT-sse2-msc.h" #else #include "SFMT-sse2.h" #endif +#elif defined(HAVE_NEON) + #include "SFMT-neon.h" #endif /** @@ -81,7 +83,7 @@ inline static int idxof(int i) { } #endif -#if (!defined(HAVE_ALTIVEC)) && (!defined(HAVE_SSE2)) +#if (!defined(HAVE_ALTIVEC)) && (!defined(HAVE_SSE2)) && (!defined(HAVE_NEON)) /** * This function fills the user-specified array with pseudorandom * integers. @@ -166,17 +168,19 @@ static uint32_t func2(uint32_t x) { * @param sfmt SFMT internal state */ static void period_certification(sfmt_t * sfmt) { - int inner = 0; + uint32_t inner = 0; int i, j; uint32_t work; uint32_t *psfmt32 = &sfmt->state[0].u[0]; const uint32_t parity[4] = {SFMT_PARITY1, SFMT_PARITY2, SFMT_PARITY3, SFMT_PARITY4}; - for (i = 0; i < 4; i++) + for (i = 0; i < 4; i++) { inner ^= psfmt32[idxof(i)] & parity[i]; - for (i = 16; i > 0; i >>= 1) + } + for (i = 16; i > 0; i >>= 1) { inner ^= inner >> i; + } inner &= 1; /* check OK */ if (inner == 1) { @@ -232,7 +236,7 @@ int sfmt_get_min_array_size64(sfmt_t * sfmt) { return SFMT_N64; } -#if !defined(HAVE_SSE2) && !defined(HAVE_ALTIVEC) +#if !defined(HAVE_SSE2) && !defined(HAVE_ALTIVEC) && !defined(HAVE_NEON) /** * This function fills the internal state array with pseudorandom * integers. diff --git a/common/sfmt/SFMT.h b/common/sfmt/SFMT.h index dca308a00..79e012d63 100644 --- a/common/sfmt/SFMT.h +++ b/common/sfmt/SFMT.h @@ -79,6 +79,15 @@ union W128_T { uint32_t u[4]; uint64_t u64[2]; }; +#elif defined(HAVE_NEON) + #include + +/** 128-bit data structure */ +union W128_T { + uint32_t u[4]; + uint64_t u64[2]; + uint32x4_t si; +}; #elif defined(HAVE_SSE2) #include @@ -247,7 +256,7 @@ inline static double sfmt_genrand_real3(sfmt_t * sfmt) */ inline static double sfmt_to_res53(uint64_t v) { - return v * (1.0/18446744073709551616.0); + return (v >> 11) * (1.0/9007199254740992.0); } /** From 35fe6f624c43ec4c8417bed2f67e2a0d9114fc0a Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Fri, 2 Oct 2020 18:14:05 +0200 Subject: [PATCH 005/126] apply clang format to proto files (#4123) * add proto files to clangify * apply clangify to proto files * remove blocksonsingleline, it didn't actually do anything also add missing space to the travis warning, emoji are monospace too --- .ci/travis-lint.sh | 2 +- .clang-format | 4 ++++ clangify.sh | 2 +- common/pb/admin_commands.proto | 1 - common/pb/command_attach_card.proto | 2 -- .../pb/command_change_zone_properties.proto | 2 +- common/pb/command_concede.proto | 1 - common/pb/command_create_token.proto | 2 -- common/pb/command_deck_del_dir.proto | 1 - common/pb/command_deck_download.proto | 1 - common/pb/command_deck_new_dir.proto | 1 - common/pb/command_deck_upload.proto | 4 ++-- common/pb/command_draw_cards.proto | 1 - common/pb/command_flip_card.proto | 2 -- common/pb/command_game_say.proto | 2 -- common/pb/command_kick_from_game.proto | 3 +-- common/pb/command_leave_game.proto | 1 - common/pb/command_move_card.proto | 1 - common/pb/command_mulligan.proto | 1 - common/pb/command_replay_modify_match.proto | 1 - common/pb/command_reverse_turn.proto | 3 +-- common/pb/command_roll_die.proto | 1 - common/pb/command_set_sideboard_plan.proto | 1 - common/pb/command_shuffle.proto | 1 - common/pb/commands.proto | 4 ++-- common/pb/event_change_zone_properties.proto | 2 +- common/pb/event_game_host_changed.proto | 1 - common/pb/event_kicked.proto | 1 - common/pb/event_leave_room.proto | 3 --- common/pb/event_notify_user.proto | 3 +-- common/pb/event_room_say.proto | 1 - common/pb/isl_message.proto | 6 ++--- common/pb/moderator_commands.proto | 15 ++++++------ common/pb/response.proto | 24 ++++++++++--------- common/pb/response_adjust_mod.proto | 2 +- common/pb/response_ban_history.proto | 2 +- common/pb/response_get_games_of_user.proto | 1 - common/pb/response_get_user_info.proto | 2 -- common/pb/response_list_users.proto | 1 - common/pb/response_replay_download.proto | 1 - common/pb/response_viewlog_history.proto | 2 +- common/pb/response_warn_history.proto | 2 +- common/pb/response_warn_list.proto | 2 +- common/pb/server_message.proto | 2 +- common/pb/serverinfo_ban.proto | 10 ++++---- common/pb/serverinfo_chat_message.proto | 10 ++++---- common/pb/serverinfo_gametype.proto | 1 - common/pb/serverinfo_replay_match.proto | 2 +- common/pb/serverinfo_warning.proto | 6 ++--- common/pb/serverinfo_zone.proto | 2 +- 50 files changed, 59 insertions(+), 90 deletions(-) diff --git a/.ci/travis-lint.sh b/.ci/travis-lint.sh index 51033ba8e..66407b7c9 100755 --- a/.ci/travis-lint.sh +++ b/.ci/travis-lint.sh @@ -18,7 +18,7 @@ case $err in *** Then commit and push those changes to this branch. *** *** Check our CONTRIBUTING.md file for more details. *** *** *** -*** Thank you ❤️ *** +*** Thank you ❤️ *** *** *** *********************************************************** diff --git a/.clang-format b/.clang-format index f2a5fa559..6347a2037 100644 --- a/.clang-format +++ b/.clang-format @@ -25,3 +25,7 @@ IndentCaseLabels: true PointerAlignment: Right SortIncludes: true IncludeBlocks: Regroup +--- +Language: Proto +AllowShortFunctionsOnASingleLine: None +SpacesInContainerLiterals: false diff --git a/clangify.sh b/clangify.sh index f2494cf50..238d32338 100755 --- a/clangify.sh +++ b/clangify.sh @@ -18,7 +18,7 @@ exclude=("servatrice/src/smtp" \ "oracle/src/zip" \ "oracle/src/lzma" \ "oracle/src/qt-json") -exts=("cpp" "h") +exts=("cpp" "h" "proto") cf_cmd="clang-format" branch="origin/master" diff --git a/common/pb/admin_commands.proto b/common/pb/admin_commands.proto index 6f974d92f..8faaec2d2 100644 --- a/common/pb/admin_commands.proto +++ b/common/pb/admin_commands.proto @@ -37,4 +37,3 @@ message Command_AdjustMod { optional bool should_be_mod = 2; optional bool should_be_judge = 3; } - diff --git a/common/pb/command_attach_card.proto b/common/pb/command_attach_card.proto index 654e57c5f..9e13edc7b 100644 --- a/common/pb/command_attach_card.proto +++ b/common/pb/command_attach_card.proto @@ -10,5 +10,3 @@ message Command_AttachCard { optional string target_zone = 4; optional sint32 target_card_id = 5 [default = -1]; } - - diff --git a/common/pb/command_change_zone_properties.proto b/common/pb/command_change_zone_properties.proto index f89e36aaa..99659ddaa 100644 --- a/common/pb/command_change_zone_properties.proto +++ b/common/pb/command_change_zone_properties.proto @@ -6,6 +6,6 @@ message Command_ChangeZoneProperties { optional Command_ChangeZoneProperties ext = 1031; } optional string zone_name = 1; - + optional bool always_reveal_top_card = 10; } diff --git a/common/pb/command_concede.proto b/common/pb/command_concede.proto index 8377d958f..9bec8e48c 100644 --- a/common/pb/command_concede.proto +++ b/common/pb/command_concede.proto @@ -6,7 +6,6 @@ message Command_Concede { } } - message Command_Unconcede { extend GameCommand { optional Command_Unconcede ext = 1032; diff --git a/common/pb/command_create_token.proto b/common/pb/command_create_token.proto index 9fc61b70e..d54256484 100644 --- a/common/pb/command_create_token.proto +++ b/common/pb/command_create_token.proto @@ -15,5 +15,3 @@ message Command_CreateToken { optional string target_zone = 9; optional sint32 target_card_id = 10 [default = -1]; } - - diff --git a/common/pb/command_deck_del_dir.proto b/common/pb/command_deck_del_dir.proto index 3364cac42..10790749f 100644 --- a/common/pb/command_deck_del_dir.proto +++ b/common/pb/command_deck_del_dir.proto @@ -7,4 +7,3 @@ message Command_DeckDelDir { } optional string path = 1; } - diff --git a/common/pb/command_deck_download.proto b/common/pb/command_deck_download.proto index 19dda1528..610a2e785 100644 --- a/common/pb/command_deck_download.proto +++ b/common/pb/command_deck_download.proto @@ -7,4 +7,3 @@ message Command_DeckDownload { } optional sint32 deck_id = 1 [default = -1]; } - diff --git a/common/pb/command_deck_new_dir.proto b/common/pb/command_deck_new_dir.proto index b6e24afcd..7611fa531 100644 --- a/common/pb/command_deck_new_dir.proto +++ b/common/pb/command_deck_new_dir.proto @@ -8,4 +8,3 @@ message Command_DeckNewDir { optional string path = 1; optional string dir_name = 2; } - diff --git a/common/pb/command_deck_upload.proto b/common/pb/command_deck_upload.proto index 0f250c5f0..63d9c80ef 100644 --- a/common/pb/command_deck_upload.proto +++ b/common/pb/command_deck_upload.proto @@ -5,7 +5,7 @@ message Command_DeckUpload { extend SessionCommand { optional Command_DeckUpload ext = 1013; } - optional string path = 1; // to upload a new deck - optional uint32 deck_id = 2; // to replace an existing deck + optional string path = 1; // to upload a new deck + optional uint32 deck_id = 2; // to replace an existing deck optional string deck_list = 3; } diff --git a/common/pb/command_draw_cards.proto b/common/pb/command_draw_cards.proto index 6851ac00c..d95e05787 100644 --- a/common/pb/command_draw_cards.proto +++ b/common/pb/command_draw_cards.proto @@ -6,4 +6,3 @@ message Command_DrawCards { } optional uint32 number = 1; } - diff --git a/common/pb/command_flip_card.proto b/common/pb/command_flip_card.proto index 07f034009..fe5047199 100644 --- a/common/pb/command_flip_card.proto +++ b/common/pb/command_flip_card.proto @@ -9,5 +9,3 @@ message Command_FlipCard { optional bool face_down = 3; optional string pt = 4; } - - diff --git a/common/pb/command_game_say.proto b/common/pb/command_game_say.proto index 6aa47e0e2..2011ee096 100644 --- a/common/pb/command_game_say.proto +++ b/common/pb/command_game_say.proto @@ -6,5 +6,3 @@ message Command_GameSay { } optional string message = 1; } - - diff --git a/common/pb/command_kick_from_game.proto b/common/pb/command_kick_from_game.proto index e95037c71..331ec2547 100644 --- a/common/pb/command_kick_from_game.proto +++ b/common/pb/command_kick_from_game.proto @@ -3,7 +3,6 @@ import "game_commands.proto"; message Command_KickFromGame { extend GameCommand { optional Command_KickFromGame ext = 1000; -} + } optional sint32 player_id = 1 [default = -1]; } - diff --git a/common/pb/command_leave_game.proto b/common/pb/command_leave_game.proto index afa1e6c4e..8518cf2fc 100644 --- a/common/pb/command_leave_game.proto +++ b/common/pb/command_leave_game.proto @@ -5,4 +5,3 @@ message Command_LeaveGame { optional Command_LeaveGame ext = 1001; } } - diff --git a/common/pb/command_move_card.proto b/common/pb/command_move_card.proto index ad3f7c573..590a834b3 100644 --- a/common/pb/command_move_card.proto +++ b/common/pb/command_move_card.proto @@ -23,4 +23,3 @@ message Command_MoveCard { optional sint32 x = 6 [default = -1]; optional sint32 y = 7 [default = -1]; } - diff --git a/common/pb/command_mulligan.proto b/common/pb/command_mulligan.proto index 38ed5a364..a48b1a9ca 100644 --- a/common/pb/command_mulligan.proto +++ b/common/pb/command_mulligan.proto @@ -6,4 +6,3 @@ message Command_Mulligan { } optional uint32 number = 7; } - diff --git a/common/pb/command_replay_modify_match.proto b/common/pb/command_replay_modify_match.proto index 6b342f44a..94e35db20 100644 --- a/common/pb/command_replay_modify_match.proto +++ b/common/pb/command_replay_modify_match.proto @@ -8,4 +8,3 @@ message Command_ReplayModifyMatch { optional sint32 game_id = 1 [default = -1]; optional bool do_not_hide = 2; } - diff --git a/common/pb/command_reverse_turn.proto b/common/pb/command_reverse_turn.proto index 193379a0a..c5cc1c4d9 100644 --- a/common/pb/command_reverse_turn.proto +++ b/common/pb/command_reverse_turn.proto @@ -3,6 +3,5 @@ import "game_commands.proto"; message Command_ReverseTurn { extend GameCommand { optional Command_ReverseTurn ext = 1034; - } + } } - diff --git a/common/pb/command_roll_die.proto b/common/pb/command_roll_die.proto index bdcc7b51c..0d0c48075 100644 --- a/common/pb/command_roll_die.proto +++ b/common/pb/command_roll_die.proto @@ -6,4 +6,3 @@ message Command_RollDie { } optional uint32 sides = 1; } - diff --git a/common/pb/command_set_sideboard_plan.proto b/common/pb/command_set_sideboard_plan.proto index 7ed0d10cb..89c676d2d 100644 --- a/common/pb/command_set_sideboard_plan.proto +++ b/common/pb/command_set_sideboard_plan.proto @@ -8,4 +8,3 @@ message Command_SetSideboardPlan { } repeated MoveCard_ToZone move_list = 1; } - diff --git a/common/pb/command_shuffle.proto b/common/pb/command_shuffle.proto index 507195a48..a99f90896 100644 --- a/common/pb/command_shuffle.proto +++ b/common/pb/command_shuffle.proto @@ -8,4 +8,3 @@ message Command_Shuffle { optional sint32 start = 2 [default = 0]; optional sint32 end = 3 [default = -1]; } - diff --git a/common/pb/commands.proto b/common/pb/commands.proto index b417550a3..b6eaf6733 100644 --- a/common/pb/commands.proto +++ b/common/pb/commands.proto @@ -7,10 +7,10 @@ import "admin_commands.proto"; message CommandContainer { optional uint64 cmd_id = 1; - + optional uint32 game_id = 10; optional uint32 room_id = 20; - + repeated SessionCommand session_command = 100; repeated GameCommand game_command = 101; repeated RoomCommand room_command = 102; diff --git a/common/pb/event_change_zone_properties.proto b/common/pb/event_change_zone_properties.proto index 0f1deb6d9..5dd56c7e4 100644 --- a/common/pb/event_change_zone_properties.proto +++ b/common/pb/event_change_zone_properties.proto @@ -6,6 +6,6 @@ message Event_ChangeZoneProperties { optional Event_ChangeZoneProperties ext = 2020; } optional string zone_name = 1; - + optional bool always_reveal_top_card = 10; } diff --git a/common/pb/event_game_host_changed.proto b/common/pb/event_game_host_changed.proto index 50e3f968f..0afe2effb 100644 --- a/common/pb/event_game_host_changed.proto +++ b/common/pb/event_game_host_changed.proto @@ -6,4 +6,3 @@ message Event_GameHostChanged { optional Event_GameHostChanged ext = 1003; } } - diff --git a/common/pb/event_kicked.proto b/common/pb/event_kicked.proto index e1fd57bdd..02036cee7 100644 --- a/common/pb/event_kicked.proto +++ b/common/pb/event_kicked.proto @@ -6,4 +6,3 @@ message Event_Kicked { optional Event_Kicked ext = 1004; } } - diff --git a/common/pb/event_leave_room.proto b/common/pb/event_leave_room.proto index 98dafe2d4..dc9f3e866 100644 --- a/common/pb/event_leave_room.proto +++ b/common/pb/event_leave_room.proto @@ -7,6 +7,3 @@ message Event_LeaveRoom { } optional string name = 1; } - - - diff --git a/common/pb/event_notify_user.proto b/common/pb/event_notify_user.proto index 5cda9578a..3a90d278b 100644 --- a/common/pb/event_notify_user.proto +++ b/common/pb/event_notify_user.proto @@ -4,7 +4,7 @@ import "session_event.proto"; message Event_NotifyUser { enum NotificationType { - UNKNOWN = 0; // Default enum value if no "type" is defined when used + UNKNOWN = 0; // Default enum value if no "type" is defined when used PROMOTED = 1; WARNING = 2; IDLEWARNING = 3; @@ -18,5 +18,4 @@ message Event_NotifyUser { optional string warning_reason = 2; optional string custom_title = 3; optional string custom_content = 4; - } diff --git a/common/pb/event_room_say.proto b/common/pb/event_room_say.proto index b5d02c443..2c6a990ea 100644 --- a/common/pb/event_room_say.proto +++ b/common/pb/event_room_say.proto @@ -14,5 +14,4 @@ message Event_RoomSay { optional string message = 2; optional RoomMessageType message_type = 3; optional uint64 time_of = 4; - } diff --git a/common/pb/isl_message.proto b/common/pb/isl_message.proto index 125205334..d4bb8d785 100644 --- a/common/pb/isl_message.proto +++ b/common/pb/isl_message.proto @@ -9,17 +9,17 @@ message IslMessage { enum MessageType { GAME_COMMAND_CONTAINER = 0; ROOM_COMMAND_CONTAINER = 1; - + RESPONSE = 10; SESSION_EVENT = 11; GAME_EVENT_CONTAINER = 12; ROOM_EVENT = 13; } optional MessageType message_type = 1; - + optional uint64 session_id = 9; optional sint32 player_id = 10 [default = -1]; - + optional CommandContainer game_command = 100; optional CommandContainer room_command = 101; diff --git a/common/pb/moderator_commands.proto b/common/pb/moderator_commands.proto index d7050ad1b..cc2156b86 100644 --- a/common/pb/moderator_commands.proto +++ b/common/pb/moderator_commands.proto @@ -60,13 +60,12 @@ message Command_ViewLogHistory { extend ModeratorCommand { optional Command_ViewLogHistory ext = 1005; } - optional string user_name = 1; // user that created message - optional string ip_address = 2; // ip address of user that created message - optional string game_name = 3; // client id of user that created the message - optional string game_id = 4; // game number the message was sent to - optional string message = 5; // raw message that was sent - repeated string log_location = 6; // destination of message (ex: main room, game room, private chat) - required uint32 date_range = 7; // the length of time (in minutes) to look back for + optional string user_name = 1; // user that created message + optional string ip_address = 2; // ip address of user that created message + optional string game_name = 3; // client id of user that created the message + optional string game_id = 4; // game number the message was sent to + optional string message = 5; // raw message that was sent + repeated string log_location = 6; // destination of message (ex: main room, game room, private chat) + required uint32 date_range = 7; // the length of time (in minutes) to look back for optional uint32 maximum_results = 8; // the maximum number of query results - } diff --git a/common/pb/response.proto b/common/pb/response.proto index 75bca97a8..e2b3a71af 100644 --- a/common/pb/response.proto +++ b/common/pb/response.proto @@ -25,21 +25,23 @@ message Response { RespAccessDenied = 20; RespUsernameInvalid = 21; RespRegistrationRequired = 22; - RespRegistrationAccepted = 23; // Server agrees to process client's registration request - RespUserAlreadyExists = 24; // Client attempted to register a name which is already registered + RespRegistrationAccepted = 23; // Server agrees to process client's registration request + RespUserAlreadyExists = 24; // Client attempted to register a name which is already registered RespEmailRequiredToRegister = 25; // Server requires email to register accounts but client did not provide one - RespTooManyRequests = 26; // Server refused to complete command because client has sent too many too quickly + RespTooManyRequests = 26; // Server refused to complete command because client has sent too many too quickly RespPasswordTooShort = 27; // Server requires a decent password - RespAccountNotActivated = 28; // Client attempted to log into a registered username but the account hasn't been activated + RespAccountNotActivated = + 28; // Client attempted to log into a registered username but the account hasn't been activated RespRegistrationDisabled = 29; // Server does not allow clients to register - RespRegistrationFailed = 30; // Server accepted a reg request but failed to perform the registration - RespActivationAccepted = 31; // Server accepted a reg user activation token - RespActivationFailed = 32; // Server didn't accept a reg user activation token - RespRegistrationAcceptedNeedsActivation = 33; // Server accepted cient registration, but it will need token activation + RespRegistrationFailed = 30; // Server accepted a reg request but failed to perform the registration + RespActivationAccepted = 31; // Server accepted a reg user activation token + RespActivationFailed = 32; // Server didn't accept a reg user activation token + RespRegistrationAcceptedNeedsActivation = + 33; // Server accepted cient registration, but it will need token activation RespClientIdRequired = 34; // Server requires client to generate and send its client id before allowing access RespClientUpdateRequired = 35; // Client is missing features that the server is requiring - RespServerFull = 36; // Server user limit reached - RespEmailBlackListed = 37; // Server has blacklisted the email address provided for registration + RespServerFull = 36; // Server user limit reached + RespEmailBlackListed = 37; // Server has blacklisted the email address provided for registration } enum ResponseType { JOIN_ROOM = 1000; @@ -64,6 +66,6 @@ message Response { } required uint64 cmd_id = 1; optional ResponseCode response_code = 2; - + extensions 100 to max; } diff --git a/common/pb/response_adjust_mod.proto b/common/pb/response_adjust_mod.proto index c91493f8f..948eec1ba 100644 --- a/common/pb/response_adjust_mod.proto +++ b/common/pb/response_adjust_mod.proto @@ -1,7 +1,7 @@ syntax = "proto2"; import "response.proto"; -message Response_AdjustMod{ +message Response_AdjustMod { extend Response { optional Response_AdjustMod ext = 1011; } diff --git a/common/pb/response_ban_history.proto b/common/pb/response_ban_history.proto index de587d6d9..696004152 100644 --- a/common/pb/response_ban_history.proto +++ b/common/pb/response_ban_history.proto @@ -2,7 +2,7 @@ syntax = "proto2"; import "response.proto"; import "serverinfo_ban.proto"; -message Response_BanHistory{ +message Response_BanHistory { extend Response { optional Response_BanHistory ext = 1012; } diff --git a/common/pb/response_get_games_of_user.proto b/common/pb/response_get_games_of_user.proto index f179bcc62..dd0ddd160 100644 --- a/common/pb/response_get_games_of_user.proto +++ b/common/pb/response_get_games_of_user.proto @@ -10,4 +10,3 @@ message Response_GetGamesOfUser { repeated ServerInfo_Room room_list = 1; repeated ServerInfo_Game game_list = 2; } - diff --git a/common/pb/response_get_user_info.proto b/common/pb/response_get_user_info.proto index fbfaaf217..0332a07ad 100644 --- a/common/pb/response_get_user_info.proto +++ b/common/pb/response_get_user_info.proto @@ -8,5 +8,3 @@ message Response_GetUserInfo { } optional ServerInfo_User user_info = 1; } - - diff --git a/common/pb/response_list_users.proto b/common/pb/response_list_users.proto index 825ae6f7f..d653521dd 100644 --- a/common/pb/response_list_users.proto +++ b/common/pb/response_list_users.proto @@ -8,4 +8,3 @@ message Response_ListUsers { } repeated ServerInfo_User user_list = 1; } - diff --git a/common/pb/response_replay_download.proto b/common/pb/response_replay_download.proto index d263f8d9b..1805280c1 100644 --- a/common/pb/response_replay_download.proto +++ b/common/pb/response_replay_download.proto @@ -7,4 +7,3 @@ message Response_ReplayDownload { } optional bytes replay_data = 1; } - diff --git a/common/pb/response_viewlog_history.proto b/common/pb/response_viewlog_history.proto index 2572d7d22..2ec0aff28 100644 --- a/common/pb/response_viewlog_history.proto +++ b/common/pb/response_viewlog_history.proto @@ -2,7 +2,7 @@ syntax = "proto2"; import "response.proto"; import "serverinfo_chat_message.proto"; -message Response_ViewLogHistory{ +message Response_ViewLogHistory { extend Response { optional Response_ViewLogHistory ext = 1015; } diff --git a/common/pb/response_warn_history.proto b/common/pb/response_warn_history.proto index a43180f3b..8108ececf 100644 --- a/common/pb/response_warn_history.proto +++ b/common/pb/response_warn_history.proto @@ -2,7 +2,7 @@ syntax = "proto2"; import "response.proto"; import "serverinfo_warning.proto"; -message Response_WarnHistory{ +message Response_WarnHistory { extend Response { optional Response_WarnHistory ext = 1013; } diff --git a/common/pb/response_warn_list.proto b/common/pb/response_warn_list.proto index 8d893103b..d48352529 100644 --- a/common/pb/response_warn_list.proto +++ b/common/pb/response_warn_list.proto @@ -1,7 +1,7 @@ syntax = "proto2"; import "response.proto"; -message Response_WarnList{ +message Response_WarnList { extend Response { optional Response_WarnList ext = 1014; } diff --git a/common/pb/server_message.proto b/common/pb/server_message.proto index a9330ab3c..50bbfb0fd 100644 --- a/common/pb/server_message.proto +++ b/common/pb/server_message.proto @@ -12,7 +12,7 @@ message ServerMessage { ROOM_EVENT = 3; } optional MessageType message_type = 1; - + optional Response response = 2; optional SessionEvent session_event = 3; optional GameEventContainer game_event_container = 4; diff --git a/common/pb/serverinfo_ban.proto b/common/pb/serverinfo_ban.proto index 2ad7ffd4d..241fc3b50 100644 --- a/common/pb/serverinfo_ban.proto +++ b/common/pb/serverinfo_ban.proto @@ -3,10 +3,10 @@ syntax = "proto2"; * Historical ban information stored in the ban table */ message ServerInfo_Ban { - required string admin_id = 1; // id of the staff member placing the ban - required string admin_name = 2; // name of the staff member placing the ban - required string ban_time = 3; // start time of the ban - required string ban_length = 4; // amount of time in minutes the ban is for - optional string ban_reason = 5; // reason seen only by moderation staff + required string admin_id = 1; // id of the staff member placing the ban + required string admin_name = 2; // name of the staff member placing the ban + required string ban_time = 3; // start time of the ban + required string ban_length = 4; // amount of time in minutes the ban is for + optional string ban_reason = 5; // reason seen only by moderation staff optional string visible_reason = 6; // reason shown to the user } diff --git a/common/pb/serverinfo_chat_message.proto b/common/pb/serverinfo_chat_message.proto index 27d4386e5..d47ec4fc7 100644 --- a/common/pb/serverinfo_chat_message.proto +++ b/common/pb/serverinfo_chat_message.proto @@ -5,12 +5,12 @@ syntax = "proto2"; * These communications are also stored in the DB log table. */ message ServerInfo_ChatMessage { - optional string time = 1; // time chat was sent - optional string sender_id = 2; // id of sender + optional string time = 1; // time chat was sent + optional string sender_id = 2; // id of sender optional string sender_name = 3; // name of sender - optional string sender_ip = 4; // ip of sender - optional string message = 5; // message + optional string sender_ip = 4; // ip of sender + optional string message = 5; // message optional string target_type = 6; // target type (room,game,chat) - optional string target_id = 7; // id of target + optional string target_id = 7; // id of target optional string target_name = 8; // name of target } diff --git a/common/pb/serverinfo_gametype.proto b/common/pb/serverinfo_gametype.proto index a135b89b6..b73841be4 100644 --- a/common/pb/serverinfo_gametype.proto +++ b/common/pb/serverinfo_gametype.proto @@ -3,4 +3,3 @@ message ServerInfo_GameType { optional sint32 game_type_id = 1; optional string description = 2; }; - diff --git a/common/pb/serverinfo_replay_match.proto b/common/pb/serverinfo_replay_match.proto index 05abeb945..7fdc6471b 100644 --- a/common/pb/serverinfo_replay_match.proto +++ b/common/pb/serverinfo_replay_match.proto @@ -3,7 +3,7 @@ import "serverinfo_replay.proto"; message ServerInfo_ReplayMatch { repeated ServerInfo_Replay replay_list = 1; - + optional sint32 game_id = 2 [default = -1]; optional string room_name = 3; optional uint32 time_started = 4; diff --git a/common/pb/serverinfo_warning.proto b/common/pb/serverinfo_warning.proto index f2efed576..20287be06 100644 --- a/common/pb/serverinfo_warning.proto +++ b/common/pb/serverinfo_warning.proto @@ -3,8 +3,8 @@ syntax = "proto2"; * Historical warning information stored in the warnings table */ message ServerInfo_Warning { - optional string user_name = 1; // name of user being warned + optional string user_name = 1; // name of user being warned optional string admin_name = 2; // name of the moderator making the warning - optional string reason = 3; // type of warning being placed - optional string time_of = 4; // time of warning + optional string reason = 3; // type of warning being placed + optional string time_of = 4; // time of warning } diff --git a/common/pb/serverinfo_zone.proto b/common/pb/serverinfo_zone.proto index d359613c4..0f0e53cc6 100644 --- a/common/pb/serverinfo_zone.proto +++ b/common/pb/serverinfo_zone.proto @@ -11,7 +11,7 @@ message ServerInfo_Zone { // setting beingLookedAt to true. // Cards in a zone with the type HiddenZone are referenced by their // list index, whereas cards in any other zone are referenced by their ids. - + PrivateZone = 0; PublicZone = 1; HiddenZone = 2; From 48c64587661fd8e9b79ecae3bac2615058804427 Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Fri, 2 Oct 2020 18:14:44 +0200 Subject: [PATCH 006/126] remove nonfunctional mana artifact detection code (#4121) mana artifacts will use the stack and be placed in the normal tablerow if you want to put it with your lands you're free to do so, as long as you promise to not say oh this should not be here three turns after shatterstorm resolved --- oracle/src/oracleimporter.cpp | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/oracle/src/oracleimporter.cpp b/oracle/src/oracleimporter.cpp index ebad71283..468aa0b06 100644 --- a/oracle/src/oracleimporter.cpp +++ b/oracle/src/oracleimporter.cpp @@ -146,23 +146,10 @@ CardInfoPtr OracleImporter::addCard(QString name, (text.contains(name + " enters the battlefield tapped") && !text.contains(name + " enters the battlefield tapped unless")); - // detect mana generator artifacts - QStringList cardTextRows = text.split("\n"); - bool mArtifact = false; - QString cardType = properties.value("type").toString(); - if (cardType.endsWith("Artifact")) { - for (int i = 0; i < cardTextRows.size(); ++i) { - cardTextRows[i].remove(QRegularExpression(R"(\".*?\")")); - if (cardTextRows[i].contains("{T}") && cardTextRows[i].contains("to your mana pool")) { - mArtifact = true; - } - } - } - // table row int tableRow = 1; QString mainCardType = properties.value("maintype").toString(); - if ((mainCardType == "Land") || mArtifact) + if ((mainCardType == "Land")) tableRow = 0; else if ((mainCardType == "Sorcery") || (mainCardType == "Instant")) tableRow = 3; From e33f802ae832d0c5b79f8f249c9287d8cc35bd54 Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Fri, 2 Oct 2020 19:54:12 +0200 Subject: [PATCH 007/126] update CONTRIBUTING.md (#4125) --- .github/CONTRIBUTING.md | 235 ++++++++++++++++++++++++++++++---------- 1 file changed, 180 insertions(+), 55 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 41defe1ea..bc08341d1 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,4 +1,6 @@ -  [Introduction](#contributing-to-cockatrice) | [Code Style Guide](#code-style-guide) | [Translations](#translations) | [Release Management](#release-management) +  [Introduction](#contributing-to-cockatrice) | [Code Style Guide]( +#code-style-guide) | [Translations](#translations) | [Release Management]( +#release-management) ---- @@ -7,50 +9,83 @@ # Contributing to Cockatrice # First off, thanks for taking the time to contribute to our project! 🎉 ❤ ️✨ -The following is a set of guidelines for contributing to Cockatrice. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. +The following is a set of guidelines for contributing to Cockatrice. These are +mostly guidelines, not rules. Use your best judgment, and feel free to propose +changes to this document in a pull request. # Recommended Setups # -For those developers who like the Linux or MacOS environment, many of our developers like working with a nifty program called [CLion](https://www.jetbrains.com/clion/). The program's a great asset and one of the best tools you'll find on these systems, but you're welcomed to use any IDE you most enjoy. +For those developers who like the Linux or MacOS environment, many of our +developers like working with a nifty program called [CLion]( +https://www.jetbrains.com/clion/). The program's a great asset and one of the +best tools you'll find on these systems, but you're welcomed to use any IDE +you most enjoy. -Developers who like Windows development tend to find [Visual Studio](https://www.visualstudio.com/) the best tool for the job. +Developers who like Windows development tend to find [Visual Studio]( +https://www.visualstudio.com/) the best tool for the job. -If you have any questions on IDEs, feel free to chat with us on [Gitter](https://gitter.im/Cockatrice/Cockatrice) and we would love to help answer your questions! +If you have any questions on IDEs, feel free to chat with us on [Gitter]( +https://gitter.im/Cockatrice/Cockatrice) and we would love to help answer +your questions! # Code Style Guide # ### Formatting and continuous integration (ci) ### -We currently use Travis CI to check your code for formatting issues, if your pull request was rejected because of this it would show a message in the logs. Click on "Details" next to the failed Travis CI build and then click on the failed build (most likely the fastest one) to see the log. +We use a separate job on Travis CI to check your code for formatting issues, if +your pull request was rejected you can check the output on their website. +Click on "Details" next to the failed Travis CI build and then click on the +Linting build (the fastest one) to see the log. -The message will look somewhat similar to this: +The message will look like this: ``` -************************************************************ -*** Your code does not meet our formatting guidelines. *** -*** Please correct it then commit and push your changes. *** -*** See our CONTRIBUTING.md file for more information. *** -************************************************************ +*********************************************************** +*** *** +*** Your code does not comply with our style guide. *** +*** *** +*** Please correct it or run the "clangify.sh" script. *** +*** Then commit and push those changes to this branch. *** +*** Check our CONTRIBUTING.md file for more details. *** +*** *** +*** Thank you ❤️ *** +*** *** +*********************************************************** ``` -The CONTRIBUTING.md file mentioned is this file. Please read [this section](#Formatting) for full information on our formatting guidelines. +The CONTRIBUTING.md file mentioned is this file. Please read [this section]( +#formatting) for full information on our formatting guidelines. ### Compatibility ### -Cockatrice is currently compiled on all platforms using C++11. You'll notice C++03 code throughout the codebase. Please feel free to help convert it over! +Cockatrice is currently compiled on all platforms using C++11. +You'll notice C++03 code throughout the codebase. Please feel free +to help convert it over! -For consistency, we use Qt data structures where possible. For example, `QString` over -`std::string` and `QList` over `std::vector`. +For consistency, we use Qt data structures where possible. For example, +`QString` over `std::string` and `QList` over `std::vector`. + +Do not use old c style casts in new code, instead use a [`static_cast<>`]( +https://en.cppreference.com/w/cpp/language/static_cast) +or other appropriate conversion. ### Formatting ### -The handy tool `clang-format` can format your code for you, it is available for almost any environment. A special `.clang-format` configuration file is included in the project and is used to format your code. +The handy tool `clang-format` can format your code for you, it is available for +almost any environment. A special `.clang-format` configuration file is +included in the project and is used to format your code. -We've also included a bash script, `clangify.sh`, that will use clang-format to format all files in one go. Use `./clangify.sh --help` to show a full help page. +We've also included a bash script, `clangify.sh`, that will use clang-format to +format all files in your pr in one go. Use `./clangify.sh --help` to show a +full help page. -To run clang-format on a single source file simply use the command `clang-format -i ` to format it in place. (some systems install clang-format with a specific version number appended, `find /usr/bin -name clang-format*` should find it for you) +To run clang-format on a single source file simply use the command +`clang-format -i ` to format it in place. (some systems install +clang-format with a specific version number appended, +`find /usr/bin -name clang-format*` should find it for you) -See [the clang-format documentation](https://clang.llvm.org/docs/ClangFormat.html) for more information about the tool. +See [the clang-format documentation]( +https://clang.llvm.org/docs/ClangFormat.html) for more information about the tool. #### Header files #### @@ -88,9 +123,15 @@ Group library includes after project includes, and in alphabetic order. Like thi Use `UpperCamelCase` for classes, structs, enums, etc. and `lowerCamelCase` for function and variable names. -Member variables aren't decorated in any way. Don't prefix or suffix with +Don't use [Hungarian Notation]( +https://en.wikipedia.org/wiki/Hungarian_notation). + +Member variables aren't decorated in any way. Don't prefix or suffix them with underscores, etc. +Use a separate line for each declaration, don't use a single line like this +`int one = 1, two = 2` and instead split them into two lines. + For arguments to constructors which have the same names as member variables, prefix those arguments with underscores: ```c++ @@ -115,7 +156,8 @@ If you find any usage of the old keywords, we encourage you to fix it. #### Braces #### -Braces should go on their own line except for control statements, the use of braces around single line statements is preferred. +Braces should go on their own line except for control statements, the use of +braces around single line statements is preferred. See the following example: ```c++ int main() @@ -136,17 +178,25 @@ int main() #### Indentation and Spacing #### -Always indent using 4 spaces, do not use tabs. Opening and closing braces should be on the same indentation layer, member access specifiers in classes or structs should not be indented. +Always indent using 4 spaces, do not use tabs. Opening and closing braces +should be on the same indentation layer, member access specifiers in classes or +structs should not be indented. -All operators and braces should be separated by spaces, do not add a space next to the inside of a brace. +All operators and braces should be separated by spaces, do not add a space next +to the inside of a brace. -If multiple lines of code that follow eachother have single line comments behind them, place all of them on the same indentation level. This indentation level should be equal to the longest line of code for each of these comments, without added spacing. +If multiple lines of code that follow eachother have single line comments +behind them, place all of them on the same indentation level. This indentation +level should be equal to the longest line of code for each of these comments, +without added spacing. #### Lines #### -Do not have trailing whitespace in your lines. Most IDEs check for this nowadays and clean it up for you. +Do not leave trailing whitespace on any line. Most IDEs check for this +nowadays and clean it up for you. -Lines should be 120 characters or less. Please break up lines that are too long into smaller parts, for example at spaces or after opening a brace. +Lines should be 120 characters or less. Please break up lines that are too long +into smaller parts, for example at spaces or after opening a brace. ### Memory Management ### @@ -182,21 +232,52 @@ as `QScopedPointer`, or, less preferably, `QSharedPointer`. The servatrice database's schema can be found at `servatrice/servatrice.sql`. Everytime the schema gets modified, some other steps are due: 1. Increment the value of `cockatrice_schema_version` in `servatrice.sql`; - 2. Increment the value of `DATABASE_SCHEMA_VERSION` in `servatrice_database_interface.h` accordingly; - 3. Create a new migration file inside the `servatrice/migrations` directory named after the new schema version. - 4. Run the `servatrice/check_schema_version.sh` script to ensure everything is fine. + 2. Increment the value of `DATABASE_SCHEMA_VERSION` in + `servatrice_database_interface.h` accordingly; + 3. Create a new migration file inside the `servatrice/migrations` directory + named after the new schema version. + 4. Run the `servatrice/check_schema_version.sh` script to ensure everything is + fine. -The migration file should include the sql statements needed to migrate the database schema and data from the previous to the new version, and an additional statement that updates `cockatrice_schema_version` to the correct value. +The migration file should include the sql statements needed to migrate the +database schema and data from the previous to the new version, and an +additional statement that updates `cockatrice_schema_version` to the correct +value. -Ensure that the migration produces the expected effects; e.g. if you add a new column, make sure the migration places it in the same order as servatrice.sql. +Ensure that the migration produces the expected effects; e.g. if you add a +new column, make sure the migration places it in the same order as +servatrice.sql. ### Protocol buffer ### -Cockatrice and Servatrice exchange data using binary messages. The syntax of these messages is defined in the `proto` files in the `common/pb` folder. These files defines the way data contained in each message is serialized using Google's [protocol buffer](https://developers.google.com/protocol-buffers/). -Any change to the `proto` file should be taken with caution and tested intensively before being merged, becaus a change to the protocol could make new clients incompatible to the old server and vice versa. +Cockatrice and Servatrice exchange data using binary messages. The syntax of +these messages is defined in the `proto` files in the `common/pb` folder. These +files define the way data contained in each message is serialized using +Google's [protocol buffers](https://developers.google.com/protocol-buffers/). +Any change to the `proto` files should be taken with caution and tested +intensively before being merged, because a change to the protocol could make +new clients incompatible to the old server and vice versa. -You can find more information on how we use Protobuf on [our wiki!](https://github.com/Cockatrice/Cockatrice/wiki/Client-server-protocol) +You can find more information on how we use Protobuf on [our wiki!]( +https://github.com/Cockatrice/Cockatrice/wiki/Client-server-protocol) +# Reviewing Pull Requests # + +After you have finished your changes to the project you should put them on a +separate branch of your fork on github and open a [pull request]( +https://docs.github.com/en/free-pro-team@latest/desktop/contributing-and-collaborating-using-github-desktop/creating-an-issue-or-pull-request +). +Your code will then be automatically compiled by Travis CI for Linux and macOS, +and by Appveyor for Windows. Additionally Travis CI will perform a [Linting +check](#formatting-and-continuous-integration-ci). If any issues come up you +can check their status at the bottom of the pull request page, click on details +to go to the CI website and see the different build logs. + +If your pull request passes our tests and has no merge conflicts, it will be +reviewed by our team members. You can then address any requested changes. When +all changes have been approved your pull request will be squashed into a single +commit and merged into the master branch by a team member. Your change will then +be included in the next release 👍 # Translations # @@ -209,17 +290,35 @@ Basic workflow for translations: ### Using Translations (for developers) ### -All the user-interface strings inside Cockatrice's source code must be written in english. -Translations to other languages are managed using [Transifex](https://www.transifex.com/projects/p/cockatrice/). +All the user-interface strings inside Cockatrice's source code must be written +in English(US). +Translations to other languages are managed using [Transifex]( +https://www.transifex.com/projects/p/cockatrice/). -Adding a new string to translate is as easy as adding the string in the 'tr("")' function, the string will be picked up as translatable automatically and translated as needed. -For example setting the text of this label in a way that the string "My name is:" can be translated: +Adding a new string to translate is as easy as adding the string in the +'tr("")' function, the string will be picked up as translatable automatically +and translated as needed. +For example, setting the text of a label in the way that the string +`"My name is:"` can be translated: ```c++ nameLabel.setText(tr("My name is:")); ``` -If you're about to propose a change that adds or modifies any translatable string in the code, you don't need to take care of adding the new strings to the translation files. -Every few days, or when a lot of new strings have been added, someone from the development team will take care of extracting all the new strings and adding them to the english translation files and making them available to translators on Transifex. +To translate a string that would have plural forms you can add the amount to +the tr call, also you can add an extra string as a hint for translators: +```c++ +QString message = tr("Everyone draws %n cards", "pop up message", amount); +``` +See [QT's wiki on translations]( +https://doc.qt.io/qt-5/i18n-source-translation.html#handling-plurals) + +If you're about to propose a change that adds or modifies any translatable +string in the code, you don't need to take care of adding the new strings to +the translation files. +Every few days, or when a lot of new strings have been added, someone from the +development team will take care of extracting all the new strings and adding +them to the english translation files and making them available to translators +on Transifex. ### Maintaining Translations (for maintainers) ### @@ -253,8 +352,9 @@ cmake .. -DUPDATE_TRANSLATIONS=OFF ``` Now you are ready to propose your change. -Once your change gets merged, Transifex will pick up the modified files automatically (checked every 24 hours) -and update the interface where translators will be able to translate the new strings. +Once your change gets merged, Transifex will pick up the modified files +automatically (checked every 24 hours) and update the interface where +translators will be able to translate the new strings. ### Releasing Translations (for maintainers) ### @@ -272,16 +372,20 @@ from Transifex to the source code and vice versa. ### Adding Translations (for translators) ### -As a translator you can help translate the new strings on [Transifex](https://www.transifex.com/projects/p/cockatrice/). -Please have a look at the specific [FAQ for translators](https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ). +As a translator you can help translate the new strings on [Transifex]( +https://www.transifex.com/projects/p/cockatrice/). +Please have a look at the specific [FAQ for translators]( +https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ). # Release Management # ### Publishing A New Beta Release ### -Travis and AppVeyor have been configured to upload files to GitHub Releases whenever a tag is pushed.
-Usually, tags are created through publishing a (pre-)release, but there's a way around that. +Travis and AppVeyor have been configured to upload files to GitHub Releases +whenever a tag is pushed.
+Usually, tags are created through publishing a (pre-)release, but there's a way +around that. To trigger Travis and AppVeyor, simply do the following: ```bash @@ -302,21 +406,42 @@ $COCKATRICE_REPO - /Location/of/repository/cockatrice.git With *MAJ.MIN.PATCH* being the NEXT release version! ``` -This will cause a tagged release to be established on the GitHub repository, which will then lead to the upload of binaries. If you use this method, the tags (releases) that you create will be marked as a "Pre-release". The `/latest` URL will not be impacted (for stable release downloads) so that's good. +This will cause a tagged release to be established on the GitHub repository, +which will then lead to the upload of binaries. If you use this method, the +tags (releases) that you create will be marked as a "Pre-release". The +`/latest` URL will not be impacted (for stable release downloads) so that's +good. -If you accidentally push a tag incorrectly (the tag is outdated, you didn't pull in the latest branch accidentally, you named the tag wrong, etc.) you can revoke the tag by doing the following: +If you accidentally push a tag incorrectly (the tag is outdated, you didn't +pull in the latest branch accidentally, you named the tag wrong, etc.) you can +revoke the tag by doing the following: ```bash git push --delete upstream $TAG_NAME git tag -d $TAG_NAME ``` -**NOTE:** Unfortunately, due to the method of how Travis and AppVeyor work, to publish a stable release you will need to make a copy of the release notes locally and then paste them into the GitHub GUI once the binaries have been uploaded by them. These CI services will automatically overwrite the name of the release (to "Cockatrice $TAG_NAME"), the status of the release (to "Pre-release"), and the release body (to "Beta build of Cockatrice"). +**NOTE:** Unfortunately, due to the method of how Travis and AppVeyor work, to +publish a stable release you will need to make a copy of the release notes +locally and then paste them into the GitHub GUI once the binaries have been +uploaded by them. These CI services will automatically overwrite the name of +the release (to "Cockatrice $TAG_NAME"), the status of the release (to +"Pre-release"), and the release body (to "Beta build of Cockatrice"). -**NOTE 2:** In the first lines of https://github.com/Cockatrice/Cockatrice/blob/master/CMakeLists.txt there's an hardcoded version number used when compiling custom (not tagged) versions. While on tagged versions these numbers are overridden by the version numbers coming from the tag title, it's good practice to keep them aligned with the real ones. +**NOTE 2:** In the first lines of [CMakeLists.txt]( +https://github.com/Cockatrice/Cockatrice/blob/master/CMakeLists.txt) +there's an hardcoded version number used when compiling custom (not tagged) +versions. While on tagged versions these numbers are overridden by the version +numbers coming from the tag title, it's good practice to keep them aligned with +the real ones. The preferred flow of operation is: - * just before a release, update the version number in CMakeLists.txt to "next release version"; - * tag the release following the previously described syntax in order to get it built by CI; + * just before a release, update the version number in CMakeLists.txt to "next + release version"; + * tag the release following the previously described syntax in order to get it + built by CI; * wait for CI to upload the binaries, double check if everything is in order - * after the release is complete, update the version number again to "next targeted beta version", typically increasing `PROJECT_VERSION_PATCH` by one. + * after the release is complete, update the version number again to "next + targeted beta version", typically increasing `PROJECT_VERSION_PATCH` by one. -**NOTE 3:** When releasing a new stable version, all the previous beta versions should be deleted. This is needed for Cockatrice to update users of the "beta" release channel to the latest version like other users. +**NOTE 3:** When releasing a new stable version, all the previous beta versions +should be deleted. This is needed for Cockatrice to update users of the "beta" +release channel to the latest version like other users. From 933077463274262f8bd5df217905334fee34a688 Mon Sep 17 00:00:00 2001 From: Zach H Date: Fri, 2 Oct 2020 14:53:21 -0400 Subject: [PATCH 008/126] Add Gitter/Discord info to Contributing (#4126) --- .github/CONTRIBUTING.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index bc08341d1..0dee728bc 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -25,9 +25,14 @@ you most enjoy. Developers who like Windows development tend to find [Visual Studio]( https://www.visualstudio.com/) the best tool for the job. -If you have any questions on IDEs, feel free to chat with us on [Gitter]( -https://gitter.im/Cockatrice/Cockatrice) and we would love to help answer -your questions! +[![Discord](https://img.shields.io/discord/314987288398659595?label=Discord&logo=discord&logoColor=white&color=7289da)](https://discord.gg/ZASRzKu) +[![Gitter Chat](https://img.shields.io/gitter/room/Cockatrice/Cockatrice.svg)](https://gitter.im/Cockatrice/Cockatrice) + +If you'd like to ask questions, get advice, or just want to say hi, +The Cockatrice Development Team uses [Discord](https://discord.gg/ZASRzKu) +for communications in the #dev channel. If you're not into Discord, we also +have a [Gitter](https://gitter.im/Cockatrice/Cockatrice) channel available, +albeit slightly less active. # Code Style Guide # From 9cf762110252020b0cb01caa6007fcd5af3767d6 Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Tue, 6 Oct 2020 22:49:29 +0200 Subject: [PATCH 009/126] rename selected card menu (#4132) --- cockatrice/src/player.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cockatrice/src/player.cpp b/cockatrice/src/player.cpp index 3c5c0b3c6..09987ead4 100644 --- a/cockatrice/src/player.cpp +++ b/cockatrice/src/player.cpp @@ -719,7 +719,7 @@ void Player::retranslateUi() counterIterator.next().value()->retranslateUi(); } - aCardMenu->setText(tr("C&ard")); + aCardMenu->setText(tr("Selec&ted cards")); for (auto &allPlayersAction : allPlayersActions) { allPlayersAction->setText(tr("&All players")); From 752ba7d90562eca4b10ba7038fa8b60e3c079fae Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Tue, 6 Oct 2020 22:51:20 +0200 Subject: [PATCH 010/126] add face down to the string if the card is face down (#4130) --- cockatrice/src/messagelogwidget.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cockatrice/src/messagelogwidget.cpp b/cockatrice/src/messagelogwidget.cpp index 1caecf241..3fc76e212 100644 --- a/cockatrice/src/messagelogwidget.cpp +++ b/cockatrice/src/messagelogwidget.cpp @@ -316,7 +316,11 @@ void MessageLogWidget::logMoveCard(Player *player, bool usesNewX = false; if (targetZoneName == tableConstant()) { soundEngine->playSound("play_card"); - finalStr = tr("%1 puts %2 into play%3."); + if (card->getFaceDown()) { + finalStr = tr("%1 puts %2 into play%3 face down."); + } else { + finalStr = tr("%1 puts %2 into play%3."); + } } else if (targetZoneName == graveyardConstant()) { finalStr = tr("%1 puts %2%3 into their graveyard."); } else if (targetZoneName == exileConstant()) { From 5df069ab1999c1907722f6fc95c1053aac8df3a9 Mon Sep 17 00:00:00 2001 From: tooomm Date: Mon, 12 Oct 2020 23:18:11 +0200 Subject: [PATCH 011/126] userlists --> account (#4139) --- cockatrice/CMakeLists.txt | 2 +- cockatrice/src/chatview/chatview.cpp | 2 +- cockatrice/src/gameselector.cpp | 2 +- cockatrice/src/gamesmodel.cpp | 2 +- cockatrice/src/playerlistwidget.cpp | 2 +- cockatrice/src/{tab_userlists.cpp => tab_account.cpp} | 2 +- cockatrice/src/{tab_userlists.h => tab_account.h} | 4 ++-- cockatrice/src/tab_room.cpp | 2 +- cockatrice/src/tab_supervisor.cpp | 2 +- cockatrice/src/user_context_menu.cpp | 2 +- cockatrice/src/userlist.cpp | 2 +- 11 files changed, 12 insertions(+), 12 deletions(-) rename cockatrice/src/{tab_userlists.cpp => tab_account.cpp} (99%) rename cockatrice/src/{tab_userlists.h => tab_account.h} (97%) diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index e97cde147..2f9ae9465 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -79,7 +79,7 @@ SET(cockatrice_SOURCES src/tab_replays.cpp src/tab_supervisor.cpp src/tab_admin.cpp - src/tab_userlists.cpp + src/tab_account.cpp src/tab_deck_editor.cpp src/tab_logs.cpp src/replay_timeline_widget.cpp diff --git a/cockatrice/src/chatview/chatview.cpp b/cockatrice/src/chatview/chatview.cpp index 858166f77..9d28c66dc 100644 --- a/cockatrice/src/chatview/chatview.cpp +++ b/cockatrice/src/chatview/chatview.cpp @@ -3,7 +3,7 @@ #include "../pixmapgenerator.h" #include "../settingscache.h" #include "../soundengine.h" -#include "../tab_userlists.h" +#include "../tab_account.h" #include "../user_context_menu.h" #include "user_level.h" diff --git a/cockatrice/src/gameselector.cpp b/cockatrice/src/gameselector.cpp index a41c81283..a6093464a 100644 --- a/cockatrice/src/gameselector.cpp +++ b/cockatrice/src/gameselector.cpp @@ -8,9 +8,9 @@ #include "pb/room_commands.pb.h" #include "pb/serverinfo_game.pb.h" #include "pending_command.h" +#include "tab_account.h" #include "tab_room.h" #include "tab_supervisor.h" -#include "tab_userlists.h" #include #include diff --git a/cockatrice/src/gamesmodel.cpp b/cockatrice/src/gamesmodel.cpp index e34b02e17..8bf5540df 100644 --- a/cockatrice/src/gamesmodel.cpp +++ b/cockatrice/src/gamesmodel.cpp @@ -3,7 +3,7 @@ #include "pb/serverinfo_game.pb.h" #include "pixmapgenerator.h" #include "settingscache.h" -#include "tab_userlists.h" +#include "tab_account.h" #include "userlist.h" #include diff --git a/cockatrice/src/playerlistwidget.cpp b/cockatrice/src/playerlistwidget.cpp index ebdf2ff5d..c4c706d99 100644 --- a/cockatrice/src/playerlistwidget.cpp +++ b/cockatrice/src/playerlistwidget.cpp @@ -5,9 +5,9 @@ #include "pb/serverinfo_playerproperties.pb.h" #include "pb/session_commands.pb.h" #include "pixmapgenerator.h" +#include "tab_account.h" #include "tab_game.h" #include "tab_supervisor.h" -#include "tab_userlists.h" #include "user_context_menu.h" #include "userlist.h" diff --git a/cockatrice/src/tab_userlists.cpp b/cockatrice/src/tab_account.cpp similarity index 99% rename from cockatrice/src/tab_userlists.cpp rename to cockatrice/src/tab_account.cpp index ad0ec8dc8..4da8dc4bb 100644 --- a/cockatrice/src/tab_userlists.cpp +++ b/cockatrice/src/tab_account.cpp @@ -1,4 +1,4 @@ -#include "tab_userlists.h" +#include "tab_account.h" #include "abstractclient.h" #include "customlineedit.h" diff --git a/cockatrice/src/tab_userlists.h b/cockatrice/src/tab_account.h similarity index 97% rename from cockatrice/src/tab_userlists.h rename to cockatrice/src/tab_account.h index caa9166f2..ba6eb78ad 100644 --- a/cockatrice/src/tab_userlists.h +++ b/cockatrice/src/tab_account.h @@ -1,5 +1,5 @@ -#ifndef TAB_USERLISTS_H -#define TAB_USERLISTS_H +#ifndef TAB_ACCOUNT_H +#define TAB_ACCOUNT_H #include "pb/serverinfo_user.pb.h" #include "tab.h" diff --git a/cockatrice/src/tab_room.cpp b/cockatrice/src/tab_room.cpp index 9cffdda39..fdeee3976 100644 --- a/cockatrice/src/tab_room.cpp +++ b/cockatrice/src/tab_room.cpp @@ -14,8 +14,8 @@ #include "pb/serverinfo_room.pb.h" #include "pending_command.h" #include "settingscache.h" +#include "tab_account.h" #include "tab_supervisor.h" -#include "tab_userlists.h" #include "userlist.h" #include diff --git a/cockatrice/src/tab_supervisor.cpp b/cockatrice/src/tab_supervisor.cpp index d6f07873f..f5a199cc6 100644 --- a/cockatrice/src/tab_supervisor.cpp +++ b/cockatrice/src/tab_supervisor.cpp @@ -13,6 +13,7 @@ #include "pb/serverinfo_user.pb.h" #include "pixmapgenerator.h" #include "settingscache.h" +#include "tab_account.h" #include "tab_admin.h" #include "tab_deck_editor.h" #include "tab_deck_storage.h" @@ -22,7 +23,6 @@ #include "tab_replays.h" #include "tab_room.h" #include "tab_server.h" -#include "tab_userlists.h" #include "userlist.h" #include diff --git a/cockatrice/src/user_context_menu.cpp b/cockatrice/src/user_context_menu.cpp index 29d7d2eb1..e32b0192b 100644 --- a/cockatrice/src/user_context_menu.cpp +++ b/cockatrice/src/user_context_menu.cpp @@ -12,9 +12,9 @@ #include "pb/response_warn_list.pb.h" #include "pb/session_commands.pb.h" #include "pending_command.h" +#include "tab_account.h" #include "tab_game.h" #include "tab_supervisor.h" -#include "tab_userlists.h" #include "userinfobox.h" #include "userlist.h" diff --git a/cockatrice/src/userlist.cpp b/cockatrice/src/userlist.cpp index 879027f98..fa39d69b6 100644 --- a/cockatrice/src/userlist.cpp +++ b/cockatrice/src/userlist.cpp @@ -8,8 +8,8 @@ #include "pb/session_commands.pb.h" #include "pending_command.h" #include "pixmapgenerator.h" +#include "tab_account.h" #include "tab_supervisor.h" -#include "tab_userlists.h" #include "user_context_menu.h" #include From 1e8464c1d42e6ff2cb5d936427b577081eb6a5aa Mon Sep 17 00:00:00 2001 From: tooomm Date: Wed, 14 Oct 2020 17:18:59 +0200 Subject: [PATCH 012/126] README: add discord badge and adjust downloads (#4142) --- README.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index df26f5e00..49dcb8d09 100644 --- a/README.md +++ b/README.md @@ -29,21 +29,22 @@ Cockatrice is an open-source, multiplatform program for playing tabletop card ga # Download [![Cockatrice Eternal Download Count](https://img.shields.io/github/downloads/cockatrice/cockatrice/total.svg)](https://tooomm.github.io/github-release-stats/?username=Cockatrice&repository=Cockatrice) -Downloads are available for full releases and the current beta version in development.
-Full releases are checkpoints featuring major feature or UI enhancements - we recommend to use those. There is no strict schedule for new full releases. +Downloads are available for full releases and the current beta version in development. There is no strict release schedule for either of them. -The beta release contains the most recently added features and bugfixes, but can be unstable. They are released as we feel need. - -- Latest `stable` release (**recommended**): [![Download from GitHub Releases](https://img.shields.io/github/release/cockatrice/cockatrice.svg)](https://github.com/cockatrice/cockatrice/releases/latest) [![DL Count on Latest Release](https://img.shields.io/github/downloads/cockatrice/cockatrice/latest/total.svg?label=downloads)](https://tooomm.github.io/github-release-stats/?username=Cockatrice&repository=Cockatrice)
+- Latest `stable` release: [![Download from GitHub Releases](https://img.shields.io/github/release/cockatrice/cockatrice.svg)](https://github.com/cockatrice/cockatrice/releases/latest) [![DL Count on Latest Release](https://img.shields.io/github/downloads/cockatrice/cockatrice/latest/total.svg?label=downloads)](https://tooomm.github.io/github-release-stats/?username=Cockatrice&repository=Cockatrice)
+ - Stable versions are checkpoints featuring major feature and UI enhancements. + - **Recommended for most users!** - Latest `beta` release: [![Download from GitHub Pre-Releases](https://img.shields.io/github/release/cockatrice/cockatrice/all.svg)](https://github.com/cockatrice/cockatrice/releases) [![DL Count on Latest Pre-Release](https://img.shields.io/github/downloads-pre/cockatrice/cockatrice/latest/total.svg?label=downloads)](https://tooomm.github.io/github-release-stats/?username=Cockatrice&repository=Cockatrice) - - Beta versions may be unstable and contain bugs. + - Beta versions include the most recently added features and bugfixes, but can be unstable. - To be a Cockatrice Beta Tester, use this version. Find more information [here](https://github.com/Cockatrice/Cockatrice/wiki/Release-Channels)! + -# Get Involved [![Gitter Chat](https://img.shields.io/gitter/room/Cockatrice/Cockatrice.svg)](https://gitter.im/Cockatrice/Cockatrice) +# Get Involved [![Discord](https://img.shields.io/discord/314987288398659595?label=Discord&logo=discord&logoColor=white)](https://discord.gg/3Z9yzmA) [![Gitter Chat](https://img.shields.io/gitter/room/Cockatrice/Cockatrice)](https://gitter.im/Cockatrice/Cockatrice) -[Chat](https://gitter.im/Cockatrice/Cockatrice) with the Cockatrice developers on Gitter. Come here to talk about the application, features, or just to hang out. For support regarding specific servers, please contact that server's admin or forum for support rather than asking here.
+Join our [Discord community](https://discord.gg/3Z9yzmA) to connect with the project or fellow users of the app. The Cockatrice developers are also available on [Gitter](https://gitter.im/Cockatrice/Cockatrice). Come here to talk about the application, features, or just to hang out.
+For support regarding specific servers, please contact that server's admin or forum for support rather than asking here.
To contribute code to the project, please review [the guidelines](https://github.com/Cockatrice/Cockatrice/blob/master/.github/CONTRIBUTING.md). We maintain two tags for contributors to find issues to work on: From b8cd3af21f7e74d3546b5dee5ceeb23c417c0262 Mon Sep 17 00:00:00 2001 From: tooomm Date: Wed, 14 Oct 2020 17:19:37 +0200 Subject: [PATCH 013/126] CONTRIBUTING: little changes (#4141) --- .github/CONTRIBUTING.md | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 0dee728bc..f926b695c 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -29,7 +29,7 @@ https://www.visualstudio.com/) the best tool for the job. [![Gitter Chat](https://img.shields.io/gitter/room/Cockatrice/Cockatrice.svg)](https://gitter.im/Cockatrice/Cockatrice) If you'd like to ask questions, get advice, or just want to say hi, -The Cockatrice Development Team uses [Discord](https://discord.gg/ZASRzKu) +the Cockatrice Development Team uses [Discord](https://discord.gg/ZASRzKu) for communications in the #dev channel. If you're not into Discord, we also have a [Gitter](https://gitter.im/Cockatrice/Cockatrice) channel available, albeit slightly less active. @@ -37,12 +37,13 @@ albeit slightly less active. # Code Style Guide # -### Formatting and continuous integration (ci) ### +### Formatting and continuous integration (CI) ### -We use a separate job on Travis CI to check your code for formatting issues, if -your pull request was rejected you can check the output on their website. -Click on "Details" next to the failed Travis CI build and then click on the -Linting build (the fastest one) to see the log. +We use a separate job on Travis CI to check your code for formatting issues. If +your pull request was rejected, you can check the output on their website. +To do so, click on Details next to the failed Travis CI build at the +bottom of your PR on the GitHub page, on the Travis page then click on our "Linting" +build (the fastest one on the very top of the list) to see the complete log. The message will look like this: ``` @@ -58,8 +59,9 @@ The message will look like this: *** *** *********************************************************** ``` -The CONTRIBUTING.md file mentioned is this file. Please read [this section]( -#formatting) for full information on our formatting guidelines. +The CONTRIBUTING.md file mentioned in that message is the file you are currently +reading. Please see [this section](#formatting) below for full information on our +formatting guidelines. ### Compatibility ### @@ -70,7 +72,7 @@ to help convert it over! For consistency, we use Qt data structures where possible. For example, `QString` over `std::string` and `QList` over `std::vector`. -Do not use old c style casts in new code, instead use a [`static_cast<>`]( +Do not use old C style casts in new code, instead use a [`static_cast<>`]( https://en.cppreference.com/w/cpp/language/static_cast) or other appropriate conversion. @@ -85,7 +87,7 @@ format all files in your pr in one go. Use `./clangify.sh --help` to show a full help page. To run clang-format on a single source file simply use the command -`clang-format -i ` to format it in place. (some systems install +`clang-format -i ` to format it in place. (Some systems install clang-format with a specific version number appended, `find /usr/bin -name clang-format*` should find it for you) @@ -102,7 +104,8 @@ Use header guards in the form of `FILE_NAME_H`. Simple functions, such as getters, may be written inline in the header file, but other functions should be written in the source file. -Group library includes after project includes, and in alphabetic order. Like this: +Group project includes first, followed by library includes. All in alphabetic order. +Like this: ```c++ // Good #include "card.h" From 8cbc4c91f69d4313eece83e1daa20f2745f7aa1f Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Wed, 21 Oct 2020 16:31:18 +0200 Subject: [PATCH 014/126] free qprocess on oracle update fail (#4134) * free qprocess on oracle update fail reload the database whenever oracle exits search for oracle in ../oracle so you can use it from the build dir * only ask for the db updater once --- cockatrice/src/window_main.cpp | 34 +++++++++++++++++++++++++--------- cockatrice/src/window_main.h | 4 ++-- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/cockatrice/src/window_main.cpp b/cockatrice/src/window_main.cpp index 38140459a..88779d877 100644 --- a/cockatrice/src/window_main.cpp +++ b/cockatrice/src/window_main.cpp @@ -769,8 +769,8 @@ void MainWindow::createMenus() } MainWindow::MainWindow(QWidget *parent) - : QMainWindow(parent), localServer(nullptr), bHasActivated(false), cardUpdateProcess(nullptr), - logviewDialog(nullptr) + : QMainWindow(parent), localServer(nullptr), bHasActivated(false), askedForDbUpdater(false), + cardUpdateProcess(nullptr), logviewDialog(nullptr) { connect(&SettingsCache::instance(), SIGNAL(pixmapCacheSizeChanged(int)), this, SLOT(pixmapCacheSizeChanged(int))); pixmapCacheSizeChanged(SettingsCache::instance().getPixmapCacheSize()); @@ -1003,6 +1003,10 @@ void MainWindow::showWindowIfHidden() void MainWindow::cardDatabaseLoadingFailed() { + if (askedForDbUpdater) { + return; + } + askedForDbUpdater = true; QMessageBox msgBox; msgBox.setWindowTitle(tr("Card database")); msgBox.setIcon(QMessageBox::Question); @@ -1109,17 +1113,34 @@ void MainWindow::actCheckCardUpdates() if (dir.exists(binaryName)) { updaterCmd = dir.absoluteFilePath(binaryName); + } else { // try and find the directory oracle is stored in the build directory + QDir findLocalDir(dir); + findLocalDir.cdUp(); + findLocalDir.cd(getCardUpdaterBinaryName()); + if (findLocalDir.exists(binaryName)) { + dir = findLocalDir; + updaterCmd = dir.absoluteFilePath(binaryName); + } } if (updaterCmd.isEmpty()) { QMessageBox::warning(this, tr("Error"), tr("Unable to run the card database updater: ") + dir.absoluteFilePath(binaryName)); + exitCardDatabaseUpdate(); return; } cardUpdateProcess->start(updaterCmd, QStringList()); } +void MainWindow::exitCardDatabaseUpdate() +{ + cardUpdateProcess->deleteLater(); + cardUpdateProcess = nullptr; + + QtConcurrent::run(db, &CardDatabase::loadCardDatabases); +} + void MainWindow::cardUpdateError(QProcess::ProcessError err) { QString error; @@ -1145,18 +1166,13 @@ void MainWindow::cardUpdateError(QProcess::ProcessError err) break; } - cardUpdateProcess->deleteLater(); - cardUpdateProcess = nullptr; - + exitCardDatabaseUpdate(); QMessageBox::warning(this, tr("Error"), tr("The card database updater exited with an error: %1").arg(error)); } void MainWindow::cardUpdateFinished(int, QProcess::ExitStatus) { - cardUpdateProcess->deleteLater(); - cardUpdateProcess = nullptr; - - QtConcurrent::run(db, &CardDatabase::loadCardDatabases); + exitCardDatabaseUpdate(); } void MainWindow::actCheckServerUpdates() diff --git a/cockatrice/src/window_main.h b/cockatrice/src/window_main.h index 47e10ad47..e9f3c7338 100644 --- a/cockatrice/src/window_main.h +++ b/cockatrice/src/window_main.h @@ -116,11 +116,11 @@ private: void createTrayIcon(); void createTrayActions(); int getNextCustomSetPrefix(QDir dataDir); - // TODO: add a preference item to choose updater name for other games inline QString getCardUpdaterBinaryName() { return "oracle"; }; + void exitCardDatabaseUpdate(); QList tabMenus; QMenu *cockatriceMenu, *dbMenu, *helpMenu, *trayIconMenu; @@ -132,7 +132,7 @@ private: RemoteClient *client; QThread *clientThread; LocalServer *localServer; - bool bHasActivated; + bool bHasActivated, askedForDbUpdater; QMessageBox serverShutdownMessageBox; QProcess *cardUpdateProcess; DlgViewLog *logviewDialog; From 2081639970dd808298b10283192eb86dfb8bf0b0 Mon Sep 17 00:00:00 2001 From: rivten Date: Wed, 21 Oct 2020 16:31:57 +0200 Subject: [PATCH 015/126] fix infinite loop when card file save fails, instead stop the execution just like the other errors in the call (#4143) --- oracle/src/oraclewizard.cpp | 40 ++++++++++++++++-------------------- oracle/src/pagetemplates.cpp | 40 ++++++++++++++++-------------------- 2 files changed, 36 insertions(+), 44 deletions(-) diff --git a/oracle/src/oraclewizard.cpp b/oracle/src/oraclewizard.cpp index f824bbf3e..675c6f585 100644 --- a/oracle/src/oraclewizard.cpp +++ b/oracle/src/oraclewizard.cpp @@ -607,35 +607,31 @@ void SaveSetsPage::updateTotalProgress(int cardsImported, int /* setIndex */, co bool SaveSetsPage::validatePage() { - bool ok = false; QString defaultPath = SettingsCache::instance().getCardDatabasePath(); QString windowName = tr("Save card database"); QString fileType = tr("XML; card database (*.xml)"); - do { - QString fileName; - if (defaultPathCheckBox->isChecked()) { - fileName = QFileDialog::getSaveFileName(this, windowName, defaultPath, fileType); - } else { - fileName = defaultPath; - } + QString fileName; + if (defaultPathCheckBox->isChecked()) { + fileName = QFileDialog::getSaveFileName(this, windowName, defaultPath, fileType); + } else { + fileName = defaultPath; + } - if (fileName.isEmpty()) { - return false; - } + if (fileName.isEmpty()) { + return false; + } - QFileInfo fi(fileName); - QDir fileDir(fi.path()); - if (!fileDir.exists() && !fileDir.mkpath(fileDir.absolutePath())) { - return false; - } + QFileInfo fi(fileName); + QDir fileDir(fi.path()); + if (!fileDir.exists() && !fileDir.mkpath(fileDir.absolutePath())) { + return false; + } - if (wizard()->importer->saveToFile(fileName, wizard()->getCardSourceUrl(), wizard()->getCardSourceVersion())) { - ok = true; - } else { - QMessageBox::critical(this, tr("Error"), tr("The file could not be saved to %1").arg(fileName)); - } - } while (!ok); + if (!wizard()->importer->saveToFile(fileName, wizard()->getCardSourceUrl(), wizard()->getCardSourceVersion())) { + QMessageBox::critical(this, tr("Error"), tr("The file could not be saved to %1").arg(fileName)); + return false; + } return true; } diff --git a/oracle/src/pagetemplates.cpp b/oracle/src/pagetemplates.cpp index 609c0b6b3..b7d3fa65c 100644 --- a/oracle/src/pagetemplates.cpp +++ b/oracle/src/pagetemplates.cpp @@ -144,35 +144,31 @@ void SimpleDownloadFilePage::actDownloadFinished() bool SimpleDownloadFilePage::saveToFile() { - bool ok = false; QString defaultPath = getDefaultSavePath(); QString windowName = getWindowTitle(); QString fileType = getFileType(); - do { - QString fileName; - if (defaultPathCheckBox->isChecked()) { - fileName = QFileDialog::getSaveFileName(this, windowName, defaultPath, fileType); - } else { - fileName = defaultPath; - } + QString fileName; + if (defaultPathCheckBox->isChecked()) { + fileName = QFileDialog::getSaveFileName(this, windowName, defaultPath, fileType); + } else { + fileName = defaultPath; + } - if (fileName.isEmpty()) { - return false; - } + if (fileName.isEmpty()) { + return false; + } - QFileInfo fi(fileName); - QDir fileDir(fi.path()); - if (!fileDir.exists() && !fileDir.mkpath(fileDir.absolutePath())) { - return false; - } + QFileInfo fi(fileName); + QDir fileDir(fi.path()); + if (!fileDir.exists() && !fileDir.mkpath(fileDir.absolutePath())) { + return false; + } - if (internalSaveToFile(fileName)) { - ok = true; - } else { - QMessageBox::critical(this, tr("Error"), tr("The file could not be saved to %1").arg(fileName)); - } - } while (!ok); + if (!internalSaveToFile(fileName)) { + QMessageBox::critical(this, tr("Error"), tr("The file could not be saved to %1").arg(fileName)); + return false; + } // clean saved downloadData downloadData = QByteArray(); From e10446f5b895f2afcd61c1d40e134fe3215e0908 Mon Sep 17 00:00:00 2001 From: Zach H Date: Thu, 22 Oct 2020 10:46:50 -0400 Subject: [PATCH 016/126] Remove annoying spectator log messages (#4138) --- cockatrice/src/tab_game.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cockatrice/src/tab_game.cpp b/cockatrice/src/tab_game.cpp index 424b9e3cc..5b503d96e 100644 --- a/cockatrice/src/tab_game.cpp +++ b/cockatrice/src/tab_game.cpp @@ -855,10 +855,8 @@ void TabGame::processGameEventContainer(const GameEventContainer &cont, Abstract case GameEvent::LEAVE: eventSpectatorLeave(event.GetExtension(Event_Leave::ext), playerId, context); break; - default: { - qDebug() << "unhandled spectator game event" << eventType; + default: break; - } } } else { if ((clients.size() > 1) && (playerId != -1)) From 1a9426149039a0e4948a7493e3094a70a348e0d7 Mon Sep 17 00:00:00 2001 From: fdipilla Date: Fri, 23 Oct 2020 16:36:02 -0300 Subject: [PATCH 017/126] Multiple background images on all zones (#4144) --- cockatrice/src/handzone.cpp | 8 +++++- cockatrice/src/player.cpp | 14 +++++++++- cockatrice/src/player.h | 7 +++++ cockatrice/src/stackzone.cpp | 8 +++++- cockatrice/src/thememanager.cpp | 45 +++++++++++++++++++++++++++++++++ cockatrice/src/thememanager.h | 7 +++-- 6 files changed, 84 insertions(+), 5 deletions(-) diff --git a/cockatrice/src/handzone.cpp b/cockatrice/src/handzone.cpp index bf6a44b8a..91d2e89e6 100644 --- a/cockatrice/src/handzone.cpp +++ b/cockatrice/src/handzone.cpp @@ -76,7 +76,13 @@ QRectF HandZone::boundingRect() const void HandZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) { - painter->fillRect(boundingRect(), themeManager->getHandBgBrush()); + QBrush brush = themeManager->getHandBgBrush(); + + if (player->getZoneId() > 0) { + // If the extra image is not found, load the default one + brush = themeManager->getExtraHandBgBrush(QString::number(player->getZoneId()), brush); + } + painter->fillRect(boundingRect(), brush); } void HandZone::reorganizeCards() diff --git a/cockatrice/src/player.cpp b/cockatrice/src/player.cpp index 09987ead4..740d069ac 100644 --- a/cockatrice/src/player.cpp +++ b/cockatrice/src/player.cpp @@ -83,7 +83,13 @@ void PlayerArea::updateBg() void PlayerArea::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) { - painter->fillRect(bRect, themeManager->getPlayerBgBrush()); + QBrush brush = themeManager->getPlayerBgBrush(); + + if (playerZoneId > 0) { + // If the extra image is not found, load the default one + brush = themeManager->getExtraPlayerBgBrush(QString::number(playerZoneId), brush); + } + painter->fillRect(boundingRect(), brush); } void PlayerArea::setSize(qreal width, qreal height) @@ -92,6 +98,11 @@ void PlayerArea::setSize(qreal width, qreal height) bRect = QRectF(0, 0, width, height); } +void PlayerArea::setPlayerZoneId(int _playerZoneId) +{ + playerZoneId = _playerZoneId; +} + Player::Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, TabGame *_parent) : QObject(_parent), game(_parent), shortcutsActive(false), defaultNumberTopCards(1), defaultNumberTopCardsToPlaceBelow(1), lastTokenDestroy(true), lastTokenTableRow(0), id(_id), active(false), @@ -3266,6 +3277,7 @@ void Player::setConceded(bool _conceded) void Player::setZoneId(int _zoneId) { zoneId = _zoneId; + playerArea->setPlayerZoneId(_zoneId); } void Player::setMirrored(bool _mirrored) diff --git a/cockatrice/src/player.h b/cockatrice/src/player.h index 4dbdf5943..b86287bb6 100644 --- a/cockatrice/src/player.h +++ b/cockatrice/src/player.h @@ -73,6 +73,7 @@ class PlayerArea : public QObject, public QGraphicsItem Q_INTERFACES(QGraphicsItem) private: QRectF bRect; + int playerZoneId; private slots: void updateBg(); @@ -94,6 +95,12 @@ public: void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; void setSize(qreal width, qreal height); + + void setPlayerZoneId(int _playerZoneId); + int getPlayerZoneId() const + { + return playerZoneId; + } }; class Player : public QObject, public QGraphicsItem diff --git a/cockatrice/src/stackzone.cpp b/cockatrice/src/stackzone.cpp index f2be30734..26f19d557 100644 --- a/cockatrice/src/stackzone.cpp +++ b/cockatrice/src/stackzone.cpp @@ -47,7 +47,13 @@ QRectF StackZone::boundingRect() const void StackZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) { - painter->fillRect(boundingRect(), themeManager->getStackBgBrush()); + QBrush brush = themeManager->getStackBgBrush(); + + if (player->getZoneId() > 0) { + // If the extra image is not found, load the default one + brush = themeManager->getExtraStackBgBrush(QString::number(player->getZoneId()), brush); + } + painter->fillRect(boundingRect(), brush); } void StackZone::handleDropEvent(const QList &dragItems, diff --git a/cockatrice/src/thememanager.cpp b/cockatrice/src/thememanager.cpp index 8b928d2a5..ee05f3550 100644 --- a/cockatrice/src/thememanager.cpp +++ b/cockatrice/src/thememanager.cpp @@ -118,6 +118,9 @@ void ThemeManager::themeChangedSlot() playerBgBrush = loadBrush(PLAYERZONE_BG_NAME, QColor(200, 200, 200)); stackBgBrush = loadBrush(STACKZONE_BG_NAME, QColor(113, 43, 43)); tableBgBrushesCache.clear(); + stackBgBrushesCache.clear(); + playerBgBrushesCache.clear(); + handBgBrushesCache.clear(); QPixmapCache::clear(); @@ -137,3 +140,45 @@ QBrush ThemeManager::getExtraTableBgBrush(QString extraNumber, QBrush &fallbackB return returnBrush; } + +QBrush ThemeManager::getExtraStackBgBrush(QString extraNumber, QBrush &fallbackBrush) +{ + QBrush returnBrush; + + if (!stackBgBrushesCache.contains(extraNumber.toInt())) { + returnBrush = loadExtraBrush(STACKZONE_BG_NAME + extraNumber, fallbackBrush); + stackBgBrushesCache.insert(extraNumber.toInt(), returnBrush); + } else { + returnBrush = stackBgBrushesCache.value(extraNumber.toInt()); + } + + return returnBrush; +} + +QBrush ThemeManager::getExtraPlayerBgBrush(QString extraNumber, QBrush &fallbackBrush) +{ + QBrush returnBrush; + + if (!playerBgBrushesCache.contains(extraNumber.toInt())) { + returnBrush = loadExtraBrush(PLAYERZONE_BG_NAME + extraNumber, fallbackBrush); + playerBgBrushesCache.insert(extraNumber.toInt(), returnBrush); + } else { + returnBrush = playerBgBrushesCache.value(extraNumber.toInt()); + } + + return returnBrush; +} + +QBrush ThemeManager::getExtraHandBgBrush(QString extraNumber, QBrush &fallbackBrush) +{ + QBrush returnBrush; + + if (!handBgBrushesCache.contains(extraNumber.toInt())) { + returnBrush = loadExtraBrush(HANDZONE_BG_NAME + extraNumber, fallbackBrush); + handBgBrushesCache.insert(extraNumber.toInt(), returnBrush); + } else { + returnBrush = handBgBrushesCache.value(extraNumber.toInt()); + } + + return returnBrush; +} diff --git a/cockatrice/src/thememanager.h b/cockatrice/src/thememanager.h index 23bad895c..3ab518b92 100644 --- a/cockatrice/src/thememanager.h +++ b/cockatrice/src/thememanager.h @@ -23,9 +23,9 @@ private: QBrush handBgBrush, stackBgBrush, tableBgBrush, playerBgBrush; QStringMap availableThemes; /* - Internal cache for table backgrounds + Internal cache for multiple backgrounds */ - QBrushMap tableBgBrushesCache; + QBrushMap tableBgBrushesCache, stackBgBrushesCache, playerBgBrushesCache, handBgBrushesCache; protected: void ensureThemeDirectoryExists(); @@ -51,6 +51,9 @@ public: } QStringMap &getAvailableThemes(); QBrush getExtraTableBgBrush(QString extraNumber, QBrush &fallbackBrush); + QBrush getExtraStackBgBrush(QString extraNumber, QBrush &fallbackBrush); + QBrush getExtraPlayerBgBrush(QString extraNumber, QBrush &fallbackBrush); + QBrush getExtraHandBgBrush(QString extraNumber, QBrush &fallbackBrush); protected slots: void themeChangedSlot(); signals: From a49c4865bbb15e27498dfa7d22734da724361439 Mon Sep 17 00:00:00 2001 From: Kaitlin <63179146+knitknit@users.noreply.github.com> Date: Tue, 27 Oct 2020 15:49:02 -0400 Subject: [PATCH 018/126] Add game filtering for spectator attributes (#4127) --- cockatrice/src/dlg_filter_games.cpp | 115 +++++++++++++----- cockatrice/src/dlg_filter_games.h | 17 ++- cockatrice/src/gameselector.cpp | 7 +- cockatrice/src/gamesmodel.cpp | 98 ++++++++++++--- cockatrice/src/gamesmodel.h | 36 +++++- .../src/settings/gamefilterssettings.cpp | 63 +++++++++- cockatrice/src/settings/gamefilterssettings.h | 14 ++- cockatrice/src/settingscache.cpp | 3 +- 8 files changed, 291 insertions(+), 62 deletions(-) diff --git a/cockatrice/src/dlg_filter_games.cpp b/cockatrice/src/dlg_filter_games.cpp index 110d5bff3..63cb8c279 100644 --- a/cockatrice/src/dlg_filter_games.cpp +++ b/cockatrice/src/dlg_filter_games.cpp @@ -27,8 +27,11 @@ DlgFilterGames::DlgFilterGames(const QMap &_allGameTypes, showBuddiesOnlyGames = new QCheckBox(tr("Show '&buddies only' games")); showBuddiesOnlyGames->setChecked(gamesProxyModel->getShowBuddiesOnlyGames()); - unavailableGamesVisibleCheckBox = new QCheckBox(tr("Show &unavailable games")); - unavailableGamesVisibleCheckBox->setChecked(gamesProxyModel->getUnavailableGamesVisible()); + showFullGames = new QCheckBox(tr("Show &full games")); + showFullGames->setChecked(gamesProxyModel->getShowFullGames()); + + showGamesThatStarted = new QCheckBox(tr("Show games &that have started")); + showGamesThatStarted->setChecked(gamesProxyModel->getShowGamesThatStarted()); showPasswordProtectedGames = new QCheckBox(tr("Show &password protected games")); showPasswordProtectedGames->setChecked(gamesProxyModel->getShowPasswordProtectedGames()); @@ -41,19 +44,19 @@ DlgFilterGames::DlgFilterGames(const QMap &_allGameTypes, 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:")); + auto *maxGameAgeLabel = new QLabel(tr("&Newer than:")); maxGameAgeLabel->setBuddy(maxGameAgeComboBox); gameNameFilterEdit = new QLineEdit; gameNameFilterEdit->setText(gamesProxyModel->getGameNameFilter()); - QLabel *gameNameFilterLabel = new QLabel(tr("Game &description:")); + auto *gameNameFilterLabel = new QLabel(tr("Game &description:")); gameNameFilterLabel->setBuddy(gameNameFilterEdit); creatorNameFilterEdit = new QLineEdit; creatorNameFilterEdit->setText(gamesProxyModel->getCreatorNameFilter()); - QLabel *creatorNameFilterLabel = new QLabel(tr("&Creator name:")); + auto *creatorNameFilterLabel = new QLabel(tr("&Creator name:")); creatorNameFilterLabel->setBuddy(creatorNameFilterEdit); - QGridLayout *generalGrid = new QGridLayout; + auto *generalGrid = new QGridLayout; generalGrid->addWidget(gameNameFilterLabel, 0, 0); generalGrid->addWidget(gameNameFilterEdit, 0, 1); generalGrid->addWidget(creatorNameFilterLabel, 1, 0); @@ -63,12 +66,12 @@ DlgFilterGames::DlgFilterGames(const QMap &_allGameTypes, generalGroupBox = new QGroupBox(tr("General")); generalGroupBox->setLayout(generalGrid); - QVBoxLayout *gameTypeFilterLayout = new QVBoxLayout; + auto *gameTypeFilterLayout = new QVBoxLayout; QMapIterator gameTypesIterator(allGameTypes); while (gameTypesIterator.hasNext()) { gameTypesIterator.next(); - QCheckBox *temp = new QCheckBox(gameTypesIterator.value()); + auto *temp = new QCheckBox(gameTypesIterator.value()); temp->setChecked(gamesProxyModel->getGameTypeFilter().contains(gameTypesIterator.key())); gameTypeFilterCheckBoxes.insert(gameTypesIterator.key(), temp); @@ -79,61 +82,86 @@ DlgFilterGames::DlgFilterGames(const QMap &_allGameTypes, gameTypeFilterGroupBox = new QGroupBox(tr("&Game types")); gameTypeFilterGroupBox->setLayout(gameTypeFilterLayout); } else - gameTypeFilterGroupBox = 0; + gameTypeFilterGroupBox = nullptr; - QLabel *maxPlayersFilterMinLabel = new QLabel(tr("at &least:")); + auto *maxPlayersFilterMinLabel = new QLabel(tr("at &least:")); maxPlayersFilterMinSpinBox = new QSpinBox; maxPlayersFilterMinSpinBox->setMinimum(1); maxPlayersFilterMinSpinBox->setMaximum(99); maxPlayersFilterMinSpinBox->setValue(gamesProxyModel->getMaxPlayersFilterMin()); maxPlayersFilterMinLabel->setBuddy(maxPlayersFilterMinSpinBox); - QLabel *maxPlayersFilterMaxLabel = new QLabel(tr("at &most:")); + auto *maxPlayersFilterMaxLabel = new QLabel(tr("at &most:")); maxPlayersFilterMaxSpinBox = new QSpinBox; maxPlayersFilterMaxSpinBox->setMinimum(1); maxPlayersFilterMaxSpinBox->setMaximum(99); maxPlayersFilterMaxSpinBox->setValue(gamesProxyModel->getMaxPlayersFilterMax()); maxPlayersFilterMaxLabel->setBuddy(maxPlayersFilterMaxSpinBox); - QGridLayout *maxPlayersFilterLayout = new QGridLayout; + auto *maxPlayersFilterLayout = new QGridLayout; maxPlayersFilterLayout->addWidget(maxPlayersFilterMinLabel, 0, 0); maxPlayersFilterLayout->addWidget(maxPlayersFilterMinSpinBox, 0, 1); maxPlayersFilterLayout->addWidget(maxPlayersFilterMaxLabel, 1, 0); maxPlayersFilterLayout->addWidget(maxPlayersFilterMaxSpinBox, 1, 1); - QGroupBox *maxPlayersGroupBox = new QGroupBox(tr("Maximum player count")); + auto *maxPlayersGroupBox = new QGroupBox(tr("Maximum player count")); maxPlayersGroupBox->setLayout(maxPlayersFilterLayout); - QGridLayout *restrictionsLayout = new QGridLayout; - restrictionsLayout->addWidget(unavailableGamesVisibleCheckBox, 0, 0); - restrictionsLayout->addWidget(showPasswordProtectedGames, 1, 0); - restrictionsLayout->addWidget(showBuddiesOnlyGames, 2, 0); - restrictionsLayout->addWidget(hideIgnoredUserGames, 3, 0); + auto *restrictionsLayout = new QGridLayout; + restrictionsLayout->addWidget(showFullGames, 0, 0); + restrictionsLayout->addWidget(showGamesThatStarted, 1, 0); + restrictionsLayout->addWidget(showPasswordProtectedGames, 2, 0); + restrictionsLayout->addWidget(showBuddiesOnlyGames, 3, 0); + restrictionsLayout->addWidget(hideIgnoredUserGames, 4, 0); - QGroupBox *restrictionsGroupBox = new QGroupBox(tr("Restrictions")); + auto *restrictionsGroupBox = new QGroupBox(tr("Restrictions")); restrictionsGroupBox->setLayout(restrictionsLayout); - QGridLayout *leftGrid = new QGridLayout; + showOnlyIfSpectatorsCanWatch = new QCheckBox(tr("Show games only if &spectators can watch")); + showOnlyIfSpectatorsCanWatch->setChecked(gamesProxyModel->getShowOnlyIfSpectatorsCanWatch()); + connect(showOnlyIfSpectatorsCanWatch, SIGNAL(toggled(bool)), this, SLOT(toggleSpectatorCheckboxEnabledness(bool))); + + showSpectatorPasswordProtected = new QCheckBox(tr("Show spectator password p&rotected games")); + showSpectatorPasswordProtected->setChecked(gamesProxyModel->getShowSpectatorPasswordProtected()); + showOnlyIfSpectatorsCanChat = new QCheckBox(tr("Show only if spectators can ch&at")); + showOnlyIfSpectatorsCanChat->setChecked(gamesProxyModel->getShowOnlyIfSpectatorsCanChat()); + showOnlyIfSpectatorsCanSeeHands = new QCheckBox(tr("Show only if spectators can see &hands")); + showOnlyIfSpectatorsCanSeeHands->setChecked(gamesProxyModel->getShowOnlyIfSpectatorsCanSeeHands()); + toggleSpectatorCheckboxEnabledness(getShowOnlyIfSpectatorsCanWatch()); + + auto *spectatorsLayout = new QGridLayout; + spectatorsLayout->addWidget(showOnlyIfSpectatorsCanWatch, 0, 0); + spectatorsLayout->addWidget(showSpectatorPasswordProtected, 1, 0); + spectatorsLayout->addWidget(showOnlyIfSpectatorsCanChat, 2, 0); + spectatorsLayout->addWidget(showOnlyIfSpectatorsCanSeeHands, 3, 0); + + auto *spectatorsGroupBox = new QGroupBox(tr("Spectators")); + spectatorsGroupBox->setLayout(spectatorsLayout); + + auto *leftGrid = new QGridLayout; leftGrid->addWidget(generalGroupBox, 0, 0, 1, 2); leftGrid->addWidget(maxPlayersGroupBox, 2, 0, 1, 2); leftGrid->addWidget(restrictionsGroupBox, 3, 0, 1, 2); - QVBoxLayout *leftColumn = new QVBoxLayout; + auto *leftColumn = new QVBoxLayout; leftColumn->addLayout(leftGrid); leftColumn->addStretch(); - QVBoxLayout *rightColumn = new QVBoxLayout; - rightColumn->addWidget(gameTypeFilterGroupBox); + auto *rightGrid = new QGridLayout; + rightGrid->addWidget(gameTypeFilterGroupBox, 0, 0, 1, 1); + rightGrid->addWidget(spectatorsGroupBox, 1, 0, 1, 1); + auto *rightColumn = new QVBoxLayout; + rightColumn->addLayout(rightGrid); - QHBoxLayout *hbox = new QHBoxLayout; + auto *hbox = new QHBoxLayout; hbox->addLayout(leftColumn); hbox->addLayout(rightColumn); - QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + auto *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(buttonBox, SIGNAL(accepted()), this, SLOT(actOk())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); - QVBoxLayout *mainLayout = new QVBoxLayout; + auto *mainLayout = new QVBoxLayout; mainLayout->addLayout(hbox); mainLayout->addWidget(buttonBox); @@ -146,14 +174,21 @@ void DlgFilterGames::actOk() accept(); } -bool DlgFilterGames::getUnavailableGamesVisible() const +void DlgFilterGames::toggleSpectatorCheckboxEnabledness(bool spectatorsEnabled) { - return unavailableGamesVisibleCheckBox->isChecked(); + showSpectatorPasswordProtected->setDisabled(!spectatorsEnabled); + showOnlyIfSpectatorsCanChat->setDisabled(!spectatorsEnabled); + showOnlyIfSpectatorsCanSeeHands->setDisabled(!spectatorsEnabled); } -void DlgFilterGames::setUnavailableGamesVisible(bool _unavailableGamesVisible) +bool DlgFilterGames::getShowFullGames() const { - unavailableGamesVisibleCheckBox->setChecked(_unavailableGamesVisible); + return showFullGames->isChecked(); +} + +bool DlgFilterGames::getShowGamesThatStarted() const +{ + return showGamesThatStarted->isChecked(); } bool DlgFilterGames::getShowBuddiesOnlyGames() const @@ -252,3 +287,23 @@ void DlgFilterGames::setMaxPlayersFilter(int _maxPlayersFilterMin, int _maxPlaye maxPlayersFilterMaxSpinBox->setValue(_maxPlayersFilterMax == -1 ? maxPlayersFilterMaxSpinBox->maximum() : _maxPlayersFilterMax); } + +bool DlgFilterGames::getShowOnlyIfSpectatorsCanWatch() const +{ + return showOnlyIfSpectatorsCanWatch->isChecked(); +} + +bool DlgFilterGames::getShowSpectatorPasswordProtected() const +{ + return showSpectatorPasswordProtected->isEnabled() && showSpectatorPasswordProtected->isChecked(); +} + +bool DlgFilterGames::getShowOnlyIfSpectatorsCanChat() const +{ + return showOnlyIfSpectatorsCanChat->isEnabled() && showOnlyIfSpectatorsCanChat->isChecked(); +} + +bool DlgFilterGames::getShowOnlyIfSpectatorsCanSeeHands() const +{ + return showOnlyIfSpectatorsCanSeeHands->isEnabled() && showOnlyIfSpectatorsCanSeeHands->isChecked(); +} \ No newline at end of file diff --git a/cockatrice/src/dlg_filter_games.h b/cockatrice/src/dlg_filter_games.h index 8c2301ad6..24c048ca8 100644 --- a/cockatrice/src/dlg_filter_games.h +++ b/cockatrice/src/dlg_filter_games.h @@ -22,7 +22,8 @@ class DlgFilterGames : public QDialog private: QGroupBox *generalGroupBox; QCheckBox *showBuddiesOnlyGames; - QCheckBox *unavailableGamesVisibleCheckBox; + QCheckBox *showFullGames; + QCheckBox *showGamesThatStarted; QCheckBox *showPasswordProtectedGames; QCheckBox *hideIgnoredUserGames; QLineEdit *gameNameFilterEdit; @@ -32,19 +33,25 @@ private: QSpinBox *maxPlayersFilterMaxSpinBox; QComboBox *maxGameAgeComboBox; + QCheckBox *showOnlyIfSpectatorsCanWatch; + QCheckBox *showSpectatorPasswordProtected; + QCheckBox *showOnlyIfSpectatorsCanChat; + QCheckBox *showOnlyIfSpectatorsCanSeeHands; + const QMap &allGameTypes; const GamesProxyModel *gamesProxyModel; private slots: void actOk(); + void toggleSpectatorCheckboxEnabledness(bool spectatorsEnabled); public: DlgFilterGames(const QMap &_allGameTypes, const GamesProxyModel *_gamesProxyModel, QWidget *parent = nullptr); - bool getUnavailableGamesVisible() const; - void setUnavailableGamesVisible(bool _unavailableGamesVisible); + bool getShowFullGames() const; + bool getShowGamesThatStarted() const; bool getShowPasswordProtectedGames() const; void setShowPasswordProtectedGames(bool _passwordProtectedGamesHidden); bool getShowBuddiesOnlyGames() const; @@ -62,6 +69,10 @@ public: void setMaxPlayersFilter(int _maxPlayersFilterMin, int _maxPlayersFilterMax); const QTime &getMaxGameAge() const; const QMap gameAgeMap; + bool getShowOnlyIfSpectatorsCanWatch() const; + bool getShowSpectatorPasswordProtected() const; + bool getShowOnlyIfSpectatorsCanChat() const; + bool getShowOnlyIfSpectatorsCanSeeHands() const; }; #endif diff --git a/cockatrice/src/gameselector.cpp b/cockatrice/src/gameselector.cpp index a6093464a..036a1817e 100644 --- a/cockatrice/src/gameselector.cpp +++ b/cockatrice/src/gameselector.cpp @@ -152,7 +152,8 @@ void GameSelector::actSetFilter() return; gameListProxyModel->setShowBuddiesOnlyGames(dlg.getShowBuddiesOnlyGames()); - gameListProxyModel->setUnavailableGamesVisible(dlg.getUnavailableGamesVisible()); + gameListProxyModel->setShowFullGames(dlg.getShowFullGames()); + gameListProxyModel->setShowGamesThatStarted(dlg.getShowGamesThatStarted()); gameListProxyModel->setShowPasswordProtectedGames(dlg.getShowPasswordProtectedGames()); gameListProxyModel->setHideIgnoredUserGames(dlg.getHideIgnoredUserGames()); gameListProxyModel->setGameNameFilter(dlg.getGameNameFilter()); @@ -160,6 +161,10 @@ void GameSelector::actSetFilter() gameListProxyModel->setGameTypeFilter(dlg.getGameTypeFilter()); gameListProxyModel->setMaxPlayersFilter(dlg.getMaxPlayersFilterMin(), dlg.getMaxPlayersFilterMax()); gameListProxyModel->setMaxGameAge(dlg.getMaxGameAge()); + gameListProxyModel->setShowOnlyIfSpectatorsCanWatch(dlg.getShowOnlyIfSpectatorsCanWatch()); + gameListProxyModel->setShowSpectatorPasswordProtected(dlg.getShowSpectatorPasswordProtected()); + gameListProxyModel->setShowOnlyIfSpectatorsCanChat(dlg.getShowOnlyIfSpectatorsCanChat()); + gameListProxyModel->setShowOnlyIfSpectatorsCanSeeHands(dlg.getShowOnlyIfSpectatorsCanSeeHands()); gameListProxyModel->saveFilterParameters(gameTypeMap); clearFilterButton->setEnabled(!gameListProxyModel->areFilterParametersSetToDefaults()); diff --git a/cockatrice/src/gamesmodel.cpp b/cockatrice/src/gamesmodel.cpp index 8bf5540df..341a5129b 100644 --- a/cockatrice/src/gamesmodel.cpp +++ b/cockatrice/src/gamesmodel.cpp @@ -23,10 +23,15 @@ enum GameListColumn SPECTATORS }; -const bool DEFAULT_UNAVAILABLE_GAMES_VISIBLE = false; +const bool DEFAULT_SHOW_FULL_GAMES = false; +const bool DEFAULT_SHOW_GAMES_THAT_STARTED = 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 bool DEFAULT_SHOW_ONLY_IF_SPECTATORS_CAN_WATCH = false; +const bool DEFAULT_SHOW_SPECTATOR_PASSWORD_PROTECTED = true; +const bool DEFAULT_SHOW_ONLY_IF_SPECTATORS_CAN_CHAT = false; +const bool DEFAULT_SHOW_ONLY_IF_SPECTATORS_CAN_SEE_HANDS = false; const int DEFAULT_MAX_PLAYERS_MIN = 1; const int DEFAULT_MAX_PLAYERS_MAX = 99; constexpr QTime DEFAULT_MAX_GAME_AGE = QTime(); @@ -288,9 +293,15 @@ void GamesProxyModel::setHideIgnoredUserGames(bool _hideIgnoredUserGames) invalidateFilter(); } -void GamesProxyModel::setUnavailableGamesVisible(bool _unavailableGamesVisible) +void GamesProxyModel::setShowFullGames(bool _showFullGames) { - unavailableGamesVisible = _unavailableGamesVisible; + showFullGames = _showFullGames; + invalidateFilter(); +} + +void GamesProxyModel::setShowGamesThatStarted(bool _showGamesThatStarted) +{ + showGamesThatStarted = _showGamesThatStarted; invalidateFilter(); } @@ -331,6 +342,30 @@ void GamesProxyModel::setMaxGameAge(const QTime &_maxGameAge) invalidateFilter(); } +void GamesProxyModel::setShowOnlyIfSpectatorsCanWatch(bool _showOnlyIfSpectatorsCanWatch) +{ + showOnlyIfSpectatorsCanWatch = _showOnlyIfSpectatorsCanWatch; + invalidateFilter(); +} + +void GamesProxyModel::setShowSpectatorPasswordProtected(bool _showSpectatorPasswordProtected) +{ + showSpectatorPasswordProtected = _showSpectatorPasswordProtected; + invalidateFilter(); +} + +void GamesProxyModel::setShowOnlyIfSpectatorsCanChat(bool _showOnlyIfSpectatorsCanChat) +{ + showOnlyIfSpectatorsCanChat = _showOnlyIfSpectatorsCanChat; + invalidateFilter(); +} + +void GamesProxyModel::setShowOnlyIfSpectatorsCanSeeHands(bool _showOnlyIfSpectatorsCanSeeHands) +{ + showOnlyIfSpectatorsCanSeeHands = _showOnlyIfSpectatorsCanSeeHands; + invalidateFilter(); +} + int GamesProxyModel::getNumFilteredGames() const { GamesModel *model = qobject_cast(sourceModel()); @@ -348,7 +383,8 @@ int GamesProxyModel::getNumFilteredGames() const void GamesProxyModel::resetFilterParameters() { - unavailableGamesVisible = DEFAULT_UNAVAILABLE_GAMES_VISIBLE; + showFullGames = DEFAULT_SHOW_FULL_GAMES; + showGamesThatStarted = DEFAULT_SHOW_GAMES_THAT_STARTED; showPasswordProtectedGames = DEFAULT_SHOW_PASSWORD_PROTECTED_GAMES; showBuddiesOnlyGames = DEFAULT_SHOW_BUDDIES_ONLY_GAMES; hideIgnoredUserGames = DEFAULT_HIDE_IGNORED_USER_GAMES; @@ -358,24 +394,33 @@ void GamesProxyModel::resetFilterParameters() maxPlayersFilterMin = DEFAULT_MAX_PLAYERS_MIN; maxPlayersFilterMax = DEFAULT_MAX_PLAYERS_MAX; maxGameAge = DEFAULT_MAX_GAME_AGE; + showOnlyIfSpectatorsCanWatch = DEFAULT_SHOW_ONLY_IF_SPECTATORS_CAN_WATCH; + showSpectatorPasswordProtected = DEFAULT_SHOW_SPECTATOR_PASSWORD_PROTECTED; + showOnlyIfSpectatorsCanChat = DEFAULT_SHOW_ONLY_IF_SPECTATORS_CAN_CHAT; + showOnlyIfSpectatorsCanSeeHands = DEFAULT_SHOW_ONLY_IF_SPECTATORS_CAN_SEE_HANDS; invalidateFilter(); } bool GamesProxyModel::areFilterParametersSetToDefaults() const { - return unavailableGamesVisible == DEFAULT_UNAVAILABLE_GAMES_VISIBLE && + return showFullGames == DEFAULT_SHOW_FULL_GAMES && showGamesThatStarted == DEFAULT_SHOW_GAMES_THAT_STARTED && showPasswordProtectedGames == DEFAULT_SHOW_PASSWORD_PROTECTED_GAMES && 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 && maxGameAge == DEFAULT_MAX_GAME_AGE; + maxPlayersFilterMax == DEFAULT_MAX_PLAYERS_MAX && maxGameAge == DEFAULT_MAX_GAME_AGE && + showOnlyIfSpectatorsCanWatch == DEFAULT_SHOW_ONLY_IF_SPECTATORS_CAN_WATCH && + showSpectatorPasswordProtected == DEFAULT_SHOW_SPECTATOR_PASSWORD_PROTECTED && + showOnlyIfSpectatorsCanChat == DEFAULT_SHOW_ONLY_IF_SPECTATORS_CAN_CHAT && + showOnlyIfSpectatorsCanSeeHands == DEFAULT_SHOW_ONLY_IF_SPECTATORS_CAN_SEE_HANDS; } void GamesProxyModel::loadFilterParameters(const QMap &allGameTypes) { GameFiltersSettings &gameFilters = SettingsCache::instance().gameFilters(); - unavailableGamesVisible = gameFilters.isUnavailableGamesVisible(); + showFullGames = gameFilters.isShowFullGames(); + showGamesThatStarted = gameFilters.isShowGamesThatStarted(); showPasswordProtectedGames = gameFilters.isShowPasswordProtectedGames(); hideIgnoredUserGames = gameFilters.isHideIgnoredUserGames(); showBuddiesOnlyGames = gameFilters.isShowBuddiesOnlyGames(); @@ -384,6 +429,10 @@ void GamesProxyModel::loadFilterParameters(const QMap &allGameType maxPlayersFilterMin = gameFilters.getMinPlayers(); maxPlayersFilterMax = gameFilters.getMaxPlayers(); maxGameAge = gameFilters.getMaxGameAge(); + showOnlyIfSpectatorsCanWatch = gameFilters.isShowOnlyIfSpectatorsCanWatch(); + showSpectatorPasswordProtected = gameFilters.isShowSpectatorPasswordProtected(); + showOnlyIfSpectatorsCanChat = gameFilters.isShowOnlyIfSpectatorsCanChat(); + showOnlyIfSpectatorsCanSeeHands = gameFilters.isShowOnlyIfSpectatorsCanSeeHands(); QMapIterator gameTypesIterator(allGameTypes); while (gameTypesIterator.hasNext()) { @@ -400,7 +449,8 @@ void GamesProxyModel::saveFilterParameters(const QMap &allGameType { GameFiltersSettings &gameFilters = SettingsCache::instance().gameFilters(); gameFilters.setShowBuddiesOnlyGames(showBuddiesOnlyGames); - gameFilters.setUnavailableGamesVisible(unavailableGamesVisible); + gameFilters.setShowFullGames(showFullGames); + gameFilters.setShowGamesThatStarted(showGamesThatStarted); gameFilters.setShowPasswordProtectedGames(showPasswordProtectedGames); gameFilters.setHideIgnoredUserGames(hideIgnoredUserGames); gameFilters.setGameNameFilter(gameNameFilter); @@ -416,6 +466,11 @@ void GamesProxyModel::saveFilterParameters(const QMap &allGameType gameFilters.setMinPlayers(maxPlayersFilterMin); gameFilters.setMaxPlayers(maxPlayersFilterMax); gameFilters.setMaxGameAge(maxGameAge); + + gameFilters.setShowOnlyIfSpectatorsCanWatch(showOnlyIfSpectatorsCanWatch); + gameFilters.setShowSpectatorPasswordProtected(showSpectatorPasswordProtected); + gameFilters.setShowOnlyIfSpectatorsCanChat(showOnlyIfSpectatorsCanChat); + gameFilters.setShowOnlyIfSpectatorsCanSeeHands(showOnlyIfSpectatorsCanSeeHands); } bool GamesProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex & /*sourceParent*/) const @@ -430,7 +485,7 @@ bool GamesProxyModel::filterAcceptsRow(int sourceRow) const #else static const QDate epochDate = QDateTime::fromTime_t(0, Qt::UTC).date(); #endif - GamesModel *model = qobject_cast(sourceModel()); + auto *model = qobject_cast(sourceModel()); if (!model) return false; @@ -443,15 +498,13 @@ bool GamesProxyModel::filterAcceptsRow(int sourceRow) const QString::fromStdString(game.creator_info().name()))) { return false; } - if (!unavailableGamesVisible) { - if (game.player_count() == game.max_players()) + if (!showFullGames && game.player_count() == game.max_players()) + return false; + if (!showGamesThatStarted && game.started()) + return false; + if (!ownUserIsRegistered) + if (game.only_registered()) return false; - if (game.started()) - return false; - if (!ownUserIsRegistered) - if (game.only_registered()) - return false; - } if (!showPasswordProtectedGames && game.with_password()) return false; if (!gameNameFilter.isEmpty()) @@ -482,6 +535,17 @@ bool GamesProxyModel::filterAcceptsRow(int sourceRow) const return false; } } + + if (showOnlyIfSpectatorsCanWatch) { + if (!game.spectators_allowed()) + return false; + if (!showSpectatorPasswordProtected && game.spectators_need_password()) + return false; + if (showOnlyIfSpectatorsCanChat && !game.spectators_can_chat()) + return false; + if (showOnlyIfSpectatorsCanSeeHands && !game.spectators_omniscient()) + return false; + } return true; } diff --git a/cockatrice/src/gamesmodel.h b/cockatrice/src/gamesmodel.h index 7cc3891f1..05fe5d085 100644 --- a/cockatrice/src/gamesmodel.h +++ b/cockatrice/src/gamesmodel.h @@ -80,12 +80,15 @@ private: // - filterAcceptsRow() bool showBuddiesOnlyGames; bool hideIgnoredUserGames; - bool unavailableGamesVisible; + bool showFullGames; + bool showGamesThatStarted; bool showPasswordProtectedGames; QString gameNameFilter, creatorNameFilter; QSet gameTypeFilter; quint32 maxPlayersFilterMin, maxPlayersFilterMax; QTime maxGameAge; + bool showOnlyIfSpectatorsCanWatch, showSpectatorPasswordProtected, showOnlyIfSpectatorsCanChat, + showOnlyIfSpectatorsCanSeeHands; public: GamesProxyModel(QObject *parent = nullptr, const TabSupervisor *_tabSupervisor = nullptr); @@ -100,11 +103,16 @@ public: return hideIgnoredUserGames; } void setHideIgnoredUserGames(bool _hideIgnoredUserGames); - bool getUnavailableGamesVisible() const + bool getShowFullGames() const { - return unavailableGamesVisible; + return showFullGames; } - void setUnavailableGamesVisible(bool _unavailableGamesVisible); + void setShowFullGames(bool _showFullGames); + bool getShowGamesThatStarted() const + { + return showGamesThatStarted; + } + void setShowGamesThatStarted(bool _showGamesThatStarted); bool getShowPasswordProtectedGames() const { return showPasswordProtectedGames; @@ -139,6 +147,26 @@ public: return maxGameAge; } void setMaxGameAge(const QTime &_maxGameAge); + bool getShowOnlyIfSpectatorsCanWatch() const + { + return showOnlyIfSpectatorsCanWatch; + } + void setShowOnlyIfSpectatorsCanWatch(bool _showOnlyIfSpectatorsCanWatch); + bool getShowSpectatorPasswordProtected() const + { + return showSpectatorPasswordProtected; + } + void setShowSpectatorPasswordProtected(bool _showSpectatorPasswordProtected); + bool getShowOnlyIfSpectatorsCanChat() const + { + return showOnlyIfSpectatorsCanChat; + } + void setShowOnlyIfSpectatorsCanChat(bool _showOnlyIfSpectatorsCanChat); + bool getShowOnlyIfSpectatorsCanSeeHands() const + { + return showOnlyIfSpectatorsCanSeeHands; + } + void setShowOnlyIfSpectatorsCanSeeHands(bool _showOnlyIfSpectatorsCanSeeHands); int getNumFilteredGames() const; void resetFilterParameters(); diff --git a/cockatrice/src/settings/gamefilterssettings.cpp b/cockatrice/src/settings/gamefilterssettings.cpp index 5a6c49c4d..8bd2c8ff4 100644 --- a/cockatrice/src/settings/gamefilterssettings.cpp +++ b/cockatrice/src/settings/gamefilterssettings.cpp @@ -28,14 +28,25 @@ bool GameFiltersSettings::isShowBuddiesOnlyGames() return previous == QVariant() ? true : previous.toBool(); } -void GameFiltersSettings::setUnavailableGamesVisible(bool enabled) +void GameFiltersSettings::setShowFullGames(bool show) { - setValue(enabled, "unavailable_games_visible", "filter_games"); + setValue(show, "show_full_games", "filter_games"); } -bool GameFiltersSettings::isUnavailableGamesVisible() +bool GameFiltersSettings::isShowFullGames() { - QVariant previous = getValue("unavailable_games_visible", "filter_games"); + QVariant previous = getValue("show_full_games", "filter_games"); + return previous == QVariant() ? false : previous.toBool(); +} + +void GameFiltersSettings::setShowGamesThatStarted(bool show) +{ + setValue(show, "show_games_that_started", "filter_games"); +} + +bool GameFiltersSettings::isShowGamesThatStarted() +{ + QVariant previous = getValue("show_games_that_started", "filter_games"); return previous == QVariant() ? false : previous.toBool(); } @@ -129,3 +140,47 @@ bool GameFiltersSettings::isGameTypeEnabled(QString gametype) QVariant previous = getValue("game_type/" + hashGameType(gametype), "filter_games"); return previous == QVariant() ? false : previous.toBool(); } + +void GameFiltersSettings::setShowOnlyIfSpectatorsCanWatch(bool show) +{ + setValue(show, "show_only_if_spectators_can_watch", "filter_games"); +} + +bool GameFiltersSettings::isShowOnlyIfSpectatorsCanWatch() +{ + QVariant previous = getValue("show_only_if_spectators_can_watch", "filter_games"); + return previous == QVariant() ? false : previous.toBool(); +} + +void GameFiltersSettings::setShowSpectatorPasswordProtected(bool show) +{ + setValue(show, "show_spectator_password_protected", "filter_games"); +} + +bool GameFiltersSettings::isShowSpectatorPasswordProtected() +{ + QVariant previous = getValue("show_spectator_password_protected", "filter_games"); + return previous == QVariant() ? true : previous.toBool(); +} + +void GameFiltersSettings::setShowOnlyIfSpectatorsCanChat(bool show) +{ + setValue(show, "show_only_if_spectators_can_chat", "filter_games"); +} + +bool GameFiltersSettings::isShowOnlyIfSpectatorsCanChat() +{ + QVariant previous = getValue("show_only_if_spectators_can_chat", "filter_games"); + return previous == QVariant() ? true : previous.toBool(); +} + +void GameFiltersSettings::setShowOnlyIfSpectatorsCanSeeHands(bool show) +{ + setValue(show, "show_only_if_spectators_can_see_hands", "filter_games"); +} + +bool GameFiltersSettings::isShowOnlyIfSpectatorsCanSeeHands() +{ + QVariant previous = getValue("show_only_if_spectators_can_see_hands", "filter_games"); + return previous == QVariant() ? true : previous.toBool(); +} \ No newline at end of file diff --git a/cockatrice/src/settings/gamefilterssettings.h b/cockatrice/src/settings/gamefilterssettings.h index e6bc40e59..fe168f53d 100644 --- a/cockatrice/src/settings/gamefilterssettings.h +++ b/cockatrice/src/settings/gamefilterssettings.h @@ -10,7 +10,8 @@ class GameFiltersSettings : public SettingsManager public: bool isShowBuddiesOnlyGames(); - bool isUnavailableGamesVisible(); + bool isShowFullGames(); + bool isShowGamesThatStarted(); bool isShowPasswordProtectedGames(); bool isHideIgnoredUserGames(); QString getGameNameFilter(); @@ -19,10 +20,15 @@ public: int getMaxPlayers(); QTime getMaxGameAge(); bool isGameTypeEnabled(QString gametype); + bool isShowOnlyIfSpectatorsCanWatch(); + bool isShowSpectatorPasswordProtected(); + bool isShowOnlyIfSpectatorsCanChat(); + bool isShowOnlyIfSpectatorsCanSeeHands(); void setShowBuddiesOnlyGames(bool show); void setHideIgnoredUserGames(bool hide); - void setUnavailableGamesVisible(bool enabled); + void setShowFullGames(bool show); + void setShowGamesThatStarted(bool show); void setShowPasswordProtectedGames(bool show); void setGameNameFilter(QString gameName); void setCreatorNameFilter(QString creatorName); @@ -31,6 +37,10 @@ public: void setMaxGameAge(const QTime &maxGameAge); void setGameTypeEnabled(QString gametype, bool enabled); void setGameHashedTypeEnabled(QString gametypeHASHED, bool enabled); + void setShowOnlyIfSpectatorsCanWatch(bool show); + void setShowSpectatorPasswordProtected(bool show); + void setShowOnlyIfSpectatorsCanChat(bool show); + void setShowOnlyIfSpectatorsCanSeeHands(bool show); signals: public slots: diff --git a/cockatrice/src/settingscache.cpp b/cockatrice/src/settingscache.cpp index 96178da98..4906ae46a 100644 --- a/cockatrice/src/settingscache.cpp +++ b/cockatrice/src/settingscache.cpp @@ -95,7 +95,8 @@ void SettingsCache::translateLegacySettings() // Game filters legacySetting.beginGroup("filter_games"); - gameFilters().setUnavailableGamesVisible(legacySetting.value("unavailable_games_visible").toBool()); + gameFilters().setShowFullGames(legacySetting.value("unavailable_games_visible").toBool()); + gameFilters().setShowGamesThatStarted(legacySetting.value("unavailable_games_visible").toBool()); gameFilters().setShowPasswordProtectedGames(legacySetting.value("show_password_protected_games").toBool()); gameFilters().setGameNameFilter(legacySetting.value("game_name_filter").toString()); gameFilters().setShowBuddiesOnlyGames(legacySetting.value("show_buddies_only_games").toBool()); From ef78fdf342e3b1c2978be8a38fdcfa9d79937dca Mon Sep 17 00:00:00 2001 From: tooomm Date: Fri, 30 Oct 2020 01:21:01 +0100 Subject: [PATCH 019/126] Fix resizing for game filter dialog (#4149) --- cockatrice/src/dlg_filter_games.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cockatrice/src/dlg_filter_games.cpp b/cockatrice/src/dlg_filter_games.cpp index 63cb8c279..e70d6daa3 100644 --- a/cockatrice/src/dlg_filter_games.cpp +++ b/cockatrice/src/dlg_filter_games.cpp @@ -150,8 +150,10 @@ DlgFilterGames::DlgFilterGames(const QMap &_allGameTypes, auto *rightGrid = new QGridLayout; rightGrid->addWidget(gameTypeFilterGroupBox, 0, 0, 1, 1); rightGrid->addWidget(spectatorsGroupBox, 1, 0, 1, 1); + auto *rightColumn = new QVBoxLayout; rightColumn->addLayout(rightGrid); + rightColumn->addStretch(); auto *hbox = new QHBoxLayout; hbox->addLayout(leftColumn); @@ -167,6 +169,8 @@ DlgFilterGames::DlgFilterGames(const QMap &_allGameTypes, setLayout(mainLayout); setWindowTitle(tr("Filter games")); + + setFixedHeight(sizeHint().height()); } void DlgFilterGames::actOk() @@ -306,4 +310,4 @@ bool DlgFilterGames::getShowOnlyIfSpectatorsCanChat() const bool DlgFilterGames::getShowOnlyIfSpectatorsCanSeeHands() const { return showOnlyIfSpectatorsCanSeeHands->isEnabled() && showOnlyIfSpectatorsCanSeeHands->isChecked(); -} \ No newline at end of file +} From 8e9d4e3a674f4217cf8a2ac1a43e889879654257 Mon Sep 17 00:00:00 2001 From: knitknit <63179146+knitknit@users.noreply.github.com> Date: Sun, 1 Nov 2020 15:02:17 -0500 Subject: [PATCH 020/126] Retain lastDrawList if a card is being moved within hand, e.g. hand reordering only. (#4152) --- common/server_player.cpp | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/common/server_player.cpp b/common/server_player.cpp index 35debaa42..e043f95f5 100644 --- a/common/server_player.cpp +++ b/common/server_player.cpp @@ -149,9 +149,9 @@ void Server_Player::setupZones() // ------------------------------------------------------------------ // Create zones - Server_CardZone *deckZone = new Server_CardZone(this, "deck", false, ServerInfo_Zone::HiddenZone); + auto *deckZone = new Server_CardZone(this, "deck", false, ServerInfo_Zone::HiddenZone); addZone(deckZone); - Server_CardZone *sbZone = new Server_CardZone(this, "sb", false, ServerInfo_Zone::HiddenZone); + auto *sbZone = new Server_CardZone(this, "sb", false, ServerInfo_Zone::HiddenZone); addZone(sbZone); addZone(new Server_CardZone(this, "table", true, ServerInfo_Zone::PublicZone)); addZone(new Server_CardZone(this, "hand", false, ServerInfo_Zone::PrivateZone)); @@ -424,12 +424,13 @@ Response::ResponseCode Server_Player::moveCard(GameEventStorage &ges, int originalPosition = cardsToMove[cardIndex].second; int position = startzone->removeCard(card); - if (startzone->getName() == "hand") { - if (undoingDraw) { - lastDrawList.removeAt(lastDrawList.indexOf(card->getId())); - } else if (lastDrawList.contains(card->getId())) { - lastDrawList.clear(); - } + + // "Undo draw" should only remain valid if the just-drawn card stays within the user's hand (e.g., they only + // reorder their hand). If a just-drawn card leaves the hand then clear lastDrawList to invalidate "undo draw." + // (Ignore the case where the card is currently being un-drawn.) + if (startzone->getName() == "hand" && targetzone->getName() != "hand" && lastDrawList.contains(card->getId()) && + !undoingDraw) { + lastDrawList.clear(); } if ((startzone == targetzone) && !startzone->hasCoords()) { @@ -1340,7 +1341,7 @@ Server_Player::cmdCreateToken(const Command_CreateToken &cmd, ResponseContainer y = 0; } - Server_Card *card = new Server_Card(cardName, newCardId(), x, y); + auto *card = new Server_Card(cardName, newCardId(), x, y); card->moveToThread(thread()); card->setPT(QString::fromStdString(cmd.pt())); card->setColor(QString::fromStdString(cmd.color())); @@ -1624,8 +1625,8 @@ Server_Player::cmdCreateCounter(const Command_CreateCounter &cmd, ResponseContai return Response::RespContextError; } - Server_Counter *c = new Server_Counter(newCounterId(), QString::fromStdString(cmd.counter_name()), - cmd.counter_color(), cmd.radius(), cmd.value()); + auto *c = new Server_Counter(newCounterId(), QString::fromStdString(cmd.counter_name()), cmd.counter_color(), + cmd.radius(), cmd.value()); addCounter(c); Event_CreateCounter event; From 0405c82cb271e4d036b5664ab3a9ee432c517163 Mon Sep 17 00:00:00 2001 From: tooomm Date: Mon, 2 Nov 2020 01:03:08 +0100 Subject: [PATCH 021/126] File name cleanup (#4154) --- cockatrice/CMakeLists.txt | 2 +- cockatrice/src/{window_sets.cpp => dlg_manage_sets.cpp} | 2 +- cockatrice/src/{window_sets.h => dlg_manage_sets.h} | 4 ++-- cockatrice/src/tab_game.cpp | 2 +- cockatrice/src/tab_logs.cpp | 2 +- cockatrice/src/window_main.cpp | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) rename cockatrice/src/{window_sets.cpp => dlg_manage_sets.cpp} (99%) rename cockatrice/src/{window_sets.h => dlg_manage_sets.h} (97%) diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 2f9ae9465..b1d213786 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -18,6 +18,7 @@ SET(cockatrice_SOURCES src/dlg_forgotpasswordrequest.cpp src/dlg_forgotpasswordreset.cpp src/dlg_forgotpasswordchallenge.cpp + src/dlg_manage_sets.cpp src/dlg_register.cpp src/dlg_tip_of_the_day.cpp src/tip_of_the_day.cpp @@ -62,7 +63,6 @@ SET(cockatrice_SOURCES src/carddragitem.cpp src/carddatabasemodel.cpp src/setsmodel.cpp - src/window_sets.cpp src/abstractgraphicsitem.cpp src/abstractcarddragitem.cpp src/dlg_settings.cpp diff --git a/cockatrice/src/window_sets.cpp b/cockatrice/src/dlg_manage_sets.cpp similarity index 99% rename from cockatrice/src/window_sets.cpp rename to cockatrice/src/dlg_manage_sets.cpp index 02898c8b4..397aa32bc 100644 --- a/cockatrice/src/window_sets.cpp +++ b/cockatrice/src/dlg_manage_sets.cpp @@ -1,4 +1,4 @@ -#include "window_sets.h" +#include "dlg_manage_sets.h" #include "customlineedit.h" #include "main.h" diff --git a/cockatrice/src/window_sets.h b/cockatrice/src/dlg_manage_sets.h similarity index 97% rename from cockatrice/src/window_sets.h rename to cockatrice/src/dlg_manage_sets.h index c9dd7f5ec..6e932b30b 100644 --- a/cockatrice/src/window_sets.h +++ b/cockatrice/src/dlg_manage_sets.h @@ -1,5 +1,5 @@ -#ifndef WINDOW_SETS_H -#define WINDOW_SETS_H +#ifndef DLG_MANAGE_SETS_H +#define DLG_MANAGE_SETS_H #include #include diff --git a/cockatrice/src/tab_game.cpp b/cockatrice/src/tab_game.cpp index 5b503d96e..c96602406 100644 --- a/cockatrice/src/tab_game.cpp +++ b/cockatrice/src/tab_game.cpp @@ -9,6 +9,7 @@ #include "deckview.h" #include "dlg_creategame.h" #include "dlg_load_remote_deck.h" +#include "dlg_manage_sets.h" #include "gamescene.h" #include "gameview.h" #include "get_pb_extension.h" @@ -52,7 +53,6 @@ #include "replay_timeline_widget.h" #include "settingscache.h" #include "tab_supervisor.h" -#include "window_sets.h" #include "zoneviewwidget.h" #include "zoneviewzone.h" diff --git a/cockatrice/src/tab_logs.cpp b/cockatrice/src/tab_logs.cpp index 2960f91d3..5c318cf95 100644 --- a/cockatrice/src/tab_logs.cpp +++ b/cockatrice/src/tab_logs.cpp @@ -2,10 +2,10 @@ #include "abstractclient.h" #include "customlineedit.h" +#include "dlg_manage_sets.h" #include "pb/moderator_commands.pb.h" #include "pb/response_viewlog_history.pb.h" #include "pending_command.h" -#include "window_sets.h" #include #include diff --git a/cockatrice/src/window_main.cpp b/cockatrice/src/window_main.cpp index 88779d877..f9feb7e26 100644 --- a/cockatrice/src/window_main.cpp +++ b/cockatrice/src/window_main.cpp @@ -25,6 +25,7 @@ #include "dlg_forgotpasswordchallenge.h" #include "dlg_forgotpasswordrequest.h" #include "dlg_forgotpasswordreset.h" +#include "dlg_manage_sets.h" #include "dlg_register.h" #include "dlg_settings.h" #include "dlg_tip_of_the_day.h" @@ -44,7 +45,6 @@ #include "tab_game.h" #include "tab_supervisor.h" #include "version_string.h" -#include "window_sets.h" #include #include From 3064621a7e94f06732245dd36e9a42cba37ea2ec Mon Sep 17 00:00:00 2001 From: tooomm Date: Tue, 3 Nov 2020 00:44:19 +0100 Subject: [PATCH 022/126] Add exclude term to search hints (#4038) --- cockatrice/resources/help/search.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cockatrice/resources/help/search.md b/cockatrice/resources/help/search.md index 31b5f2b4f..481789cb9 100644 --- a/cockatrice/resources/help/search.md +++ b/cockatrice/resources/help/search.md @@ -49,7 +49,7 @@ The search bar recognizes a set of special commands similar to some other card d
[e:lea,leb](#e:lea,leb) (Cards that appear in Alpha or Beta)
e:lea,leb -(e:lea e:leb) (Cards that appear in Alpha or Beta but not in both editions)
-
Inverse:
+
Negate:
[c:wu -c:m](#c:wu -c:m) (Any card that is white or blue, but not multicolored)
Branching:
From f11f072e0abdd3f2573ea6428595eb41d7edfd60 Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Mon, 9 Nov 2020 01:35:54 +0100 Subject: [PATCH 023/126] add missing mysql connector dependencies to docker images (#4160) --- .ci/ArchLinux/Dockerfile | 1 + .ci/DebianBuster/Dockerfile | 1 + .ci/Fedora32/Dockerfile | 1 + .ci/UbuntuBionic/Dockerfile | 1 + .ci/UbuntuFocal/Dockerfile | 1 + 5 files changed, 5 insertions(+) diff --git a/.ci/ArchLinux/Dockerfile b/.ci/ArchLinux/Dockerfile index b9f96e291..308854ebd 100644 --- a/.ci/ArchLinux/Dockerfile +++ b/.ci/ArchLinux/Dockerfile @@ -5,6 +5,7 @@ RUN pacman --sync --refresh --sysupgrade --needed --noconfirm \ ccache \ cmake \ git \ + mariadb-libs \ protobuf \ qt5-base \ qt5-multimedia \ diff --git a/.ci/DebianBuster/Dockerfile b/.ci/DebianBuster/Dockerfile index f81a08ac0..82109e70c 100644 --- a/.ci/DebianBuster/Dockerfile +++ b/.ci/DebianBuster/Dockerfile @@ -9,6 +9,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ g++ \ git \ liblzma-dev \ + libmariadb-dev-compat \ libprotobuf-dev \ libqt5multimedia5-plugins \ libqt5sql5-mysql \ diff --git a/.ci/Fedora32/Dockerfile b/.ci/Fedora32/Dockerfile index a6f8d5829..7a0a2e74f 100644 --- a/.ci/Fedora32/Dockerfile +++ b/.ci/Fedora32/Dockerfile @@ -10,6 +10,7 @@ RUN dnf install -y \ git \ hicolor-icon-theme \ libappstream-glib \ + mariadb-devel \ protobuf-devel \ qt5-{qttools,qtsvg,qtmultimedia,qtwebsockets}-devel \ rpm-build \ diff --git a/.ci/UbuntuBionic/Dockerfile b/.ci/UbuntuBionic/Dockerfile index 459ed0d61..10f340fdf 100644 --- a/.ci/UbuntuBionic/Dockerfile +++ b/.ci/UbuntuBionic/Dockerfile @@ -9,6 +9,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ g++ \ git \ liblzma-dev \ + libmariadb-dev-compat \ libprotobuf-dev \ libqt5multimedia5-plugins \ libqt5sql5-mysql \ diff --git a/.ci/UbuntuFocal/Dockerfile b/.ci/UbuntuFocal/Dockerfile index 1bd81271b..1b6b47c82 100644 --- a/.ci/UbuntuFocal/Dockerfile +++ b/.ci/UbuntuFocal/Dockerfile @@ -10,6 +10,7 @@ RUN apt-get update && \ g++ \ git \ liblzma-dev \ + libmariadb-dev-compat \ libprotobuf-dev \ libqt5multimedia5-plugins \ libqt5sql5-mysql \ From 68074b0f74dd672097c76cf2e4dbd312634b2a01 Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Fri, 13 Nov 2020 20:55:01 +0100 Subject: [PATCH 024/126] check if node is a dir before deletion (#4165) --- cockatrice/src/tab_deck_storage.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cockatrice/src/tab_deck_storage.cpp b/cockatrice/src/tab_deck_storage.cpp index db1716ad2..096faec26 100644 --- a/cockatrice/src/tab_deck_storage.cpp +++ b/cockatrice/src/tab_deck_storage.cpp @@ -196,6 +196,9 @@ void TabDeckStorage::uploadFinished(const Response &r, const CommandContainer &c void TabDeckStorage::actDeleteLocalDeck() { QModelIndex curLeft = localDirView->selectionModel()->currentIndex(); + if (localDirModel->isDir(curLeft)) + return; + if (QMessageBox::warning(this, tr("Delete local file"), tr("Are you sure you want to delete \"%1\"?").arg(localDirModel->fileName(curLeft)), QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) From 0966a8e90fd331117d15f57452701bf74568bbfc Mon Sep 17 00:00:00 2001 From: Zach H Date: Fri, 13 Nov 2020 15:36:20 -0500 Subject: [PATCH 025/126] Fix #566 by allowing for DeckName/Comments to be resized to the max, and even hidden! (#4157) --- cockatrice/src/tab_deck_editor.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cockatrice/src/tab_deck_editor.cpp b/cockatrice/src/tab_deck_editor.cpp index aab5f2da7..bcb465013 100644 --- a/cockatrice/src/tab_deck_editor.cpp +++ b/cockatrice/src/tab_deck_editor.cpp @@ -26,8 +26,6 @@ #include #include #include -#include -#include #include #include #include @@ -89,6 +87,7 @@ void TabDeckEditor::createDeckDock() commentsLabel = new QLabel(); commentsLabel->setObjectName("commentsLabel"); commentsEdit = new QTextEdit; + commentsEdit->setMinimumHeight(nameEdit->minimumSizeHint().height()); commentsEdit->setObjectName("commentsEdit"); commentsLabel->setBuddy(commentsEdit); connect(commentsEdit, SIGNAL(textChanged()), this, SLOT(updateComments())); @@ -147,7 +146,7 @@ void TabDeckEditor::createDeckDock() auto *split = new QSplitter; split->setObjectName("deckSplitter"); split->setOrientation(Qt::Vertical); - split->setChildrenCollapsible(false); + split->setChildrenCollapsible(true); split->addWidget(topWidget); split->addWidget(bottomWidget); split->setStretchFactor(0, 1); From d07bf1211a66a8037676e10adb617758761a54d6 Mon Sep 17 00:00:00 2001 From: tooomm Date: Fri, 13 Nov 2020 22:01:44 +0100 Subject: [PATCH 026/126] Improve dialogs (#4153) --- cockatrice/src/dlg_creategame.cpp | 2 +- cockatrice/src/dlg_update.cpp | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/cockatrice/src/dlg_creategame.cpp b/cockatrice/src/dlg_creategame.cpp index ad5742116..2c16889d7 100644 --- a/cockatrice/src/dlg_creategame.cpp +++ b/cockatrice/src/dlg_creategame.cpp @@ -95,7 +95,7 @@ void DlgCreateGame::sharedCtor() grid->addWidget(generalGroupBox, 0, 0); grid->addWidget(joinRestrictionsGroupBox, 0, 1); grid->addWidget(gameTypeGroupBox, 1, 0); - grid->addWidget(spectatorsGroupBox, 1, 1); + grid->addWidget(spectatorsGroupBox, 1, 1, Qt::AlignTop); grid->addWidget(rememberGameSettings, 2, 0); buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok); diff --git a/cockatrice/src/dlg_update.cpp b/cockatrice/src/dlg_update.cpp index c679bd5c9..86ba00a98 100644 --- a/cockatrice/src/dlg_update.cpp +++ b/cockatrice/src/dlg_update.cpp @@ -52,6 +52,10 @@ DlgUpdate::DlgUpdate(QWidget *parent) : QDialog(parent) parentLayout->addWidget(buttonBox); setLayout(parentLayout); + setWindowTitle(tr("Check for Client Updates")); + + setFixedHeight(sizeHint().height()); + setFixedWidth(sizeHint().width()); // Check for SSL (this probably isn't necessary) if (!QSslSocket::supportsSsl()) { From ca5f1dd43426d1fb7f383914bdc0bc1f79bbe5c1 Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Mon, 23 Nov 2020 01:57:51 +0100 Subject: [PATCH 027/126] do some guesswork if cards can't be found (#4131) modify up the simplifyCardName function to ignore right halves add guessCard function that prioritises full card names the simple ones fix imports for misformatted split cards or double faced cards introduces a small concession: not completely formatted names with a shared name on the left side will get mixed up, eg "bind" but not "Bind" this should be fine considering how this would fix a lot more cards --- cockatrice/src/carddatabase.cpp | 31 +++++++++++++++++++++++-------- cockatrice/src/carddatabase.h | 1 + cockatrice/src/cardframe.cpp | 2 +- cockatrice/src/cardinfowidget.cpp | 5 +++-- cockatrice/src/deck_loader.cpp | 2 +- cockatrice/src/player.cpp | 2 +- 6 files changed, 30 insertions(+), 13 deletions(-) diff --git a/cockatrice/src/carddatabase.cpp b/cockatrice/src/carddatabase.cpp index 3ae2b98ab..48b72b311 100644 --- a/cockatrice/src/carddatabase.cpp +++ b/cockatrice/src/carddatabase.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -291,22 +292,23 @@ void CardInfo::refreshCachedSetNames() QString CardInfo::simplifyName(const QString &name) { - QString simpleName(name); + static const QRegularExpression spaceOrSplit("(\\s+|\\/\\/.*)"); + static const QRegularExpression nonAlnum("[^a-z0-9]"); + + QString simpleName = name.toLower(); + + // remove spaces and right halves of split cards + simpleName.remove(spaceOrSplit); // So Aetherling would work, but not Ætherling since 'Æ' would get replaced // with nothing. simpleName.replace("æ", "ae"); - simpleName.replace("Æ", "AE"); // Replace Jötun Grunt with Jotun Grunt. simpleName = simpleName.normalized(QString::NormalizationForm_KD); - // Replace dashes with spaces so that we can say "garruk the veil cursed" - // instead of the unintuitive "garruk the veilcursed". - simpleName = simpleName.replace("-", " "); - - simpleName.remove(QRegExp("[^a-zA-Z0-9 ]")); - simpleName = simpleName.toLower(); + // remove all non alphanumeric characters from the name + simpleName.remove(nonAlnum); return simpleName; } @@ -437,6 +439,19 @@ CardInfoPtr CardDatabase::getCardBySimpleName(const QString &cardName) const return getCardFromMap(simpleNameCards, CardInfo::simplifyName(cardName)); } +CardInfoPtr CardDatabase::guessCard(const QString &cardName) const +{ + CardInfoPtr temp = getCard(cardName); + if (temp == nullptr) { // get card by simple name instead + temp = getCardBySimpleName(cardName); + if (temp == nullptr) { // still could not find the card, so simplify the cardName too + QString simpleCardName = CardInfo::simplifyName(cardName); + temp = getCardBySimpleName(simpleCardName); + } + } + return temp; // returns nullptr if not found +} + CardSetPtr CardDatabase::getSet(const QString &setName) { if (sets.contains(setName)) { diff --git a/cockatrice/src/carddatabase.h b/cockatrice/src/carddatabase.h index b18909147..fe884c51b 100644 --- a/cockatrice/src/carddatabase.h +++ b/cockatrice/src/carddatabase.h @@ -409,6 +409,7 @@ public: void removeCard(CardInfoPtr card); CardInfoPtr getCard(const QString &cardName) const; QList getCards(const QStringList &cardNames) const; + CardInfoPtr guessCard(const QString &cardName) const; /* * Get a card by its simple name. The name will be simplified in this diff --git a/cockatrice/src/cardframe.cpp b/cockatrice/src/cardframe.cpp index eeec5bc22..a9044652f 100644 --- a/cockatrice/src/cardframe.cpp +++ b/cockatrice/src/cardframe.cpp @@ -107,7 +107,7 @@ void CardFrame::setCard(CardInfoPtr card) void CardFrame::setCard(const QString &cardName) { - setCard(db->getCardBySimpleName(cardName)); + setCard(db->guessCard(cardName)); } void CardFrame::setCard(AbstractCardItem *card) diff --git a/cockatrice/src/cardinfowidget.cpp b/cockatrice/src/cardinfowidget.cpp index cb864c4f7..4c0d16717 100644 --- a/cockatrice/src/cardinfowidget.cpp +++ b/cockatrice/src/cardinfowidget.cpp @@ -65,9 +65,10 @@ void CardInfoWidget::setCard(CardInfoPtr card) void CardInfoWidget::setCard(const QString &cardName) { - setCard(db->getCardBySimpleName(cardName)); - if (!info) + setCard(db->guessCard(cardName)); + if (info == nullptr) { text->setInvalidCardName(cardName); + } } void CardInfoWidget::setCard(AbstractCardItem *card) diff --git a/cockatrice/src/deck_loader.cpp b/cockatrice/src/deck_loader.cpp index 8f8cd38c3..2967e0181 100644 --- a/cockatrice/src/deck_loader.cpp +++ b/cockatrice/src/deck_loader.cpp @@ -291,7 +291,7 @@ QString DeckLoader::getCardZoneFromName(QString cardName, QString currentZoneNam QString DeckLoader::getCompleteCardName(const QString cardName) const { if (db) { - CardInfoPtr temp = db->getCardBySimpleName(cardName); + CardInfoPtr temp = db->guessCard(cardName); if (temp) { return temp->getName(); } diff --git a/cockatrice/src/player.cpp b/cockatrice/src/player.cpp index 740d069ac..7b8195846 100644 --- a/cockatrice/src/player.cpp +++ b/cockatrice/src/player.cpp @@ -1242,7 +1242,7 @@ void Player::actCreateToken() lastTokenName = dlg.getName(); lastTokenPT = dlg.getPT(); - CardInfoPtr correctedCard = db->getCardBySimpleName(lastTokenName); + CardInfoPtr correctedCard = db->guessCard(lastTokenName); if (correctedCard) { lastTokenName = correctedCard->getName(); lastTokenTableRow = TableZone::clampValidTableRow(2 - correctedCard->getTableRow()); From 45d838a0b33c1083b81979aa8751f805a57edd9b Mon Sep 17 00:00:00 2001 From: Zach H Date: Sun, 22 Nov 2020 20:06:25 -0500 Subject: [PATCH 028/126] Search full subdirectory for custom databases (#4137) * Fix #2324 by allowing for iteration & symlinks * Ensure alphabetical sorting --- cockatrice/src/carddatabase.cpp | 21 +- servatrice/src/smtp/qxtmailattachment.h | 34 +-- servatrice/src/smtp/qxtmailmessage.cpp | 283 +++++++++--------------- 3 files changed, 140 insertions(+), 198 deletions(-) diff --git a/cockatrice/src/carddatabase.cpp b/cockatrice/src/carddatabase.cpp index 48b72b311..983f1733b 100644 --- a/cockatrice/src/carddatabase.cpp +++ b/cockatrice/src/carddatabase.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -534,11 +535,21 @@ LoadStatus CardDatabase::loadCardDatabases() loadCardDatabase(SettingsCache::instance().getTokenDatabasePath()); // load tokens database loadCardDatabase(SettingsCache::instance().getSpoilerCardDatabasePath()); // load spoilers database - // load custom card databases - QDir dir(SettingsCache::instance().getCustomCardDatabasePath()); - for (const QString &fileName : - dir.entryList(QStringList("*.xml"), QDir::Files | QDir::Readable, QDir::Name | QDir::IgnoreCase)) { - loadCardDatabase(dir.absoluteFilePath(fileName)); + // find all custom card databases, recursively & following symlinks + // then load them alphabetically + QDirIterator customDatabaseIterator(SettingsCache::instance().getCustomCardDatabasePath(), QStringList() << "*.xml", + QDir::Files, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks); + QStringList databasePaths; + while (customDatabaseIterator.hasNext()) { + customDatabaseIterator.next(); + databasePaths.push_back(customDatabaseIterator.filePath()); + } + databasePaths.sort(); + + for (auto i = 0; i < databasePaths.size(); ++i) { + const auto &databasePath = databasePaths.at(i); + qDebug() << "Loading Custom Set" << i << "(" << databasePath << ")"; + loadCardDatabase(databasePath); } // AFTER all the cards have been loaded diff --git a/servatrice/src/smtp/qxtmailattachment.h b/servatrice/src/smtp/qxtmailattachment.h index 397b117f7..62a0e9ad5 100644 --- a/servatrice/src/smtp/qxtmailattachment.h +++ b/servatrice/src/smtp/qxtmailattachment.h @@ -27,41 +27,41 @@ #include "qxtglobal.h" -#include -#include #include +#include +#include #include #include -#include +#include struct QxtMailAttachmentPrivate; class QXT_NETWORK_EXPORT QxtMailAttachment { public: QxtMailAttachment(); - QxtMailAttachment(const QxtMailAttachment& other); - QxtMailAttachment(const QByteArray& content, const QString& contentType = QString("application/octet-stream")); - QxtMailAttachment(QIODevice* content, const QString& contentType = QString("application/octet-stream")); - QxtMailAttachment& operator=(const QxtMailAttachment& other); + QxtMailAttachment(const QxtMailAttachment &other); + QxtMailAttachment(const QByteArray &content, const QString &contentType = QString("application/octet-stream")); + QxtMailAttachment(QIODevice *content, const QString &contentType = QString("application/octet-stream")); + QxtMailAttachment &operator=(const QxtMailAttachment &other); ~QxtMailAttachment(); - static QxtMailAttachment fromFile(const QString& filename); + static QxtMailAttachment fromFile(const QString &filename); - QIODevice* content() const; - void setContent(const QByteArray& content); - void setContent(QIODevice* content); + QIODevice *content() const; + void setContent(const QByteArray &content); + void setContent(QIODevice *content); bool deleteContent() const; void setDeleteContent(bool enable); QString contentType() const; - void setContentType(const QString& contentType); + void setContentType(const QString &contentType); QHash extraHeaders() const; - QByteArray extraHeader(const QString&) const; - bool hasExtraHeader(const QString&) const; - void setExtraHeader(const QString& key, const QString& value); - void setExtraHeaders(const QHash&); - void removeExtraHeader(const QString& key); + QByteArray extraHeader(const QString &) const; + bool hasExtraHeader(const QString &) const; + void setExtraHeader(const QString &key, const QString &value); + void setExtraHeaders(const QHash &); + void removeExtraHeader(const QString &key); QByteArray mimeData(); diff --git a/servatrice/src/smtp/qxtmailmessage.cpp b/servatrice/src/smtp/qxtmailmessage.cpp index 9d6de47d2..45452aede 100644 --- a/servatrice/src/smtp/qxtmailmessage.cpp +++ b/servatrice/src/smtp/qxtmailmessage.cpp @@ -23,7 +23,6 @@ ** ****************************************************************************/ - /*! * \class QxtMailMessage * \inmodule QxtNetwork @@ -31,22 +30,26 @@ * TODO: {implicitshared} */ - #include "qxtmailmessage.h" + #include "qxtmail_p.h" + +#include #include #include -#include #include - struct QxtMailMessagePrivate : public QSharedData { - QxtMailMessagePrivate() {} - QxtMailMessagePrivate(const QxtMailMessagePrivate& other) - : QSharedData(other), rcptTo(other.rcptTo), rcptCc(other.rcptCc), rcptBcc(other.rcptBcc), - subject(other.subject), body(other.body), sender(other.sender), - extraHeaders(other.extraHeaders), attachments(other.attachments) {} + QxtMailMessagePrivate() + { + } + QxtMailMessagePrivate(const QxtMailMessagePrivate &other) + : QSharedData(other), rcptTo(other.rcptTo), rcptCc(other.rcptCc), rcptBcc(other.rcptBcc), + subject(other.subject), body(other.body), sender(other.sender), extraHeaders(other.extraHeaders), + attachments(other.attachments) + { + } QStringList rcptTo, rcptCc, rcptBcc; QString subject, body, sender; QHash extraHeaders; @@ -59,12 +62,12 @@ QxtMailMessage::QxtMailMessage() qxt_d = new QxtMailMessagePrivate; } -QxtMailMessage::QxtMailMessage(const QxtMailMessage& other) : qxt_d(other.qxt_d) +QxtMailMessage::QxtMailMessage(const QxtMailMessage &other) : qxt_d(other.qxt_d) { // trivial copy constructor } -QxtMailMessage::QxtMailMessage(const QString& sender, const QString& recipient) +QxtMailMessage::QxtMailMessage(const QString &sender, const QString &recipient) { qxt_d = new QxtMailMessagePrivate; setSender(sender); @@ -76,7 +79,7 @@ QxtMailMessage::~QxtMailMessage() // trivial destructor } -QxtMailMessage& QxtMailMessage::operator=(const QxtMailMessage & other) +QxtMailMessage &QxtMailMessage::operator=(const QxtMailMessage &other) { qxt_d = other.qxt_d; return *this; @@ -87,7 +90,7 @@ QString QxtMailMessage::sender() const return qxt_d->sender; } -void QxtMailMessage::setSender(const QString& a) +void QxtMailMessage::setSender(const QString &a) { qxt_d->sender = a; } @@ -97,7 +100,7 @@ QString QxtMailMessage::subject() const return qxt_d->subject; } -void QxtMailMessage::setSubject(const QString& a) +void QxtMailMessage::setSubject(const QString &a) { qxt_d->subject = a; } @@ -107,7 +110,7 @@ QString QxtMailMessage::body() const return qxt_d->body; } -void QxtMailMessage::setBody(const QString& a) +void QxtMailMessage::setBody(const QString &a) { qxt_d->body = a; } @@ -121,7 +124,7 @@ QStringList QxtMailMessage::recipients(QxtMailMessage::RecipientType type) const return qxt_d->rcptTo; } -void QxtMailMessage::addRecipient(const QString& a, QxtMailMessage::RecipientType type) +void QxtMailMessage::addRecipient(const QString &a, QxtMailMessage::RecipientType type) { if (type == Bcc) qxt_d->rcptBcc.append(a); @@ -131,7 +134,7 @@ void QxtMailMessage::addRecipient(const QString& a, QxtMailMessage::RecipientTyp qxt_d->rcptTo.append(a); } -void QxtMailMessage::removeRecipient(const QString& a) +void QxtMailMessage::removeRecipient(const QString &a) { qxt_d->rcptTo.removeAll(a); qxt_d->rcptCc.removeAll(a); @@ -143,32 +146,31 @@ QHash QxtMailMessage::extraHeaders() const return qxt_d->extraHeaders; } -QByteArray QxtMailMessage::extraHeader(const QString& key) const +QByteArray QxtMailMessage::extraHeader(const QString &key) const { return qxt_d->extraHeaders[key.toLower()].toLatin1(); } -bool QxtMailMessage::hasExtraHeader(const QString& key) const +bool QxtMailMessage::hasExtraHeader(const QString &key) const { return qxt_d->extraHeaders.contains(key.toLower()); } -void QxtMailMessage::setExtraHeader(const QString& key, const QString& value) +void QxtMailMessage::setExtraHeader(const QString &key, const QString &value) { qxt_d->extraHeaders[key.toLower()] = value; } -void QxtMailMessage::setExtraHeaders(const QHash& a) +void QxtMailMessage::setExtraHeaders(const QHash &a) { - QHash& headers = qxt_d->extraHeaders; + QHash &headers = qxt_d->extraHeaders; headers.clear(); - foreach(const QString& key, a.keys()) - { + foreach (const QString &key, a.keys()) { headers[key.toLower()] = a[key]; } } -void QxtMailMessage::removeExtraHeader(const QString& key) +void QxtMailMessage::removeExtraHeader(const QString &key) { qxt_d->extraHeaders.remove(key.toLower()); } @@ -178,46 +180,40 @@ QHash QxtMailMessage::attachments() const return qxt_d->attachments; } -QxtMailAttachment QxtMailMessage::attachment(const QString& filename) const +QxtMailAttachment QxtMailMessage::attachment(const QString &filename) const { return qxt_d->attachments[filename]; } -void QxtMailMessage::addAttachment(const QString& filename, const QxtMailAttachment& attach) +void QxtMailMessage::addAttachment(const QString &filename, const QxtMailAttachment &attach) { - if (qxt_d->attachments.contains(filename)) - { + if (qxt_d->attachments.contains(filename)) { qWarning() << "QxtMailMessage::addAttachment: " << filename << " already in use"; int i = 1; - while (qxt_d->attachments.contains(filename + "." + QString::number(i))) - { + while (qxt_d->attachments.contains(filename + "." + QString::number(i))) { i++; } - qxt_d->attachments[filename+"."+QString::number(i)] = attach; - } - else - { + qxt_d->attachments[filename + "." + QString::number(i)] = attach; + } else { qxt_d->attachments[filename] = attach; } } -void QxtMailMessage::removeAttachment(const QString& filename) +void QxtMailMessage::removeAttachment(const QString &filename) { qxt_d->attachments.remove(filename); } -QByteArray qxt_fold_mime_header(const QString& key, const QString& value, QTextCodec* latin1, const QByteArray& prefix) +QByteArray qxt_fold_mime_header(const QString &key, const QString &value, QTextCodec *latin1, const QByteArray &prefix) { QByteArray rv = ""; QByteArray line = key.toLatin1() + ": "; - if (!prefix.isEmpty()) line += prefix; - if (!value.contains("=?") && latin1->canEncode(value)) - { + if (!prefix.isEmpty()) + line += prefix; + if (!value.contains("=?") && latin1->canEncode(value)) { bool firstWord = true; - foreach(const QByteArray& word, value.toLatin1().split(' ')) - { - if (line.size() > 78) - { + foreach (const QByteArray &word, value.toLatin1().split(' ')) { + if (line.size() > 78) { rv = rv + line + "\r\n"; line.clear(); } @@ -227,9 +223,7 @@ QByteArray qxt_fold_mime_header(const QString& key, const QString& value, QTextC line += " " + word; firstWord = false; } - } - else - { + } else { // The text cannot be losslessly encoded as Latin-1. Therefore, we // must use quoted-printable or base64 encoding. This is a quick // heuristic based on the first 100 characters to see which @@ -237,43 +231,33 @@ QByteArray qxt_fold_mime_header(const QString& key, const QString& value, QTextC QByteArray utf8 = value.toUtf8(); int ct = utf8.length(); int nonAscii = 0; - for (int i = 0; i < ct && i < 100; i++) - { - if (QXT_MUST_QP(utf8[i])) nonAscii++; + for (int i = 0; i < ct && i < 100; i++) { + if (QXT_MUST_QP(utf8[i])) + nonAscii++; } - if (nonAscii > 20) - { + if (nonAscii > 20) { // more than 20%-ish non-ASCII characters: use base64 QByteArray base64 = utf8.toBase64(); ct = base64.length(); line += "=?utf-8?b?"; - for (int i = 0; i < ct; i += 4) - { - if (line.length() > 72) - { + for (int i = 0; i < ct; i += 4) { + if (line.length() > 72) { rv += line + "?\r\n"; line = " =?utf-8?b?"; } line = line + base64.mid(i, 4); } - } - else - { + } else { // otherwise use Q-encoding line += "=?utf-8?q?"; - for (int i = 0; i < ct; i++) - { - if (line.length() > 73) - { + for (int i = 0; i < ct; i++) { + if (line.length() > 73) { rv += line + "?\r\n"; line = " =?utf-8?q?"; } - if (QXT_MUST_QP(utf8[i]) || utf8[i] == ' ') - { + if (QXT_MUST_QP(utf8[i]) || utf8[i] == ' ') { line += "=" + utf8.mid(i, 1).toHex().toUpper(); - } - else - { + } else { line += utf8[i]; } } @@ -290,81 +274,67 @@ QByteArray QxtMailMessage::rfc2822() const // Use base64 if requested bool useBase64 = (extraHeader("Content-Transfer-Encoding").toLower() == "base64"); // Check to see if plain text is ASCII-clean; assume it isn't if QP or base64 was requested - QTextCodec* latin1 = QTextCodec::codecForName("latin1"); + QTextCodec *latin1 = QTextCodec::codecForName("latin1"); bool bodyIsAscii = latin1->canEncode(body()) && !useQuotedPrintable && !useBase64; QHash attach = attachments(); QByteArray rv; - if (!sender().isEmpty() && !hasExtraHeader("From")) - { + if (!sender().isEmpty() && !hasExtraHeader("From")) { rv += qxt_fold_mime_header("From", sender(), latin1); } - if (!qxt_d->rcptTo.isEmpty()) - { + if (!qxt_d->rcptTo.isEmpty()) { rv += qxt_fold_mime_header("To", qxt_d->rcptTo.join(", "), latin1); } - if (!qxt_d->rcptCc.isEmpty()) - { + if (!qxt_d->rcptCc.isEmpty()) { rv += qxt_fold_mime_header("Cc", qxt_d->rcptCc.join(", "), latin1); } - if (!subject().isEmpty()) - { + if (!subject().isEmpty()) { rv += qxt_fold_mime_header("Subject", subject(), latin1); } - if (!bodyIsAscii) - { + if (!bodyIsAscii) { if (!hasExtraHeader("MIME-Version") && !attach.count()) rv += "MIME-Version: 1.0\r\n"; // If no transfer encoding has been requested, guess. // Heuristic: If >20% of the first 100 characters aren't // 7-bit clean, use base64, otherwise use Q-P. - if(!bodyIsAscii && !useQuotedPrintable && !useBase64) - { + if (!bodyIsAscii && !useQuotedPrintable && !useBase64) { QString b = body(); int nonAscii = 0; int ct = b.length(); - for (int i = 0; i < ct && i < 100; i++) - { - if (QXT_MUST_QP(b[i])) nonAscii++; + for (int i = 0; i < ct && i < 100; i++) { + if (QXT_MUST_QP(b[i])) + nonAscii++; } useQuotedPrintable = !(nonAscii > 20); useBase64 = !useQuotedPrintable; } } - if (attach.count()) - { + if (attach.count()) { if (qxt_d->boundary.isEmpty()) qxt_d->boundary = QUuid::createUuid().toString().toLatin1().replace("{", "").replace("}", ""); if (!hasExtraHeader("MIME-Version")) rv += "MIME-Version: 1.0\r\n"; if (!hasExtraHeader("Content-Type")) rv += "Content-Type: multipart/mixed; boundary=" + qxt_d->boundary + "\r\n"; - } - else if (!bodyIsAscii && !hasExtraHeader("Content-Transfer-Encoding")) - { - if (!useQuotedPrintable) - { + } else if (!bodyIsAscii && !hasExtraHeader("Content-Transfer-Encoding")) { + if (!useQuotedPrintable) { // base64 rv += "Content-Transfer-Encoding: base64\r\n"; - } - else - { + } else { // quoted-printable rv += "Content-Transfer-Encoding: quoted-printable\r\n"; } } - foreach(const QString& r, qxt_d->extraHeaders.keys()) - { - if ((r.toLower() == "content-type" || r.toLower() == "content-transfer-encoding") && attach.count()) - { + foreach (const QString &r, qxt_d->extraHeaders.keys()) { + if ((r.toLower() == "content-type" || r.toLower() == "content-transfer-encoding") && attach.count()) { // Since we're in multipart mode, we'll be outputting this later continue; } @@ -373,8 +343,7 @@ QByteArray QxtMailMessage::rfc2822() const rv += "\r\n"; - if (attach.count()) - { + if (attach.count()) { // we're going to have attachments, so output the lead-in for the message body rv += "This is a message with multiple parts in MIME format.\r\n"; rv += "--" + qxt_d->boundary + "\r\nContent-Type: "; @@ -382,19 +351,13 @@ QByteArray QxtMailMessage::rfc2822() const rv += extraHeader("Content-Type") + "\r\n"; else rv += "text/plain; charset=UTF-8\r\n"; - if (hasExtraHeader("Content-Transfer-Encoding")) - { + if (hasExtraHeader("Content-Transfer-Encoding")) { rv += "Content-Transfer-Encoding: " + extraHeader("Content-Transfer-Encoding") + "\r\n"; - } - else if (!bodyIsAscii) - { - if (!useQuotedPrintable) - { + } else if (!bodyIsAscii) { + if (!useQuotedPrintable) { // base64 rv += "Content-Transfer-Encoding: base64\r\n"; - } - else - { + } else { // quoted-printable rv += "Content-Transfer-Encoding: quoted-printable\r\n"; } @@ -402,132 +365,100 @@ QByteArray QxtMailMessage::rfc2822() const rv += "\r\n"; } - if (bodyIsAscii) - { + if (bodyIsAscii) { QByteArray b = latin1->fromUnicode(body()); int len = b.length(); QByteArray line = ""; QByteArray word = ""; - for (int i = 0; i < len; i++) - { - if (b[i] == '\n' || b[i] == '\r') - { - if (line.isEmpty()) - { + for (int i = 0; i < len; i++) { + if (b[i] == '\n' || b[i] == '\r') { + if (line.isEmpty()) { line = word; word = ""; - } - else if (line.length() + word.length() + 1 <= 78) - { + } else if (line.length() + word.length() + 1 <= 78) { line = line + ' ' + word; word = ""; } - if(line[0] == '.') + if (line[0] == '.') rv += "."; rv += line + "\r\n"; - if ((b[i+1] == '\n' || b[i+1] == '\r') && b[i] != b[i+1]) - { + if ((b[i + 1] == '\n' || b[i + 1] == '\r') && b[i] != b[i + 1]) { // If we're looking at a CRLF pair, skip the second half i++; } line = word; - } - else if (b[i] == ' ') - { - if (line.length() + word.length() + 1 > 78) - { - if(line[0] == '.') + } else if (b[i] == ' ') { + if (line.length() + word.length() + 1 > 78) { + if (line[0] == '.') rv += "."; rv += line + "\r\n"; line = word; - } - else if (line.isEmpty()) - { + } else if (line.isEmpty()) { line = word; - } - else - { + } else { line = line + ' ' + word; } word = ""; - } - else - { + } else { word += b[i]; } } - if (line.length() + word.length() + 1 > 78) - { - if(line[0] == '.') + if (line.length() + word.length() + 1 > 78) { + if (line[0] == '.') rv += "."; rv += line + "\r\n"; line = word; - } - else if (!word.isEmpty()) - { + } else if (!word.isEmpty()) { line += ' ' + word; } - if(!line.isEmpty()) { - if(line[0] == '.') + if (!line.isEmpty()) { + if (line[0] == '.') rv += "."; rv += line + "\r\n"; } - } - else if (useQuotedPrintable) - { + } else if (useQuotedPrintable) { QByteArray b = body().toUtf8(); int ct = b.length(); QByteArray line; - for (int i = 0; i < ct; i++) - { - if(b[i] == '\n' || b[i] == '\r') - { - if(line[0] == '.') + for (int i = 0; i < ct; i++) { + if (b[i] == '\n' || b[i] == '\r') { + if (line[0] == '.') rv += "."; rv += line + "\r\n"; line = ""; - if ((b[i+1] == '\n' || b[i+1] == '\r') && b[i] != b[i+1]) - { + if ((b[i + 1] == '\n' || b[i + 1] == '\r') && b[i] != b[i + 1]) { // If we're looking at a CRLF pair, skip the second half i++; } - } - else if (line.length() > 74) - { + } else if (line.length() > 74) { rv += line + "=\r\n"; line = ""; } - if (QXT_MUST_QP(b[i])) - { + if (QXT_MUST_QP(b[i])) { line += "=" + b.mid(i, 1).toHex().toUpper(); - } - else - { + } else { line += b[i]; } } - if(!line.isEmpty()) { - if(line[0] == '.') + if (!line.isEmpty()) { + if (line[0] == '.') rv += "."; rv += line + "\r\n"; } - } - else /* base64 */ + } else /* base64 */ { QByteArray b = body().toUtf8().toBase64(); int ct = b.length(); - for (int i = 0; i < ct; i += 78) - { + for (int i = 0; i < ct; i += 78) { rv += b.mid(i, 78) + "\r\n"; } } - if (attach.count()) - { - foreach(const QString& filename, attach.keys()) - { + if (attach.count()) { + foreach (const QString &filename, attach.keys()) { rv += "--" + qxt_d->boundary + "\r\n"; - rv += qxt_fold_mime_header("Content-Disposition", QDir(filename).dirName(), latin1, "attachment; filename="); + rv += + qxt_fold_mime_header("Content-Disposition", QDir(filename).dirName(), latin1, "attachment; filename="); rv += attach[filename].mimeData(); } rv += "--" + qxt_d->boundary + "--\r\n"; From 4aaedf64d259526cfc4be94827479692fbf418bf Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Mon, 23 Nov 2020 02:20:48 +0100 Subject: [PATCH 029/126] add github actions (#4164) --- .ci/{Fedora32 => Fedora33}/Dockerfile | 2 +- .ci/{travis-compile.sh => compile.sh} | 6 +- .ci/{travis-docker.sh => docker.sh} | 15 +- .ci/get_github_upload_url.sh | 19 ++ .ci/{travis-lint.sh => lint.sh} | 14 +- .github/CONTRIBUTING.md | 95 ++++----- .github/workflows/clangify.yml | 26 +++ .github/workflows/linux-builds.yml | 120 +++++++++++ .github/workflows/macos-builds.yml | 184 +++++++++++++++++ .travis.yml | 276 -------------------------- README.md | 2 +- 11 files changed, 425 insertions(+), 334 deletions(-) rename .ci/{Fedora32 => Fedora33}/Dockerfile (96%) rename .ci/{travis-compile.sh => compile.sh} (92%) rename .ci/{travis-docker.sh => docker.sh} (91%) create mode 100755 .ci/get_github_upload_url.sh rename .ci/{travis-lint.sh => lint.sh} (82%) create mode 100644 .github/workflows/clangify.yml create mode 100644 .github/workflows/linux-builds.yml create mode 100644 .github/workflows/macos-builds.yml delete mode 100644 .travis.yml diff --git a/.ci/Fedora32/Dockerfile b/.ci/Fedora33/Dockerfile similarity index 96% rename from .ci/Fedora32/Dockerfile rename to .ci/Fedora33/Dockerfile index 7a0a2e74f..ee11d2548 100644 --- a/.ci/Fedora32/Dockerfile +++ b/.ci/Fedora33/Dockerfile @@ -1,4 +1,4 @@ -FROM fedora:32 +FROM fedora:33 RUN dnf install -y \ @development-tools \ diff --git a/.ci/travis-compile.sh b/.ci/compile.sh similarity index 92% rename from .ci/travis-compile.sh rename to .ci/compile.sh index db461f946..e54d43ffb 100755 --- a/.ci/travis-compile.sh +++ b/.ci/compile.sh @@ -1,6 +1,6 @@ #!/bin/bash -# This script is to be used in .travis.yaml from the project root directory, do not use it from somewhere else. +# This script is to be used by the ci environment from the project root directory, do not use it from somewhere else. # Read arguments while [[ "$@" ]]; do @@ -58,7 +58,7 @@ done # Check formatting using clang-format if [[ $CHECK_FORMAT ]]; then - source ./.ci/travis-lint.sh + source ./.ci/lint.sh fi set -e @@ -69,7 +69,7 @@ mkdir -p build cd build if ! [[ $CORE_AMOUNT ]]; then - CORE_AMOUNT="2" # travis machines have 2 cores + CORE_AMOUNT="2" # default machines have 2 cores fi # Add cmake flags diff --git a/.ci/travis-docker.sh b/.ci/docker.sh similarity index 91% rename from .ci/travis-docker.sh rename to .ci/docker.sh index 3ba66b40b..6fc1e2bc2 100644 --- a/.ci/travis-docker.sh +++ b/.ci/docker.sh @@ -1,6 +1,7 @@ #!/bin/bash -# This script is to be sourced in .travis.yaml from the project root directory, do not use it from somewhere else. +# This script is to be used by the ci environment from the project root directory, do not use it from somewhere else. + # Creates or loads docker images to use in compilation, creates RUN function to start compilation on the docker image. # --get loads the image from a previously saved image cache, will build if no image is found # --build builds the image from the Dockerfile in .ci/$NAME @@ -9,7 +10,7 @@ # uses env: NAME CACHE BUILD GET SAVE (correspond to args: --set-cache --build --get --save) # sets env: RUN CCACHE_DIR IMAGE_NAME RUN_ARGS RUN_OPTS BUILD_SCRIPT # exitcode: 1 for failure, 2 for missing dockerfile, 3 for invalid arguments -export BUILD_SCRIPT=".ci/travis-compile.sh" +export BUILD_SCRIPT=".ci/compile.sh" project_name="cockatrice" save_extension=".tar.gz" @@ -64,10 +65,14 @@ if ! [[ -r $docker_dir/Dockerfile ]]; then return 2 # even if the image is cached, we do not want to run if there is no way to build this image fi -if ! [[ -d $CACHE ]]; then - echo "could not find cache dir: $CACHE" >&2 - unset CACHE +if ! [[ $CACHE ]]; then + echo "cache dir is not set!" >&2 else + if ! [[ -d $CACHE ]]; then + echo "could not find cache dir: $CACHE" >&2 + mkdir -p $CACHE + unset GET # the dir is empty + fi if [[ $GET || $SAVE ]]; then img_dir="$CACHE/$image_cache" img_save="$img_dir/$IMAGE_NAME$save_extension" diff --git a/.ci/get_github_upload_url.sh b/.ci/get_github_upload_url.sh new file mode 100755 index 000000000..824e5e60a --- /dev/null +++ b/.ci/get_github_upload_url.sh @@ -0,0 +1,19 @@ +#!/bin/bash +# this script is to be used by the ci to fetch the github upload url +# using curl and jq +[[ $ref ]] || missing+=" ref" +[[ $repo ]] || missing+=" repo" +if [[ $missing ]]; then + echo "missing env:$missing" >&2 + exit 2 +fi +tag="${ref##*/}" +api_url="https://api.github.com/repos/$repo/releases/tags/$tag" +upload_url="$(curl "$api_url" | jq -r '.upload_url')" +if [[ $upload_url && $upload_url != null ]]; then + echo "$upload_url" + exit 0 +else + echo "failed to fetch upload url from $api_url" >&2 + exit 1 +fi diff --git a/.ci/travis-lint.sh b/.ci/lint.sh similarity index 82% rename from .ci/travis-lint.sh rename to .ci/lint.sh index 66407b7c9..e8939da9f 100755 --- a/.ci/travis-lint.sh +++ b/.ci/lint.sh @@ -1,9 +1,19 @@ #!/bin/bash -# Check formatting using clang-format +# fetch master branch +git fetch origin master + +# unshallow if needed +echo "Finding merge base" +if ! git merge-base origin/master HEAD; then + echo "Could not find merge base, unshallowing repo" + git fetch --unshallow +fi + +# Check formatting using clangify echo "Checking your code using clang-format..." -diff="$(./clangify.sh --diff --cf-version)" +diff="$(./clangify.sh --diff --cf-version --branch origin/master)" err=$? case $err in diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index f926b695c..2b36452f6 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -39,11 +39,10 @@ albeit slightly less active. ### Formatting and continuous integration (CI) ### -We use a separate job on Travis CI to check your code for formatting issues. If -your pull request was rejected, you can check the output on their website. -To do so, click on Details next to the failed Travis CI build at the -bottom of your PR on the GitHub page, on the Travis page then click on our "Linting" -build (the fastest one on the very top of the list) to see the complete log. +We use a separate job on the CI to check your code for formatting issues. If +your pull request failed the test, you can check the output on the checks tab. +It's the first job called "linter", you can click the "Run clangify" step to +see the output of the test. The message will look like this: ``` @@ -59,9 +58,9 @@ The message will look like this: *** *** *********************************************************** ``` -The CONTRIBUTING.md file mentioned in that message is the file you are currently -reading. Please see [this section](#formatting) below for full information on our -formatting guidelines. +The CONTRIBUTING.md file mentioned in that message is the file you are +currently reading. Please see [this section](#formatting) below for full +information on our formatting guidelines. ### Compatibility ### @@ -272,11 +271,11 @@ https://github.com/Cockatrice/Cockatrice/wiki/Client-server-protocol) # Reviewing Pull Requests # After you have finished your changes to the project you should put them on a -separate branch of your fork on github and open a [pull request]( +separate branch of your fork on GitHub and open a [pull request]( https://docs.github.com/en/free-pro-team@latest/desktop/contributing-and-collaborating-using-github-desktop/creating-an-issue-or-pull-request ). -Your code will then be automatically compiled by Travis CI for Linux and macOS, -and by Appveyor for Windows. Additionally Travis CI will perform a [Linting +Your code will then be automatically compiled by GitHub actions for Linux and +macOS, and by Appveyor for Windows. Additionally GitHub will perform a [Linting check](#formatting-and-continuous-integration-ci). If any issues come up you can check their status at the bottom of the pull request page, click on details to go to the CI website and see the different build logs. @@ -292,7 +291,7 @@ be included in the next release 👍 Basic workflow for translations: 1. Developer adds a `tr("foo")` string in the code; 2. Every few days, a maintainer updates the `*_en.ts files` with the new strings; - 3. Transifex picks up the new files from github every 24 hours; + 3. Transifex picks up the new files from GitHub every 24 hours; 4. Translators translate the new untranslated strings on Transifex; 5. Before a release, a maintainer fetches the updated translations from Transifex. @@ -388,37 +387,38 @@ https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ). # Release Management # -### Publishing A New Beta Release ### +### Publishing A New Release ### -Travis and AppVeyor have been configured to upload files to GitHub Releases -whenever a tag is pushed.
-Usually, tags are created through publishing a (pre-)release, but there's a way -around that. +GitHub Actions will upload binaries (Linux & macOS) when any release is published +on GitHub.
+AppVeyor is configured to upload binaries (Windows) to GitHub Releases whenever a +tag is pushed.
+You can create a tag when publishing a (pre-)release, but you can also push one +manually and use it in the release. -To trigger Travis and AppVeyor, simply do the following: +To create a tag, simply do the following: ```bash -cd $COCKATRICE_REPO git checkout master git remote update -p git pull git tag $TAG_NAME -git push upstream $TAG_NAME +git push $UPSTREAM $TAG_NAME ``` You should define the variables as such: ``` -upstream - git@github.com:Cockatrice/Cockatrice.git -$COCKATRICE_REPO - /Location/of/repository/cockatrice.git -`$TAG_NAME` should be: +`$UPSTREAM` - the remote for git@github.com:Cockatrice/Cockatrice.git +`$TAG_NAME` should be formatted as: - `YYYY-MM-DD-Release-MAJ.MIN.PATCH` for **stable releases** - `YYYY-MM-DD-Development-MAJ.MIN.PATCH-beta.X` for **beta releases**
With *MAJ.MIN.PATCH* being the NEXT release version! ``` This will cause a tagged release to be established on the GitHub repository, -which will then lead to the upload of binaries. If you use this method, the -tags (releases) that you create will be marked as a "Pre-release". The -`/latest` URL will not be impacted (for stable release downloads) so that's -good. +which will then lead to the upload of binaries by Appveyor. +If you use a SemVer tag including "beta", the release that will be created at GitHub +by the CI will be marked as a "Pre-release". +The target of the `.../latest` URL will not be changed in that case - +it always points to the latest stable release and not pre-releases. If you accidentally push a tag incorrectly (the tag is outdated, you didn't pull in the latest branch accidentally, you named the tag wrong, etc.) you can @@ -428,28 +428,31 @@ git push --delete upstream $TAG_NAME git tag -d $TAG_NAME ``` -**NOTE:** Unfortunately, due to the method of how Travis and AppVeyor work, to -publish a stable release you will need to make a copy of the release notes -locally and then paste them into the GitHub GUI once the binaries have been -uploaded by them. These CI services will automatically overwrite the name of -the release (to "Cockatrice $TAG_NAME"), the status of the release (to -"Pre-release"), and the release body (to "Beta build of Cockatrice"). +**NOTE:** Unfortunately, due to the method of how AppVeyor works, to publish a +stable release you will need to make a copy of the drafted release notes locally before +pushing the tag, and paste them back into the GitHub releases GUI once the binaries have +been uploaded by the CI. +
+The AppVeyor service will automatically overwrite the name of the release to +"Cockatrice $TAG_NAME". +In addition, for beta releases, the status of the release will be set to "Pre-release", and +the release body will get renamed to "Beta build of Cockatrice". **NOTE 2:** In the first lines of [CMakeLists.txt]( https://github.com/Cockatrice/Cockatrice/blob/master/CMakeLists.txt) -there's an hardcoded version number used when compiling custom (not tagged) +there's an hardcoded version number used when compiling from master or custom (not tagged) versions. While on tagged versions these numbers are overridden by the version -numbers coming from the tag title, it's good practice to keep them aligned with -the real ones. +numbers coming from the tag title, it's good practice to increment the ones at CMake +after every full release to stress that master is ahead of the last stable release. The preferred flow of operation is: - * just before a release, update the version number in CMakeLists.txt to "next - release version"; - * tag the release following the previously described syntax in order to get it - built by CI; - * wait for CI to upload the binaries, double check if everything is in order - * after the release is complete, update the version number again to "next - targeted beta version", typically increasing `PROJECT_VERSION_PATCH` by one. + * Just before a release, make sure the version number in CMakeLists.txt is set + to the same release version you are about to tag. + * Tag the release following the previously described syntax in order to get it correctly + built and deployed by CI. + * Wait for them to upload all binaries, then double check if everything is in order. + * After the release is complete, update the CMake version number again to the next + targeted beta version, typically increasing `PROJECT_VERSION_PATCH` by one. -**NOTE 3:** When releasing a new stable version, all the previous beta versions -should be deleted. This is needed for Cockatrice to update users of the "beta" -release channel to the latest version like other users. +**NOTE 3:** When releasing a new stable version, all previous beta releases (and tags) +should be deleted. This is needed for Cockatrice to update users of the "Beta" +release channel correctly to the latest stable version as well. diff --git a/.github/workflows/clangify.yml b/.github/workflows/clangify.yml new file mode 100644 index 000000000..e97a0091d --- /dev/null +++ b/.github/workflows/clangify.yml @@ -0,0 +1,26 @@ +name: Clangify + +on: + pull_request: + branches: + - master + +jobs: + linter: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 20 # should be enough to find merge base + + - name: Install clang-format + shell: bash + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends clang-format + + - name: Run clangify + shell: bash + run: ./.ci/lint.sh diff --git a/.github/workflows/linux-builds.yml b/.github/workflows/linux-builds.yml new file mode 100644 index 000000000..3e061fe9b --- /dev/null +++ b/.github/workflows/linux-builds.yml @@ -0,0 +1,120 @@ +name: Build on Linux (Docker) + +on: + pull_request: + branches: + - master + release: + types: + - published + +jobs: + build: + strategy: + fail-fast: false + matrix: + distro: # these names correspond to the files in .ci/$distro + - UbuntuFocal + - UbuntuBionic + - ArchLinux + - DebianBuster + - Fedora33 + include: + - distro: UbuntuFocal + package: DEB + test: skip # UbuntuFocal has a broken qt for debug builds + + - distro: UbuntuBionic + package: DEB + + - distro: ArchLinux + package: skip # we are packaged in arch already + allow-failure: yes + + - distro: DebianBuster + package: DEB + + - distro: Fedora33 + package: RPM + test: skip # Fedora is our slowest build + + runs-on: ubuntu-latest + + continue-on-error: ${{matrix.allow-failure == 'yes'}} + + env: + NAME: ${{matrix.distro}} + CACHE: /tmp/${{matrix.distro}}-cache # ${{runner.temp}} does not work? + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Get cache timestamp + id: cache_timestamp + shell: bash + run: echo "::set-output name=timestamp::$(date -u '+%Y%m%d%H%M%S')" + + - name: Restore cache + uses: actions/cache@v2 + env: + timestamp: ${{steps.cache_timestamp.outputs.timestamp}} + with: + path: ${{env.CACHE}} + key: docker-${{matrix.distro}}-cache-${{env.timestamp}} + restore-keys: | + docker-${{matrix.distro}}-cache- + + - name: Build ${{matrix.distro}} Docker image + shell: bash + run: | + source .ci/docker.sh --build + export BUILD_SCRIPT="./ccache-stats.sh" + echo "ccache --show-stats" >$BUILD_SCRIPT + RUN # show stats in container instead of build + + - name: Build debug and test + if: matrix.test != 'skip' + shell: bash + run: | + source .ci/docker.sh + RUN --server --debug --test + + - name: Build release package + if: matrix.package != 'skip' + id: build_package + shell: bash + run: | + source .ci/docker.sh + RUN --server --release --package ${{matrix.distro}} ${{matrix.package}} + file=$(cd build && echo Cockatrice-*.*) + echo "::set-output name=file::$file" + + - name: Upload artifacts + if: matrix.package != 'skip' + uses: actions/upload-artifact@v2 + with: + name: ${{matrix.distro}}-package + path: build/${{steps.build_package.outputs.file}} + + - name: Get release upload url + if: matrix.package != 'skip' && startsWith(github.ref, 'refs/tags/') + id: get_url + shell: bash + env: + ref: "${{github.ref}}" + repo: "${{github.repository}}" + run: | + url="$(./.ci/get_github_upload_url.sh)" + echo "::set-output name=upload_url::$url" + + - name: Upload release to GitHub + if: steps.get_url.outcome == 'success' + uses: actions/upload-release-asset@v1.0.2 + env: + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + with: + upload_url: ${{steps.get_url.outputs.upload_url}} + asset_path: build/${{steps.build_package.outputs.file}} + asset_name: ${{steps.build_package.outputs.file}} + asset_content_type: binary_package # required but arbitrary diff --git a/.github/workflows/macos-builds.yml b/.github/workflows/macos-builds.yml new file mode 100644 index 000000000..f24bc0782 --- /dev/null +++ b/.github/workflows/macos-builds.yml @@ -0,0 +1,184 @@ +name: Build on macOS + +on: + pull_request: + branches: + - master + release: + types: + - published + +jobs: + build: + strategy: + fail-fast: false + matrix: + target: + - Debug + - ElCapitan + - Mojave + - Catalina + - BigSur + include: + - target: Debug # tests only + os: macos-latest + xcode: 11.7 + type: Debug + do_tests: 0 # tests do not work yet on mac + make_package: false + + - target: ElCapitan # xcode 8.2.1 should be compatible with macos 10.11.5 + os: macos-10.13 # runs on HighSierra + allow-failure: yes # we don't know if it'll be added + xcode: 8.2.1 + type: Release + do_tests: 0 + make_package: true + + - target: Mojave # xcode 10.3 should be compatible with macos 10.14.3 + os: macos-10.15 # runs on Catalina + xcode: 10.3 + type: Release + do_tests: 0 + make_package: true + + - target: Catalina + os: macos-10.15 + xcode: 11.7 + type: Release + do_tests: 0 + make_package: true + + - target: BigSur + os: macos-11.0 + xcode: 12.2 + type: Release + do_tests: 0 + make_package: true + + runs-on: ${{matrix.os}} + + continue-on-error: ${{matrix.allow-failure == 'yes'}} + + env: + CCACHE_DIR: ~/.ccache + DEVELOPER_DIR: + /Applications/Xcode_${{matrix.xcode}}.app/Contents/Developer + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Install dependencies using homebrew + shell: bash + # cmake cannot find the mysql connector + # neither of these works: mariadb-connector-c mysql-connector-c++ + run: brew install ccache protobuf + +# in case we'd want to modify this for windows (should be its own workflow): +# - if: runner.os == 'windows' +# name: Install dependencies using vcpkg +# shell: bash +# run: vcpkg install protobuf liblzma zlib --triplet x64-windows + + - name: Install QT using homebrew + id: brew_install_qt + continue-on-error: true + shell: bash + run: brew install qt --force-bottle + + - name: Install QT using actions + if: steps.brew_install_qt.outcome == 'failure' + uses: jurplel/install-qt-action@v2 + + - name: Get ccache timestamp + id: ccache_timestamp + shell: bash + run: echo "::set-output name=timestamp::$(date -u '+%Y%m%d%H%M%S')" + + - name: Restore ccache cache + uses: actions/cache@v2 + env: + timestamp: ${{steps.ccache_timestamp.outputs.timestamp}} + with: + path: ${{env.CCACHE_DIR}} + key: ${{runner.os}}-xcode-${{matrix.xcode}}-ccache-${{env.timestamp}} + restore-keys: | + ${{runner.os}}-xcode-${{matrix.xcode}}-ccache- + + - name: Create build environment + run: cmake -E make_directory build + + - name: Configure CMake + shell: bash + working-directory: build + run: | + ccache --show-stats + mkdir -p $CCACHE_DIR + ls $CCACHE_DIR + if [[ ${{steps.brew_install_qt.outcome}} == 'success' ]]; then + cmake_args=" -DCMAKE_PREFIX_PATH=$(echo /usr/local/Cellar/qt/*)" + echo "added$cmake_args cmake argument for homebrew" + fi + cmake_args+=" -DTEST=${{matrix.do_tests}}" + cmake .. -DCMAKE_BUILD_TYPE=${{matrix.type}} -DWITH_SERVER=1 $cmake_args + + - name: Build on Xcode ${{matrix.xcode}} + shell: bash + working-directory: build + run: cmake --build . + + - name: Test + if: matrix.do_tests == 1 + shell: bash + working-directory: build + run: cmake --build . --target test + + - name: Package for ${{matrix.target}} + if: matrix.make_package + id: build_package + shell: bash + working-directory: build + run: | + # temporary workaround for big sur images having old cmake + if [[ ${{matrix.os}} == macos-11.0 ]]; then + curl -L https://github.com/Kitware/CMake/releases/download/v3.19.0/cmake-3.19.0-Darwin-x86_64.tar.gz | tar -xz + ./cmake-3.19.0-Darwin-x86_64/CMake.app/Contents/bin/cpack --config ./CPackConfig.cmake + else + cmake --build . --target package + fi + file=$(echo Cockatrice-*.*) + extension="${file##*.}" + name="${file%.*}" + newfile="$name-macOS-${{matrix.target}}.$extension" + mv "$file" "$newfile" # avoid file name conflicts + echo "::set-output name=file::$newfile" + + - name: Upload artifacts + if: matrix.make_package + uses: actions/upload-artifact@v2 + with: + name: macOS-${{matrix.target}}-xcode-${{matrix.xcode}}-dmg + path: build/${{steps.build_package.outputs.file}} + + - name: Get release upload URL + if: matrix.make_package && startsWith(github.ref, 'refs/tags/') + id: get_url + shell: bash + env: + ref: "${{github.ref}}" + repo: "${{github.repository}}" + run: | + url=$(./.ci/get_github_upload_url.sh) + echo "::set-output name=upload_url::$url" + + - name: Upload release for ${{matrix.target}} + if: steps.get_url.outcome == 'success' + uses: actions/upload-release-asset@v1.0.2 + env: + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + with: + upload_url: ${{steps.get_url.outputs.upload_url}} + asset_path: build/${{steps.build_package.outputs.file}} + asset_name: ${{steps.build_package.outputs.file}} + asset_content_type: binary_package # required but arbitrary diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index fbbbcbb38..000000000 --- a/.travis.yml +++ /dev/null @@ -1,276 +0,0 @@ -os: linux -dist: bionic -language: cpp -compiler: gcc - -git: - depth: 15 - -jobs: - include: - - #Static Code Analysis - - name: Check code style / Linting - if: tag IS NOT present - os: linux - group: stable - script: bash ./.ci/travis-lint.sh - - - #Ubuntu Bionic (on Docker) - - name: Ubuntu Bionic (Debug + Tests) - if: tag IS NOT present - os: linux - services: docker - language: minimal - env: NAME=UbuntuBionic CACHE=$HOME/$NAME - cache: - directories: - - $CACHE - before_install: source .ci/travis-docker.sh --build - script: RUN --server --debug --test - - - name: Ubuntu Bionic (Release) - if: (branch = master AND NOT type = pull_request) OR tag IS present - os: linux - services: docker - language: minimal - env: NAME=UbuntuBionic CACHE=$HOME/$NAME - cache: - directories: - - $CACHE - before_install: source .ci/travis-docker.sh --build - script: RUN --server --package $NAME --release - - #Ubuntu Focal (on Docker) - - name: Ubuntu Focal (Compile) - if: tag IS NOT present - os: linux - services: docker - language: minimal - env: NAME=UbuntuFocal CACHE=$HOME/$NAME - cache: - directories: - - $CACHE - before_install: source .ci/travis-docker.sh --build - script: RUN --server - - - name: Ubuntu Focal (Release) - if: (branch = master AND NOT type = pull_request) OR tag IS present - os: linux - services: docker - language: minimal - env: NAME=UbuntuFocal CACHE=$HOME/$NAME - cache: - directories: - - $CACHE - before_install: source .ci/travis-docker.sh --build - script: RUN --server --package $NAME --release - - #Debian Buster (on Docker) - - name: Debian Buster (Compile) - if: tag IS NOT present - os: linux - services: docker - language: minimal - env: NAME=DebianBuster CACHE=$HOME/$NAME - cache: - directories: - - $CACHE - before_install: source .ci/travis-docker.sh --build - script: RUN --server - - - name: Debian Buster (Release) - if: (branch = master AND NOT type = pull_request) OR tag IS present - os: linux - services: docker - language: minimal - env: NAME=DebianBuster CACHE=$HOME/$NAME - cache: - directories: - - $CACHE - before_install: source .ci/travis-docker.sh --build - script: RUN --server --package $NAME --release - - - #Fedora 32 (on Docker) - - name: Fedora 32 (Compile) - if: tag IS NOT present - services: docker - language: minimal - env: NAME=Fedora32 CACHE=$HOME/$NAME - cache: - directories: - - $CACHE - before_install: source .ci/travis-docker.sh --build - script: RUN --server - - - name: Fedora 32 (Release) - if: (branch = master AND NOT type = pull_request) OR tag IS present - services: docker - language: minimal - env: NAME=Fedora32 CACHE=$HOME/$NAME - cache: - directories: - - $CACHE - before_install: source .ci/travis-docker.sh --build - script: RUN --server --package $NAME RPM --release - - - #macOS High Sierra - - name: macOS High Sierra (Debug) - if: tag IS NOT present - os: osx - osx_image: xcode10.1 - cache: - - ccache - - directories: - - $HOME/Library/Caches/Homebrew - addons: - homebrew: - packages: - - ccache - - protobuf - - qt - - xz - update: true - script: bash ./.ci/travis-compile.sh --server --install --debug - before_cache: - - brew cleanup - - - name: macOS High Sierra (Release) - if: (branch = master AND NOT type = pull_request) OR tag IS present - os: osx - osx_image: xcode10.1 - env: OSX_VERSION=10.13 - cache: - - ccache - - directories: - - $HOME/Library/Caches/Homebrew - addons: - homebrew: - packages: - - ccache - - protobuf - - qt - - xz - update: true - script: bash ./.ci/travis-compile.sh --server --package "macos$OSX_VERSION" --release - before_cache: - - brew cleanup - - #macOS Catalina - - name: macOS Catalina (Debug) - if: tag IS NOT present - os: osx - osx_image: xcode11.6 - cache: - - ccache - - directories: - - $HOME/Library/Caches/Homebrew - addons: - homebrew: - packages: - - ccache - - protobuf - - qt - - xz - update: true - script: bash ./.ci/travis-compile.sh --server --install --debug - before_cache: - - brew cleanup - - - name: macOS Catalina (Release) - if: (branch = master AND NOT type = pull_request) OR tag IS present - os: osx - osx_image: xcode11.6 - env: OSX_VERSION=10.15 - cache: - - ccache - - directories: - - $HOME/Library/Caches/Homebrew - addons: - homebrew: - packages: - - ccache - - protobuf - - qt - - xz - update: true - script: bash ./.ci/travis-compile.sh --server --package "macos$OSX_VERSION" --release --zip - before_cache: - - brew cleanup - - - #Arch Linux (on Docker) (allow failures) - - name: Arch Linux (Debug) - if: tag IS NOT present - os: linux - services: docker - language: minimal - env: NAME=ArchLinux CACHE=$HOME/$NAME - cache: - directories: - - $CACHE - before_install: source .ci/travis-docker.sh --build - script: RUN --server --debug - - allow_failures: - # Arch linux is always the latest version and by definition not stable - - name: Arch Linux (Debug) - # Report build completion even if optional builds are not completed - fast_finish: true - - -# Builds for pull requests skip the deployment step altogether -deploy: -# Deploy configuration for "beta" releases - - provider: releases - token: - secure: mLMF41q7xgOR1sjczsilEy7HQis2PkZCzhfOGbn/8FoOQnmmPOZjrsdhn06ZSl3SFsbfCLuClDYXAbFscQmdgjcGN5AmHV+JYfW650QEuQa/f4/lQFsVRtEqUA1O3FQ0OuRxdpCfJubZBdFVH8SbZ93GLC5zXJbkWQNq+xCX1fU= - name: "Cockatrice $TRAVIS_TAG" - release_notes: "Beta release of Cockatrice" - skip_cleanup: true - file_glob: true - file: "build/Cockatrice-*" - overwrite: true - draft: false - prerelease: true - on: - tags: true - repo: Cockatrice/Cockatrice - condition: $TRAVIS_TAG =~ ([0-9]|[1-9][0-9])(\.([0-9]|[1-9][0-9])){2}-beta(\.([2-9]|[1-9][0-9]))?$ # regex to match semver naming convention for beta pre-releases - -# Deploy configuration for "stable" releases - - provider: releases - token: - secure: mLMF41q7xgOR1sjczsilEy7HQis2PkZCzhfOGbn/8FoOQnmmPOZjrsdhn06ZSl3SFsbfCLuClDYXAbFscQmdgjcGN5AmHV+JYfW650QEuQa/f4/lQFsVRtEqUA1O3FQ0OuRxdpCfJubZBdFVH8SbZ93GLC5zXJbkWQNq+xCX1fU= - skip_cleanup: true - file_glob: true - file: "build/Cockatrice-*" - overwrite: true - draft: false - prerelease: false - on: - tags: true - repo: Cockatrice/Cockatrice - condition: $TRAVIS_TAG =~ ([0-9]|[1-9][0-9])(\.([0-9]|[1-9][0-9])){2}$ # regex to match semver naming convention for stable full releases - - -notifications: - email: false - webhooks: - urls: - - https://webhooks.gitter.im/e/d94969c3b01b22cbdcb7 - on_success: change - on_failure: change - on_start: never - on_cancel: change - on_error: change - - -# Announcements of build image updates: https://docs.travis-ci.com/user/build-environment-updates/ -# For precise versions of preinstalled tools on the VM, check “Build system information” in the build log! -# Official validator for ".travis.yml" config file: https://yaml.travis-ci.org -# Official Travis CI Build Config Explorer: https://config.travis-ci.com/explore -# Travis CI config documentation: https://docs.travis-ci.com/user/customizing-the-build diff --git a/README.md b/README.md index 49dcb8d09..ca9183a99 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ Cockatrice uses Transifex for translations. You can help us bring Cockatrice and Check out our [Translator FAQ](https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ) for more information about contributing!
-# Build [![Travis Build Status - master](https://travis-ci.org/Cockatrice/Cockatrice.svg?branch=master)](https://travis-ci.org/Cockatrice/Cockatrice) [![Appveyor Build Status - master](https://ci.appveyor.com/api/projects/status/oauxf5a0sj689rcg/branch/master?svg=true)](https://ci.appveyor.com/project/ZeldaZach/cockatrice/branch/master) +# Build ![macOS builds](https://github.com/Cockatrice/Cockatrice/workflows/Build%20on%20macOS/badge.svg) ![linux builds](https://github.com/Cockatrice/Cockatrice/workflows/Build%20on%20Linux%20(Docker)/badge.svg) [![Appveyor Build Status - master](https://ci.appveyor.com/api/projects/status/oauxf5a0sj689rcg/branch/master?svg=true)](https://ci.appveyor.com/project/ZeldaZach/cockatrice/branch/master) **Detailed compiling instructions are on the Cockatrice wiki under [Compiling Cockatrice](https://github.com/Cockatrice/Cockatrice/wiki/Compiling-Cockatrice)** From 8441cb7ba9ae2fa508bac6f68672e8f523507a27 Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Mon, 23 Nov 2020 02:21:43 +0100 Subject: [PATCH 030/126] refactor pingClockTimeout (#4169) * refactor pingClockTimeout try to see if it changes #3954 * use lcoks and unlocks again --- common/server_game.cpp | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/common/server_game.cpp b/common/server_game.cpp index 3996c69b8..63db0503b 100644 --- a/common/server_game.cpp +++ b/common/server_game.cpp @@ -169,26 +169,28 @@ void Server_Game::pingClockTimeout() GameEventStorage ges; ges.setGameEventContext(Context_PingChanged()); - QList pingList; - QMapIterator playerIterator(players); bool allPlayersInactive = true; int playerCount = 0; - while (playerIterator.hasNext()) { - Server_Player *player = playerIterator.next().value(); + for (auto *player : players) { + if (player == nullptr) + continue; + if (!player->getSpectator()) ++playerCount; - const int oldPingTime = player->getPingTime(); - player->playerMutex.lock(); + int oldPingTime = player->getPingTime(); int newPingTime; - if (player->getUserInterface()) + player->playerMutex.lock(); + if (player->getUserInterface()) { newPingTime = player->getUserInterface()->getLastCommandTime(); - else + } else { newPingTime = -1; + } player->playerMutex.unlock(); - if ((newPingTime != -1) && !player->getSpectator()) + if ((newPingTime != -1) && !player->getSpectator()) { allPlayersInactive = false; + } if ((abs(oldPingTime - newPingTime) > 1) || ((newPingTime == -1) && (oldPingTime != -1)) || ((newPingTime != -1) && (oldPingTime == -1))) { @@ -203,10 +205,12 @@ void Server_Game::pingClockTimeout() const int maxTime = room->getServer()->getMaxGameInactivityTime(); if (allPlayersInactive) { - if (((++inactivityCounter >= maxTime) && (maxTime > 0)) || (playerCount < maxPlayers)) + if (((maxTime > 0) && (++inactivityCounter >= maxTime)) || (playerCount < maxPlayers)) { deleteLater(); - } else + } + } else { inactivityCounter = 0; + } } int Server_Game::getPlayerCount() const From 0d842b5a35134be6ddc25ef2aeae38584a179c65 Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Mon, 23 Nov 2020 02:23:18 +0100 Subject: [PATCH 031/126] Resurrect 2655 (#4136) * fix #2640 * clangify Co-authored-by: Fabio Bas --- cockatrice/src/player.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/cockatrice/src/player.cpp b/cockatrice/src/player.cpp index 7b8195846..a9c054eb1 100644 --- a/cockatrice/src/player.cpp +++ b/cockatrice/src/player.cpp @@ -359,6 +359,7 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, T aCreateAnotherToken->setEnabled(false); createPredefinedTokenMenu = new QMenu(QString()); + createPredefinedTokenMenu->setEnabled(false); playerMenu->addSeparator(); countersMenu = playerMenu->addMenu(QString()); @@ -380,6 +381,7 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, T if (local || judge) { aCardMenu = new QAction(this); + aCardMenu->setEnabled(false); playerMenu->addSeparator(); playerMenu->addAction(aCardMenu); } else { @@ -921,6 +923,7 @@ void Player::initSayMenu() sayMenu->clear(); int count = SettingsCache::instance().messages().getCount(); + sayMenu->setEnabled(count > 0); for (int i = 0; i < count; ++i) { auto *newAction = new QAction(SettingsCache::instance().messages().getMessageAt(i), this); @@ -938,10 +941,14 @@ void Player::setDeck(const DeckLoader &_deck) aOpenDeckInDeckEditor->setEnabled(deck); createPredefinedTokenMenu->clear(); + createPredefinedTokenMenu->setEnabled(false); predefinedTokens.clear(); InnerDecklistNode *tokenZone = dynamic_cast(deck->getRoot()->findChild(DECK_ZONE_TOKENS)); - if (tokenZone) + if (tokenZone) { + if (tokenZone->size() > 0) + createPredefinedTokenMenu->setEnabled(true); + for (int i = 0; i < tokenZone->size(); ++i) { const QString tokenName = tokenZone->at(i)->getName(); predefinedTokens.append(tokenName); @@ -951,6 +958,7 @@ void Player::setDeck(const DeckLoader &_deck) } connect(a, SIGNAL(triggered()), this, SLOT(actCreatePredefinedToken())); } + } } void Player::actViewLibrary() @@ -3230,6 +3238,7 @@ void Player::addRelatedCardActions(const CardItem *card, QMenu *cardMenu) void Player::setCardMenu(QMenu *menu) { if (aCardMenu) { + aCardMenu->setEnabled(menu != nullptr); aCardMenu->setMenu(menu); } } From 6e00db4ef6c84b131f29256944a6042c2461e669 Mon Sep 17 00:00:00 2001 From: Zach H Date: Sun, 22 Nov 2020 20:25:23 -0500 Subject: [PATCH 032/126] Fix racetime condition with token cloning (#4156) * Fix #2820 by removing (this->setCursor) as this was null by the time we hit this component due to a racetime condition. * check if card player pointer is valid before setting cursor Co-authored-by: ebbit1q --- cockatrice/src/carditem.cpp | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/cockatrice/src/carditem.cpp b/cockatrice/src/carditem.cpp index 19b3b691d..3c4ed3ee4 100644 --- a/cockatrice/src/carditem.cpp +++ b/cockatrice/src/carditem.cpp @@ -48,22 +48,22 @@ CardItem::~CardItem() void CardItem::prepareDelete() { - if (owner) { + if (owner != nullptr) { if (owner->getCardMenu() == cardMenu) { - owner->setCardMenu(0); - owner->getGame()->setActiveCard(0); + owner->setCardMenu(nullptr); + owner->getGame()->setActiveCard(nullptr); } - owner = 0; + owner = nullptr; } while (!attachedCards.isEmpty()) { - attachedCards.first()->setZone(0); // so that it won't try to call reorganizeCards() - attachedCards.first()->setAttachedTo(0); + attachedCards.first()->setZone(nullptr); // so that it won't try to call reorganizeCards() + attachedCards.first()->setAttachedTo(nullptr); } - if (attachedTo) { + if (attachedTo != nullptr) { attachedTo->removeAttachedCard(this); - attachedTo = 0; + attachedTo = nullptr; } } @@ -265,9 +265,10 @@ CardDragItem *CardItem::createDragItem(int _id, const QPointF &_pos, const QPoin void CardItem::deleteDragItem() { - if (dragItem) + if (dragItem) { dragItem->deleteLater(); - dragItem = NULL; + } + dragItem = nullptr; } void CardItem::drawArrow(const QColor &arrowColor) @@ -362,7 +363,7 @@ void CardItem::playCard(bool faceDown) void CardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { if (event->button() == Qt::RightButton) { - if (cardMenu && !cardMenu->isEmpty() && owner) { + if (cardMenu && !cardMenu->isEmpty() && owner != nullptr) { owner->updateCardMenu(this); cardMenu->exec(event->screenPos()); } @@ -370,7 +371,7 @@ void CardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) (!SettingsCache::instance().getDoubleClickToPlay())) { bool hideCard = false; if (zone && zone->getIsView()) { - ZoneViewZone *view = static_cast(zone); + auto *view = static_cast(zone); if (view->getRevealZone() && !view->getWriteableRevealZone()) hideCard = true; } @@ -381,7 +382,9 @@ void CardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) } } - setCursor(Qt::OpenHandCursor); + if (owner != nullptr){ // cards without owner will be deleted + setCursor(Qt::OpenHandCursor); + } AbstractCardItem::mouseReleaseEvent(event); } From 51b24bb92c4383d3a07bb37a846319cbad2f817e Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Mon, 23 Nov 2020 02:28:56 +0100 Subject: [PATCH 033/126] refactor getting game age (#4095) --- cockatrice/src/gamesmodel.cpp | 43 +++++++++++++++++++++++------------ cockatrice/src/gamesmodel.h | 3 --- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/cockatrice/src/gamesmodel.cpp b/cockatrice/src/gamesmodel.cpp index 341a5129b..1b7aefeaf 100644 --- a/cockatrice/src/gamesmodel.cpp +++ b/cockatrice/src/gamesmodel.cpp @@ -10,6 +10,7 @@ #include #include #include +#include enum GameListColumn { @@ -38,23 +39,35 @@ constexpr QTime DEFAULT_MAX_GAME_AGE = QTime(); const QString GamesModel::getGameCreatedString(const int secs) { + static const QTime zeroTime{0, 0}; + static const int halfHourSecs = zeroTime.secsTo(QTime(1, 0)) / 2; + static const int halfMinSecs = zeroTime.secsTo(QTime(0, 1)) / 2; + static const int wrapSeconds = zeroTime.secsTo(zeroTime.addSecs(-halfHourSecs)); // round up - QString ret; - if (secs < SECS_PER_MIN * 2) // for first min we display "New" - ret = tr("New"); - else if (secs < SECS_PER_MIN * 10) // from 2 - 10 mins we show the mins - ret = QString("%1 min").arg(QString::number(secs / SECS_PER_MIN)); - else if (secs < SECS_PER_MIN * 60) { // from 10 mins to 1h we aggregate every 10 mins - int unitOfTen = secs / SECS_PER_TEN_MIN; - QString str = "%1%2"; - ret = str.arg(QString::number(unitOfTen), "0+ min"); - } else { // from 1 hr onward we show hrs - int hours = secs / SECS_PER_HOUR; - if (secs % SECS_PER_HOUR >= SECS_PER_MIN * 30) // if the room is open for 1hr 30 mins, we round to 2hrs - ++hours; - ret = QString("%1+ h").arg(QString::number(hours)); + if (secs >= wrapSeconds) { // QTime wraps after a day + return tr(">1 day"); } - return ret; + + QTime total = zeroTime.addSecs(secs); + QTime totalRounded = total.addSecs(halfMinSecs); // round up + QString form; + int amount; + if (totalRounded.hour()) { + amount = total.addSecs(halfHourSecs).hour(); // round up separately + form = tr("%1%2 hr", "short age in hours", amount); + } else if (total.minute() < 2) { // games are new during their first minute + return tr("new"); + } else { + amount = totalRounded.minute(); + form = tr("%1%2 min", "short age in minutes", amount); + } + + for (int aggregate : {40, 20, 10, 5}) { // floor to values in this list + if (amount >= aggregate) { + return form.arg(">").arg(aggregate); + } + } + return form.arg("").arg(amount); } GamesModel::GamesModel(const QMap &_rooms, const QMap &_gameTypes, QObject *parent) diff --git a/cockatrice/src/gamesmodel.h b/cockatrice/src/gamesmodel.h index 05fe5d085..eb14fcbda 100644 --- a/cockatrice/src/gamesmodel.h +++ b/cockatrice/src/gamesmodel.h @@ -21,9 +21,6 @@ private: QMap gameTypes; static const int NUM_COLS = 8; - static const int SECS_PER_MIN = 60; - static const int SECS_PER_TEN_MIN = 600; - static const int SECS_PER_HOUR = 3600; public: static const int SORT_ROLE = Qt::UserRole + 1; From 589fbcdcd5d0a78f64d606c4efbcd57742abe76a Mon Sep 17 00:00:00 2001 From: tooomm Date: Mon, 23 Nov 2020 18:24:49 +0100 Subject: [PATCH 034/126] clearify wording (#4173) --- cockatrice/src/tab_server.cpp | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/cockatrice/src/tab_server.cpp b/cockatrice/src/tab_server.cpp index aae93506f..03919f364 100644 --- a/cockatrice/src/tab_server.cpp +++ b/cockatrice/src/tab_server.cpp @@ -112,7 +112,7 @@ void RoomSelector::processListRoomsEvent(const Event_ListRooms &event) QString RoomSelector::getRoomPermissionDisplay(const ServerInfo_Room &room) { /* - * A room can have a permission level and a privilege level. How ever we want to display only the necessary + * A server room can have a permission level and a privilege level. How ever we want to display only the necessary * information on the server tab needed to inform users of required permissions to enter a room. If the room has a * privilege level the server tab will display the privilege level in the "permissions" column in the row however if * the room contains a permissions level for the room the permissions level defined for the room will be displayed. @@ -206,19 +206,23 @@ void TabServer::joinRoomFinished(const Response &r, case Response::RespOk: break; case Response::RespNameNotFound: - QMessageBox::critical(this, tr("Error"), tr("Failed to join the room: it doesn't exist on the server.")); + QMessageBox::critical(this, tr("Error"), + tr("Failed to join the server room: it doesn't exist on the server.")); return; case Response::RespContextError: - QMessageBox::critical(this, tr("Error"), - tr("The server thinks you are in the room but your client is unable to display it. " - "Try restarting your client.")); + QMessageBox::critical( + this, tr("Error"), + tr("The server thinks you are in the server room but your client is unable to display it. " + "Try restarting your client.")); return; case Response::RespUserLevelTooLow: - QMessageBox::critical(this, tr("Error"), tr("You do not have the required permission to join this room.")); + QMessageBox::critical(this, tr("Error"), + tr("You do not have the required permission to join this server room.")); return; default: - QMessageBox::critical(this, tr("Error"), - tr("Failed to join the room due to an unknown error: %1.").arg(r.response_code())); + QMessageBox::critical( + this, tr("Error"), + tr("Failed to join the server room due to an unknown error: %1.").arg(r.response_code())); return; } From 2f62671d8a0db81d79a59f3d1edb33612076bb23 Mon Sep 17 00:00:00 2001 From: tooomm Date: Mon, 23 Nov 2020 18:27:35 +0100 Subject: [PATCH 035/126] More changes to GitHub Actions (#4175) --- .github/workflows/linux-builds.yml | 3 +++ .github/workflows/macos-builds.yml | 3 +++ README.md | 6 ++++-- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/linux-builds.yml b/.github/workflows/linux-builds.yml index 3e061fe9b..f34fa3686 100644 --- a/.github/workflows/linux-builds.yml +++ b/.github/workflows/linux-builds.yml @@ -1,6 +1,9 @@ name: Build on Linux (Docker) on: + push: + branches: + - master pull_request: branches: - master diff --git a/.github/workflows/macos-builds.yml b/.github/workflows/macos-builds.yml index f24bc0782..151486bd7 100644 --- a/.github/workflows/macos-builds.yml +++ b/.github/workflows/macos-builds.yml @@ -1,6 +1,9 @@ name: Build on macOS on: + push: + branches: + - master pull_request: branches: - master diff --git a/README.md b/README.md index ca9183a99..8df0c4e37 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@

Cockatrice | Download | - Get Involved | + Get Involved | Community | Translations | Build | @@ -79,7 +79,9 @@ Cockatrice uses Transifex for translations. You can help us bring Cockatrice and Check out our [Translator FAQ](https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ) for more information about contributing!
-# Build ![macOS builds](https://github.com/Cockatrice/Cockatrice/workflows/Build%20on%20macOS/badge.svg) ![linux builds](https://github.com/Cockatrice/Cockatrice/workflows/Build%20on%20Linux%20(Docker)/badge.svg) [![Appveyor Build Status - master](https://ci.appveyor.com/api/projects/status/oauxf5a0sj689rcg/branch/master?svg=true)](https://ci.appveyor.com/project/ZeldaZach/cockatrice/branch/master) +# Build [![Linux builds - master](https://github.com/Cockatrice/Cockatrice/workflows/Build%20on%20Linux%20(Docker)/badge.svg)](https://github.com/Cockatrice/Cockatrice/actions?query=workflow%3A%22Build+on+Linux+%28Docker%29%22+branch%3Amaster) [![macOS builds - master](https://github.com/Cockatrice/Cockatrice/workflows/Build%20on%20macOS/badge.svg)](https://github.com/Cockatrice/Cockatrice/actions?query=workflow%3A%22Build+on+macOS%22+branch%3Amaster) [![Windows builds - master](https://ci.appveyor.com/api/projects/status/oauxf5a0sj689rcg/branch/master?svg=true)](https://ci.appveyor.com/project/ZeldaZach/cockatrice/branch/master) + + **Detailed compiling instructions are on the Cockatrice wiki under [Compiling Cockatrice](https://github.com/Cockatrice/Cockatrice/wiki/Compiling-Cockatrice)** From f3cf1f0ddeb723b2dd226172314a47048ed6b239 Mon Sep 17 00:00:00 2001 From: tooomm Date: Mon, 23 Nov 2020 19:23:47 +0100 Subject: [PATCH 036/126] pin badge to master (#4177) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8df0c4e37..bb7ab5d2a 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ Cockatrice uses Transifex for translations. You can help us bring Cockatrice and Check out our [Translator FAQ](https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ) for more information about contributing!
-# Build [![Linux builds - master](https://github.com/Cockatrice/Cockatrice/workflows/Build%20on%20Linux%20(Docker)/badge.svg)](https://github.com/Cockatrice/Cockatrice/actions?query=workflow%3A%22Build+on+Linux+%28Docker%29%22+branch%3Amaster) [![macOS builds - master](https://github.com/Cockatrice/Cockatrice/workflows/Build%20on%20macOS/badge.svg)](https://github.com/Cockatrice/Cockatrice/actions?query=workflow%3A%22Build+on+macOS%22+branch%3Amaster) [![Windows builds - master](https://ci.appveyor.com/api/projects/status/oauxf5a0sj689rcg/branch/master?svg=true)](https://ci.appveyor.com/project/ZeldaZach/cockatrice/branch/master) +# Build [![Linux builds - master](https://github.com/Cockatrice/Cockatrice/workflows/Build%20on%20Linux%20(Docker)/badge.svg?branch=master)](https://github.com/Cockatrice/Cockatrice/actions?query=workflow%3A%22Build+on+Linux+%28Docker%29%22+branch%3Amaster) [![macOS builds - master](https://github.com/Cockatrice/Cockatrice/workflows/Build%20on%20macOS/badge.svg?branch=master)](https://github.com/Cockatrice/Cockatrice/actions?query=workflow%3A%22Build+on+macOS%22+branch%3Amaster) [![Windows builds - master](https://ci.appveyor.com/api/projects/status/oauxf5a0sj689rcg/branch/master?svg=true)](https://ci.appveyor.com/project/ZeldaZach/cockatrice/branch/master) From 9f9581c2bec17a37e0f41598862711f3cedd4e54 Mon Sep 17 00:00:00 2001 From: Zach H Date: Mon, 23 Nov 2020 16:12:41 -0500 Subject: [PATCH 037/126] Support MTGJSONv5 format in Oracle downloader (#4162) * Fix #4043, Support MTGJSONv5 format in Oracle downloader * Auto redirect V4 downloads to V5, as we won't support V4 after this change * clangify >_> * Remove null values and account for IDs missing * fix split cards and double faced cards somewhat * do not consider double faced cards duplicates * fix promo double sided cards * typo * fix alternative versions of cards with (letter) * zach says this is more readable * pre qt 5.10 compatibility Co-authored-by: ebbit1q --- oracle/src/oracleimporter.cpp | 129 +++++++++++++++++++++------------- oracle/src/oracleimporter.h | 12 ++-- oracle/src/oraclewizard.cpp | 49 +++++++------ oracle/src/oraclewizard.h | 2 +- oracle/src/pagetemplates.cpp | 2 +- 5 files changed, 118 insertions(+), 76 deletions(-) diff --git a/oracle/src/oracleimporter.cpp b/oracle/src/oracleimporter.cpp index 468aa0b06..72eaaed26 100644 --- a/oracle/src/oracleimporter.cpp +++ b/oracle/src/oracleimporter.cpp @@ -3,16 +3,15 @@ #include "carddbparser/cockatricexml4.h" #include "qt-json/json.h" -#include #include #include #include -SplitCardPart::SplitCardPart(const int _index, +SplitCardPart::SplitCardPart(const QString &_name, const QString &_text, const QVariantHash &_properties, const CardInfoPerSet _setInfo) - : index(_index), text(_text), properties(_properties), setInfo(_setInfo) + : name(_name), text(_text), properties(_properties), setInfo(_setInfo) { } @@ -25,7 +24,7 @@ bool OracleImporter::readSetsFromByteArray(const QByteArray &data) QList newSetList; bool ok; - setsMap = QtJson::Json::parse(QString(data), ok).toMap(); + setsMap = QtJson::Json::parse(QString(data), ok).toMap().value("data").toMap(); if (!ok) { qDebug() << "error: QtJson::Json::parse()"; return false; @@ -162,14 +161,9 @@ CardInfoPtr OracleImporter::addCard(QString name, // upsideDown (flip cards) bool upsideDown = false; - QStringList additionalNames = properties.value("names").toStringList(); QString layout = properties.value("layout").toString(); if (layout == "flip") { - if (properties.value("side").toString() != "front") { - upsideDown = true; - } - // reset the side property, since the card has no back image - properties.insert("side", "front"); + upsideDown = properties.value("side").toString() != "a"; } // insert the card and its properties @@ -179,36 +173,41 @@ CardInfoPtr OracleImporter::addCard(QString name, CardInfoPtr newCard = CardInfo::newInstance(name, text, isToken, properties, relatedCards, reverseRelatedCards, setsInfo, cipt, tableRow, upsideDown); + if (name.isEmpty()) { + qDebug() << "warning: an empty card was added to set" << setInfo.getPtr()->getShortName(); + } cards.insert(name, newCard); + return newCard; } -QString OracleImporter::getStringPropertyFromMap(QVariantMap card, QString propertyName) +QString OracleImporter::getStringPropertyFromMap(const QVariantMap &card, const QString &propertyName) { return card.contains(propertyName) ? card.value(propertyName).toString() : QString(""); } -int OracleImporter::importCardsFromSet(CardSetPtr currentSet, const QList &cardsList, bool skipSpecialCards) +int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, + const QList &cardsList, + bool skipSpecialCards) { + // mtgjson name => xml name static const QMap cardProperties{ - // mtgjson name => xml name {"manaCost", "manacost"}, {"convertedManaCost", "cmc"}, {"type", "type"}, {"loyalty", "loyalty"}, {"layout", "layout"}, {"side", "side"}, }; - static const QMap setInfoProperties{// mtgjson name => xml name - {"multiverseId", "muid"}, - {"scryfallId", "uuid"}, - {"number", "num"}, - {"rarity", "rarity"}}; + // mtgjson name => xml name + static const QMap setInfoProperties{{"number", "num"}, {"rarity", "rarity"}}; + + // mtgjson name => xml name + static const QMap identifierProperties{{"multiverseId", "muid"}, {"scryfallId", "uuid"}}; int numCards = 0; QMultiMap splitCards; QString ptSeparator("/"); QVariantMap card; - QString layout, name, text, colors, colorIdentity, maintype, power, toughness; + QString layout, name, text, colors, colorIdentity, maintype, power, toughness, faceName; static const bool isToken = false; - QStringList additionalNames; QVariantHash properties; CardInfoPerSet setInfo; QList relatedCards; @@ -219,6 +218,11 @@ int OracleImporter::importCardsFromSet(CardSetPtr currentSet, const QList it3(identifierProperties); + while (it3.hasNext()) { + it3.next(); + auto mtgjsonProperty = it3.key(); + auto xmlPropertyName = it3.value(); + auto propertyValue = getStringPropertyFromMap(card.value("identifiers").toMap(), mtgjsonProperty); + if (!propertyValue.isEmpty()) { + setInfo.setProperty(xmlPropertyName, propertyValue); + } } + QString numComponent{}; if (skipSpecialCards) { - // skip promo cards if it's not the only print - if (allNameProps.contains(name)) { - continue; + QString numProperty = setInfo.getProperty("num"); + // skip promo cards if it's not the only print, cards with two faces are different cards + if (allNameProps.contains(faceName)) { + // check for alternative versions + if (layout != "normal") + continue; + + // alternative versions have a letter in the end of num like abc + // note this will also catch p and s, those will get removed later anyway + QChar lastChar = numProperty.at(numProperty.size() - 1); + if (!lastChar.isLetter()) + continue; + + numComponent = " (" + QString(lastChar) + ")"; + faceName += numComponent; // add to facename to make it unique } if (getStringPropertyFromMap(card, "isPromo") == "true") { - specialPromoCards.insert(name, cardVar); + specialPromoCards.insert(faceName, cardVar); continue; } - QString numProperty = setInfo.getProperty("num"); bool skip = false; // skip cards containing special stuff in the collectors number like promo cards for (const QString &specialChar : specialNumChars) { @@ -282,10 +309,10 @@ int OracleImporter::importCardsFromSet(CardSetPtr currentSet, const QList 1) { - for (const QString &additionalName : additionalNames) { - if (additionalName != name) + + // add other face for split cards as card relation + if (!getStringPropertyFromMap(card, "side").isEmpty()) { + properties["cmc"] = getStringPropertyFromMap(card, "faceConvertedManaCost"); + if (layout == "meld") { // meld cards don't work + QRegularExpression meldNameRegex{"then meld them into ([\\.]*)"}; + QString additionalName = meldNameRegex.match(text).captured(1); + if (!additionalName.isNull()) { relatedCards.append(new CardRelation(additionalName, true)); + } + } else { + for (const QString &additionalName : name.split(" // ")) { + if (additionalName != faceName) { + relatedCards.append(new CardRelation(additionalName, true)); + } + } } + name = faceName; } - CardInfoPtr newCard = addCard(name, text, isToken, properties, relatedCards, setInfo); + CardInfoPtr newCard = addCard(name + numComponent, text, isToken, properties, relatedCards, setInfo); numCards++; } } @@ -350,21 +385,22 @@ int OracleImporter::importCardsFromSet(CardSetPtr currentSet, const QList splitCardParts = splitCards.values(nameSplit); - // sort them by index (aka position) + // sort them by face name std::sort(splitCardParts.begin(), splitCardParts.end(), - [](const SplitCardPart &a, const SplitCardPart &b) -> bool { return a.getIndex() < b.getIndex(); }); + [](const SplitCardPart &a, const SplitCardPart &b) -> bool { return a.getName() < b.getName(); }); text = QString(""); properties.clear(); relatedCards.clear(); - int lastIndex = -1; + QString lastName{}; for (const SplitCardPart &tmp : splitCardParts) { // some sets have 2 different variations of the same split card, // eg. Fire // Ice in WC02. Avoid adding duplicates. - if (lastIndex == tmp.getIndex()) + if (lastName == tmp.getName()) continue; - lastIndex = tmp.getIndex(); + + lastName = tmp.getName(); if (!text.isEmpty()) text.append(splitCardTextSeparator); @@ -375,7 +411,6 @@ int OracleImporter::importCardsFromSet(CardSetPtr currentSet, const QList &cards, bool skipSpecialNums = true); + int importCardsFromSet(const CardSetPtr ¤tSet, const QList &cards, bool skipSpecialNums = true); QList &getSets() { return allSets; @@ -123,7 +123,7 @@ public: void clear(); protected: - inline QString getStringPropertyFromMap(QVariantMap card, QString propertyName); + inline QString getStringPropertyFromMap(const QVariantMap &card, const QString &propertyName); void sortAndReduceColors(QString &colors); }; diff --git a/oracle/src/oraclewizard.cpp b/oracle/src/oraclewizard.cpp index 675c6f585..0da8ecaaf 100644 --- a/oracle/src/oraclewizard.cpp +++ b/oracle/src/oraclewizard.cpp @@ -20,7 +20,6 @@ #include #include #include -#include #include #include #include @@ -38,15 +37,16 @@ #define ZIP_SIGNATURE "PK" // Xz stream header: 0xFD + "7zXZ" #define XZ_SIGNATURE "\xFD\x37\x7A\x58\x5A" -#define ALLSETS_URL_FALLBACK "https://www.mtgjson.com/files/AllPrintings.json" -#define MTGJSON_VERSION_URL "https://www.mtgjson.com/files/version.json" +#define MTGJSON_V4_URL_COMPONENT "mtgjson.com/files/" +#define ALLSETS_URL_FALLBACK "https://www.mtgjson.com/api/v5/AllPrintings.json" +#define MTGJSON_VERSION_URL "https://www.mtgjson.com/api/v5/Meta.json" #ifdef HAS_LZMA -#define ALLSETS_URL "https://www.mtgjson.com/files/AllPrintings.json.xz" +#define ALLSETS_URL "https://www.mtgjson.com/api/v5/AllPrintings.json.xz" #elif defined(HAS_ZLIB) -#define ALLSETS_URL "https://www.mtgjson.com/files/AllPrintings.json.zip" +#define ALLSETS_URL "https://www.mtgjson.com/api/v5/AllPrintings.json.zip" #else -#define ALLSETS_URL "https://www.mtgjson.com/files/AllPrintings.json" +#define ALLSETS_URL "https://www.mtgjson.com/api/v5/AllPrintings.json" #endif #define TOKENS_URL "https://raw.githubusercontent.com/Cockatrice/Magic-Token/master/tokens.xml" @@ -299,7 +299,13 @@ bool LoadSetsPage::validatePage() // else, try to import sets if (urlRadioButton->isChecked()) { - QUrl url = QUrl::fromUserInput(urlLineEdit->text()); + // If a user attempts to download from V4, redirect them to V5 + if (urlLineEdit->text().contains(MTGJSON_V4_URL_COMPONENT)) { + actRestoreDefaultUrl(); + } + + const auto url = QUrl::fromUserInput(urlLineEdit->text()); + if (!url.isValid()) { QMessageBox::critical(this, tr("Error"), tr("The provided URL is not valid.")); return false; @@ -342,23 +348,24 @@ bool LoadSetsPage::validatePage() } #include -void LoadSetsPage::downloadSetsFile(QUrl url) +void LoadSetsPage::downloadSetsFile(const QUrl &url) { wizard()->setCardSourceVersion("unknown"); - QString urlString = url.toString(); + const auto urlString = url.toString(); if (urlString == ALLSETS_URL || urlString == ALLSETS_URL_FALLBACK) { - QUrl versionUrl = QUrl::fromUserInput(MTGJSON_VERSION_URL); - QNetworkReply *versionReply = wizard()->nam->get(QNetworkRequest(versionUrl)); + const auto versionUrl = QUrl::fromUserInput(MTGJSON_VERSION_URL); + auto *versionReply = wizard()->nam->get(QNetworkRequest(versionUrl)); connect(versionReply, &QNetworkReply::finished, [this, versionReply]() { if (versionReply->error() == QNetworkReply::NoError) { - QByteArray jsonData = versionReply->readAll(); - QJsonParseError jsonError; - QJsonDocument jsonResponse = QJsonDocument::fromJson(jsonData, &jsonError); + auto jsonData = versionReply->readAll(); + QJsonParseError jsonError{}; + auto jsonResponse = QJsonDocument::fromJson(jsonData, &jsonError); if (jsonError.error == QJsonParseError::NoError) { - QVariantMap jsonMap = jsonResponse.toVariant().toMap(); - QString versionString = jsonMap["version"].toString(); + const auto jsonMap = jsonResponse.toVariant().toMap(); + + auto versionString = jsonMap.value("meta").toMap().value("version").toString(); if (versionString.isEmpty()) { versionString = "unknown"; } @@ -372,7 +379,7 @@ void LoadSetsPage::downloadSetsFile(QUrl url) wizard()->setCardSourceUrl(url.toString()); - QNetworkReply *reply = wizard()->nam->get(QNetworkRequest(url)); + auto *reply = wizard()->nam->get(QNetworkRequest(url)); connect(reply, SIGNAL(finished()), this, SLOT(actDownloadFinishedSetsFile())); connect(reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(actDownloadProgressSetsFile(qint64, qint64))); @@ -391,7 +398,7 @@ void LoadSetsPage::actDownloadFinishedSetsFile() { // check for a reply auto *reply = dynamic_cast(sender()); - QNetworkReply::NetworkError errorCode = reply->error(); + auto errorCode = reply->error(); if (errorCode != QNetworkReply::NoError) { QMessageBox::critical(this, tr("Error"), tr("Network error: %1.").arg(reply->errorString())); @@ -402,9 +409,9 @@ void LoadSetsPage::actDownloadFinishedSetsFile() return; } - int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + auto statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); if (statusCode == 301 || statusCode == 302) { - QUrl redirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); + const auto redirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); qDebug() << "following redirect url:" << redirectUrl.toString(); downloadSetsFile(redirectUrl); reply->deleteLater(); @@ -414,7 +421,7 @@ void LoadSetsPage::actDownloadFinishedSetsFile() progressLabel->hide(); progressBar->hide(); - // save allsets.json url, but only if the user customized it and download was successfull + // save AllPrintings.json url, but only if the user customized it and download was successful if (urlLineEdit->text() != QString(ALLSETS_URL)) { wizard()->settings->setValue("allsetsurl", urlLineEdit->text()); } else { diff --git a/oracle/src/oraclewizard.h b/oracle/src/oraclewizard.h index 28f0d2e08..362c19def 100644 --- a/oracle/src/oraclewizard.h +++ b/oracle/src/oraclewizard.h @@ -113,7 +113,7 @@ protected: void initializePage() override; bool validatePage() override; void readSetsFromByteArray(QByteArray data); - void downloadSetsFile(QUrl url); + void downloadSetsFile(const QUrl &url); private: QRadioButton *urlRadioButton; diff --git a/oracle/src/pagetemplates.cpp b/oracle/src/pagetemplates.cpp index b7d3fa65c..b4d8d358b 100644 --- a/oracle/src/pagetemplates.cpp +++ b/oracle/src/pagetemplates.cpp @@ -125,7 +125,7 @@ void SimpleDownloadFilePage::actDownloadFinished() return; } - // save downlaoded file url, but only if the user customized it and download was successfull + // save downloaded file url, but only if the user customized it and download was successful if (urlLineEdit->text() != getDefaultUrl()) { wizard()->settings->setValue(getCustomUrlSettingsKey(), urlLineEdit->text()); } else { From 46cf50d4686f7f89f8513157f1f086bdaf615b52 Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Tue, 24 Nov 2020 00:09:02 +0100 Subject: [PATCH 038/126] add ubuntu 20.10 Groovy Gorilla (#4178) --- .ci/UbuntuGroovy/Dockerfile | 25 +++++++++++++++++++++++++ .github/workflows/linux-builds.yml | 4 ++++ 2 files changed, 29 insertions(+) create mode 100644 .ci/UbuntuGroovy/Dockerfile diff --git a/.ci/UbuntuGroovy/Dockerfile b/.ci/UbuntuGroovy/Dockerfile new file mode 100644 index 000000000..92528ba3f --- /dev/null +++ b/.ci/UbuntuGroovy/Dockerfile @@ -0,0 +1,25 @@ +FROM ubuntu:groovy + +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + build-essential \ + ccache \ + clang-format \ + cmake \ + file \ + g++ \ + git \ + liblzma-dev \ + libmariadb-dev-compat \ + libprotobuf-dev \ + libqt5multimedia5-plugins \ + libqt5sql5-mysql \ + libqt5svg5-dev \ + libqt5websockets5-dev \ + protobuf-compiler \ + qt5-default \ + qtmultimedia5-dev \ + qttools5-dev \ + qttools5-dev-tools \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* diff --git a/.github/workflows/linux-builds.yml b/.github/workflows/linux-builds.yml index f34fa3686..d228da747 100644 --- a/.github/workflows/linux-builds.yml +++ b/.github/workflows/linux-builds.yml @@ -17,12 +17,16 @@ jobs: fail-fast: false matrix: distro: # these names correspond to the files in .ci/$distro + - UbuntuGroovy - UbuntuFocal - UbuntuBionic - ArchLinux - DebianBuster - Fedora33 include: + - distro: UbuntuGroovy + package: DEB + - distro: UbuntuFocal package: DEB test: skip # UbuntuFocal has a broken qt for debug builds From b0239c11ab3533b8b3eb00e0cb1c8ffa5d61494f Mon Sep 17 00:00:00 2001 From: leiftw Date: Tue, 24 Nov 2020 20:58:28 +0100 Subject: [PATCH 039/126] Fix typo in #L135 to conform with #L138 (#4182) --- oracle/src/oracleimporter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oracle/src/oracleimporter.cpp b/oracle/src/oracleimporter.cpp index 72eaaed26..9b5ee9d4b 100644 --- a/oracle/src/oracleimporter.cpp +++ b/oracle/src/oracleimporter.cpp @@ -132,7 +132,7 @@ CardInfoPtr OracleImporter::addCard(QString name, sortAndReduceColors(allColors); properties.insert("colors", allColors); } - QString allColorIdent = properties.value("colorIdenity").toString(); + QString allColorIdent = properties.value("coloridentity").toString(); if (allColorIdent.size() > 1) { sortAndReduceColors(allColorIdent); properties.insert("coloridentity", allColorIdent); From 99b0abe7fe211ba66d36679c5d2bc8580f867b68 Mon Sep 17 00:00:00 2001 From: tooomm Date: Thu, 26 Nov 2020 03:14:05 +0100 Subject: [PATCH 040/126] GitHub Actions: don't run on .md only changes (#4183) --- .github/workflows/linux-builds.yml | 4 ++++ README.md | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/linux-builds.yml b/.github/workflows/linux-builds.yml index d228da747..d6bfa0d2d 100644 --- a/.github/workflows/linux-builds.yml +++ b/.github/workflows/linux-builds.yml @@ -4,9 +4,13 @@ on: push: branches: - master + paths-ignore: + - '**.md' pull_request: branches: - master + paths-ignore: + - '**.md' release: types: - published diff --git a/README.md b/README.md index bb7ab5d2a..a1834da34 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ Cockatrice uses the [Google Developer Documentation Style Guide](https://develop - [reddit r/Cockatrice](https://reddit.com/r/cockatrice) -# Translations [![Cockatrice on Transiflex](https://tx-assets.scdn5.secure.raxcdn.com/static/charts/images/tx-logo-micro.c5603f91c780.png)](https://www.transifex.com/projects/p/cockatrice/) +# Translations [![Cockatrice on Transifex](https://tx-assets.scdn5.secure.raxcdn.com/static/charts/images/tx-logo-micro.c5603f91c780.png)](https://www.transifex.com/projects/p/cockatrice/) Cockatrice uses Transifex for translations. You can help us bring Cockatrice and Oracle to your language or just edit single wordings right from within your browser by visiting our [Transifex project page](https://www.transifex.com/projects/p/cockatrice/).
@@ -163,6 +163,7 @@ A out of box working docker-compose file has been added to help setup in Windows Docker in Windows requires additional steps in form of using Docker Desktop to allow resource sharing from the drive the volumes are mapped from, as well as potential workarounds needed to get file sharing working in Windows. This [StackOverflow discussion sheds some light on it](https://stackoverflow.com/questions/42203488/settings-to-windows-firewall-to-allow-docker-for-windows-to-share-drive) + # License [![GPLv2 License](https://img.shields.io/github/license/Cockatrice/Cockatrice.svg)](https://github.com/Cockatrice/Cockatrice/blob/master/LICENSE) Cockatrice is free software, licensed under the [GPLv2](https://github.com/Cockatrice/Cockatrice/blob/master/LICENSE). From b4740ad3953850df228e6fbffb69304e299064e7 Mon Sep 17 00:00:00 2001 From: tooomm Date: Thu, 26 Nov 2020 19:14:00 +0100 Subject: [PATCH 041/126] Update README.md (#4189) * Update README.md * don't run on .md-only changes --- .github/workflows/macos-builds.yml | 4 ++++ README.md | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/macos-builds.yml b/.github/workflows/macos-builds.yml index 151486bd7..6c91cdcbf 100644 --- a/.github/workflows/macos-builds.yml +++ b/.github/workflows/macos-builds.yml @@ -4,9 +4,13 @@ on: push: branches: - master + paths-ignore: + - '**.md' pull_request: branches: - master + paths-ignore: + - '**.md' release: types: - published diff --git a/README.md b/README.md index a1834da34..439708c56 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ Check out our [Translator FAQ](https://github.com/Cockatrice/Cockatrice/wiki/Tra -**Detailed compiling instructions are on the Cockatrice wiki under [Compiling Cockatrice](https://github.com/Cockatrice/Cockatrice/wiki/Compiling-Cockatrice)** +**Detailed compiling instructions can be found on the Cockatrice wiki under [Compiling Cockatrice](https://github.com/Cockatrice/Cockatrice/wiki/Compiling-Cockatrice)** Dependencies: *(for minimum requirements search our [CMake file](https://github.com/Cockatrice/Cockatrice/blob/master/CMakeLists.txt))* - [Qt](https://www.qt.io/developers/) From c047a8ae3cb473e16ba75e1f94a4f9adebd76a97 Mon Sep 17 00:00:00 2001 From: Joel Bethke Date: Thu, 26 Nov 2020 15:22:44 -0600 Subject: [PATCH 042/126] ui: Add shortcut for "Save deck as..." (#4188) Fixes: #4174 --- cockatrice/src/shortcutssettings.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cockatrice/src/shortcutssettings.h b/cockatrice/src/shortcutssettings.h index 420854d0e..1c1989eea 100644 --- a/cockatrice/src/shortcutssettings.h +++ b/cockatrice/src/shortcutssettings.h @@ -219,7 +219,7 @@ private: parseSequenceString("Ctrl+S"), ShortcutGroup::Deck_Editor)}, {"TabDeckEditor/aSaveDeckAs", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Save Deck as..."), - parseSequenceString(""), + parseSequenceString("Ctrl+Shift+S"), ShortcutGroup::Deck_Editor)}, {"TabDeckEditor/aSaveDeckToClipboard", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Save Deck to Clipboard, Annotated"), From 9e702ec358363005e930ad6050f605814871d2b5 Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Sat, 28 Nov 2020 09:13:42 +0100 Subject: [PATCH 043/126] update link to master builds on appveyor (#4180) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 439708c56..e0ac98259 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ Cockatrice uses Transifex for translations. You can help us bring Cockatrice and Check out our [Translator FAQ](https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ) for more information about contributing!
-# Build [![Linux builds - master](https://github.com/Cockatrice/Cockatrice/workflows/Build%20on%20Linux%20(Docker)/badge.svg?branch=master)](https://github.com/Cockatrice/Cockatrice/actions?query=workflow%3A%22Build+on+Linux+%28Docker%29%22+branch%3Amaster) [![macOS builds - master](https://github.com/Cockatrice/Cockatrice/workflows/Build%20on%20macOS/badge.svg?branch=master)](https://github.com/Cockatrice/Cockatrice/actions?query=workflow%3A%22Build+on+macOS%22+branch%3Amaster) [![Windows builds - master](https://ci.appveyor.com/api/projects/status/oauxf5a0sj689rcg/branch/master?svg=true)](https://ci.appveyor.com/project/ZeldaZach/cockatrice/branch/master) +# Build [![Linux builds - master](https://github.com/Cockatrice/Cockatrice/workflows/Build%20on%20Linux%20(Docker)/badge.svg?branch=master)](https://github.com/Cockatrice/Cockatrice/actions?query=workflow%3A%22Build+on+Linux+%28Docker%29%22+branch%3Amaster) [![macOS builds - master](https://github.com/Cockatrice/Cockatrice/workflows/Build%20on%20macOS/badge.svg?branch=master)](https://github.com/Cockatrice/Cockatrice/actions?query=workflow%3A%22Build+on+macOS%22+branch%3Amaster) [![Windows builds - master](https://ci.appveyor.com/api/projects/status/oauxf5a0sj689rcg/branch/master?svg=true)](https://ci.appveyor.com/project/ZeldaZach/cockatrice-k884w) From 402c09e02886fb75a3b696ac5c43f361e87d74ba Mon Sep 17 00:00:00 2001 From: tooomm Date: Sat, 28 Nov 2020 09:14:15 +0100 Subject: [PATCH 044/126] Update CONTRIBUTING and more travis removing (#4179) --- .appveyor.yml | 2 -- .dockerignore | 1 - .github/CONTRIBUTING.md | 25 ++++++++++++++++--------- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 2b9672882..8a89b2644 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -4,14 +4,12 @@ version: build {build} # More details here: https://www.appveyor.com/docs/appveyor-yml and https://www.appveyor.com/docs/how-to/filtering-commits skip_commits: files: - - .ci/travis-* - .github/ - .tx/ - webclient/ - .clang-format - .*ignore - .codacy.yml - - .travis.yml - '**/*.md' - Dockerfile - LICENSE diff --git a/.dockerignore b/.dockerignore index 075acd689..2abeb2727 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,7 +1,6 @@ .git/ build/ .github/ -.travis/ .tx/ cockatrice/ doc/ diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 2b36452f6..29173257c 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -389,12 +389,15 @@ https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ). ### Publishing A New Release ### -GitHub Actions will upload binaries (Linux & macOS) when any release is published -on GitHub.
-AppVeyor is configured to upload binaries (Windows) to GitHub Releases whenever a -tag is pushed.
-You can create a tag when publishing a (pre-)release, but you can also push one -manually and use it in the release. +We use [GitHub Releases](https://github.com/Cockatrice/Cockatrice/releases) to publish +new stable versions and betas: +- GitHub Actions will upload binaries (**Linux & macOS**) when any release is published +on GitHub. +- AppVeyor will upload binaries (**Windows**) whenever a +git "tag" is pushed to the repository. + +To trigger both processes correctly you can publish a tagged (pre-)release right on GitHub, +or create a tag manually first and use it for the (pre-)release creation afterwards. To create a tag, simply do the following: ```bash @@ -413,12 +416,16 @@ You should define the variables as such: With *MAJ.MIN.PATCH* being the NEXT release version! ``` +>**Important:** You have to use the naming pattern `-beta` for the first beta release of one +version, followed by `-beta.2`, `-beta.3` and counting... +>This is crucial as Appveyor listens only for correct tags matching our defined pattern. + This will cause a tagged release to be established on the GitHub repository, -which will then lead to the upload of binaries by Appveyor. +which will then lead to the upload of binaries by AppVeyor. If you use a SemVer tag including "beta", the release that will be created at GitHub -by the CI will be marked as a "Pre-release". +by AppVeyor will be marked as a "Pre-release" automatically. The target of the `.../latest` URL will not be changed in that case - -it always points to the latest stable release and not pre-releases. +it always points to the latest stable release and not pre-releases/betas. If you accidentally push a tag incorrectly (the tag is outdated, you didn't pull in the latest branch accidentally, you named the tag wrong, etc.) you can From 56a51c783485e6e93e139d78ce8ea9f64377c67a Mon Sep 17 00:00:00 2001 From: Joel Bethke Date: Sun, 29 Nov 2020 01:33:13 -0600 Subject: [PATCH 045/126] ui: Fix Qt depreaction warnings (#4195) --- cockatrice/src/abstractcarditem.cpp | 4 ++-- cockatrice/src/abstractcounter.cpp | 2 +- cockatrice/src/chatview/chatview.cpp | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cockatrice/src/abstractcarditem.cpp b/cockatrice/src/abstractcarditem.cpp index 0f04fd6f9..5325409ef 100644 --- a/cockatrice/src/abstractcarditem.cpp +++ b/cockatrice/src/abstractcarditem.cpp @@ -287,14 +287,14 @@ void AbstractCardItem::mousePressEvent(QGraphicsSceneMouseEvent *event) } if (event->button() == Qt::LeftButton) setCursor(Qt::ClosedHandCursor); - else if (event->button() == Qt::MidButton) + else if (event->button() == Qt::MiddleButton) emit showCardInfoPopup(event->screenPos(), name); event->accept(); } void AbstractCardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { - if (event->button() == Qt::MidButton) + if (event->button() == Qt::MiddleButton) emit deleteCardInfoPopup(name); // This function ensures the parent function doesn't mess around with our selection. diff --git a/cockatrice/src/abstractcounter.cpp b/cockatrice/src/abstractcounter.cpp index 683de8dca..17057c969 100644 --- a/cockatrice/src/abstractcounter.cpp +++ b/cockatrice/src/abstractcounter.cpp @@ -127,7 +127,7 @@ void AbstractCounter::setValue(int _value) void AbstractCounter::mousePressEvent(QGraphicsSceneMouseEvent *event) { if (isUnderMouse() && player->getLocalOrJudge()) { - if (event->button() == Qt::MidButton || (QApplication::keyboardModifiers() & Qt::ShiftModifier)) { + if (event->button() == Qt::MiddleButton || (QApplication::keyboardModifiers() & Qt::ShiftModifier)) { if (menu) menu->exec(event->screenPos()); event->accept(); diff --git a/cockatrice/src/chatview/chatview.cpp b/cockatrice/src/chatview/chatview.cpp index 9d28c66dc..3dc88061d 100644 --- a/cockatrice/src/chatview/chatview.cpp +++ b/cockatrice/src/chatview/chatview.cpp @@ -529,12 +529,12 @@ void ChatView::mousePressEvent(QMouseEvent *event) { switch (hoveredItemType) { case HoveredCard: { - if ((event->button() == Qt::MidButton) || (event->button() == Qt::LeftButton)) + if ((event->button() == Qt::MiddleButton) || (event->button() == Qt::LeftButton)) emit showCardInfoPopup(event->globalPos(), hoveredContent); break; } case HoveredUser: { - if (event->button() != Qt::MidButton) { + if (event->button() != Qt::MiddleButton) { const int delimiterIndex = hoveredContent.indexOf("_"); const QString userName = hoveredContent.mid(delimiterIndex + 1); switch (event->button()) { @@ -564,7 +564,7 @@ void ChatView::mousePressEvent(QMouseEvent *event) void ChatView::mouseReleaseEvent(QMouseEvent *event) { - if ((event->button() == Qt::MidButton) || (event->button() == Qt::LeftButton)) + if ((event->button() == Qt::MiddleButton) || (event->button() == Qt::LeftButton)) emit deleteCardInfoPopup(QString("_")); QTextBrowser::mouseReleaseEvent(event); From 38606bdb87028bd81e56eb88719d172652deac95 Mon Sep 17 00:00:00 2001 From: Derek Chiang Date: Sun, 29 Nov 2020 18:11:35 -0800 Subject: [PATCH 046/126] Display a system tray notification when a player joins your game (#4194) * Display a system tray notification when a player joins your game * Display game ID in join message --- cockatrice/src/tab_game.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cockatrice/src/tab_game.cpp b/cockatrice/src/tab_game.cpp index c96602406..cbcc3ac12 100644 --- a/cockatrice/src/tab_game.cpp +++ b/cockatrice/src/tab_game.cpp @@ -53,6 +53,7 @@ #include "replay_timeline_widget.h" #include "settingscache.h" #include "tab_supervisor.h" +#include "window_main.h" #include "zoneviewwidget.h" #include "zoneviewzone.h" @@ -1192,6 +1193,11 @@ void TabGame::eventJoin(const Event_Join &event, int /*eventPlayerId*/, const Ga } else { Player *newPlayer = addPlayer(playerId, playerInfo.user_info()); messageLog->logJoin(newPlayer); + if (trayIcon) { + QString gameId(QString::number(gameInfo.game_id())); + trayIcon->showMessage(tr("A player has joined game #%1").arg(gameId), + tr("%1 has joined the game").arg(newPlayer->getName())); + } } playerListWidget->addPlayer(playerInfo); emitUserEvent(); From 8845a23d5d6c5f3a2eba14b12585931f90153102 Mon Sep 17 00:00:00 2001 From: Joel Bethke Date: Mon, 30 Nov 2020 22:54:50 -0600 Subject: [PATCH 047/126] CI: Fix up Windows builds and add GitHub Actions for Windows (#4193) * ci: Add vcpkg submodule * cmake: Fix NSIS not detecting platform arch * cmake: Add QTDIR(64|32) This change adds a new var for QTDIR(32|64) which makes it easier to specify a specific directory for Qt, rather than having to edit the prefix path directly, which can get pretty messy. Functionally, this shouldn't affect any builds that are already finding Qt as part of path, and should not affect Linux/macOS. * cmake: Bump min cmake version * ci: Add GitHub Actions for Windows --- .github/workflows/windows-builds.yml | 214 +++++++++++++++++++++++++++ .gitmodules | 3 + CMakeLists.txt | 38 +++-- vcpkg | 1 + vcpkg.txt | 4 + 5 files changed, 248 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/windows-builds.yml create mode 100644 .gitmodules create mode 160000 vcpkg create mode 100644 vcpkg.txt diff --git a/.github/workflows/windows-builds.yml b/.github/workflows/windows-builds.yml new file mode 100644 index 000000000..9b394cf2b --- /dev/null +++ b/.github/workflows/windows-builds.yml @@ -0,0 +1,214 @@ +name: 'Build on Windows' + +on: + push: + branches: + - master + paths-ignore: + - '**.md' + tags: + - '*' + pull_request: + branches: + - master + paths-ignore: + - '**.md' + +jobs: + win64: + name: 'Windows 64-bit' + runs-on: [windows-latest] + env: + QT_VERSION: '5.12.9' + CMAKE_GENERATOR: "Visual Studio 16 2019" + steps: + - name: 'Add msbuild to PATH' + uses: microsoft/setup-msbuild@v1.0.2 + - name: 'Checkout' + uses: actions/checkout@v2 + with: + submodules: 'recursive' + - name: 'Get Cockatrice git info' + shell: bash + working-directory: ${{ github.workspace }} + run: | + git fetch --prune --unshallow + echo "GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD)" >> $GITHUB_ENV + echo "GIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV + echo "GIT_TAG=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV + - name: 'Restore Qt 64-bit from cache' + id: cache-qt32 + uses: actions/cache@v2 + with: + path: | + ${{ runner.workspace }}/Qt64 + key: ${{ runner.os }}-QtCache-64bit + - name: 'Install 64-bit Qt' + uses: jurplel/install-qt-action@v2 + with: + cached: ${{ steps.cache-qt.outputs.cache-hit }} + version: '${{ env.QT_VERSION }}' + arch: 'win64_msvc2017_64' + dir: ${{ runner.workspace }}/Qt64 + - name: 'Restore or setup vcpkg' + uses: lukka/run-vcpkg@v6 + with: + vcpkgArguments: '@${{ github.workspace }}/vcpkg.txt' + vcpkgDirectory: '${{ github.workspace }}/vcpkg' + appendedCacheKey: ${{ hashFiles('**/vcpkg.txt') }} + vcpkgTriplet: x64-windows + - name: 'Configure Cockatrice 64-bit' + working-directory: ${{ github.workspace }} + run: | + New-Item build64 -type directory -force + cd build64 + cmake .. -G "${{ env.CMAKE_GENERATOR }}" -A "x64" -DQTDIR="${{ runner.workspace }}\Qt64\Qt\5.12.9\msvc2017_64" -DCMAKE_BUILD_TYPE="Release" -DWITH_SERVER=1 -DTEST=t + - name: 'Build Cockatrice 64-bit' + working-directory: ${{ github.workspace }} + run: msbuild /m /p:Configuration=Release .\build64\Cockatrice.sln + - name: 'Build Cockatrice Installer Package 64-bit' + working-directory: ${{ github.workspace }} + run: | + cd build64 + msbuild /m /p:Configuration=Release PACKAGE.vcxproj + cp *.exe ../Cockatrice-${{ env.GIT_TAG }}-${{ env.GIT_HASH }}-64bit-installer.exe + - name: 'Run Tests' + working-directory: ${{ github.workspace }}/build64 + run: ctest -T Test -C Release + - name: 'Publish' + if: success() + uses: actions/upload-artifact@v2 + with: + name: 'Cockatrice-${{ env.GIT_TAG }}-64bit' + path: './*.exe' + win32: + name: 'Windows 32-bit' + runs-on: [windows-latest] + env: + QT_VERSION: '5.12.9' + CMAKE_GENERATOR: "Visual Studio 16 2019" + steps: + - name: 'Add msbuild to PATH' + uses: microsoft/setup-msbuild@v1.0.2 + - name: 'Checkout' + uses: actions/checkout@v2 + with: + submodules: 'recursive' + - name: 'Get Cockatrice git info' + shell: bash + working-directory: ${{ github.workspace }} + run: | + git fetch --prune --unshallow + echo "GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD)" >> $GITHUB_ENV + echo "GIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV + echo "GIT_TAG=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV + - name: 'Restore Qt from cache' + id: cache-qt32 + uses: actions/cache@v2 + with: + path: | + ${{ runner.workspace }}/Qt32 + key: ${{ runner.os }}-QtCache-32bit + - name: 'Install 32-bit Qt' + uses: jurplel/install-qt-action@v2 + with: + cached: ${{ steps.cache-qt.outputs.cache-hit }} + version: '${{ env.QT_VERSION }}' + arch: 'win32_msvc2017' + dir: ${{ runner.workspace }}/Qt32 + - name: 'Restore or setup vcpkg' + uses: lukka/run-vcpkg@v6 + with: + vcpkgArguments: '@${{ github.workspace }}/vcpkg.txt' + vcpkgDirectory: '${{ github.workspace }}/vcpkg' + appendedCacheKey: ${{ hashFiles('**/vcpkg.txt') }} + vcpkgTriplet: x86-windows + - name: 'Configure Cockatrice 32-bit' + working-directory: ${{ github.workspace }} + run: | + New-Item build32 -type directory -force + cd build32 + cmake .. -G "${{ env.CMAKE_GENERATOR }}" -A "Win32" -DQTDIR="${{ runner.workspace }}\Qt32\Qt\5.12.9\msvc2017" -DCMAKE_BUILD_TYPE="Release" -DWITH_SERVER=1 -DTEST=1 + - name: 'Build Cockatrice 32-bit' + working-directory: ${{ github.workspace }} + run: msbuild /m /p:Configuration=Release .\build32\Cockatrice.sln + - name: 'Build Cockatrice Installer Package 32-bit' + working-directory: ${{ github.workspace }} + run: | + cd build32 + msbuild /m /p:Configuration=Release PACKAGE.vcxproj + cp *.exe ../Cockatrice-${{ env.GIT_TAG }}-${{ env.GIT_HASH }}-32bit-installer.exe + - name: 'Run Tests' + working-directory: ${{ github.workspace }}/build32 + run: ctest -T Test -C Release + - name: 'Publish' + if: success() + uses: actions/upload-artifact@v2 + with: + name: 'Cockatrice-${{ env.GIT_TAG }}-32bit' + path: './*.exe' + make-release: + name: 'Create and upload release' + runs-on: [ubuntu-latest] + if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') + needs: [win32,win64] + steps: + - name: 'Checkout' + uses: actions/checkout@v2 + with: + submodules: 'recursive' + - name: 'Fetch git tags and generate file name' + shell: bash + run: | + git fetch --prune --unshallow + echo "GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD)" >> $GITHUB_ENV + echo "GIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV + echo "GIT_TAG=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV + - name: 'Checking if beta' + if: contains(env.GIT_TAG, 'beta') + shell: bash + run: | + echo 'IS_BETA=true' >> $GITHUB_ENV + - name: 'Checking if beta' + if: "!contains(env.GIT_TAG, 'beta')" + shell: bash + run: | + echo 'IS_BETA=false' >> $GITHUB_ENV + - name: 'Create Release' + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ env.GIT_TAG }} + release_name: Cockatrice ${{ env.GIT_TAG }} + draft: true + prerelease: ${{ env.IS_BETA }} + - name: 'Generate filenames' + shell: bash + run: | + FILE_NAME=Cockatrice-${{ env.GIT_TAG }}-${{ env.GIT_HASH }} + echo "FILE_NAME=${FILE_NAME}" >> $GITHUB_ENV + - name: 'Download artifacts' + uses: actions/download-artifact@v2 + with: + path: ./ + - name: 'Upload 32bit to release' + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./Cockatrice-${{ env.GIT_TAG }}-32bit/${{ env.FILE_NAME }}-32bit-installer.exe + asset_name: Cockatrice-${{ env.GIT_TAG }}-32bit-installer.exe + asset_content_type: application/octet-stream + - name: 'Upload 64bit to release' + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./Cockatrice-${{ env.GIT_TAG }}-64bit/${{ env.FILE_NAME }}-64bit-installer.exe + asset_name: Cockatrice-${{ env.GIT_TAG }}-64bit-installer.exe + asset_content_type: application/octet-stream + diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..a0a57f3d7 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "vcpkg"] + path = vcpkg + url = https://github.com/microsoft/vcpkg.git diff --git a/CMakeLists.txt b/CMakeLists.txt index f4a678d39..ded6b956c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,15 +6,7 @@ # like the installation path, compilation flags etc.. # Cmake 3.1 is required to enable C++11 support correctly -cmake_minimum_required(VERSION 3.1) - -if(POLICY CMP0064) - cmake_policy(SET CMP0064 NEW) -endif() - -if(POLICY CMP0071) - cmake_policy(SET CMP0071 NEW) -endif() +cmake_minimum_required(VERSION 3.10) # Default to "Release" build type # User-provided value for CMAKE_BUILD_TYPE must be checked before the PROJECT() call @@ -35,6 +27,17 @@ if(USE_CCACHE) endif() endif() +if(WIN32) + # Use vcpkg toolchain on Windows + set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake + CACHE STRING "Vcpkg toolchain file") + # Qt path set by user or env var + if (QTDIR OR DEFINED ENV{QTDIR} OR DEFINED ENV{QTDIR32} OR DEFINED ENV{QTDIR64}) + else() + set(QTDIR "" CACHE PATH "Path to Qt (e.g. C:/Qt/5.7/msvc2015_64)") + message(WARNING "QTDIR variable is missing. Please set this variable to specify path to Qt (e.g. C:/Qt/5.7/msvc2015_64)") + endif() +endif() # A project name is needed for CPack # Version can be overriden by git tags, see cmake/getversion.cmake PROJECT("Cockatrice" VERSION 2.7.6) @@ -139,8 +142,15 @@ ENDIF() FIND_PACKAGE(Threads REQUIRED) # Find Qt5 -OPTION(UPDATE_TRANSLATIONS "Update translations on compile" OFF) -MESSAGE(STATUS "UPDATE TRANSLATIONS: ${UPDATE_TRANSLATIONS}") +if(DEFINED QTDIR${_lib_suffix}) + list(APPEND CMAKE_PREFIX_PATH "${QTDIR${_lib_suffix}}") +elseif(DEFINED QTDIR) + list(APPEND CMAKE_PREFIX_PATH "${QTDIR}") +elseif(DEFINED ENV{QTDIR${_lib_suffix}}) + list(APPEND CMAKE_PREFIX_PATH "$ENV{QTDIR${_lib_suffix}}") +elseif(DEFINED ENV{QTDIR}) + list(APPEND CMAKE_PREFIX_PATH "$ENV{QTDIR}") +endif() FIND_PACKAGE(Qt5Core 5.5.0 REQUIRED) @@ -161,6 +171,10 @@ ELSE() MESSAGE(FATAL_ERROR "No Qt5 found!") ENDIF() +# Check for translation updates +OPTION(UPDATE_TRANSLATIONS "Update translations on compile" OFF) +MESSAGE(STATUS "UPDATE TRANSLATIONS: ${UPDATE_TRANSLATIONS}") + set(CMAKE_AUTOMOC TRUE) # Find other needed libraries @@ -215,7 +229,7 @@ if(UNIX) endif() elseif(WIN32) set(CPACK_GENERATOR NSIS ${CPACK_GENERATOR}) - if("${CMAKE_GENERATOR}" MATCHES "(Win64|IA64)") + if("${CMAKE_GENERATOR_PLATFORM}" MATCHES "(x64)") set(TRICE_IS_64_BIT 1) else() set(TRICE_IS_64_BIT 0) diff --git a/vcpkg b/vcpkg new file mode 160000 index 000000000..62fe6ffbb --- /dev/null +++ b/vcpkg @@ -0,0 +1 @@ +Subproject commit 62fe6ffbbbae9149fb8c48cde2a34b809e2a3008 diff --git a/vcpkg.txt b/vcpkg.txt new file mode 100644 index 000000000..359f420d6 --- /dev/null +++ b/vcpkg.txt @@ -0,0 +1,4 @@ +protobuf +liblzma +zlib +gtest \ No newline at end of file From 34e951298f9e088ad40a0177d922e5259cdcacf7 Mon Sep 17 00:00:00 2001 From: Zach H Date: Tue, 1 Dec 2020 11:30:22 -0500 Subject: [PATCH 048/126] Address a handful of warnings from #6095 (#4199) --- cockatrice/src/dlg_update.cpp | 16 ++++++++-------- cockatrice/src/dlg_update.h | 8 ++++---- cockatrice/src/playertarget.cpp | 13 +++++++------ cockatrice/src/remoteclient.cpp | 20 ++++++++++---------- cockatrice/src/remoteclient.h | 6 +++--- cockatrice/src/tab_deck_editor.cpp | 21 +++++++++++---------- cockatrice/src/userinfobox.cpp | 11 ++++++----- 7 files changed, 49 insertions(+), 46 deletions(-) diff --git a/cockatrice/src/dlg_update.cpp b/cockatrice/src/dlg_update.cpp index 86ba00a98..98d318adc 100644 --- a/cockatrice/src/dlg_update.cpp +++ b/cockatrice/src/dlg_update.cpp @@ -45,7 +45,7 @@ DlgUpdate::DlgUpdate(QWidget *parent) : QDialog(parent) connect(stopDownload, SIGNAL(clicked()), this, SLOT(cancelDownload())); connect(ok, SIGNAL(clicked()), this, SLOT(closeDialog())); - QVBoxLayout *parentLayout = new QVBoxLayout(this); + auto *parentLayout = new QVBoxLayout(this); parentLayout->addWidget(descriptionLabel); parentLayout->addWidget(statusLabel); parentLayout->addWidget(progress); @@ -54,8 +54,8 @@ DlgUpdate::DlgUpdate(QWidget *parent) : QDialog(parent) setLayout(parentLayout); setWindowTitle(tr("Check for Client Updates")); - setFixedHeight(sizeHint().height()); - setFixedWidth(sizeHint().width()); + setFixedHeight(this->sizeHint().height()); + setFixedWidth(this->sizeHint().width()); // Check for SSL (this probably isn't necessary) if (!QSslSocket::supportsSsl()) { @@ -144,7 +144,7 @@ void DlgUpdate::finishedUpdateCheck(bool needToUpdate, bool isCompatible, Releas return; } - publishDate = release->getPublishDate().toString(Qt::DefaultLocaleLongDate); + publishDate = release->getPublishDate().toString(QLocale().dateFormat(QLocale::LongFormat)); if (isCompatible) { int reply; reply = QMessageBox::question( @@ -194,19 +194,19 @@ void DlgUpdate::enableOkButton(bool enable) ok->setEnabled(enable); } -void DlgUpdate::setLabel(QString newText) +void DlgUpdate::setLabel(const QString &newText) { statusLabel->setText(newText); } -void DlgUpdate::updateCheckError(QString errorString) +void DlgUpdate::updateCheckError(const QString &errorString) { setLabel(tr("Error")); QMessageBox::critical(this, tr("Update Error"), tr("An error occurred while checking for updates:") + QString(" ") + errorString); } -void DlgUpdate::downloadError(QString errorString) +void DlgUpdate::downloadError(const QString &errorString) { setLabel(tr("Error")); enableUpdateButton(true); @@ -214,7 +214,7 @@ void DlgUpdate::downloadError(QString errorString) tr("An error occurred while downloading an update:") + QString(" ") + errorString); } -void DlgUpdate::downloadSuccessful(QUrl filepath) +void DlgUpdate::downloadSuccessful(const QUrl &filepath) { setLabel(tr("Installing...")); // Try to open the installer. If it opens, quit Cockatrice diff --git a/cockatrice/src/dlg_update.h b/cockatrice/src/dlg_update.h index 2c8f2bbb8..42cfc5f58 100644 --- a/cockatrice/src/dlg_update.h +++ b/cockatrice/src/dlg_update.h @@ -19,10 +19,10 @@ private slots: void gotoDownloadPage(); void downloadUpdate(); void cancelDownload(); - void updateCheckError(QString errorString); - void downloadSuccessful(QUrl filepath); + void updateCheckError(const QString &errorString); + void downloadSuccessful(const QUrl &filepath); void downloadProgressMade(qint64 bytesRead, qint64 totalBytes); - void downloadError(QString errorString); + void downloadError(const QString &errorString); void closeDialog(); private: @@ -31,7 +31,7 @@ private: void enableOkButton(bool enable); void addStopDownloadAndRemoveOthers(bool enable); void beginUpdateCheck(); - void setLabel(QString text); + void setLabel(const QString &text); QLabel *statusLabel, *descriptionLabel; QProgressBar *progress; QPushButton *manualDownload, *gotoDownload, *ok, *stopDownload; diff --git a/cockatrice/src/playertarget.cpp b/cockatrice/src/playertarget.cpp index fb4c9f3d6..194bfcda1 100644 --- a/cockatrice/src/playertarget.cpp +++ b/cockatrice/src/playertarget.cpp @@ -24,7 +24,7 @@ PlayerCounter::PlayerCounter(Player *_player, QRectF PlayerCounter::boundingRect() const { - return QRectF(0, 0, 50, 30); + return {0, 0, 50, 30}; } void PlayerCounter::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) @@ -56,13 +56,14 @@ void PlayerCounter::paint(QPainter *painter, const QStyleOptionGraphicsItem * /* } PlayerTarget::PlayerTarget(Player *_owner, QGraphicsItem *parentItem, QWidget *_game) - : ArrowTarget(_owner, parentItem), playerCounter(0), game(_game) + : ArrowTarget(_owner, parentItem), playerCounter(nullptr), game(_game) { setCacheMode(DeviceCoordinateCache); const std::string &bmp = _owner->getUserInfo()->avatar_bmp(); - if (!fullPixmap.loadFromData((const uchar *)bmp.data(), bmp.size())) + if (!fullPixmap.loadFromData((const uchar *)bmp.data(), static_cast(bmp.size()))) { fullPixmap = QPixmap(); + } } PlayerTarget::~PlayerTarget() @@ -74,7 +75,7 @@ PlayerTarget::~PlayerTarget() QRectF PlayerTarget::boundingRect() const { - return QRectF(0, 0, 160, 64); + return {0, 0, 160, 64}; } void PlayerTarget::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) @@ -153,7 +154,7 @@ void PlayerTarget::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*o AbstractCounter *PlayerTarget::addCounter(int _counterId, const QString &_name, int _value) { if (playerCounter) { - disconnect(playerCounter, 0, this, 0); + disconnect(playerCounter, nullptr, this, nullptr); playerCounter->delCounter(); } @@ -167,5 +168,5 @@ AbstractCounter *PlayerTarget::addCounter(int _counterId, const QString &_name, void PlayerTarget::counterDeleted() { - playerCounter = 0; + playerCounter = nullptr; } diff --git a/cockatrice/src/remoteclient.cpp b/cockatrice/src/remoteclient.cpp index d6fb7bd8f..47f227440 100644 --- a/cockatrice/src/remoteclient.cpp +++ b/cockatrice/src/remoteclient.cpp @@ -337,9 +337,9 @@ void RemoteClient::websocketMessageReceived(const QByteArray &message) void RemoteClient::sendCommandContainer(const CommandContainer &cont) { #if GOOGLE_PROTOBUF_VERSION > 3001000 - unsigned int size = cont.ByteSizeLong(); + auto size = static_cast(cont.ByteSizeLong()); #else - unsigned int size = cont.ByteSize(); + auto size = static_cast(cont.ByteSize()); #endif #ifdef QT_DEBUG qDebug() << "OUT" << size << QString::fromStdString(cont.ShortDebugString()); @@ -419,7 +419,7 @@ void RemoteClient::doActivateToServer(const QString &_token) token = _token.trimmed(); - connectToHost(lastHostname, static_cast(lastPort)); + connectToHost(lastHostname, lastPort); setStatus(StatusActivating); } @@ -502,7 +502,7 @@ void RemoteClient::disconnectFromServer() emit sigDisconnectFromServer(); } -QString RemoteClient::getSrvClientID(const QString _hostname) +QString RemoteClient::getSrvClientID(const QString &_hostname) { QString srvClientID = SettingsCache::instance().getClientID(); QHostInfo hostInfo = QHostInfo::fromName(_hostname); @@ -518,11 +518,11 @@ QString RemoteClient::getSrvClientID(const QString _hostname) return uniqueServerClientID; } -bool RemoteClient::newMissingFeatureFound(QString _serversMissingFeatures) +bool RemoteClient::newMissingFeatureFound(const QString &_serversMissingFeatures) { bool newMissingFeature = false; QStringList serversMissingFeaturesList = _serversMissingFeatures.split(","); - foreach (const QString &feature, serversMissingFeaturesList) { + for (const QString &feature : serversMissingFeaturesList) { if (!feature.isEmpty()) { if (!SettingsCache::instance().getKnownMissingFeatures().contains(feature)) return true; @@ -535,7 +535,7 @@ void RemoteClient::clearNewClientFeatures() { QString newKnownMissingFeatures; QStringList existingKnownMissingFeatures = SettingsCache::instance().getKnownMissingFeatures().split(","); - foreach (const QString &existingKnownFeature, existingKnownMissingFeatures) { + for (const QString &existingKnownFeature : existingKnownMissingFeatures) { if (!existingKnownFeature.isEmpty()) { if (!clientFeatures.contains(existingKnownFeature)) newKnownMissingFeatures.append("," + existingKnownFeature); @@ -566,7 +566,7 @@ void RemoteClient::doRequestForgotPasswordToServer(const QString &hostname, unsi lastHostname = hostname; lastPort = port; - connectToHost(lastHostname, static_cast(lastPort)); + connectToHost(lastHostname, lastPort); setStatus(StatusRequestingForgotPassword); } @@ -598,7 +598,7 @@ void RemoteClient::doSubmitForgotPasswordResetToServer(const QString &hostname, token = _token.trimmed(); password = _newpassword; - connectToHost(lastHostname, static_cast(lastPort)); + connectToHost(lastHostname, lastPort); setStatus(StatusSubmitForgotPasswordReset); } @@ -632,7 +632,7 @@ void RemoteClient::doSubmitForgotPasswordChallengeToServer(const QString &hostna lastPort = port; email = _email; - connectToHost(lastHostname, static_cast(lastPort)); + connectToHost(lastHostname, lastPort); setStatus(StatusSubmitForgotPasswordChallenge); } diff --git a/cockatrice/src/remoteclient.h b/cockatrice/src/remoteclient.h index 3639a0ab9..9ae7e6284 100644 --- a/cockatrice/src/remoteclient.h +++ b/cockatrice/src/remoteclient.h @@ -97,10 +97,10 @@ private: QTcpSocket *socket; QWebSocket *websocket; QString lastHostname; - int lastPort; + unsigned int lastPort; - QString getSrvClientID(QString _hostname); - bool newMissingFeatureFound(QString _serversMissingFeatures); + QString getSrvClientID(const QString &_hostname); + bool newMissingFeatureFound(const QString &_serversMissingFeatures); void clearNewClientFeatures(); void connectToHost(const QString &hostname, unsigned int port); diff --git a/cockatrice/src/tab_deck_editor.cpp b/cockatrice/src/tab_deck_editor.cpp index bcb465013..061412c07 100644 --- a/cockatrice/src/tab_deck_editor.cpp +++ b/cockatrice/src/tab_deck_editor.cpp @@ -138,9 +138,9 @@ void TabDeckEditor::createDeckDock() lowerLayout->addWidget(deckView, 1, 0, 1, 5); // Create widgets for both layouts to make splitter work correctly - QWidget *topWidget = new QWidget; + auto *topWidget = new QWidget; topWidget->setLayout(upperLayout); - QWidget *bottomWidget = new QWidget; + auto *bottomWidget = new QWidget; bottomWidget->setLayout(lowerLayout); auto *split = new QSplitter; @@ -163,7 +163,7 @@ void TabDeckEditor::createDeckDock() deckDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); deckDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable); - QWidget *deckDockContents = new QWidget(); + auto *deckDockContents = new QWidget(); deckDockContents->setObjectName("deckDockContents"); deckDockContents->setLayout(rightFrame); deckDock->setWidget(deckDockContents); @@ -187,7 +187,7 @@ void TabDeckEditor::createCardInfoDock() cardInfoDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); cardInfoDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable); - QWidget *cardInfoDockContents = new QWidget(); + auto *cardInfoDockContents = new QWidget(); cardInfoDockContents->setObjectName("cardInfoDockContents"); cardInfoDockContents->setLayout(cardInfoFrame); cardInfoDock->setWidget(cardInfoDockContents); @@ -249,7 +249,7 @@ void TabDeckEditor::createFiltersDock() filterDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable); - QWidget *filterDockContents = new QWidget(this); + auto *filterDockContents = new QWidget(this); filterDockContents->setObjectName("filterDockContents"); filterDockContents->setLayout(filterFrame); filterDock->setWidget(filterDockContents); @@ -475,7 +475,7 @@ void TabDeckEditor::databaseCustomMenu(QPoint point) relatedMenu->setDisabled(true); } else { for (const CardRelation *rel : relatedCards) { - QString relatedCardName = rel->getName(); + const QString &relatedCardName = rel->getName(); QAction *relatedCard = relatedMenu->addAction(relatedCardName); connect(relatedCard, &QAction::triggered, cardInfo, [this, relatedCardName] { cardInfo->setCard(relatedCardName); }); @@ -874,7 +874,7 @@ void TabDeckEditor::actSaveDeckToClipboardRaw() void TabDeckEditor::actPrintDeck() { - QPrintPreviewDialog *dlg = new QPrintPreviewDialog(this); + auto *dlg = new QPrintPreviewDialog(this); connect(dlg, SIGNAL(paintRequested(QPrinter *)), deckModel, SLOT(printDeckList(QPrinter *))); dlg->exec(); } @@ -932,8 +932,9 @@ void TabDeckEditor::actClearFilterAll() void TabDeckEditor::actClearFilterOne() { QModelIndexList selIndexes = filterView->selectionModel()->selectedIndexes(); - foreach (QModelIndex idx, selIndexes) + for (QModelIndex idx : selIndexes) { filterModel->removeRow(idx.row(), idx.parent()); + } } void TabDeckEditor::recursiveExpand(const QModelIndex &index) @@ -1248,7 +1249,7 @@ void TabDeckEditor::showSearchSyntaxHelp() .replace(QRegularExpression("^(##)(.*)", opts), "

\\2

") .replace(QRegularExpression("^(#)(.*)", opts), "

\\2

") .replace(QRegularExpression("^------*", opts), "
") - .replace(QRegularExpression("\\[([^\[]+)\\]\\(([^\\)]+)\\)", opts), "\\1"); + .replace(QRegularExpression(R"(\[([^[]+)\]\(([^\)]+)\))", opts), R"(\1)"); auto browser = new QTextBrowser; browser->setParent(this, Qt::Window | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowMinMaxButtonsHint | @@ -1261,6 +1262,6 @@ void TabDeckEditor::showSearchSyntaxHelp() browser->document()->setDefaultStyleSheet(sheet); browser->setHtml(text); - connect(browser, &QTextBrowser::anchorClicked, [=](QUrl link) { searchEdit->setText(link.fragment()); }); + connect(browser, &QTextBrowser::anchorClicked, [=](const QUrl &link) { searchEdit->setText(link.fragment()); }); browser->show(); } diff --git a/cockatrice/src/userinfobox.cpp b/cockatrice/src/userinfobox.cpp index 7a767475b..33b0ad61c 100644 --- a/cockatrice/src/userinfobox.cpp +++ b/cockatrice/src/userinfobox.cpp @@ -31,13 +31,13 @@ UserInfoBox::UserInfoBox(AbstractClient *_client, bool _editable, QWidget *paren avatarLabel.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); avatarLabel.setAlignment(Qt::AlignCenter); - QHBoxLayout *avatarLayout = new QHBoxLayout; + auto *avatarLayout = new QHBoxLayout; avatarLayout->setContentsMargins(0, 0, 0, 0); avatarLayout->addStretch(1); avatarLayout->addWidget(&avatarLabel, 3); avatarLayout->addStretch(1); - QGridLayout *mainLayout = new QGridLayout; + auto *mainLayout = new QGridLayout; mainLayout->addLayout(avatarLayout, 0, 0, 1, 3); mainLayout->addWidget(&nameLabel, 1, 0, 1, 3); mainLayout->addWidget(&realNameLabel1, 2, 0, 1, 1); @@ -53,7 +53,7 @@ UserInfoBox::UserInfoBox(AbstractClient *_client, bool _editable, QWidget *paren mainLayout->setColumnStretch(2, 10); if (editable) { - QHBoxLayout *buttonsLayout = new QHBoxLayout; + auto *buttonsLayout = new QHBoxLayout; buttonsLayout->addWidget(&editButton); buttonsLayout->addWidget(&passwordButton); buttonsLayout->addWidget(&avatarButton); @@ -85,10 +85,11 @@ void UserInfoBox::updateInfo(const ServerInfo_User &user) { const UserLevelFlags userLevel(user.user_level()); - const std::string bmp = user.avatar_bmp(); - if (!avatarPixmap.loadFromData((const uchar *)bmp.data(), bmp.size())) + const std::string &bmp = user.avatar_bmp(); + if (!avatarPixmap.loadFromData((const uchar *)bmp.data(), static_cast(bmp.size()))) { avatarPixmap = UserLevelPixmapGenerator::generatePixmap(64, userLevel, false, QString::fromStdString(user.privlevel())); + } avatarLabel.setPixmap(avatarPixmap.scaled(400, 200, Qt::KeepAspectRatio, Qt::SmoothTransformation)); nameLabel.setText(QString::fromStdString(user.name())); From 37053cc9090e1535ce8fd709a2611cc9560ef826 Mon Sep 17 00:00:00 2001 From: tooomm Date: Tue, 1 Dec 2020 22:38:50 +0100 Subject: [PATCH 049/126] Remove AppVeyor config (#4200) * readability * update ci badge for windows * Delete .appveyor.yml --- .appveyor.yml | 116 --------------------------- .github/workflows/windows-builds.yml | 36 ++++++++- README.md | 4 +- 3 files changed, 35 insertions(+), 121 deletions(-) delete mode 100644 .appveyor.yml diff --git a/.appveyor.yml b/.appveyor.yml deleted file mode 100644 index 8a89b2644..000000000 --- a/.appveyor.yml +++ /dev/null @@ -1,116 +0,0 @@ -version: build {build} - -# Skipping commits affecting specific files (GitHub only). -# More details here: https://www.appveyor.com/docs/appveyor-yml and https://www.appveyor.com/docs/how-to/filtering-commits -skip_commits: - files: - - .github/ - - .tx/ - - webclient/ - - .clang-format - - .*ignore - - .codacy.yml - - '**/*.md' - - Dockerfile - - LICENSE - -skip_branch_with_pr: true - -clone_depth: 15 - -image: Visual Studio 2017 - -cache: - - c:\Tools\vcpkg\installed - -environment: - matrix: - - target_arch: win64 - qt_ver: 5.12\msvc2017_64 - cmake_generator: Visual Studio 15 2017 Win64 - cmake_toolset: v141,host=x64 - vcpkg_arch: x64 - - - target_arch: win32 - qt_ver: 5.12\msvc2017 - cmake_generator: Visual Studio 15 2017 - cmake_toolset: v141 - vcpkg_arch: x86 - -install: - - cd C:\Tools\vcpkg - - git pull -q - - .\bootstrap-vcpkg.bat - - cd %APPVEYOR_BUILD_FOLDER% - - vcpkg remove --outdated --recurse - - vcpkg install openssl protobuf liblzma zlib gtest --triplet %vcpkg_arch%-windows - -services: - - mysql - -build_script: - - ps: | - New-Item -ItemType directory -Path $env:APPVEYOR_BUILD_FOLDER\build - Set-Location -Path $env:APPVEYOR_BUILD_FOLDER\build - $vcpkgbindir = "C:\Tools\vcpkg\installed\$env:vcpkg_arch-windows\bin" - $mysqldll = "c:\Program Files\MySQL\MySQL Server 5.7\lib\libmysql.dll" - cmake --version - cmake .. -G "$env:cmake_generator" -T "$env:cmake_toolset" "-DCMAKE_C_FLAGS=/MP" "-DCMAKE_CXX_FLAGS=/MP" "-DCMAKE_TOOLCHAIN_FILE=c:/tools/vcpkg/scripts/buildsystems/vcpkg.cmake" "-DCMAKE_PREFIX_PATH=c:/Qt/$env:qt_ver;$vcpkgbindir" "-DWITH_SERVER=1" "-DMYSQLCLIENT_LIBRARIES=$mysqldll" "-DTEST=1" - - msbuild PACKAGE.vcxproj /p:Configuration=Release /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" /m - - ps: | - $exe = dir -name *.exe - $new_name = $exe.Replace(".exe", "-${env:target_arch}.exe") - Push-AppveyorArtifact $exe -FileName $new_name - $cmake_name = $exe.Replace(".exe", "-${env:target_arch}.cmake.txt") - Push-AppveyorArtifact CMakeCache.txt -FileName $cmake_name - $json = New-Object PSObject - (New-Object PSObject | Add-Member -PassThru NoteProperty bin $new_name | Add-Member -PassThru NoteProperty cmake $cmake_name | Add-Member -PassThru NoteProperty commit $env:APPVEYOR_REPO_COMMIT) | ConvertTo-JSON | Out-File -FilePath "latest-$env:target_arch" -Encoding ASCII - Push-AppveyorArtifact "latest-$env:target_arch" - $version = $matches['content'] - -test_script: - - ps: | - $env:Path += ";c:/Qt/$env:qt_ver/bin" - ctest -T Test -C Release - $ctest_success = $? - $XSLInputElement = New-Object System.Xml.Xsl.XslCompiledTransform - $XSLInputElement.Load("https://raw.githubusercontent.com/rpavlik/jenkins-ctest-plugin/master/ctest-to-junit.xsl") - $XSLInputElement.Transform((Resolve-Path .\Testing\*\Test.xml), (Join-Path (Resolve-Path .) "ctest-to-junit-results.xml")) - $wc = New-Object 'System.Net.WebClient' - $wc.UploadFile("https://ci.appveyor.com/api/testresults/junit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\ctest-to-junit-results.xml)) - if (-not $ctest_success) { throw "Tests failed" } - - -# Builds for pull requests skip the deployment step altogether -deploy: -# Deploy configuration for "beta" releases - - provider: GitHub - auth_token: - secure: z8Xh1lSCYtvs0SUfhOK6AijCFk0Rgf5jAxu7QvBByR42NG1SxFHPOmyrOllkfy1u - tag: "$(APPVEYOR_REPO_TAG_NAME)" - release: "Cockatrice $(APPVEYOR_REPO_TAG_NAME)" - description: "Beta release of Cockatrice" - artifact: /.*\.exe/ - draft: false - prerelease: true - on: - APPVEYOR_REPO_TAG: true - APPVEYOR_REPO_TAG_NAME: /([0-9]|[1-9][0-9])(\.([0-9]|[1-9][0-9])){2}-beta(\.([2-9]|[1-9][0-9]))?$/ # regex to match semver naming convention for beta pre-releases - -# Deploy configuration for "stable" releases - - provider: GitHub - auth_token: - secure: z8Xh1lSCYtvs0SUfhOK6AijCFk0Rgf5jAxu7QvBByR42NG1SxFHPOmyrOllkfy1u - tag: "$(APPVEYOR_REPO_TAG_NAME)" - release: "Cockatrice $(APPVEYOR_REPO_TAG_NAME)" - artifact: /.*\.exe/ - draft: false - prerelease: false - on: - APPVEYOR_REPO_TAG: true - APPVEYOR_REPO_TAG_NAME: /([0-9]|[1-9][0-9])(\.([0-9]|[1-9][0-9])){2}$/ # regex to match semver naming convention for stable full releases - - -# Announcements of build image updates: https://www.appveyor.com/updates/ -# Official validator for ".appveyor.yml" config file: https://ci.appveyor.com/tools/validate-yaml -# AppVeyor config documentation: https://www.appveyor.com/docs/build-configuration/ diff --git a/.github/workflows/windows-builds.yml b/.github/workflows/windows-builds.yml index 9b394cf2b..9b405e86a 100644 --- a/.github/workflows/windows-builds.yml +++ b/.github/workflows/windows-builds.yml @@ -21,13 +21,16 @@ jobs: env: QT_VERSION: '5.12.9' CMAKE_GENERATOR: "Visual Studio 16 2019" + steps: - name: 'Add msbuild to PATH' uses: microsoft/setup-msbuild@v1.0.2 + - name: 'Checkout' uses: actions/checkout@v2 with: submodules: 'recursive' + - name: 'Get Cockatrice git info' shell: bash working-directory: ${{ github.workspace }} @@ -36,6 +39,7 @@ jobs: echo "GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD)" >> $GITHUB_ENV echo "GIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV echo "GIT_TAG=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV + - name: 'Restore Qt 64-bit from cache' id: cache-qt32 uses: actions/cache@v2 @@ -43,6 +47,7 @@ jobs: path: | ${{ runner.workspace }}/Qt64 key: ${{ runner.os }}-QtCache-64bit + - name: 'Install 64-bit Qt' uses: jurplel/install-qt-action@v2 with: @@ -50,6 +55,7 @@ jobs: version: '${{ env.QT_VERSION }}' arch: 'win64_msvc2017_64' dir: ${{ runner.workspace }}/Qt64 + - name: 'Restore or setup vcpkg' uses: lukka/run-vcpkg@v6 with: @@ -57,43 +63,52 @@ jobs: vcpkgDirectory: '${{ github.workspace }}/vcpkg' appendedCacheKey: ${{ hashFiles('**/vcpkg.txt') }} vcpkgTriplet: x64-windows + - name: 'Configure Cockatrice 64-bit' working-directory: ${{ github.workspace }} run: | New-Item build64 -type directory -force cd build64 cmake .. -G "${{ env.CMAKE_GENERATOR }}" -A "x64" -DQTDIR="${{ runner.workspace }}\Qt64\Qt\5.12.9\msvc2017_64" -DCMAKE_BUILD_TYPE="Release" -DWITH_SERVER=1 -DTEST=t + - name: 'Build Cockatrice 64-bit' working-directory: ${{ github.workspace }} run: msbuild /m /p:Configuration=Release .\build64\Cockatrice.sln + - name: 'Build Cockatrice Installer Package 64-bit' working-directory: ${{ github.workspace }} run: | cd build64 msbuild /m /p:Configuration=Release PACKAGE.vcxproj cp *.exe ../Cockatrice-${{ env.GIT_TAG }}-${{ env.GIT_HASH }}-64bit-installer.exe + - name: 'Run Tests' working-directory: ${{ github.workspace }}/build64 run: ctest -T Test -C Release + - name: 'Publish' if: success() uses: actions/upload-artifact@v2 with: name: 'Cockatrice-${{ env.GIT_TAG }}-64bit' path: './*.exe' + win32: name: 'Windows 32-bit' runs-on: [windows-latest] env: QT_VERSION: '5.12.9' CMAKE_GENERATOR: "Visual Studio 16 2019" + steps: - name: 'Add msbuild to PATH' uses: microsoft/setup-msbuild@v1.0.2 + - name: 'Checkout' uses: actions/checkout@v2 with: submodules: 'recursive' + - name: 'Get Cockatrice git info' shell: bash working-directory: ${{ github.workspace }} @@ -102,6 +117,7 @@ jobs: echo "GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD)" >> $GITHUB_ENV echo "GIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV echo "GIT_TAG=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV + - name: 'Restore Qt from cache' id: cache-qt32 uses: actions/cache@v2 @@ -109,6 +125,7 @@ jobs: path: | ${{ runner.workspace }}/Qt32 key: ${{ runner.os }}-QtCache-32bit + - name: 'Install 32-bit Qt' uses: jurplel/install-qt-action@v2 with: @@ -116,6 +133,7 @@ jobs: version: '${{ env.QT_VERSION }}' arch: 'win32_msvc2017' dir: ${{ runner.workspace }}/Qt32 + - name: 'Restore or setup vcpkg' uses: lukka/run-vcpkg@v6 with: @@ -123,57 +141,68 @@ jobs: vcpkgDirectory: '${{ github.workspace }}/vcpkg' appendedCacheKey: ${{ hashFiles('**/vcpkg.txt') }} vcpkgTriplet: x86-windows + - name: 'Configure Cockatrice 32-bit' working-directory: ${{ github.workspace }} run: | New-Item build32 -type directory -force cd build32 cmake .. -G "${{ env.CMAKE_GENERATOR }}" -A "Win32" -DQTDIR="${{ runner.workspace }}\Qt32\Qt\5.12.9\msvc2017" -DCMAKE_BUILD_TYPE="Release" -DWITH_SERVER=1 -DTEST=1 + - name: 'Build Cockatrice 32-bit' working-directory: ${{ github.workspace }} run: msbuild /m /p:Configuration=Release .\build32\Cockatrice.sln + - name: 'Build Cockatrice Installer Package 32-bit' working-directory: ${{ github.workspace }} run: | cd build32 msbuild /m /p:Configuration=Release PACKAGE.vcxproj cp *.exe ../Cockatrice-${{ env.GIT_TAG }}-${{ env.GIT_HASH }}-32bit-installer.exe + - name: 'Run Tests' working-directory: ${{ github.workspace }}/build32 run: ctest -T Test -C Release + - name: 'Publish' if: success() uses: actions/upload-artifact@v2 with: name: 'Cockatrice-${{ env.GIT_TAG }}-32bit' path: './*.exe' + make-release: name: 'Create and upload release' runs-on: [ubuntu-latest] if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') needs: [win32,win64] + steps: - name: 'Checkout' uses: actions/checkout@v2 with: submodules: 'recursive' - - name: 'Fetch git tags and generate file name' + + - name: 'Fetch git tags' shell: bash run: | git fetch --prune --unshallow echo "GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD)" >> $GITHUB_ENV echo "GIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV echo "GIT_TAG=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV + - name: 'Checking if beta' if: contains(env.GIT_TAG, 'beta') shell: bash run: | echo 'IS_BETA=true' >> $GITHUB_ENV + - name: 'Checking if beta' if: "!contains(env.GIT_TAG, 'beta')" shell: bash run: | echo 'IS_BETA=false' >> $GITHUB_ENV + - name: 'Create Release' id: create_release uses: actions/create-release@v1 @@ -184,15 +213,18 @@ jobs: release_name: Cockatrice ${{ env.GIT_TAG }} draft: true prerelease: ${{ env.IS_BETA }} + - name: 'Generate filenames' shell: bash run: | FILE_NAME=Cockatrice-${{ env.GIT_TAG }}-${{ env.GIT_HASH }} echo "FILE_NAME=${FILE_NAME}" >> $GITHUB_ENV + - name: 'Download artifacts' uses: actions/download-artifact@v2 with: path: ./ + - name: 'Upload 32bit to release' uses: actions/upload-release-asset@v1 env: @@ -202,6 +234,7 @@ jobs: asset_path: ./Cockatrice-${{ env.GIT_TAG }}-32bit/${{ env.FILE_NAME }}-32bit-installer.exe asset_name: Cockatrice-${{ env.GIT_TAG }}-32bit-installer.exe asset_content_type: application/octet-stream + - name: 'Upload 64bit to release' uses: actions/upload-release-asset@v1 env: @@ -211,4 +244,3 @@ jobs: asset_path: ./Cockatrice-${{ env.GIT_TAG }}-64bit/${{ env.FILE_NAME }}-64bit-installer.exe asset_name: Cockatrice-${{ env.GIT_TAG }}-64bit-installer.exe asset_content_type: application/octet-stream - diff --git a/README.md b/README.md index e0ac98259..e21067af1 100644 --- a/README.md +++ b/README.md @@ -79,9 +79,7 @@ Cockatrice uses Transifex for translations. You can help us bring Cockatrice and Check out our [Translator FAQ](https://github.com/Cockatrice/Cockatrice/wiki/Translation-FAQ) for more information about contributing!
-# Build [![Linux builds - master](https://github.com/Cockatrice/Cockatrice/workflows/Build%20on%20Linux%20(Docker)/badge.svg?branch=master)](https://github.com/Cockatrice/Cockatrice/actions?query=workflow%3A%22Build+on+Linux+%28Docker%29%22+branch%3Amaster) [![macOS builds - master](https://github.com/Cockatrice/Cockatrice/workflows/Build%20on%20macOS/badge.svg?branch=master)](https://github.com/Cockatrice/Cockatrice/actions?query=workflow%3A%22Build+on+macOS%22+branch%3Amaster) [![Windows builds - master](https://ci.appveyor.com/api/projects/status/oauxf5a0sj689rcg/branch/master?svg=true)](https://ci.appveyor.com/project/ZeldaZach/cockatrice-k884w) - - +# Build [![Linux builds - master](https://github.com/Cockatrice/Cockatrice/workflows/Build%20on%20Linux%20(Docker)/badge.svg?branch=master)](https://github.com/Cockatrice/Cockatrice/actions?query=workflow%3A%22Build+on+Linux+%28Docker%29%22+branch%3Amaster) [![macOS builds - master](https://github.com/Cockatrice/Cockatrice/workflows/Build%20on%20macOS/badge.svg?branch=master)](https://github.com/Cockatrice/Cockatrice/actions?query=workflow%3A%22Build+on+macOS%22+branch%3Amaster) [![Windows builds - master](https://github.com/Cockatrice/Cockatrice/workflows/Build%20on%20Windows/badge.svg?branch=master)](https://github.com/Cockatrice/Cockatrice/actions?query=workflow%3A%22Build+on+Windows%22+branch%3Amaster) **Detailed compiling instructions can be found on the Cockatrice wiki under [Compiling Cockatrice](https://github.com/Cockatrice/Cockatrice/wiki/Compiling-Cockatrice)** From 77be6a120c5a56b26e90f9c6603671aa871a23f2 Mon Sep 17 00:00:00 2001 From: tooomm Date: Tue, 1 Dec 2020 22:57:27 +0100 Subject: [PATCH 050/126] fix broken image (#4202) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e21067af1..4865a7c28 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ Cockatrice uses the [Google Developer Documentation Style Guide](https://develop - [reddit r/Cockatrice](https://reddit.com/r/cockatrice) -# Translations [![Cockatrice on Transifex](https://tx-assets.scdn5.secure.raxcdn.com/static/charts/images/tx-logo-micro.c5603f91c780.png)](https://www.transifex.com/projects/p/cockatrice/) +# Translations [![Transifex Project](https://img.shields.io/badge/translate-on%20transifex-brightgreen)](https://www.transifex.com/projects/p/cockatrice/) Cockatrice uses Transifex for translations. You can help us bring Cockatrice and Oracle to your language or just edit single wordings right from within your browser by visiting our [Transifex project page](https://www.transifex.com/projects/p/cockatrice/).
From 0ce813b8262da0f838b0a545c033e28af9c1e67e Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Sun, 6 Dec 2020 03:36:27 +0100 Subject: [PATCH 051/126] restore saved previous server (#4206) fix #3617 --- cockatrice/src/dlg_connect.cpp | 5 +++-- cockatrice/src/settings/serverssettings.cpp | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/cockatrice/src/dlg_connect.cpp b/cockatrice/src/dlg_connect.cpp index bf34c68f5..df290d645 100644 --- a/cockatrice/src/dlg_connect.cpp +++ b/cockatrice/src/dlg_connect.cpp @@ -193,12 +193,13 @@ void DlgConnect::rebuildComboBoxList(int failure) savedHostList = uci.getServerInfo(); bool autoConnectEnabled = static_cast(SettingsCache::instance().servers().getAutoConnect()); + QString previousHostName = SettingsCache::instance().servers().getPrevioushostName(); QString autoConnectSaveName = SettingsCache::instance().servers().getSaveName(); int index = 0; for (const auto &pair : savedHostList) { - auto tmp = pair.second; + const auto &tmp = pair.second; QString saveName = tmp.getSaveName(); if (saveName.size()) { previousHosts->addItem(saveName); @@ -207,7 +208,7 @@ void DlgConnect::rebuildComboBoxList(int failure) if (saveName.compare(autoConnectSaveName) == 0) { previousHosts->setCurrentIndex(index); } - } else if (saveName.compare("Rooster Ranges") == 0) { + } else if (saveName.compare(previousHostName) == 0) { previousHosts->setCurrentIndex(index); } diff --git a/cockatrice/src/settings/serverssettings.cpp b/cockatrice/src/settings/serverssettings.cpp index a4d831b32..6e9e4699e 100644 --- a/cockatrice/src/settings/serverssettings.cpp +++ b/cockatrice/src/settings/serverssettings.cpp @@ -50,7 +50,8 @@ QString ServersSettings::getSite(QString defaultSite) QString ServersSettings::getPrevioushostName() { - return getValue("previoushostName", "server").toString(); + QVariant value = getValue("previoushostName", "server"); + return value == QVariant() ? "Rooster Ranges" : value.toString(); } int ServersSettings::getPrevioushostindex(const QString &saveName) From dbf6cd745e77c99fff489ecbc9e34e258b54d475 Mon Sep 17 00:00:00 2001 From: Joel Bethke Date: Mon, 7 Dec 2020 10:50:32 -0600 Subject: [PATCH 052/126] cmake: Minor updates (#4204) --- CMakeLists.txt | 35 +++++++++++++++++++++++------------ cmake/getversion.cmake | 42 +++++------------------------------------- 2 files changed, 28 insertions(+), 49 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ded6b956c..65c43187d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,10 +38,16 @@ if(WIN32) message(WARNING "QTDIR variable is missing. Please set this variable to specify path to Qt (e.g. C:/Qt/5.7/msvc2015_64)") endif() endif() + # A project name is needed for CPack # Version can be overriden by git tags, see cmake/getversion.cmake PROJECT("Cockatrice" VERSION 2.7.6) +# Set release name if not provided via env/cmake var +if(NOT DEFINED GIT_TAG_RELEASENAME) + set(GIT_TAG_RELEASENAME "Blessed Sanctuary") +endif() + # Use c++11 for all targets set(CMAKE_CXX_STANDARD 11 CACHE STRING "C++ ISO Standard") set(CMAKE_CXX_STANDARD_REQUIRED True) @@ -52,7 +58,6 @@ set(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS true) # Search path for cmake modules SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) -# Retrieve git version hash include(getversion) # Create a header and a cpp file containing the version hash @@ -95,7 +100,7 @@ if(UNIX) endif() endif() elseif(WIN32) - set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/release) + set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/rundir/${CMAKE_BUILD_TYPE}) endif() # Treat warnings as errors (Debug builds only) @@ -103,10 +108,8 @@ option(WARNING_AS_ERROR "Treat warnings as errors in debug builds" ON) # Define proper compilation flags IF(MSVC) - # Visual Studio: - # Maximum optimization - # Disable warning C4251 - set(CMAKE_CXX_FLAGS_RELEASE "/Ox /MD /wd4251") + # Visual Studio: Maximum optimization, disable warning C4251 + set(CMAKE_CXX_FLAGS_RELEASE "/Ox /MD /wd4251 ") # Generate complete debugging information #set(CMAKE_CXX_FLAGS_DEBUG "/Zi") ELSEIF (CMAKE_COMPILER_IS_GNUCXX) @@ -142,14 +145,20 @@ ENDIF() FIND_PACKAGE(Threads REQUIRED) # Find Qt5 +if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(_lib_suffix 64) +else() + set(_lib_suffix 32) +endif() + if(DEFINED QTDIR${_lib_suffix}) - list(APPEND CMAKE_PREFIX_PATH "${QTDIR${_lib_suffix}}") + list(APPEND CMAKE_PREFIX_PATH "${QTDIR${_lib_suffix}}") elseif(DEFINED QTDIR) - list(APPEND CMAKE_PREFIX_PATH "${QTDIR}") + list(APPEND CMAKE_PREFIX_PATH "${QTDIR}") elseif(DEFINED ENV{QTDIR${_lib_suffix}}) - list(APPEND CMAKE_PREFIX_PATH "$ENV{QTDIR${_lib_suffix}}") + list(APPEND CMAKE_PREFIX_PATH "$ENV{QTDIR${_lib_suffix}}") elseif(DEFINED ENV{QTDIR}) - list(APPEND CMAKE_PREFIX_PATH "$ENV{QTDIR}") + list(APPEND CMAKE_PREFIX_PATH "$ENV{QTDIR}") endif() FIND_PACKAGE(Qt5Core 5.5.0 REQUIRED) @@ -180,7 +189,9 @@ set(CMAKE_AUTOMOC TRUE) # Find other needed libraries FIND_PACKAGE(Protobuf REQUIRED) IF(NOT EXISTS "${Protobuf_PROTOC_EXECUTABLE}") - MESSAGE(FATAL_ERROR "No protoc command found!") + MESSAGE(FATAL_ERROR "No protoc command found!") +ELSE() + MESSAGE(STATUS "Protoc version ${Protobuf_VERSION} found!") ENDIF() #Find OpenSSL @@ -272,7 +283,7 @@ if(WITH_ORACLE) endif() # Compile dbconverter (default on) -option(WITH_DBCONVERTER "build oracle" ON) +option(WITH_DBCONVERTER "build dbconverter" ON) if(WITH_DBCONVERTER) add_subdirectory(dbconverter) SET(CPACK_INSTALL_CMAKE_PROJECTS "Dbconverter;Dbconverter;ALL;/" ${CPACK_INSTALL_CMAKE_PROJECTS}) diff --git a/cmake/getversion.cmake b/cmake/getversion.cmake index bc8476d23..e01cf4fa7 100644 --- a/cmake/getversion.cmake +++ b/cmake/getversion.cmake @@ -38,23 +38,6 @@ function(get_commit_date) set(GIT_COMMIT_DATE "${GIT_COM_DATE}" PARENT_SCOPE) endfunction() -function(clean_release_name name) - # Release Cockatrice 2.7.3: Dawn of Hope · Cockatrice/Cockatrice · GitHub - # Remove prefix - STRING(REGEX REPLACE "Release Cockatrice [0-9\.]+: " "" name "${name}") - # Remove suffix - STRING(REPLACE " · Cockatrice/Cockatrice · GitHub" "" name "${name}") - # Remove all unwanted chars - STRING(REGEX REPLACE "[^A-Za-z0-9_ ]" "" name "${name}") - # Strip (trim) whitespaces - STRING(STRIP "${name}" name) - # Replace all spaces with underscores - STRING(REPLACE " " "_" name "${name}") - - MESSAGE(STATUS "Friendly tag name: ${name}") - set(GIT_TAG_RELEASENAME "${name}" PARENT_SCOPE) -endfunction() - function(get_tag_name commit) if(${commit} STREQUAL "unknown") return() @@ -163,23 +146,8 @@ function(get_tag_name commit) set(PROJECT_VERSION_LABEL ${GIT_TAG_LABEL} PARENT_SCOPE) elseif(${GIT_TAG_TYPE} STREQUAL "Release") set(PROJECT_VERSION_LABEL "" PARENT_SCOPE) - - # get version name from github - set(GIT_TAG_TEMP_FILE "${PROJECT_BINARY_DIR}/tag_informations.txt") - set(GIT_TAG_TEMP_URL "https://github.com/Cockatrice/Cockatrice/releases/tag/${GIT_TAG}") - message(STATUS "Fetching tag informations from ${GIT_TAG_TEMP_URL}") - file(REMOVE "${GIT_TAG_TEMP_FILE}") - file(DOWNLOAD "${GIT_TAG_TEMP_URL}" "${GIT_TAG_TEMP_FILE}" STATUS status LOG log INACTIVITY_TIMEOUT 30 TIMEOUT 300 SHOW_PROGRESS) - list(GET status 0 err) - list(GET status 1 msg) - if(err) - message(WARNING "Download failed with error ${msg}: ${log}") - return() - endif() - file(STRINGS "${GIT_TAG_TEMP_FILE}" GIT_TAG_RAW_RELEASENAME REGEX "Release Cockatrice .*" LIMIT_COUNT 1 ENCODING UTF-8) - - clean_release_name("${GIT_TAG_RAW_RELEASENAME}") - set(PROJECT_VERSION_RELEASENAME "${GIT_TAG_RELEASENAME}" PARENT_SCOPE) + # set release name from env var + set(PROJECT_VERSION_RELEASENAME "${GIT_TAG_RELEASENAME}" PARENT_SCOPE) endif() endfunction() @@ -188,9 +156,9 @@ endfunction() # fallback defaults set(GIT_COMMIT_ID "unknown") -set(GIT_COMMIT_DATE "unknown") -set(GIT_COMMIT_DATE_FRIENDLY "unknown") -set(PROJECT_VERSION_LABEL "custom(unknown)") +set(GIT_COMMIT_DATE "") +set(GIT_COMMIT_DATE_FRIENDLY "") +set(PROJECT_VERSION_LABEL "") set(PROJECT_VERSION_RELEASENAME "") find_package(Git) From f595a61d5022c9d5f2a144451e13573981652d3d Mon Sep 17 00:00:00 2001 From: ebbit1q Date: Wed, 9 Dec 2020 08:08:00 +0100 Subject: [PATCH 053/126] fix #4198 (#4209) --- oracle/src/oracleimporter.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/oracle/src/oracleimporter.cpp b/oracle/src/oracleimporter.cpp index 9b5ee9d4b..6a112be8f 100644 --- a/oracle/src/oracleimporter.cpp +++ b/oracle/src/oracleimporter.cpp @@ -160,11 +160,8 @@ CardInfoPtr OracleImporter::addCard(QString name, properties.insert("side", side); // upsideDown (flip cards) - bool upsideDown = false; QString layout = properties.value("layout").toString(); - if (layout == "flip") { - upsideDown = properties.value("side").toString() != "a"; - } + bool upsideDown = layout == "flip" && side == "back"; // insert the card and its properties QList reverseRelatedCards; From d5b36e8b8aeae44f9a85dab86eef0a9a614745a1 Mon Sep 17 00:00:00 2001 From: saadbruno Date: Mon, 28 Dec 2020 18:43:45 -0300 Subject: [PATCH 054/126] Disabled strict mode for MySQL on the docker-compose file (#4214) --- docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose.yml b/docker-compose.yml index 515aeb643..7cef0ffe0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,6 +3,7 @@ version: '3' services: mysql: image: mysql + command: --sql_mode= environment: - MYSQL_ROOT_PASSWORD=root-password - MYSQL_DATABASE=servatrice From 0457e65751c9192509b51063709eff3e63fd8e50 Mon Sep 17 00:00:00 2001 From: Jeremy Letto Date: Thu, 31 Dec 2020 16:08:15 -0600 Subject: [PATCH 055/126] Webatrice P.O.C. (#3854) * port webclient POC into react shell * Abstract websocket messaging behind redux store * refactor architecture * add rooms store * introduce application service layer and login form * display room messages * implement roomSay * improve Room view styling * display room games * improve gameList update logic * hide protected games * improve game update logic * move mapping to earlier lifecycle hook * add autoscroll to bottom * tabs to spaces, refresh guard * implement server joins/leaves * show users in room * add material-ui to build * refactor, add room joins/leaves to store and render * begin using Material UI components * fix spectatorsCount * remove unused package * improve Server and Room styling * fix scroll context * route on room join * refactor room path * add auth guard * refactor authGuard export * add missing files * clear store on disconnect, add logout button to Account view * fix disconnect handling * Safari fixes * organize current todos * improve login page and server status tracking * improve login page * introduce sorting arch, refine reducers, begin viewLogHistory * audit fix for handlebars * implement moderator log view * comply with code style rules * remove original POC from codebase * add missing semi * minor improvements, begin registration functionality * retry as ws when wss fails additionally, dont mutate the default options when connecting * retain user/pass in WebClient.options for login * take protocol off of options, make it a connect param that defaults to wss * cleanup server page styling * match wss logic with desktop client * add virtual scroll component, add context menu to UserDisplay * revert VirtualTable on messages * improve styling for Room view * add routing to Player view * increase tooltip delay * begin implementing Account view * disable app level contextMenu * implement buddy/ignore list management * fix gitignore Co-authored-by: Jay Letto Co-authored-by: skwerlman Co-authored-by: Jeremy Letto --- webclient/.gitignore | 23 + webclient/README.md | 70 + webclient/copy_shared_files.sh | 3 + webclient/imgs/cockatrice.png | Bin 3220 -> 0 bytes webclient/index.html | 433 - .../js/images/ui-icons_444444_256x240.png | Bin 7006 -> 0 bytes .../js/images/ui-icons_555555_256x240.png | Bin 7074 -> 0 bytes .../js/images/ui-icons_777620_256x240.png | Bin 4676 -> 0 bytes .../js/images/ui-icons_777777_256x240.png | Bin 7013 -> 0 bytes .../js/images/ui-icons_cc0000_256x240.png | Bin 4632 -> 0 bytes .../js/images/ui-icons_ffffff_256x240.png | Bin 6313 -> 0 bytes webclient/js/jquery-3.2.1.min.js | 4 - webclient/js/jquery-ui-1.12.1.min.css | 7 - webclient/js/jquery-ui-1.12.1.min.js | 13 - webclient/js/protobuf-6.7.0.min.js | 10 - webclient/package-lock.json | 14059 ++++++++++++++++ webclient/package.json | 58 + webclient/pb | 1 - webclient/public/favicon.ico | Bin 0 -> 22382 bytes webclient/public/index.html | 44 + webclient/public/logo192.png | Bin 0 -> 8581 bytes webclient/public/logo512.png | Bin 0 -> 22920 bytes webclient/public/manifest.json | 25 + webclient/public/pb/.gitignore | 4 + webclient/public/reset.css | 48 + webclient/public/robots.txt | 2 + webclient/src/AppShell/Account/Account.css | 49 + webclient/src/AppShell/Account/Account.tsx | 122 + .../Account/AddToBuddies/AddToBuddies.tsx | 18 + .../Account/AddToIgnore/AddToIgnore.tsx | 18 + webclient/src/AppShell/AppShell.css | 10 + webclient/src/AppShell/AppShell.tsx | 39 + webclient/src/AppShell/AppShellRoutes.tsx | 29 + webclient/src/AppShell/Decks/Decks.css | 0 webclient/src/AppShell/Decks/Decks.tsx | 19 + webclient/src/AppShell/Game/Game.css | 0 webclient/src/AppShell/Game/Game.tsx | 19 + webclient/src/AppShell/Header/Header.css | 77 + webclient/src/AppShell/Header/Header.tsx | 119 + webclient/src/AppShell/Header/logo.png | Bin 0 -> 25695 bytes .../AppShell/Logs/LogResults/LogResults.css | 3 + .../AppShell/Logs/LogResults/LogResults.tsx | 122 + webclient/src/AppShell/Logs/Logs.css | 14 + webclient/src/AppShell/Logs/Logs.tsx | 102 + .../AppShell/Logs/SearchForm/SearchForm.css | 35 + .../AppShell/Logs/SearchForm/SearchForm.tsx | 68 + webclient/src/AppShell/Player/Player.css | 0 webclient/src/AppShell/Player/Player.tsx | 19 + webclient/src/AppShell/Room/Games/Games.css | 30 + webclient/src/AppShell/Room/Games/Games.tsx | 144 + .../src/AppShell/Room/Messages/Messages.css | 17 + .../src/AppShell/Room/Messages/Messages.tsx | 31 + webclient/src/AppShell/Room/Room.css | 39 + webclient/src/AppShell/Room/Room.tsx | 103 + .../AppShell/Room/SayMessage/SayMessage.tsx | 18 + .../Server/ConnectForm/ConnectForm.css | 14 + .../Server/ConnectForm/ConnectForm.tsx | 49 + .../Server/RegisterForm/RegisterForm.css | 14 + .../Server/RegisterForm/RegisterForm.tsx | 58 + webclient/src/AppShell/Server/Rooms/Rooms.css | 26 + webclient/src/AppShell/Server/Rooms/Rooms.tsx | 61 + webclient/src/AppShell/Server/Server.css | 61 + webclient/src/AppShell/Server/Server.tsx | 162 + .../CheckboxField/CheckboxField.css | 0 .../CheckboxField/CheckboxField.tsx | 25 + .../components/InputAction/InputAction.css | 19 + .../components/InputAction/InputAction.tsx | 23 + .../components/InputField/InputField.tsx | 17 + .../ScrollToBottomOnChanges.tsx | 25 + .../components/SelectField/SelectField.css | 4 + .../components/SelectField/SelectField.tsx | 30 + .../ThreePaneLayout/ThreePaneLayout.css | 33 + .../ThreePaneLayout/ThreePaneLayout.tsx | 47 + .../components/UserDisplay/UserDisplay.css | 11 + .../components/UserDisplay/UserDisplay.tsx | 153 + .../components/VirtualList/VirtualList.css | 3 + .../components/VirtualList/VirtualList.tsx | 35 + .../src/AppShell/common/guards/AuthGuard.tsx | 26 + .../src/AppShell/common/guards/ModGuard.tsx | 27 + .../common/services/AuthenticationService.tsx | 23 + .../common/services/ModeratorService.tsx | 7 + .../AppShell/common/services/RoomsService.tsx | 11 + .../common/services/RouterService.tsx | 7 + .../common/services/SessionService.tsx | 19 + .../src/AppShell/common/services/index.ts | 4 + webclient/src/AppShell/common/types/index.tsx | 2 + .../src/AppShell/common/types/routes.tsx | 10 + webclient/src/AppShell/common/types/user.tsx | 3 + webclient/src/WebClient/ProtoFiles.tsx | 157 + webclient/src/WebClient/WebClient.tsx | 329 + .../src/WebClient/commands/RoomCommands.tsx | 27 + .../WebClient/commands/SessionCommands.tsx | 234 + webclient/src/WebClient/commands/index.tsx | 2 + .../WebClient/events/RoomEvents/JoinRoom.tsx | 7 + .../WebClient/events/RoomEvents/LeaveRoom.tsx | 7 + .../WebClient/events/RoomEvents/ListGames.tsx | 7 + .../WebClient/events/RoomEvents/RoomSay.tsx | 7 + .../src/WebClient/events/RoomEvents/index.tsx | 4 + .../events/SessionEvents/AddToList.tsx | 18 + .../events/SessionEvents/ConnectionClosed.tsx | 39 + .../events/SessionEvents/ListRooms.tsx | 16 + .../events/SessionEvents/NotifyUser.tsx | 6 + .../SessionEvents/PlayerPropertiesChanges.tsx | 6 + .../events/SessionEvents/RemoveFromList.tsx | 18 + .../SessionEvents/ServerIdentification.tsx | 19 + .../events/SessionEvents/ServerMessage.tsx | 6 + .../events/SessionEvents/ServerShutdown.tsx | 6 + .../events/SessionEvents/UserJoined.tsx | 6 + .../events/SessionEvents/UserLeft.tsx | 6 + .../events/SessionEvents/UserMessage.tsx | 6 + .../WebClient/events/SessionEvents/index.ts | 12 + .../WebClient/services/NormalizeService.tsx | 44 + .../src/WebClient/services/RoomService.tsx | 53 + .../src/WebClient/services/SessionService.tsx | 94 + webclient/src/WebClient/services/index.ts | 3 + webclient/src/WebClient/util/guid.util.tsx | 8 + webclient/src/WebClient/util/index.tsx | 2 + .../src/WebClient/util/sanitizeHtml.util.tsx | 51 + webclient/src/images/Images.tsx | 7 + webclient/src/images/countries/.gitignore | 6 + webclient/src/images/countries/_Countries.tsx | 501 + webclient/src/index.css | 57 + webclient/src/index.tsx | 7 + webclient/src/react-app-env.d.ts | 1 + webclient/src/store/common/SortUtil.tsx | 147 + webclient/src/store/common/index.ts | 1 + webclient/src/store/index.ts | 1 + webclient/src/store/rooms/index.ts | 6 + webclient/src/store/rooms/rooms.actions.tsx | 53 + webclient/src/store/rooms/rooms.dispatch.tsx | 48 + .../src/store/rooms/rooms.interfaces.tsx | 36 + webclient/src/store/rooms/rooms.reducer.tsx | 270 + webclient/src/store/rooms/rooms.selectors.tsx | 26 + webclient/src/store/rooms/rooms.types.tsx | 13 + webclient/src/store/rootReducer.tsx | 12 + webclient/src/store/server/index.ts | 6 + webclient/src/store/server/server.actions.tsx | 73 + .../src/store/server/server.dispatch.tsx | 68 + .../src/store/server/server.interfaces.tsx | 40 + webclient/src/store/server/server.reducer.tsx | 210 + .../src/store/server/server.selectors.tsx | 18 + webclient/src/store/server/server.types.tsx | 20 + webclient/src/store/store.tsx | 9 + webclient/src/types/game.tsx | 3 + webclient/src/types/index.ts | 5 + webclient/src/types/room.tsx | 21 + webclient/src/types/server.tsx | 36 + webclient/src/types/sort.tsx | 9 + webclient/src/types/user.tsx | 25 + webclient/style.css | 114 - webclient/tsconfig.json | 29 + webclient/webclient.js | 489 - 152 files changed, 19573 insertions(+), 1071 deletions(-) create mode 100644 webclient/.gitignore create mode 100644 webclient/README.md create mode 100755 webclient/copy_shared_files.sh delete mode 100644 webclient/imgs/cockatrice.png delete mode 100755 webclient/index.html delete mode 100644 webclient/js/images/ui-icons_444444_256x240.png delete mode 100644 webclient/js/images/ui-icons_555555_256x240.png delete mode 100644 webclient/js/images/ui-icons_777620_256x240.png delete mode 100644 webclient/js/images/ui-icons_777777_256x240.png delete mode 100644 webclient/js/images/ui-icons_cc0000_256x240.png delete mode 100644 webclient/js/images/ui-icons_ffffff_256x240.png delete mode 100644 webclient/js/jquery-3.2.1.min.js delete mode 100644 webclient/js/jquery-ui-1.12.1.min.css delete mode 100644 webclient/js/jquery-ui-1.12.1.min.js delete mode 100644 webclient/js/protobuf-6.7.0.min.js create mode 100644 webclient/package-lock.json create mode 100644 webclient/package.json delete mode 120000 webclient/pb create mode 100644 webclient/public/favicon.ico create mode 100644 webclient/public/index.html create mode 100644 webclient/public/logo192.png create mode 100644 webclient/public/logo512.png create mode 100644 webclient/public/manifest.json create mode 100644 webclient/public/pb/.gitignore create mode 100644 webclient/public/reset.css create mode 100644 webclient/public/robots.txt create mode 100644 webclient/src/AppShell/Account/Account.css create mode 100644 webclient/src/AppShell/Account/Account.tsx create mode 100644 webclient/src/AppShell/Account/AddToBuddies/AddToBuddies.tsx create mode 100644 webclient/src/AppShell/Account/AddToIgnore/AddToIgnore.tsx create mode 100644 webclient/src/AppShell/AppShell.css create mode 100644 webclient/src/AppShell/AppShell.tsx create mode 100644 webclient/src/AppShell/AppShellRoutes.tsx create mode 100644 webclient/src/AppShell/Decks/Decks.css create mode 100644 webclient/src/AppShell/Decks/Decks.tsx create mode 100644 webclient/src/AppShell/Game/Game.css create mode 100644 webclient/src/AppShell/Game/Game.tsx create mode 100644 webclient/src/AppShell/Header/Header.css create mode 100644 webclient/src/AppShell/Header/Header.tsx create mode 100644 webclient/src/AppShell/Header/logo.png create mode 100644 webclient/src/AppShell/Logs/LogResults/LogResults.css create mode 100644 webclient/src/AppShell/Logs/LogResults/LogResults.tsx create mode 100644 webclient/src/AppShell/Logs/Logs.css create mode 100644 webclient/src/AppShell/Logs/Logs.tsx create mode 100644 webclient/src/AppShell/Logs/SearchForm/SearchForm.css create mode 100644 webclient/src/AppShell/Logs/SearchForm/SearchForm.tsx create mode 100644 webclient/src/AppShell/Player/Player.css create mode 100644 webclient/src/AppShell/Player/Player.tsx create mode 100644 webclient/src/AppShell/Room/Games/Games.css create mode 100644 webclient/src/AppShell/Room/Games/Games.tsx create mode 100644 webclient/src/AppShell/Room/Messages/Messages.css create mode 100644 webclient/src/AppShell/Room/Messages/Messages.tsx create mode 100644 webclient/src/AppShell/Room/Room.css create mode 100644 webclient/src/AppShell/Room/Room.tsx create mode 100644 webclient/src/AppShell/Room/SayMessage/SayMessage.tsx create mode 100644 webclient/src/AppShell/Server/ConnectForm/ConnectForm.css create mode 100644 webclient/src/AppShell/Server/ConnectForm/ConnectForm.tsx create mode 100644 webclient/src/AppShell/Server/RegisterForm/RegisterForm.css create mode 100644 webclient/src/AppShell/Server/RegisterForm/RegisterForm.tsx create mode 100644 webclient/src/AppShell/Server/Rooms/Rooms.css create mode 100644 webclient/src/AppShell/Server/Rooms/Rooms.tsx create mode 100644 webclient/src/AppShell/Server/Server.css create mode 100644 webclient/src/AppShell/Server/Server.tsx create mode 100644 webclient/src/AppShell/common/components/CheckboxField/CheckboxField.css create mode 100644 webclient/src/AppShell/common/components/CheckboxField/CheckboxField.tsx create mode 100644 webclient/src/AppShell/common/components/InputAction/InputAction.css create mode 100644 webclient/src/AppShell/common/components/InputAction/InputAction.tsx create mode 100644 webclient/src/AppShell/common/components/InputField/InputField.tsx create mode 100644 webclient/src/AppShell/common/components/ScrollToBottomOnChanges/ScrollToBottomOnChanges.tsx create mode 100644 webclient/src/AppShell/common/components/SelectField/SelectField.css create mode 100644 webclient/src/AppShell/common/components/SelectField/SelectField.tsx create mode 100644 webclient/src/AppShell/common/components/ThreePaneLayout/ThreePaneLayout.css create mode 100644 webclient/src/AppShell/common/components/ThreePaneLayout/ThreePaneLayout.tsx create mode 100644 webclient/src/AppShell/common/components/UserDisplay/UserDisplay.css create mode 100644 webclient/src/AppShell/common/components/UserDisplay/UserDisplay.tsx create mode 100644 webclient/src/AppShell/common/components/VirtualList/VirtualList.css create mode 100644 webclient/src/AppShell/common/components/VirtualList/VirtualList.tsx create mode 100644 webclient/src/AppShell/common/guards/AuthGuard.tsx create mode 100644 webclient/src/AppShell/common/guards/ModGuard.tsx create mode 100644 webclient/src/AppShell/common/services/AuthenticationService.tsx create mode 100644 webclient/src/AppShell/common/services/ModeratorService.tsx create mode 100644 webclient/src/AppShell/common/services/RoomsService.tsx create mode 100644 webclient/src/AppShell/common/services/RouterService.tsx create mode 100644 webclient/src/AppShell/common/services/SessionService.tsx create mode 100644 webclient/src/AppShell/common/services/index.ts create mode 100644 webclient/src/AppShell/common/types/index.tsx create mode 100644 webclient/src/AppShell/common/types/routes.tsx create mode 100644 webclient/src/AppShell/common/types/user.tsx create mode 100644 webclient/src/WebClient/ProtoFiles.tsx create mode 100644 webclient/src/WebClient/WebClient.tsx create mode 100644 webclient/src/WebClient/commands/RoomCommands.tsx create mode 100644 webclient/src/WebClient/commands/SessionCommands.tsx create mode 100644 webclient/src/WebClient/commands/index.tsx create mode 100644 webclient/src/WebClient/events/RoomEvents/JoinRoom.tsx create mode 100644 webclient/src/WebClient/events/RoomEvents/LeaveRoom.tsx create mode 100644 webclient/src/WebClient/events/RoomEvents/ListGames.tsx create mode 100644 webclient/src/WebClient/events/RoomEvents/RoomSay.tsx create mode 100644 webclient/src/WebClient/events/RoomEvents/index.tsx create mode 100644 webclient/src/WebClient/events/SessionEvents/AddToList.tsx create mode 100644 webclient/src/WebClient/events/SessionEvents/ConnectionClosed.tsx create mode 100644 webclient/src/WebClient/events/SessionEvents/ListRooms.tsx create mode 100644 webclient/src/WebClient/events/SessionEvents/NotifyUser.tsx create mode 100644 webclient/src/WebClient/events/SessionEvents/PlayerPropertiesChanges.tsx create mode 100644 webclient/src/WebClient/events/SessionEvents/RemoveFromList.tsx create mode 100644 webclient/src/WebClient/events/SessionEvents/ServerIdentification.tsx create mode 100644 webclient/src/WebClient/events/SessionEvents/ServerMessage.tsx create mode 100644 webclient/src/WebClient/events/SessionEvents/ServerShutdown.tsx create mode 100644 webclient/src/WebClient/events/SessionEvents/UserJoined.tsx create mode 100644 webclient/src/WebClient/events/SessionEvents/UserLeft.tsx create mode 100644 webclient/src/WebClient/events/SessionEvents/UserMessage.tsx create mode 100644 webclient/src/WebClient/events/SessionEvents/index.ts create mode 100644 webclient/src/WebClient/services/NormalizeService.tsx create mode 100644 webclient/src/WebClient/services/RoomService.tsx create mode 100644 webclient/src/WebClient/services/SessionService.tsx create mode 100644 webclient/src/WebClient/services/index.ts create mode 100644 webclient/src/WebClient/util/guid.util.tsx create mode 100644 webclient/src/WebClient/util/index.tsx create mode 100644 webclient/src/WebClient/util/sanitizeHtml.util.tsx create mode 100644 webclient/src/images/Images.tsx create mode 100644 webclient/src/images/countries/.gitignore create mode 100644 webclient/src/images/countries/_Countries.tsx create mode 100644 webclient/src/index.css create mode 100644 webclient/src/index.tsx create mode 100644 webclient/src/react-app-env.d.ts create mode 100644 webclient/src/store/common/SortUtil.tsx create mode 100644 webclient/src/store/common/index.ts create mode 100644 webclient/src/store/index.ts create mode 100644 webclient/src/store/rooms/index.ts create mode 100644 webclient/src/store/rooms/rooms.actions.tsx create mode 100644 webclient/src/store/rooms/rooms.dispatch.tsx create mode 100644 webclient/src/store/rooms/rooms.interfaces.tsx create mode 100644 webclient/src/store/rooms/rooms.reducer.tsx create mode 100644 webclient/src/store/rooms/rooms.selectors.tsx create mode 100644 webclient/src/store/rooms/rooms.types.tsx create mode 100644 webclient/src/store/rootReducer.tsx create mode 100644 webclient/src/store/server/index.ts create mode 100644 webclient/src/store/server/server.actions.tsx create mode 100644 webclient/src/store/server/server.dispatch.tsx create mode 100644 webclient/src/store/server/server.interfaces.tsx create mode 100644 webclient/src/store/server/server.reducer.tsx create mode 100644 webclient/src/store/server/server.selectors.tsx create mode 100644 webclient/src/store/server/server.types.tsx create mode 100644 webclient/src/store/store.tsx create mode 100644 webclient/src/types/game.tsx create mode 100644 webclient/src/types/index.ts create mode 100644 webclient/src/types/room.tsx create mode 100644 webclient/src/types/server.tsx create mode 100644 webclient/src/types/sort.tsx create mode 100644 webclient/src/types/user.tsx delete mode 100755 webclient/style.css create mode 100644 webclient/tsconfig.json delete mode 100755 webclient/webclient.js diff --git a/webclient/.gitignore b/webclient/.gitignore new file mode 100644 index 000000000..4d29575de --- /dev/null +++ b/webclient/.gitignore @@ -0,0 +1,23 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/webclient/README.md b/webclient/README.md new file mode 100644 index 000000000..4d5a61944 --- /dev/null +++ b/webclient/README.md @@ -0,0 +1,70 @@ +This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). + +## Available Scripts + +In the project directory, you can run: + +### `npm start` + +Runs the app in the development mode.
+Open [http://localhost:3000](http://localhost:3000) to view it in the browser. + +The page will reload if you make edits.
+You will also see any lint errors in the console. + +### `npm test` + +Launches the test runner in the interactive watch mode.
+See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. + +### `npm run build` + +Builds the app for production to the `build` folder.
+It correctly bundles React in production mode and optimizes the build for the best performance. + +The build is minified and the filenames include the hashes.
+Your app is ready to be deployed! + +See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. + +### `npm run eject` + +**Note: this is a one-way operation. Once you `eject`, you can’t go back!** + +If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. + +Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. + +You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. + +## Learn More + +You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). + +To learn React, check out the [React documentation](https://reactjs.org/). + +## To-Do List + +1) RefreshGuard modal + - there is no browser support for displaying custom output to window.onbeforeunload + - we should also display a custom modal explaining why they shouldnt refresh or navigate from the site + - ideally, the custom popup can be synced with the alert, so when the alert is closed, the modal closes too + +2) Disable AutoScrollToBottom when the user has scrolled up + - when the user scrolls back to bottom, it should renable + - renable after a period of inactivity (3 minutes?) + +3) Figure out how to type components w/ RouteComponentProps + - Component> + +4) clear input onSubmit + +5) figure out how to reflect server status changes in the ui + +6) Account page + +7) Register/Forgot Passoword forms + +8) Message User + +9) Main Nav scheme diff --git a/webclient/copy_shared_files.sh b/webclient/copy_shared_files.sh new file mode 100755 index 000000000..82fcaf6c2 --- /dev/null +++ b/webclient/copy_shared_files.sh @@ -0,0 +1,3 @@ +#!/bin/bash +cp -a ../common/pb/. ./public/pb/ +cp -a ../cockatrice/resources/countries/. ./src/images/countries diff --git a/webclient/imgs/cockatrice.png b/webclient/imgs/cockatrice.png deleted file mode 100644 index 3816009b665a85ab6cab403b7266dabba235b8ac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3220 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 - - - Cockatrice web client - - - - - - -
-

Cockatrice web client

-
-
-Loading cockatrice web client... -
- - - - - - - - - - - - - - diff --git a/webclient/js/images/ui-icons_444444_256x240.png b/webclient/js/images/ui-icons_444444_256x240.png deleted file mode 100644 index c2daae1663d47f397a01fd4166bce9b6b9ac4183..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7006 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 W$6)X8PycD00qQC`%4LeS5&s7ix^mY5 diff --git a/webclient/js/images/ui-icons_777620_256x240.png b/webclient/js/images/ui-icons_777620_256x240.png deleted file mode 100644 index d2f58d25581c605a5538fa50408fe09c4a30740d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4676 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 diff --git a/webclient/js/images/ui-icons_777777_256x240.png b/webclient/js/images/ui-icons_777777_256x240.png deleted file mode 100644 index 1d532588b989c5fd03e7a63f7e829b017c35d3cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7013 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 ZrDX-T^mH1q5`RzvpbBd8WwK_0{|mNJD+vGq diff --git a/webclient/js/images/ui-icons_cc0000_256x240.png b/webclient/js/images/ui-icons_cc0000_256x240.png deleted file mode 100644 index 2825f2004d10b413a76317d47384e6139abcbe00..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4632 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 Hjt~C}Fe}Pt diff --git a/webclient/js/jquery-3.2.1.min.js b/webclient/js/jquery-3.2.1.min.js deleted file mode 100644 index 644d35e27..000000000 --- a/webclient/js/jquery-3.2.1.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/*! jQuery v3.2.1 | (c) JS Foundation and other contributors | jquery.org/license */ -!function(a,b){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){"use strict";var c=[],d=a.document,e=Object.getPrototypeOf,f=c.slice,g=c.concat,h=c.push,i=c.indexOf,j={},k=j.toString,l=j.hasOwnProperty,m=l.toString,n=m.call(Object),o={};function p(a,b){b=b||d;var c=b.createElement("script");c.text=a,b.head.appendChild(c).parentNode.removeChild(c)}var q="3.2.1",r=function(a,b){return new r.fn.init(a,b)},s=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,t=/^-ms-/,u=/-([a-z])/g,v=function(a,b){return b.toUpperCase()};r.fn=r.prototype={jquery:q,constructor:r,length:0,toArray:function(){return f.call(this)},get:function(a){return null==a?f.call(this):a<0?this[a+this.length]:this[a]},pushStack:function(a){var b=r.merge(this.constructor(),a);return b.prevObject=this,b},each:function(a){return r.each(this,a)},map:function(a){return this.pushStack(r.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(f.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(a<0?b:0);return this.pushStack(c>=0&&c0&&b-1 in a)}var x=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=function(a,b){for(var c=0,d=a.length;c+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(N),U=new RegExp("^"+L+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+N),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),aa=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:d<0?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ba=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ca=function(a,b){return b?"\0"===a?"\ufffd":a.slice(0,-1)+"\\"+a.charCodeAt(a.length-1).toString(16)+" ":"\\"+a},da=function(){m()},ea=ta(function(a){return a.disabled===!0&&("form"in a||"label"in a)},{dir:"parentNode",next:"legend"});try{G.apply(D=H.call(v.childNodes),v.childNodes),D[v.childNodes.length].nodeType}catch(fa){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s=b&&b.ownerDocument,w=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==w&&9!==w&&11!==w)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==w&&(l=Z.exec(a)))if(f=l[1]){if(9===w){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(s&&(j=s.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(l[2])return G.apply(d,b.getElementsByTagName(a)),d;if((f=l[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==w)s=b,r=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(ba,ca):b.setAttribute("id",k=u),o=g(a),h=o.length;while(h--)o[h]="#"+k+" "+sa(o[h]);r=o.join(","),s=$.test(a)&&qa(b.parentNode)||b}if(r)try{return G.apply(d,s.querySelectorAll(r)),d}catch(x){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(P,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("fieldset");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&a.sourceIndex-b.sourceIndex;if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return function(b){return"form"in b?b.parentNode&&b.disabled===!1?"label"in b?"label"in b.parentNode?b.parentNode.disabled===a:b.disabled===a:b.isDisabled===a||b.isDisabled!==!a&&ea(b)===a:b.disabled===a:"label"in b&&b.disabled===a}}function pa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function qa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return!!b&&"HTML"!==b.nodeName},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),v!==n&&(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(n.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){return a.getAttribute("id")===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}}):(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c,d,e,f=b.getElementById(a);if(f){if(c=f.getAttributeNode("id"),c&&c.value===a)return[f];e=b.getElementsByName(a),d=0;while(f=e[d++])if(c=f.getAttributeNode("id"),c&&c.value===a)return[f]}return[]}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){if("undefined"!=typeof b.getElementsByClassName&&p)return b.getElementsByClassName(a)},r=[],q=[],(c.qsa=Y.test(n.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){a.innerHTML="";var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+K+"*[*^$|!~]?="),2!==a.querySelectorAll(":enabled").length&&q.push(":enabled",":disabled"),o.appendChild(a).disabled=!0,2!==a.querySelectorAll(":disabled").length&&q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Y.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"*"),s.call(a,"[s!='']:x"),r.push("!=",N)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Y.test(o.compareDocumentPosition),t=b||Y.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?I(k,a)-I(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?I(k,a)-I(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?la(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(S,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.escape=function(a){return(a+"").replace(ba,ca)},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(_,aa),a[3]=(a[3]||a[4]||a[5]||"").replace(_,aa),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return V.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&T.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(_,aa).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:!b||(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(O," ")+" ").indexOf(c)>-1:"|="===b&&(e===c||e.slice(0,c.length+1)===c+"-"))}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(P,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(_,aa),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return U.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(_,aa).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:oa(!1),disabled:oa(!0),checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:pa(function(){return[0]}),last:pa(function(a,b){return[b-1]}),eq:pa(function(a,b,c){return[c<0?c+b:c]}),even:pa(function(a,b){for(var c=0;c=0;)a.push(d);return a}),gt:pa(function(a,b,c){for(var d=c<0?c+b:c;++d1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function va(a,b,c){for(var d=0,e=b.length;d-1&&(f[j]=!(g[j]=l))}}else r=wa(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ya(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ta(function(a){return a===b},h,!0),l=ta(function(a){return I(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];i1&&ua(m),i>1&&sa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(P,"$1"),c,i0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=E.call(i));u=wa(u)}G.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&ga.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=ya(b[c]),f[u]?d.push(f):e.push(f);f=A(a,za(e,d)),f.selector=a}return f},i=ga.select=function(a,b,c,e){var f,i,j,k,l,m="function"==typeof a&&a,n=!e&&g(a=m.selector||a);if(c=c||[],1===n.length){if(i=n[0]=n[0].slice(0),i.length>2&&"ID"===(j=i[0]).type&&9===b.nodeType&&p&&d.relative[i[1].type]){if(b=(d.find.ID(j.matches[0].replace(_,aa),b)||[])[0],!b)return c;m&&(b=b.parentNode),a=a.slice(i.shift().value.length)}f=V.needsContext.test(a)?0:i.length;while(f--){if(j=i[f],d.relative[k=j.type])break;if((l=d.find[k])&&(e=l(j.matches[0].replace(_,aa),$.test(i[0].type)&&qa(b.parentNode)||b))){if(i.splice(f,1),a=e.length&&sa(i),!a)return G.apply(c,e),c;break}}}return(m||h(a,n))(e,b,!p,c,!b||$.test(a)&&qa(b.parentNode)||b),c},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("fieldset"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){if(!c)return a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){if(!c&&"input"===a.nodeName.toLowerCase())return a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(J,function(a,b,c){var d;if(!c)return a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);r.find=x,r.expr=x.selectors,r.expr[":"]=r.expr.pseudos,r.uniqueSort=r.unique=x.uniqueSort,r.text=x.getText,r.isXMLDoc=x.isXML,r.contains=x.contains,r.escapeSelector=x.escape;var y=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&r(a).is(c))break;d.push(a)}return d},z=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},A=r.expr.match.needsContext;function B(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()}var C=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i,D=/^.[^:#\[\.,]*$/;function E(a,b,c){return r.isFunction(b)?r.grep(a,function(a,d){return!!b.call(a,d,a)!==c}):b.nodeType?r.grep(a,function(a){return a===b!==c}):"string"!=typeof b?r.grep(a,function(a){return i.call(b,a)>-1!==c}):D.test(b)?r.filter(b,a,c):(b=r.filter(b,a),r.grep(a,function(a){return i.call(b,a)>-1!==c&&1===a.nodeType}))}r.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?r.find.matchesSelector(d,a)?[d]:[]:r.find.matches(a,r.grep(b,function(a){return 1===a.nodeType}))},r.fn.extend({find:function(a){var b,c,d=this.length,e=this;if("string"!=typeof a)return this.pushStack(r(a).filter(function(){for(b=0;b1?r.uniqueSort(c):c},filter:function(a){return this.pushStack(E(this,a||[],!1))},not:function(a){return this.pushStack(E(this,a||[],!0))},is:function(a){return!!E(this,"string"==typeof a&&A.test(a)?r(a):a||[],!1).length}});var F,G=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,H=r.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||F,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:G.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof r?b[0]:b,r.merge(this,r.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),C.test(e[1])&&r.isPlainObject(b))for(e in b)r.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&(this[0]=f,this.length=1),this}return a.nodeType?(this[0]=a,this.length=1,this):r.isFunction(a)?void 0!==c.ready?c.ready(a):a(r):r.makeArray(a,this)};H.prototype=r.fn,F=r(d);var I=/^(?:parents|prev(?:Until|All))/,J={children:!0,contents:!0,next:!0,prev:!0};r.fn.extend({has:function(a){var b=r(a,this),c=b.length;return this.filter(function(){for(var a=0;a-1:1===c.nodeType&&r.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?r.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?i.call(r(a),this[0]):i.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(r.uniqueSort(r.merge(this.get(),r(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function K(a,b){while((a=a[b])&&1!==a.nodeType);return a}r.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return y(a,"parentNode")},parentsUntil:function(a,b,c){return y(a,"parentNode",c)},next:function(a){return K(a,"nextSibling")},prev:function(a){return K(a,"previousSibling")},nextAll:function(a){return y(a,"nextSibling")},prevAll:function(a){return y(a,"previousSibling")},nextUntil:function(a,b,c){return y(a,"nextSibling",c)},prevUntil:function(a,b,c){return y(a,"previousSibling",c)},siblings:function(a){return z((a.parentNode||{}).firstChild,a)},children:function(a){return z(a.firstChild)},contents:function(a){return B(a,"iframe")?a.contentDocument:(B(a,"template")&&(a=a.content||a),r.merge([],a.childNodes))}},function(a,b){r.fn[a]=function(c,d){var e=r.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=r.filter(d,e)),this.length>1&&(J[a]||r.uniqueSort(e),I.test(a)&&e.reverse()),this.pushStack(e)}});var L=/[^\x20\t\r\n\f]+/g;function M(a){var b={};return r.each(a.match(L)||[],function(a,c){b[c]=!0}),b}r.Callbacks=function(a){a="string"==typeof a?M(a):r.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=e||a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),c<=h&&h--}),this},has:function(a){return a?r.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||b||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j};function N(a){return a}function O(a){throw a}function P(a,b,c,d){var e;try{a&&r.isFunction(e=a.promise)?e.call(a).done(b).fail(c):a&&r.isFunction(e=a.then)?e.call(a,b,c):b.apply(void 0,[a].slice(d))}catch(a){c.apply(void 0,[a])}}r.extend({Deferred:function(b){var c=[["notify","progress",r.Callbacks("memory"),r.Callbacks("memory"),2],["resolve","done",r.Callbacks("once memory"),r.Callbacks("once memory"),0,"resolved"],["reject","fail",r.Callbacks("once memory"),r.Callbacks("once memory"),1,"rejected"]],d="pending",e={state:function(){return d},always:function(){return f.done(arguments).fail(arguments),this},"catch":function(a){return e.then(null,a)},pipe:function(){var a=arguments;return r.Deferred(function(b){r.each(c,function(c,d){var e=r.isFunction(a[d[4]])&&a[d[4]];f[d[1]](function(){var a=e&&e.apply(this,arguments);a&&r.isFunction(a.promise)?a.promise().progress(b.notify).done(b.resolve).fail(b.reject):b[d[0]+"With"](this,e?[a]:arguments)})}),a=null}).promise()},then:function(b,d,e){var f=0;function g(b,c,d,e){return function(){var h=this,i=arguments,j=function(){var a,j;if(!(b=f&&(d!==O&&(h=void 0,i=[a]),c.rejectWith(h,i))}};b?k():(r.Deferred.getStackHook&&(k.stackTrace=r.Deferred.getStackHook()),a.setTimeout(k))}}return r.Deferred(function(a){c[0][3].add(g(0,a,r.isFunction(e)?e:N,a.notifyWith)),c[1][3].add(g(0,a,r.isFunction(b)?b:N)),c[2][3].add(g(0,a,r.isFunction(d)?d:O))}).promise()},promise:function(a){return null!=a?r.extend(a,e):e}},f={};return r.each(c,function(a,b){var g=b[2],h=b[5];e[b[1]]=g.add,h&&g.add(function(){d=h},c[3-a][2].disable,c[0][2].lock),g.add(b[3].fire),f[b[0]]=function(){return f[b[0]+"With"](this===f?void 0:this,arguments),this},f[b[0]+"With"]=g.fireWith}),e.promise(f),b&&b.call(f,f),f},when:function(a){var b=arguments.length,c=b,d=Array(c),e=f.call(arguments),g=r.Deferred(),h=function(a){return function(c){d[a]=this,e[a]=arguments.length>1?f.call(arguments):c,--b||g.resolveWith(d,e)}};if(b<=1&&(P(a,g.done(h(c)).resolve,g.reject,!b),"pending"===g.state()||r.isFunction(e[c]&&e[c].then)))return g.then();while(c--)P(e[c],h(c),g.reject);return g.promise()}});var Q=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;r.Deferred.exceptionHook=function(b,c){a.console&&a.console.warn&&b&&Q.test(b.name)&&a.console.warn("jQuery.Deferred exception: "+b.message,b.stack,c)},r.readyException=function(b){a.setTimeout(function(){throw b})};var R=r.Deferred();r.fn.ready=function(a){return R.then(a)["catch"](function(a){r.readyException(a)}),this},r.extend({isReady:!1,readyWait:1,ready:function(a){(a===!0?--r.readyWait:r.isReady)||(r.isReady=!0,a!==!0&&--r.readyWait>0||R.resolveWith(d,[r]))}}),r.ready.then=R.then;function S(){d.removeEventListener("DOMContentLoaded",S), -a.removeEventListener("load",S),r.ready()}"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(r.ready):(d.addEventListener("DOMContentLoaded",S),a.addEventListener("load",S));var T=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===r.type(c)){e=!0;for(h in c)T(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,r.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(r(a),c)})),b))for(;h1,null,!0)},removeData:function(a){return this.each(function(){X.remove(this,a)})}}),r.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=W.get(a,b),c&&(!d||Array.isArray(c)?d=W.access(a,b,r.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=r.queue(a,b),d=c.length,e=c.shift(),f=r._queueHooks(a,b),g=function(){r.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return W.get(a,c)||W.access(a,c,{empty:r.Callbacks("once memory").add(function(){W.remove(a,[b+"queue",c])})})}}),r.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length\x20\t\r\n\f]+)/i,la=/^$|\/(?:java|ecma)script/i,ma={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ma.optgroup=ma.option,ma.tbody=ma.tfoot=ma.colgroup=ma.caption=ma.thead,ma.th=ma.td;function na(a,b){var c;return c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[],void 0===b||b&&B(a,b)?r.merge([a],c):c}function oa(a,b){for(var c=0,d=a.length;c-1)e&&e.push(f);else if(j=r.contains(f.ownerDocument,f),g=na(l.appendChild(f),"script"),j&&oa(g),c){k=0;while(f=g[k++])la.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),o.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="",o.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var ra=d.documentElement,sa=/^key/,ta=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,ua=/^([^.]*)(?:\.(.+)|)/;function va(){return!0}function wa(){return!1}function xa(){try{return d.activeElement}catch(a){}}function ya(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)ya(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=wa;else if(!e)return a;return 1===f&&(g=e,e=function(a){return r().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=r.guid++)),a.each(function(){r.event.add(this,b,e,d,c)})}r.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=W.get(a);if(q){c.handler&&(f=c,c=f.handler,e=f.selector),e&&r.find.matchesSelector(ra,e),c.guid||(c.guid=r.guid++),(i=q.events)||(i=q.events={}),(g=q.handle)||(g=q.handle=function(b){return"undefined"!=typeof r&&r.event.triggered!==b.type?r.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(L)||[""],j=b.length;while(j--)h=ua.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n&&(l=r.event.special[n]||{},n=(e?l.delegateType:l.bindType)||n,l=r.event.special[n]||{},k=r.extend({type:n,origType:p,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&r.expr.match.needsContext.test(e),namespace:o.join(".")},f),(m=i[n])||(m=i[n]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,o,g)!==!1||a.addEventListener&&a.addEventListener(n,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),r.event.global[n]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=W.hasData(a)&&W.get(a);if(q&&(i=q.events)){b=(b||"").match(L)||[""],j=b.length;while(j--)if(h=ua.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n){l=r.event.special[n]||{},n=(d?l.delegateType:l.bindType)||n,m=i[n]||[],h=h[2]&&new RegExp("(^|\\.)"+o.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&p!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,o,q.handle)!==!1||r.removeEvent(a,n,q.handle),delete i[n])}else for(n in i)r.event.remove(a,n+b[j],c,d,!0);r.isEmptyObject(i)&&W.remove(a,"handle events")}},dispatch:function(a){var b=r.event.fix(a),c,d,e,f,g,h,i=new Array(arguments.length),j=(W.get(this,"events")||{})[b.type]||[],k=r.event.special[b.type]||{};for(i[0]=b,c=1;c=1))for(;j!==this;j=j.parentNode||this)if(1===j.nodeType&&("click"!==a.type||j.disabled!==!0)){for(f=[],g={},c=0;c-1:r.find(e,this,null,[j]).length),g[e]&&f.push(d);f.length&&h.push({elem:j,handlers:f})}return j=this,i\x20\t\r\n\f]*)[^>]*)\/>/gi,Aa=/\s*$/g;function Ea(a,b){return B(a,"table")&&B(11!==b.nodeType?b:b.firstChild,"tr")?r(">tbody",a)[0]||a:a}function Fa(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function Ga(a){var b=Ca.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Ha(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(W.hasData(a)&&(f=W.access(a),g=W.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;c1&&"string"==typeof q&&!o.checkClone&&Ba.test(q))return a.each(function(e){var f=a.eq(e);s&&(b[0]=q.call(this,e,f.html())),Ja(f,b,c,d)});if(m&&(e=qa(b,a[0].ownerDocument,!1,a,d),f=e.firstChild,1===e.childNodes.length&&(e=f),f||d)){for(h=r.map(na(e,"script"),Fa),i=h.length;l")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=r.contains(a.ownerDocument,a);if(!(o.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||r.isXMLDoc(a)))for(g=na(h),f=na(a),d=0,e=f.length;d0&&oa(g,!i&&na(a,"script")),h},cleanData:function(a){for(var b,c,d,e=r.event.special,f=0;void 0!==(c=a[f]);f++)if(U(c)){if(b=c[W.expando]){if(b.events)for(d in b.events)e[d]?r.event.remove(c,d):r.removeEvent(c,d,b.handle);c[W.expando]=void 0}c[X.expando]&&(c[X.expando]=void 0)}}}),r.fn.extend({detach:function(a){return Ka(this,a,!0)},remove:function(a){return Ka(this,a)},text:function(a){return T(this,function(a){return void 0===a?r.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return Ja(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ea(this,a);b.appendChild(a)}})},prepend:function(){return Ja(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ea(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ja(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ja(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(r.cleanData(na(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null!=a&&a,b=null==b?a:b,this.map(function(){return r.clone(this,a,b)})},html:function(a){return T(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!Aa.test(a)&&!ma[(ka.exec(a)||["",""])[1].toLowerCase()]){a=r.htmlPrefilter(a);try{for(;c1)}});function _a(a,b,c,d,e){return new _a.prototype.init(a,b,c,d,e)}r.Tween=_a,_a.prototype={constructor:_a,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||r.easing._default,this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(r.cssNumber[c]?"":"px")},cur:function(){var a=_a.propHooks[this.prop];return a&&a.get?a.get(this):_a.propHooks._default.get(this)},run:function(a){var b,c=_a.propHooks[this.prop];return this.options.duration?this.pos=b=r.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):_a.propHooks._default.set(this),this}},_a.prototype.init.prototype=_a.prototype,_a.propHooks={_default:{get:function(a){var b;return 1!==a.elem.nodeType||null!=a.elem[a.prop]&&null==a.elem.style[a.prop]?a.elem[a.prop]:(b=r.css(a.elem,a.prop,""),b&&"auto"!==b?b:0)},set:function(a){r.fx.step[a.prop]?r.fx.step[a.prop](a):1!==a.elem.nodeType||null==a.elem.style[r.cssProps[a.prop]]&&!r.cssHooks[a.prop]?a.elem[a.prop]=a.now:r.style(a.elem,a.prop,a.now+a.unit)}}},_a.propHooks.scrollTop=_a.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},r.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},_default:"swing"},r.fx=_a.prototype.init,r.fx.step={};var ab,bb,cb=/^(?:toggle|show|hide)$/,db=/queueHooks$/;function eb(){bb&&(d.hidden===!1&&a.requestAnimationFrame?a.requestAnimationFrame(eb):a.setTimeout(eb,r.fx.interval),r.fx.tick())}function fb(){return a.setTimeout(function(){ab=void 0}),ab=r.now()}function gb(a,b){var c,d=0,e={height:a};for(b=b?1:0;d<4;d+=2-b)c=ca[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function hb(a,b,c){for(var d,e=(kb.tweeners[b]||[]).concat(kb.tweeners["*"]),f=0,g=e.length;f1)},removeAttr:function(a){return this.each(function(){r.removeAttr(this,a)})}}),r.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return"undefined"==typeof a.getAttribute?r.prop(a,b,c):(1===f&&r.isXMLDoc(a)||(e=r.attrHooks[b.toLowerCase()]||(r.expr.match.bool.test(b)?lb:void 0)),void 0!==c?null===c?void r.removeAttr(a,b):e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:(a.setAttribute(b,c+""),c):e&&"get"in e&&null!==(d=e.get(a,b))?d:(d=r.find.attr(a,b), -null==d?void 0:d))},attrHooks:{type:{set:function(a,b){if(!o.radioValue&&"radio"===b&&B(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}},removeAttr:function(a,b){var c,d=0,e=b&&b.match(L);if(e&&1===a.nodeType)while(c=e[d++])a.removeAttribute(c)}}),lb={set:function(a,b,c){return b===!1?r.removeAttr(a,c):a.setAttribute(c,c),c}},r.each(r.expr.match.bool.source.match(/\w+/g),function(a,b){var c=mb[b]||r.find.attr;mb[b]=function(a,b,d){var e,f,g=b.toLowerCase();return d||(f=mb[g],mb[g]=e,e=null!=c(a,b,d)?g:null,mb[g]=f),e}});var nb=/^(?:input|select|textarea|button)$/i,ob=/^(?:a|area)$/i;r.fn.extend({prop:function(a,b){return T(this,r.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[r.propFix[a]||a]})}}),r.extend({prop:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return 1===f&&r.isXMLDoc(a)||(b=r.propFix[b]||b,e=r.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=r.find.attr(a,"tabindex");return b?parseInt(b,10):nb.test(a.nodeName)||ob.test(a.nodeName)&&a.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),o.optSelected||(r.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null},set:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}}),r.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){r.propFix[this.toLowerCase()]=this});function pb(a){var b=a.match(L)||[];return b.join(" ")}function qb(a){return a.getAttribute&&a.getAttribute("class")||""}r.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).addClass(a.call(this,b,qb(this)))});if("string"==typeof a&&a){b=a.match(L)||[];while(c=this[i++])if(e=qb(c),d=1===c.nodeType&&" "+pb(e)+" "){g=0;while(f=b[g++])d.indexOf(" "+f+" ")<0&&(d+=f+" ");h=pb(d),e!==h&&c.setAttribute("class",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).removeClass(a.call(this,b,qb(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof a&&a){b=a.match(L)||[];while(c=this[i++])if(e=qb(c),d=1===c.nodeType&&" "+pb(e)+" "){g=0;while(f=b[g++])while(d.indexOf(" "+f+" ")>-1)d=d.replace(" "+f+" "," ");h=pb(d),e!==h&&c.setAttribute("class",h)}}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):r.isFunction(a)?this.each(function(c){r(this).toggleClass(a.call(this,c,qb(this),b),b)}):this.each(function(){var b,d,e,f;if("string"===c){d=0,e=r(this),f=a.match(L)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else void 0!==a&&"boolean"!==c||(b=qb(this),b&&W.set(this,"__className__",b),this.setAttribute&&this.setAttribute("class",b||a===!1?"":W.get(this,"__className__")||""))})},hasClass:function(a){var b,c,d=0;b=" "+a+" ";while(c=this[d++])if(1===c.nodeType&&(" "+pb(qb(c))+" ").indexOf(b)>-1)return!0;return!1}});var rb=/\r/g;r.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=r.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,r(this).val()):a,null==e?e="":"number"==typeof e?e+="":Array.isArray(e)&&(e=r.map(e,function(a){return null==a?"":a+""})),b=r.valHooks[this.type]||r.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=r.valHooks[e.type]||r.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(rb,""):null==c?"":c)}}}),r.extend({valHooks:{option:{get:function(a){var b=r.find.attr(a,"value");return null!=b?b:pb(r.text(a))}},select:{get:function(a){var b,c,d,e=a.options,f=a.selectedIndex,g="select-one"===a.type,h=g?null:[],i=g?f+1:e.length;for(d=f<0?i:g?f:0;d-1)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),r.each(["radio","checkbox"],function(){r.valHooks[this]={set:function(a,b){if(Array.isArray(b))return a.checked=r.inArray(r(a).val(),b)>-1}},o.checkOn||(r.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var sb=/^(?:focusinfocus|focusoutblur)$/;r.extend(r.event,{trigger:function(b,c,e,f){var g,h,i,j,k,m,n,o=[e||d],p=l.call(b,"type")?b.type:b,q=l.call(b,"namespace")?b.namespace.split("."):[];if(h=i=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!sb.test(p+r.event.triggered)&&(p.indexOf(".")>-1&&(q=p.split("."),p=q.shift(),q.sort()),k=p.indexOf(":")<0&&"on"+p,b=b[r.expando]?b:new r.Event(p,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=q.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:r.makeArray(c,[b]),n=r.event.special[p]||{},f||!n.trigger||n.trigger.apply(e,c)!==!1)){if(!f&&!n.noBubble&&!r.isWindow(e)){for(j=n.delegateType||p,sb.test(j+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),i=h;i===(e.ownerDocument||d)&&o.push(i.defaultView||i.parentWindow||a)}g=0;while((h=o[g++])&&!b.isPropagationStopped())b.type=g>1?j:n.bindType||p,m=(W.get(h,"events")||{})[b.type]&&W.get(h,"handle"),m&&m.apply(h,c),m=k&&h[k],m&&m.apply&&U(h)&&(b.result=m.apply(h,c),b.result===!1&&b.preventDefault());return b.type=p,f||b.isDefaultPrevented()||n._default&&n._default.apply(o.pop(),c)!==!1||!U(e)||k&&r.isFunction(e[p])&&!r.isWindow(e)&&(i=e[k],i&&(e[k]=null),r.event.triggered=p,e[p](),r.event.triggered=void 0,i&&(e[k]=i)),b.result}},simulate:function(a,b,c){var d=r.extend(new r.Event,c,{type:a,isSimulated:!0});r.event.trigger(d,null,b)}}),r.fn.extend({trigger:function(a,b){return this.each(function(){r.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];if(c)return r.event.trigger(a,b,c,!0)}}),r.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(a,b){r.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),r.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),o.focusin="onfocusin"in a,o.focusin||r.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){r.event.simulate(b,a.target,r.event.fix(a))};r.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=W.access(d,b);e||d.addEventListener(a,c,!0),W.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=W.access(d,b)-1;e?W.access(d,b,e):(d.removeEventListener(a,c,!0),W.remove(d,b))}}});var tb=a.location,ub=r.now(),vb=/\?/;r.parseXML=function(b){var c;if(!b||"string"!=typeof b)return null;try{c=(new a.DOMParser).parseFromString(b,"text/xml")}catch(d){c=void 0}return c&&!c.getElementsByTagName("parsererror").length||r.error("Invalid XML: "+b),c};var wb=/\[\]$/,xb=/\r?\n/g,yb=/^(?:submit|button|image|reset|file)$/i,zb=/^(?:input|select|textarea|keygen)/i;function Ab(a,b,c,d){var e;if(Array.isArray(b))r.each(b,function(b,e){c||wb.test(a)?d(a,e):Ab(a+"["+("object"==typeof e&&null!=e?b:"")+"]",e,c,d)});else if(c||"object"!==r.type(b))d(a,b);else for(e in b)Ab(a+"["+e+"]",b[e],c,d)}r.param=function(a,b){var c,d=[],e=function(a,b){var c=r.isFunction(b)?b():b;d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(null==c?"":c)};if(Array.isArray(a)||a.jquery&&!r.isPlainObject(a))r.each(a,function(){e(this.name,this.value)});else for(c in a)Ab(c,a[c],b,e);return d.join("&")},r.fn.extend({serialize:function(){return r.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=r.prop(this,"elements");return a?r.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!r(this).is(":disabled")&&zb.test(this.nodeName)&&!yb.test(a)&&(this.checked||!ja.test(a))}).map(function(a,b){var c=r(this).val();return null==c?null:Array.isArray(c)?r.map(c,function(a){return{name:b.name,value:a.replace(xb,"\r\n")}}):{name:b.name,value:c.replace(xb,"\r\n")}}).get()}});var Bb=/%20/g,Cb=/#.*$/,Db=/([?&])_=[^&]*/,Eb=/^(.*?):[ \t]*([^\r\n]*)$/gm,Fb=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Gb=/^(?:GET|HEAD)$/,Hb=/^\/\//,Ib={},Jb={},Kb="*/".concat("*"),Lb=d.createElement("a");Lb.href=tb.href;function Mb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(L)||[];if(r.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Nb(a,b,c,d){var e={},f=a===Jb;function g(h){var i;return e[h]=!0,r.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Ob(a,b){var c,d,e=r.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&r.extend(!0,a,d),a}function Pb(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}if(f)return f!==i[0]&&i.unshift(f),c[f]}function Qb(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}r.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:tb.href,type:"GET",isLocal:Fb.test(tb.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Kb,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":r.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Ob(Ob(a,r.ajaxSettings),b):Ob(r.ajaxSettings,a)},ajaxPrefilter:Mb(Ib),ajaxTransport:Mb(Jb),ajax:function(b,c){"object"==typeof b&&(c=b,b=void 0),c=c||{};var e,f,g,h,i,j,k,l,m,n,o=r.ajaxSetup({},c),p=o.context||o,q=o.context&&(p.nodeType||p.jquery)?r(p):r.event,s=r.Deferred(),t=r.Callbacks("once memory"),u=o.statusCode||{},v={},w={},x="canceled",y={readyState:0,getResponseHeader:function(a){var b;if(k){if(!h){h={};while(b=Eb.exec(g))h[b[1].toLowerCase()]=b[2]}b=h[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return k?g:null},setRequestHeader:function(a,b){return null==k&&(a=w[a.toLowerCase()]=w[a.toLowerCase()]||a,v[a]=b),this},overrideMimeType:function(a){return null==k&&(o.mimeType=a),this},statusCode:function(a){var b;if(a)if(k)y.always(a[y.status]);else for(b in a)u[b]=[u[b],a[b]];return this},abort:function(a){var b=a||x;return e&&e.abort(b),A(0,b),this}};if(s.promise(y),o.url=((b||o.url||tb.href)+"").replace(Hb,tb.protocol+"//"),o.type=c.method||c.type||o.method||o.type,o.dataTypes=(o.dataType||"*").toLowerCase().match(L)||[""],null==o.crossDomain){j=d.createElement("a");try{j.href=o.url,j.href=j.href,o.crossDomain=Lb.protocol+"//"+Lb.host!=j.protocol+"//"+j.host}catch(z){o.crossDomain=!0}}if(o.data&&o.processData&&"string"!=typeof o.data&&(o.data=r.param(o.data,o.traditional)),Nb(Ib,o,c,y),k)return y;l=r.event&&o.global,l&&0===r.active++&&r.event.trigger("ajaxStart"),o.type=o.type.toUpperCase(),o.hasContent=!Gb.test(o.type),f=o.url.replace(Cb,""),o.hasContent?o.data&&o.processData&&0===(o.contentType||"").indexOf("application/x-www-form-urlencoded")&&(o.data=o.data.replace(Bb,"+")):(n=o.url.slice(f.length),o.data&&(f+=(vb.test(f)?"&":"?")+o.data,delete o.data),o.cache===!1&&(f=f.replace(Db,"$1"),n=(vb.test(f)?"&":"?")+"_="+ub++ +n),o.url=f+n),o.ifModified&&(r.lastModified[f]&&y.setRequestHeader("If-Modified-Since",r.lastModified[f]),r.etag[f]&&y.setRequestHeader("If-None-Match",r.etag[f])),(o.data&&o.hasContent&&o.contentType!==!1||c.contentType)&&y.setRequestHeader("Content-Type",o.contentType),y.setRequestHeader("Accept",o.dataTypes[0]&&o.accepts[o.dataTypes[0]]?o.accepts[o.dataTypes[0]]+("*"!==o.dataTypes[0]?", "+Kb+"; q=0.01":""):o.accepts["*"]);for(m in o.headers)y.setRequestHeader(m,o.headers[m]);if(o.beforeSend&&(o.beforeSend.call(p,y,o)===!1||k))return y.abort();if(x="abort",t.add(o.complete),y.done(o.success),y.fail(o.error),e=Nb(Jb,o,c,y)){if(y.readyState=1,l&&q.trigger("ajaxSend",[y,o]),k)return y;o.async&&o.timeout>0&&(i=a.setTimeout(function(){y.abort("timeout")},o.timeout));try{k=!1,e.send(v,A)}catch(z){if(k)throw z;A(-1,z)}}else A(-1,"No Transport");function A(b,c,d,h){var j,m,n,v,w,x=c;k||(k=!0,i&&a.clearTimeout(i),e=void 0,g=h||"",y.readyState=b>0?4:0,j=b>=200&&b<300||304===b,d&&(v=Pb(o,y,d)),v=Qb(o,v,y,j),j?(o.ifModified&&(w=y.getResponseHeader("Last-Modified"),w&&(r.lastModified[f]=w),w=y.getResponseHeader("etag"),w&&(r.etag[f]=w)),204===b||"HEAD"===o.type?x="nocontent":304===b?x="notmodified":(x=v.state,m=v.data,n=v.error,j=!n)):(n=x,!b&&x||(x="error",b<0&&(b=0))),y.status=b,y.statusText=(c||x)+"",j?s.resolveWith(p,[m,x,y]):s.rejectWith(p,[y,x,n]),y.statusCode(u),u=void 0,l&&q.trigger(j?"ajaxSuccess":"ajaxError",[y,o,j?m:n]),t.fireWith(p,[y,x]),l&&(q.trigger("ajaxComplete",[y,o]),--r.active||r.event.trigger("ajaxStop")))}return y},getJSON:function(a,b,c){return r.get(a,b,c,"json")},getScript:function(a,b){return r.get(a,void 0,b,"script")}}),r.each(["get","post"],function(a,b){r[b]=function(a,c,d,e){return r.isFunction(c)&&(e=e||d,d=c,c=void 0),r.ajax(r.extend({url:a,type:b,dataType:e,data:c,success:d},r.isPlainObject(a)&&a))}}),r._evalUrl=function(a){return r.ajax({url:a,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},r.fn.extend({wrapAll:function(a){var b;return this[0]&&(r.isFunction(a)&&(a=a.call(this[0])),b=r(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this},wrapInner:function(a){return r.isFunction(a)?this.each(function(b){r(this).wrapInner(a.call(this,b))}):this.each(function(){var b=r(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=r.isFunction(a);return this.each(function(c){r(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(a){return this.parent(a).not("body").each(function(){r(this).replaceWith(this.childNodes)}),this}}),r.expr.pseudos.hidden=function(a){return!r.expr.pseudos.visible(a)},r.expr.pseudos.visible=function(a){return!!(a.offsetWidth||a.offsetHeight||a.getClientRects().length)},r.ajaxSettings.xhr=function(){try{return new a.XMLHttpRequest}catch(b){}};var Rb={0:200,1223:204},Sb=r.ajaxSettings.xhr();o.cors=!!Sb&&"withCredentials"in Sb,o.ajax=Sb=!!Sb,r.ajaxTransport(function(b){var c,d;if(o.cors||Sb&&!b.crossDomain)return{send:function(e,f){var g,h=b.xhr();if(h.open(b.type,b.url,b.async,b.username,b.password),b.xhrFields)for(g in b.xhrFields)h[g]=b.xhrFields[g];b.mimeType&&h.overrideMimeType&&h.overrideMimeType(b.mimeType),b.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest");for(g in e)h.setRequestHeader(g,e[g]);c=function(a){return function(){c&&(c=d=h.onload=h.onerror=h.onabort=h.onreadystatechange=null,"abort"===a?h.abort():"error"===a?"number"!=typeof h.status?f(0,"error"):f(h.status,h.statusText):f(Rb[h.status]||h.status,h.statusText,"text"!==(h.responseType||"text")||"string"!=typeof h.responseText?{binary:h.response}:{text:h.responseText},h.getAllResponseHeaders()))}},h.onload=c(),d=h.onerror=c("error"),void 0!==h.onabort?h.onabort=d:h.onreadystatechange=function(){4===h.readyState&&a.setTimeout(function(){c&&d()})},c=c("abort");try{h.send(b.hasContent&&b.data||null)}catch(i){if(c)throw i}},abort:function(){c&&c()}}}),r.ajaxPrefilter(function(a){a.crossDomain&&(a.contents.script=!1)}),r.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(a){return r.globalEval(a),a}}}),r.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),r.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(e,f){b=r("