diff --git a/.ci/release_template.md b/.ci/release_template.md index 75671f90e..ecd758ebf 100644 --- a/.ci/release_template.md +++ b/.ci/release_template.md @@ -4,13 +4,13 @@ git push -d origin --REPLACE-WITH-BETA-LIST-- --> -
 Pre-compiled binaries we serve:
  - Windows 10+
  - Windows 7+
- - macOS 13+ ("Ventura") / Apple M
+ - macOS 14+ ("Sonoma") / Apple M
  - macOS 13+ ("Ventura") / Intel
  - Ubuntu 24.04 LTS ("Noble Numbat")
  - Ubuntu 22.04 LTS ("Jammy Jellyfish")
@@ -19,8 +19,9 @@ include different targets -->
  - Debian 11 ("Bullseye")
  - Fedora 41
  - Fedora 40
-We are also packaged in Arch Linux's official "extra" repository, courtesy of @FFY00
-General Linux support is available via a flatpak package (Flathub)
+ 
+We are also packaged in Arch Linux's official "extra" repository, courtesy of @FFY00
+General Linux support is available via a flatpak package (Flathub)
 
@@ -28,22 +29,24 @@ include different targets --> We're pleased to announce the newest official release: --REPLACE-WITH-RELEASE-TITLE-- -We hope you enjoy the changes made and we have listed all changes, with their corresponding tickets, since the last version of Cockatrice was released for your convenience. +We hope you enjoy the changes made! All improvements with their corresponding tickets since the last version of Cockatrice are listed in the changelog below. -If you ever encounter a bug, have a suggestion or idea, or feel a need for a developer to look into something, please feel free to [open a ticket](https://github.com/Cockatrice/Cockatrice/issues). ([How to create a GitHub Ticket for Cockatrice](https://github.com/Cockatrice/Cockatrice/wiki/How-to-Create-a-GitHub-Ticket-Regarding-Cockatrice)) +If you ever encounter a bug, have a suggestion or idea, or feel a need for a developer to look into something, please feel free to [open a ticket](https://github.com/Cockatrice/Cockatrice/issues). ([How to create a Ticket for Cockatrice](https://github.com/Cockatrice/Cockatrice/wiki/How-to-Create-a-GitHub-Ticket-Regarding-Cockatrice)) -For any information relating to Cockatrice, please take a look at our official site: **https://cockatrice.github.io** +For basic information related to the app and getting started, please take a look at our official site: **https://cockatrice.github.io** -If you'd like to help contribute to Cockatrice in any way, check out our [README](https://github.com/Cockatrice/Cockatrice#get-involved-). We're always available to answer questions you may have on how the program works and how you can provide a meaningful contribution. +If you'd like to help and contribute to Cockatrice in any way, check out our [README](https://github.com/Cockatrice/Cockatrice#get-involved-). +We're always available to answer questions you may have on how the program works and how you can provide a meaningful contribution. ## Upgrading Cockatrice -- Run the internal software updater: Help → Check for Client Updates +Run the internal software updater: Help → Check for Client Updates Don't forget to update your card database right after! (Help → Check for Card Updates...) @@ -60,14 +63,14 @@ Remove empty headers when done. --> -### ⚠️ Important: +### 🔖 Highlights: ### ✨ New Features: -### 🐛 Fixed Bugs / Resolved issues: +### 🐛 Fixed Bugs / Resolved Issues:
-📘 Show all changes (--REPLACE-WITH-COMMIT-COUNT-- commits) +Show all changes (--REPLACE-WITH-COMMIT-COUNT-- commits) ### User Interface @@ -88,5 +91,6 @@ Remove empty headers when done. ## Special Thanks -We continue to find it amazing that so many people contribute their time, knowledge, code, testing and more to the project. We'd like to thank the entire Cockatrice community for their efforts. +It's amazing that so many people contribute their time, knowledge, code, testing and more to the project. +We'd like to thank the entire Cockatrice community for their efforts! 🙏 diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 1f4ee4e67..99c05deee 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -212,7 +212,7 @@ jobs: - target: 14 soc: Apple os: macos-14 - xcode: "14.3.1" + xcode: "15.4" type: Release core_count: 3 make_package: 1 @@ -220,7 +220,7 @@ jobs: - target: 14 soc: Apple os: macos-14 - xcode: "14.3.1" + xcode: "15.4" type: Debug core_count: 3 diff --git a/cmake/getversion.cmake b/cmake/getversion.cmake index 5bec3548d..cb27ebd87 100644 --- a/cmake/getversion.cmake +++ b/cmake/getversion.cmake @@ -19,7 +19,7 @@ function(get_commit_id) PARENT_SCOPE ) set(PROJECT_VERSION_LABEL - "custom(${GIT_COM_ID})" + "custom-${GIT_COM_ID}" PARENT_SCOPE ) endfunction() diff --git a/cockatrice/src/client/network/replay_timeline_widget.cpp b/cockatrice/src/client/network/replay_timeline_widget.cpp index 83b1abda0..deb77d623 100644 --- a/cockatrice/src/client/network/replay_timeline_widget.cpp +++ b/cockatrice/src/client/network/replay_timeline_widget.cpp @@ -162,6 +162,10 @@ void ReplayTimelineWidget::processNewEvents(PlaybackMode playbackMode) if (playbackMode == BACKWARD_SKIP || currentTime - replayTimeline[currentEvent] > BIG_SKIP_MS) options |= Player::EventProcessingOption::SKIP_REVEAL_WINDOW; + // backwards skip => always skip tap animation + if (playbackMode == BACKWARD_SKIP) + options |= Player::EventProcessingOption::SKIP_TAP_ANIMATION; + emit processNextEvent(options); ++currentEvent; } diff --git a/cockatrice/src/client/tabs/tab_deck_editor.cpp b/cockatrice/src/client/tabs/tab_deck_editor.cpp index 0c9edbb0b..1e2284186 100644 --- a/cockatrice/src/client/tabs/tab_deck_editor.cpp +++ b/cockatrice/src/client/tabs/tab_deck_editor.cpp @@ -748,9 +748,7 @@ bool TabDeckEditor::confirmClose() { if (modified) { tabSupervisor->setCurrentWidget(this); - QMessageBox::StandardButton ret = QMessageBox::warning( - this, tr("Are you sure?"), tr("The decklist has been modified.\nDo you want to save the changes?"), - QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel); + int ret = createSaveConfirmationWindow()->exec(); if (ret == QMessageBox::Save) return actSaveDeck(); else if (ret == QMessageBox::Cancel) @@ -767,13 +765,16 @@ void TabDeckEditor::closeRequest() void TabDeckEditor::actNewDeck() { - if (SettingsCache::instance().getOpenDeckInNewTab()) { - emit openDeckEditor(nullptr); + auto deckOpenLocation = confirmOpen(false); + + if (deckOpenLocation == CANCELLED) { return; } - if (!confirmClose()) + if (deckOpenLocation == NEW_TAB) { + emit openDeckEditor(nullptr); return; + } deckModel->cleanList(); nameEdit->setText(QString()); @@ -785,10 +786,11 @@ void TabDeckEditor::actNewDeck() void TabDeckEditor::actLoadDeck() { - bool openInNewTab = SettingsCache::instance().getOpenDeckInNewTab() && !isBlankNewDeck(); + auto deckOpenLocation = confirmOpen(); - if (!openInNewTab && !confirmClose()) + if (deckOpenLocation == CANCELLED) { return; + } QFileDialog dialog(this, tr("Load deck")); dialog.setDirectory(SettingsCache::instance().getDeckPath()); @@ -801,7 +803,7 @@ void TabDeckEditor::actLoadDeck() auto *l = new DeckLoader; if (l->loadFromFile(fileName, fmt)) { - if (openInNewTab) { + if (deckOpenLocation == NEW_TAB) { emit openDeckEditor(l); } else { setSaveStatus(false); @@ -878,16 +880,17 @@ bool TabDeckEditor::actSaveDeckAs() void TabDeckEditor::actLoadDeckFromClipboard() { - bool openInNewTab = SettingsCache::instance().getOpenDeckInNewTab() && !isBlankNewDeck(); + auto deckOpenLocation = confirmOpen(); - if (!openInNewTab && !confirmClose()) + if (deckOpenLocation == CANCELLED) { return; + } DlgLoadDeckFromClipboard dlg(this); if (!dlg.exec()) return; - if (openInNewTab) { + if (deckOpenLocation == NEW_TAB) { emit openDeckEditor(dlg.getDeckList()); } else { setDeck(dlg.getDeckList()); @@ -987,6 +990,70 @@ void TabDeckEditor::recursiveExpand(const QModelIndex &index) deckView->expand(index); } +/** + * @brief Displays the save confirmation dialogue that is shown before loading a deck, if required. Takes into + * account the `openDeckInNewTab` settting. + * + * @param openInSameTabIfBlank Open the deck in the same tab instead of a new tab if the current tab is completely + * blank. Only relevant when the `openDeckInNewTab` setting is enabled. + * + * @returns An enum that indicates if and where to load the deck + */ +TabDeckEditor::DeckOpenLocation TabDeckEditor::confirmOpen(const bool openInSameTabIfBlank) +{ + // handle `openDeckInNewTab` setting + if (SettingsCache::instance().getOpenDeckInNewTab()) { + if (openInSameTabIfBlank && isBlankNewDeck()) { + return SAME_TAB; + } else { + return NEW_TAB; + } + } + + // early return if deck is unmodified + if (!modified) { + return SAME_TAB; + } + + // do the save confirmation dialogue + tabSupervisor->setCurrentWidget(this); + + QMessageBox *msgBox = createSaveConfirmationWindow(); + QPushButton *newTabButton = msgBox->addButton(tr("Open in new tab"), QMessageBox::ApplyRole); + + int ret = msgBox->exec(); + + // `exec()` returns an opaque value if a non-standard button was clicked. + // Directly check if newTabButton was clicked before switching over the standard buttons. + if (msgBox->clickedButton() == newTabButton) { + return NEW_TAB; + } + + switch (ret) { + case QMessageBox::Save: + return actSaveDeck() ? SAME_TAB : CANCELLED; + case QMessageBox::Discard: + return SAME_TAB; + default: + return CANCELLED; + } +} + +/** + * @brief Creates the base save confirmation dialogue box. + * + * @returns A QMessageBox that can be further modified + */ +QMessageBox *TabDeckEditor::createSaveConfirmationWindow() +{ + QMessageBox *msgBox = new QMessageBox(this); + msgBox->setIcon(QMessageBox::Warning); + msgBox->setWindowTitle(tr("Are you sure?")); + msgBox->setText(tr("The decklist has been modified.\nDo you want to save the changes?")); + msgBox->setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel); + return msgBox; +} + /** * @brief Returns true if this tab is a blank newly opened tab, as if it was just created with the `New Deck` action. */ diff --git a/cockatrice/src/client/tabs/tab_deck_editor.h b/cockatrice/src/client/tabs/tab_deck_editor.h index b8c45c110..6aff1cc31 100644 --- a/cockatrice/src/client/tabs/tab_deck_editor.h +++ b/cockatrice/src/client/tabs/tab_deck_editor.h @@ -22,6 +22,7 @@ class Response; class FilterTreeModel; class FilterBuilder; class QGroupBox; +class QMessageBox; class QHBoxLayout; class QVBoxLayout; class QPushButton; @@ -100,6 +101,19 @@ private slots: void showSearchSyntaxHelp(); private: + /** + * @brief Which tab to open the new deck in + */ + enum DeckOpenLocation + { + CANCELLED, + SAME_TAB, + NEW_TAB + }; + + DeckOpenLocation confirmOpen(const bool openInSameTabIfBlank = true); + QMessageBox *createSaveConfirmationWindow(); + bool isBlankNewDeck() const; CardInfoPtr currentCardInfo() const; void addCardHelper(QString zoneName); diff --git a/cockatrice/src/client/tabs/tab_game.cpp b/cockatrice/src/client/tabs/tab_game.cpp index 68c60ff15..a37e05567 100644 --- a/cockatrice/src/client/tabs/tab_game.cpp +++ b/cockatrice/src/client/tabs/tab_game.cpp @@ -648,8 +648,7 @@ void TabGame::replayRewind() messageLog->clearChat(); // reset phase markers - currentPhase = -1; - phasesToolbar->reset(); + setActivePhase(-1); } void TabGame::incrementGameTime() diff --git a/cockatrice/src/client/ui/phases_toolbar.cpp b/cockatrice/src/client/ui/phases_toolbar.cpp index f43d9f00d..036a7c19e 100644 --- a/cockatrice/src/client/ui/phases_toolbar.cpp +++ b/cockatrice/src/client/ui/phases_toolbar.cpp @@ -86,23 +86,6 @@ void PhaseButton::updateAnimation() update(); } -/** - * @brief Immediately resets the button to the inactive state, without going through the animation. - */ -void PhaseButton::reset() -{ - activeAnimationTimer->stop(); - active = false; - - if (highlightable) { - activeAnimationCounter = 0; - } else { - activeAnimationCounter = 9; - } - - update(); -} - void PhaseButton::mousePressEvent(QGraphicsSceneMouseEvent * /*event*/) { emit clicked(); @@ -268,16 +251,6 @@ void PhasesToolbar::phaseButtonClicked() emit sendGameCommand(cmd, -1); } -/** - * @brief Immediately resets the toolbar to its initial state, with all phases inactive. - */ -void PhasesToolbar::reset() -{ - for (auto &i : buttonList) { - i->reset(); - } -} - void PhasesToolbar::actNextTurn() { emit sendGameCommand(Command_NextTurn(), -1); diff --git a/cockatrice/src/client/ui/phases_toolbar.h b/cockatrice/src/client/ui/phases_toolbar.h index 1595c65d5..3567e741b 100644 --- a/cockatrice/src/client/ui/phases_toolbar.h +++ b/cockatrice/src/client/ui/phases_toolbar.h @@ -45,7 +45,6 @@ public: { return active; } - void reset(); void triggerDoubleClickAction(); signals: void clicked(); @@ -86,7 +85,6 @@ public: public slots: void setActivePhase(int phase); void triggerPhaseAction(int phase); - void reset(); private slots: void phaseButtonClicked(); void actNextTurn(); diff --git a/cockatrice/src/client/ui/picture_loader.cpp b/cockatrice/src/client/ui/picture_loader.cpp index ad18a9071..a28a98eac 100644 --- a/cockatrice/src/client/ui/picture_loader.cpp +++ b/cockatrice/src/client/ui/picture_loader.cpp @@ -1,17 +1,14 @@ #include "picture_loader.h" -#include "../../game/cards/card_database.h" #include "../../game/cards/card_database_manager.h" #include "../../settings/cache_settings.h" -#include "theme_manager.h" #include #include #include #include -#include #include -#include +#include #include #include #include @@ -23,7 +20,6 @@ #include #include #include -#include #include #include #include @@ -136,6 +132,8 @@ PictureLoaderWorker::PictureLoaderWorker() #endif auto cache = new QNetworkDiskCache(this); cache->setCacheDirectory(SettingsCache::instance().getNetworkCachePath()); + cache->setMaximumCacheSize(1024L * 1024L * + static_cast(SettingsCache::instance().getNetworkCacheSizeInMB())); // Note: the settings is in MB, but QNetworkDiskCache uses bytes connect(&SettingsCache::instance(), &SettingsCache::networkCacheSizeChanged, cache, [cache](int newSizeInMB) { cache->setMaximumCacheSize(1024L * 1024L * static_cast(newSizeInMB)); }); @@ -145,6 +143,13 @@ PictureLoaderWorker::PictureLoaderWorker() networkManager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy); connect(networkManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(picDownloadFinished(QNetworkReply *))); + cacheFilePath = SettingsCache::instance().getRedirectCachePath() + REDIRECT_CACHE_FILENAME; + loadRedirectCache(); + cleanStaleEntries(); + + connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, + &PictureLoaderWorker::saveRedirectCache); + pictureLoaderThread = new QThread; pictureLoaderThread->start(QThread::LowPriority); moveToThread(pictureLoaderThread); @@ -447,11 +452,104 @@ bool PictureLoaderWorker::imageIsBlackListed(const QByteArray &picData) QNetworkReply *PictureLoaderWorker::makeRequest(const QUrl &url) { + // Check if the redirect is cached + QUrl cachedRedirect = getCachedRedirect(url); + if (!cachedRedirect.isEmpty()) { + qDebug().nospace() << "PictureLoader: [card: " << cardBeingDownloaded.getCard()->getCorrectedName() + << " set: " << cardBeingDownloaded.getSetName() << "]: Using cached redirect for " + << url.toDisplayString() << " to " << cachedRedirect.toDisplayString(); + return makeRequest(cachedRedirect); // Use the cached redirect + } + QNetworkRequest req(url); + if (!picDownload) { req.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysCache); } - return networkManager->get(req); + + QNetworkReply *reply = networkManager->get(req); + + connect(reply, &QNetworkReply::finished, this, [this, reply, url]() { + QVariant redirectTarget = reply->attribute(QNetworkRequest::RedirectionTargetAttribute); + + if (redirectTarget.isValid()) { + QUrl redirectUrl = redirectTarget.toUrl(); + if (redirectUrl.isRelative()) { + redirectUrl = url.resolved(redirectUrl); + } + + cacheRedirect(url, redirectUrl); + qDebug().nospace() << "PictureLoader: [card: " << cardBeingDownloaded.getCard()->getCorrectedName() + << " set: " << cardBeingDownloaded.getSetName() << "]: Caching redirect from " + << url.toDisplayString() << " to " << redirectUrl.toDisplayString(); + } + + reply->deleteLater(); + }); + + return reply; +} + +void PictureLoaderWorker::cacheRedirect(const QUrl &originalUrl, const QUrl &redirectUrl) +{ + redirectCache[originalUrl] = qMakePair(redirectUrl, QDateTime::currentDateTimeUtc()); + saveRedirectCache(); +} + +QUrl PictureLoaderWorker::getCachedRedirect(const QUrl &originalUrl) const +{ + if (redirectCache.contains(originalUrl)) { + return redirectCache[originalUrl].first; + } + return {}; +} + +void PictureLoaderWorker::loadRedirectCache() +{ + QSettings settings(cacheFilePath, QSettings::IniFormat); + + redirectCache.clear(); + int size = settings.beginReadArray(REDIRECT_HEADER_NAME); + for (int i = 0; i < size; ++i) { + settings.setArrayIndex(i); + QUrl originalUrl = settings.value(REDIRECT_ORIGINAL_URL).toUrl(); + QUrl redirectUrl = settings.value(REDIRECT_URL).toUrl(); + QDateTime timestamp = settings.value(REDIRECT_TIMESTAMP).toDateTime(); + + if (originalUrl.isValid() && redirectUrl.isValid()) { + redirectCache[originalUrl] = qMakePair(redirectUrl, timestamp); + } + } + settings.endArray(); +} + +void PictureLoaderWorker::saveRedirectCache() const +{ + QSettings settings(cacheFilePath, QSettings::IniFormat); + + settings.beginWriteArray(REDIRECT_HEADER_NAME, static_cast(redirectCache.size())); + int index = 0; + for (auto it = redirectCache.cbegin(); it != redirectCache.cend(); ++it) { + settings.setArrayIndex(index++); + settings.setValue(REDIRECT_ORIGINAL_URL, it.key()); + settings.setValue(REDIRECT_URL, it.value().first); + settings.setValue(REDIRECT_TIMESTAMP, it.value().second); + } + settings.endArray(); +} + +void PictureLoaderWorker::cleanStaleEntries() +{ + QDateTime now = QDateTime::currentDateTimeUtc(); + + auto it = redirectCache.begin(); + while (it != redirectCache.end()) { + if (it.value().second.addDays(SettingsCache::instance().getRedirectCacheTtl()) < now) { + it = redirectCache.erase(it); // Remove stale entry + } else { + ++it; + } + } } void PictureLoaderWorker::picDownloadFinished(QNetworkReply *reply) diff --git a/cockatrice/src/client/ui/picture_loader.h b/cockatrice/src/client/ui/picture_loader.h index 1c840eb40..245114358 100644 --- a/cockatrice/src/client/ui/picture_loader.h +++ b/cockatrice/src/client/ui/picture_loader.h @@ -11,6 +11,12 @@ class QNetworkAccessManager; class QNetworkReply; class QThread; +#define REDIRECT_HEADER_NAME "redirects" +#define REDIRECT_ORIGINAL_URL "original" +#define REDIRECT_URL "redirect" +#define REDIRECT_TIMESTAMP "timestamp" +#define REDIRECT_CACHE_FILENAME "cache.ini" + class PictureToLoad { private: @@ -83,6 +89,9 @@ private: QList loadQueue; QMutex mutex; QNetworkAccessManager *networkManager; + QHash> redirectCache; // Stores redirect and timestamp + QString cacheFilePath; // Path to persistent storage + static constexpr int CacheTTLInDays = 30; // TODO: Make user configurable QList cardsToDownload; PictureToLoad cardBeingLoaded; PictureToLoad cardBeingDownloaded; @@ -91,6 +100,12 @@ private: bool cardImageExistsOnDisk(QString &setName, QString &correctedCardName); bool imageIsBlackListed(const QByteArray &); QNetworkReply *makeRequest(const QUrl &url); + void cacheRedirect(const QUrl &originalUrl, const QUrl &redirectUrl); + QUrl getCachedRedirect(const QUrl &originalUrl) const; + void loadRedirectCache(); + void saveRedirectCache() const; + void cleanStaleEntries(); + private slots: void picDownloadFinished(QNetworkReply *reply); void picDownloadFailed(); diff --git a/cockatrice/src/client/ui/window_main.cpp b/cockatrice/src/client/ui/window_main.cpp index 4de3b3cb3..b30f5556d 100644 --- a/cockatrice/src/client/ui/window_main.cpp +++ b/cockatrice/src/client/ui/window_main.cpp @@ -666,6 +666,7 @@ void MainWindow::retranslateUi() aOpenCustomFolder->setText(tr("Open custom image folder")); aOpenCustomsetsFolder->setText(tr("Open custom sets folder")); aAddCustomSet->setText(tr("Add custom sets/cards")); + aReloadCardDatabase->setText(tr("Reload card database")); helpMenu->setTitle(tr("&Help")); aAbout->setText(tr("&About Cockatrice")); @@ -714,6 +715,8 @@ void MainWindow::createActions() connect(aOpenCustomsetsFolder, SIGNAL(triggered()), this, SLOT(actOpenCustomsetsFolder())); aAddCustomSet = new QAction(QString(), this); connect(aAddCustomSet, SIGNAL(triggered()), this, SLOT(actAddCustomSet())); + aReloadCardDatabase = new QAction(QString(), this); + connect(aReloadCardDatabase, SIGNAL(triggered()), this, SLOT(actReloadCardDatabase())); aAbout = new QAction(this); connect(aAbout, SIGNAL(triggered()), this, SLOT(actAbout())); @@ -788,6 +791,8 @@ void MainWindow::createMenus() dbMenu->addAction(aOpenCustomFolder); dbMenu->addAction(aOpenCustomsetsFolder); dbMenu->addAction(aAddCustomSet); + dbMenu->addSeparator(); + dbMenu->addAction(aReloadCardDatabase); helpMenu = menuBar()->addMenu(QString()); helpMenu->addAction(aAbout); @@ -878,6 +883,7 @@ void MainWindow::startupConfigCheck() // no config found, 99% new clean install qDebug() << "Startup: old client version empty, assuming first start after clean install"; alertForcedOracleRun(VERSION_STRING, false); + SettingsCache::instance().downloads().resetToDefaultURLs(); // populate the download urls SettingsCache::instance().setClientVersion(VERSION_STRING); } else if (SettingsCache::instance().getClientVersion() != VERSION_STRING) { // config found, from another (presumably older) version @@ -1324,6 +1330,11 @@ int MainWindow::getNextCustomSetPrefix(QDir dataDir) return maxIndex + 1; } +void MainWindow::actReloadCardDatabase() +{ + const auto reloadOk1 = QtConcurrent::run([] { CardDatabaseManager::getInstance()->loadCardDatabases(); }); +} + void MainWindow::actManageSets() { wndSets = new WndSets(this); diff --git a/cockatrice/src/client/ui/window_main.h b/cockatrice/src/client/ui/window_main.h index 1813b3b4a..2b240beb1 100644 --- a/cockatrice/src/client/ui/window_main.h +++ b/cockatrice/src/client/ui/window_main.h @@ -98,6 +98,7 @@ private slots: void actOpenCustomFolder(); void actOpenCustomsetsFolder(); void actAddCustomSet(); + void actReloadCardDatabase(); void actManageSets(); void actEditTokens(); @@ -125,7 +126,7 @@ private: QMenu *cockatriceMenu, *dbMenu, *helpMenu, *trayIconMenu; QAction *aConnect, *aDisconnect, *aSinglePlayer, *aWatchReplay, *aDeckEditor, *aFullScreen, *aSettings, *aExit, *aAbout, *aTips, *aCheckCardUpdates, *aRegister, *aForgotPassword, *aUpdate, *aViewLog, *aManageSets, - *aEditTokens, *aOpenCustomFolder, *aOpenCustomsetsFolder, *aAddCustomSet, *aShow; + *aEditTokens, *aOpenCustomFolder, *aOpenCustomsetsFolder, *aAddCustomSet, *aReloadCardDatabase, *aShow; TabSupervisor *tabSupervisor; WndSets *wndSets; RemoteClient *client; diff --git a/cockatrice/src/dialogs/dlg_connect.cpp b/cockatrice/src/dialogs/dlg_connect.cpp index d618d3ae4..27fff0509 100644 --- a/cockatrice/src/dialogs/dlg_connect.cpp +++ b/cockatrice/src/dialogs/dlg_connect.cpp @@ -80,8 +80,7 @@ DlgConnect::DlgConnect(QWidget *parent) : QDialog(parent) autoConnectCheckBox->setEnabled(false); } - connect(savePasswordCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), this, - SLOT(passwordSaved(QT_STATE_CHANGED_T))); + connect(savePasswordCheckBox, &QCheckBox::QT_STATE_CHANGED, this, &DlgConnect::passwordSaved); serverIssuesLabel = new QLabel(tr("If you have any trouble connecting or registering then contact the server staff for help!")); diff --git a/cockatrice/src/dialogs/dlg_create_game.cpp b/cockatrice/src/dialogs/dlg_create_game.cpp index 536077de2..c8131a1cf 100644 --- a/cockatrice/src/dialogs/dlg_create_game.cpp +++ b/cockatrice/src/dialogs/dlg_create_game.cpp @@ -81,8 +81,7 @@ void DlgCreateGame::sharedCtor() spectatorsAllowedCheckBox = new QCheckBox(tr("&Spectators can watch")); spectatorsAllowedCheckBox->setChecked(true); - connect(spectatorsAllowedCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), this, - SLOT(spectatorsAllowedChanged(QT_STATE_CHANGED_T))); + connect(spectatorsAllowedCheckBox, &QCheckBox::QT_STATE_CHANGED, this, &DlgCreateGame::spectatorsAllowedChanged); spectatorsNeedPasswordCheckBox = new QCheckBox(tr("Spectators &need a password to watch")); spectatorsCanTalkCheckBox = new QCheckBox(tr("Spectators can &chat")); spectatorsSeeEverythingCheckBox = new QCheckBox(tr("Spectators can see &hands")); diff --git a/cockatrice/src/dialogs/dlg_filter_games.cpp b/cockatrice/src/dialogs/dlg_filter_games.cpp index 6b99d0046..f988c5a49 100644 --- a/cockatrice/src/dialogs/dlg_filter_games.cpp +++ b/cockatrice/src/dialogs/dlg_filter_games.cpp @@ -276,7 +276,7 @@ int DlgFilterGames::getMaxPlayersFilterMax() const return maxPlayersFilterMaxSpinBox->value(); } -const QTime &DlgFilterGames::getMaxGameAge() const +QTime DlgFilterGames::getMaxGameAge() const { int index = maxGameAgeComboBox->currentIndex(); if (index < 0 || index >= gameAgeMap.size()) { // index is out of bounds diff --git a/cockatrice/src/dialogs/dlg_filter_games.h b/cockatrice/src/dialogs/dlg_filter_games.h index 0b1fd333c..77e5cbb2e 100644 --- a/cockatrice/src/dialogs/dlg_filter_games.h +++ b/cockatrice/src/dialogs/dlg_filter_games.h @@ -67,7 +67,7 @@ public: int getMaxPlayersFilterMin() const; int getMaxPlayersFilterMax() const; void setMaxPlayersFilter(int _maxPlayersFilterMin, int _maxPlayersFilterMax); - const QTime &getMaxGameAge() const; + QTime getMaxGameAge() const; const QMap gameAgeMap; bool getShowOnlyIfSpectatorsCanWatch() const; bool getShowSpectatorPasswordProtected() const; diff --git a/cockatrice/src/dialogs/dlg_settings.cpp b/cockatrice/src/dialogs/dlg_settings.cpp index efdff930a..babe49213 100644 --- a/cockatrice/src/dialogs/dlg_settings.cpp +++ b/cockatrice/src/dialogs/dlg_settings.cpp @@ -74,10 +74,9 @@ GeneralSettingsPage::GeneralSettingsPage() connect(&languageBox, SIGNAL(currentIndexChanged(int)), this, SLOT(languageBoxChanged(int))); connect(&updateReleaseChannelBox, SIGNAL(currentIndexChanged(int)), &settings, SLOT(setUpdateReleaseChannel(int))); - connect(&updateNotificationCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &settings, - SLOT(setNotifyAboutUpdate(QT_STATE_CHANGED_T))); - connect(&newVersionOracleCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &settings, - SLOT(setNotifyAboutNewVersion(QT_STATE_CHANGED_T))); + connect(&updateNotificationCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, &SettingsCache::setNotifyAboutUpdate); + connect(&newVersionOracleCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, + &SettingsCache::setNotifyAboutNewVersion); connect(&showTipsOnStartup, SIGNAL(clicked(bool)), &settings, SLOT(setShowTipsOnStartup(bool))); auto *personalGrid = new QGridLayout; @@ -324,12 +323,10 @@ AppearanceSettingsPage::AppearanceSettingsPage() themeGroupBox->setLayout(themeGrid); displayCardNamesCheckBox.setChecked(settings.getDisplayCardNames()); - connect(&displayCardNamesCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &settings, - SLOT(setDisplayCardNames(QT_STATE_CHANGED_T))); + connect(&displayCardNamesCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, &SettingsCache::setDisplayCardNames); cardScalingCheckBox.setChecked(settings.getScaleCards()); - connect(&cardScalingCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &settings, - SLOT(setCardScaling(QT_STATE_CHANGED_T))); + connect(&cardScalingCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, &SettingsCache::setCardScaling); verticalCardOverlapPercentBox.setValue(settings.getStackCardOverlapPercent()); verticalCardOverlapPercentBox.setRange(0, 80); @@ -346,12 +343,10 @@ AppearanceSettingsPage::AppearanceSettingsPage() cardsGroupBox->setLayout(cardsGrid); horizontalHandCheckBox.setChecked(settings.getHorizontalHand()); - connect(&horizontalHandCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &settings, - SLOT(setHorizontalHand(QT_STATE_CHANGED_T))); + connect(&horizontalHandCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, &SettingsCache::setHorizontalHand); leftJustifiedHandCheckBox.setChecked(settings.getLeftJustified()); - connect(&leftJustifiedHandCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &settings, - SLOT(setLeftJustified(QT_STATE_CHANGED_T))); + connect(&leftJustifiedHandCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, &SettingsCache::setLeftJustified); auto *handGrid = new QGridLayout; handGrid->addWidget(&horizontalHandCheckBox, 0, 0, 1, 2); @@ -361,8 +356,8 @@ AppearanceSettingsPage::AppearanceSettingsPage() handGroupBox->setLayout(handGrid); invertVerticalCoordinateCheckBox.setChecked(settings.getInvertVerticalCoordinate()); - connect(&invertVerticalCoordinateCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &settings, - SLOT(setInvertVerticalCoordinate(QT_STATE_CHANGED_T))); + connect(&invertVerticalCoordinateCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, + &SettingsCache::setInvertVerticalCoordinate); minPlayersForMultiColumnLayoutEdit.setMinimum(2); minPlayersForMultiColumnLayoutEdit.setValue(settings.getMinPlayersForMultiColumnLayout()); @@ -442,37 +437,37 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage() { // general settings and notification settings notificationsEnabledCheckBox.setChecked(SettingsCache::instance().getNotificationsEnabled()); - connect(¬ificationsEnabledCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), - SLOT(setNotificationsEnabled(QT_STATE_CHANGED_T))); - connect(¬ificationsEnabledCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), this, - SLOT(setNotificationEnabled(QT_STATE_CHANGED_T))); + connect(¬ificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setNotificationsEnabled); + connect(¬ificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, this, + &UserInterfaceSettingsPage::setNotificationEnabled); specNotificationsEnabledCheckBox.setChecked(SettingsCache::instance().getSpectatorNotificationsEnabled()); specNotificationsEnabledCheckBox.setEnabled(SettingsCache::instance().getNotificationsEnabled()); - connect(&specNotificationsEnabledCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), - SLOT(setSpectatorNotificationsEnabled(QT_STATE_CHANGED_T))); + connect(&specNotificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setSpectatorNotificationsEnabled); buddyConnectNotificationsEnabledCheckBox.setChecked( SettingsCache::instance().getBuddyConnectNotificationsEnabled()); buddyConnectNotificationsEnabledCheckBox.setEnabled(SettingsCache::instance().getNotificationsEnabled()); - connect(&buddyConnectNotificationsEnabledCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), - &SettingsCache::instance(), SLOT(setBuddyConnectNotificationsEnabled(QT_STATE_CHANGED_T))); + connect(&buddyConnectNotificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setBuddyConnectNotificationsEnabled); doubleClickToPlayCheckBox.setChecked(SettingsCache::instance().getDoubleClickToPlay()); - connect(&doubleClickToPlayCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), - SLOT(setDoubleClickToPlay(QT_STATE_CHANGED_T))); + connect(&doubleClickToPlayCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setDoubleClickToPlay); playToStackCheckBox.setChecked(SettingsCache::instance().getPlayToStack()); - connect(&playToStackCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), - SLOT(setPlayToStack(QT_STATE_CHANGED_T))); + connect(&playToStackCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setPlayToStack); annotateTokensCheckBox.setChecked(SettingsCache::instance().getAnnotateTokens()); - connect(&annotateTokensCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), - SLOT(setAnnotateTokens(QT_STATE_CHANGED_T))); + connect(&annotateTokensCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setAnnotateTokens); useTearOffMenusCheckBox.setChecked(SettingsCache::instance().getUseTearOffMenus()); connect(&useTearOffMenusCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), - [](QT_STATE_CHANGED_T state) { SettingsCache::instance().setUseTearOffMenus(state == Qt::Checked); }); + [](const QT_STATE_CHANGED_T state) { SettingsCache::instance().setUseTearOffMenus(state == Qt::Checked); }); auto *generalGrid = new QGridLayout; generalGrid->addWidget(&doubleClickToPlayCheckBox, 0, 0); @@ -493,8 +488,8 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage() // animation settings tapAnimationCheckBox.setChecked(SettingsCache::instance().getTapAnimation()); - connect(&tapAnimationCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), - SLOT(setTapAnimation(QT_STATE_CHANGED_T))); + connect(&tapAnimationCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setTapAnimation); auto *animationGrid = new QGridLayout; animationGrid->addWidget(&tapAnimationCheckBox, 0, 0); @@ -504,8 +499,8 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage() // deck editor settings openDeckInNewTabCheckBox.setChecked(SettingsCache::instance().getOpenDeckInNewTab()); - connect(&openDeckInNewTabCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), - SLOT(setOpenDeckInNewTab(QT_STATE_CHANGED_T))); + connect(&openDeckInNewTabCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setOpenDeckInNewTab); auto *deckEditorGrid = new QGridLayout; deckEditorGrid->addWidget(&openDeckInNewTabCheckBox, 0, 0); @@ -524,7 +519,7 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage() setLayout(mainLayout); } -void UserInterfaceSettingsPage::setNotificationEnabled(int i) +void UserInterfaceSettingsPage::setNotificationEnabled(QT_STATE_CHANGED_T i) { specNotificationsEnabledCheckBox.setEnabled(i != 0); buddyConnectNotificationsEnabledCheckBox.setEnabled(i != 0); @@ -554,8 +549,8 @@ void UserInterfaceSettingsPage::retranslateUi() DeckEditorSettingsPage::DeckEditorSettingsPage() { picDownloadCheckBox.setChecked(SettingsCache::instance().getPicDownload()); - connect(&picDownloadCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), - SLOT(setPicDownload(QT_STATE_CHANGED_T))); + connect(&picDownloadCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setPicDownload); urlLinkLabel.setTextInteractionFlags(Qt::LinksAccessibleByMouse); urlLinkLabel.setOpenExternalLinks(true); @@ -588,8 +583,7 @@ DeckEditorSettingsPage::DeckEditorSettingsPage() connect(urlList->model(), SIGNAL(rowsMoved(const QModelIndex, int, int, const QModelIndex, int)), this, SLOT(urlListChanged(const QModelIndex, int, int, const QModelIndex, int))); - for (int i = 0; i < SettingsCache::instance().downloads().getCount(); i++) - urlList->addItem(SettingsCache::instance().downloads().getDownloadUrlAt(i)); + urlList->addItems(SettingsCache::instance().downloads().getAllURLs()); auto aAdd = new QAction(this); aAdd->setIcon(QPixmap("theme:icons/increment")); @@ -604,16 +598,16 @@ DeckEditorSettingsPage::DeckEditorSettingsPage() aRemove->setToolTip(tr("Remove URL")); connect(aRemove, SIGNAL(triggered()), this, SLOT(actRemoveURL())); - auto *messageToolBar = new QToolBar; - messageToolBar->setOrientation(Qt::Vertical); - messageToolBar->addAction(aAdd); - messageToolBar->addAction(aRemove); - messageToolBar->addAction(aEdit); - messageToolBar->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); + auto *urlToolBar = new QToolBar; + urlToolBar->setOrientation(Qt::Vertical); + urlToolBar->addAction(aAdd); + urlToolBar->addAction(aRemove); + urlToolBar->addAction(aEdit); + urlToolBar->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); - auto *messageListLayout = new QHBoxLayout; - messageListLayout->addWidget(messageToolBar); - messageListLayout->addWidget(urlList); + auto *urlListLayout = new QHBoxLayout; + urlListLayout->addWidget(urlToolBar); + urlListLayout->addWidget(urlList); // pixmap cache pixmapCacheEdit.setMinimum(PIXMAPCACHE_SIZE_MIN); @@ -629,11 +623,22 @@ DeckEditorSettingsPage::DeckEditorSettingsPage() networkCacheEdit.setValue(SettingsCache::instance().getNetworkCacheSizeInMB()); networkCacheEdit.setSuffix(" MB"); + networkRedirectCacheTtlEdit.setMinimum(NETWORK_REDIRECT_CACHE_TTL_MIN); + networkRedirectCacheTtlEdit.setMaximum(NETWORK_REDIRECT_CACHE_TTL_MAX); + networkRedirectCacheTtlEdit.setSingleStep(1); + networkRedirectCacheTtlEdit.setValue(SettingsCache::instance().getRedirectCacheTtl()); + networkRedirectCacheTtlEdit.setSuffix(" " + tr("Day(s)")); + auto networkCacheLayout = new QHBoxLayout; networkCacheLayout->addStretch(); networkCacheLayout->addWidget(&networkCacheLabel); networkCacheLayout->addWidget(&networkCacheEdit); + auto networkRedirectCacheLayout = new QHBoxLayout; + networkRedirectCacheLayout->addStretch(); + networkRedirectCacheLayout->addWidget(&networkRedirectCacheTtlLabel); + networkRedirectCacheLayout->addWidget(&networkRedirectCacheTtlEdit); + auto pixmapCacheLayout = new QHBoxLayout; pixmapCacheLayout->addStretch(); pixmapCacheLayout->addWidget(&pixmapCacheLabel); @@ -642,11 +647,12 @@ DeckEditorSettingsPage::DeckEditorSettingsPage() // Top Layout lpGeneralGrid->addWidget(&picDownloadCheckBox, 0, 0); lpGeneralGrid->addWidget(&resetDownloadURLs, 0, 1); - lpGeneralGrid->addLayout(messageListLayout, 1, 0, 1, 2); + lpGeneralGrid->addLayout(urlListLayout, 1, 0, 1, 2); lpGeneralGrid->addLayout(networkCacheLayout, 2, 0, 1, 2); lpGeneralGrid->addLayout(pixmapCacheLayout, 3, 0, 1, 2); - lpGeneralGrid->addWidget(&urlLinkLabel, 4, 0); - lpGeneralGrid->addWidget(&clearDownloadedPicsButton, 4, 1); + lpGeneralGrid->addLayout(networkRedirectCacheLayout, 4, 0, 1, 2); + lpGeneralGrid->addWidget(&urlLinkLabel, 5, 0); + lpGeneralGrid->addWidget(&clearDownloadedPicsButton, 5, 1); // Spoiler Layout lpSpoilerGrid->addWidget(&mcDownloadSpoilersCheckBox, 0, 0); @@ -657,13 +663,15 @@ DeckEditorSettingsPage::DeckEditorSettingsPage() lpSpoilerGrid->addWidget(updateNowButton, 2, 1); lpSpoilerGrid->addWidget(&infoOnSpoilersLabel, 3, 0, 1, 3, Qt::AlignTop); - // On a change to the check box, hide/unhide the other fields + // On a change to the checkbox, hide/un-hide the other fields connect(&mcDownloadSpoilersCheckBox, SIGNAL(toggled(bool)), &SettingsCache::instance(), SLOT(setDownloadSpoilerStatus(bool))); connect(&mcDownloadSpoilersCheckBox, SIGNAL(toggled(bool)), this, SLOT(setSpoilersEnabled(bool))); connect(&pixmapCacheEdit, SIGNAL(valueChanged(int)), &SettingsCache::instance(), SLOT(setPixmapCacheSize(int))); connect(&networkCacheEdit, SIGNAL(valueChanged(int)), &SettingsCache::instance(), SLOT(setNetworkCacheSizeInMB(int))); + connect(&networkRedirectCacheTtlEdit, SIGNAL(valueChanged(int)), &SettingsCache::instance(), + SLOT(setNetworkRedirectCacheTtl(int))); mpGeneralGroupBox = new QGroupBox; mpGeneralGroupBox->setLayout(lpGeneralGrid); @@ -680,7 +688,7 @@ DeckEditorSettingsPage::DeckEditorSettingsPage() void DeckEditorSettingsPage::resetDownloadedURLsButtonClicked() { - SettingsCache::instance().downloads().clear(); + SettingsCache::instance().downloads().resetToDefaultURLs(); urlList->clear(); urlList->addItems(SettingsCache::instance().downloads().getAllURLs()); QMessageBox::information(this, tr("Success"), tr("Download URLs have been reset.")); @@ -760,11 +768,13 @@ void DeckEditorSettingsPage::actEditURL() void DeckEditorSettingsPage::storeSettings() { qInfo() << "URL Priority Reset"; - SettingsCache::instance().downloads().clear(); + + QStringList downloadUrls; for (int i = 0; i < urlList->count(); i++) { qInfo() << "Priority" << i << ":" << urlList->item(i)->text(); - SettingsCache::instance().downloads().setDownloadUrlAt(i, urlList->item(i)->text()); + downloadUrls << urlList->item(i)->text(); } + SettingsCache::instance().downloads().setDownloadUrls(downloadUrls); } void DeckEditorSettingsPage::urlListChanged(const QModelIndex &, int, int, const QModelIndex &, int) @@ -844,39 +854,40 @@ void DeckEditorSettingsPage::retranslateUi() urlLinkLabel.setText(QString("%2").arg(WIKI_CUSTOM_PIC_URL).arg(tr("How to add a custom URL"))); clearDownloadedPicsButton.setText(tr("Delete Downloaded Images")); resetDownloadURLs.setText(tr("Reset Download URLs")); - networkCacheLabel.setText(tr("Downloaded images directory size:")); + networkCacheLabel.setText(tr("Network Cache Size:")); networkCacheEdit.setToolTip(tr("On-disk cache for downloaded pictures")); - pixmapCacheLabel.setText(tr("Picture cache size:")); + networkRedirectCacheTtlLabel.setText(tr("Redirect Cache TTL:")); + networkRedirectCacheTtlEdit.setToolTip(tr("How long cached redirects for urls are valid for.")); + pixmapCacheLabel.setText(tr("Picture Cache Size:")); pixmapCacheEdit.setToolTip(tr("In-memory cache for pictures not currently on screen")); } MessagesSettingsPage::MessagesSettingsPage() { chatMentionCheckBox.setChecked(SettingsCache::instance().getChatMention()); - connect(&chatMentionCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), - SLOT(setChatMention(QT_STATE_CHANGED_T))); + connect(&chatMentionCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setChatMention); chatMentionCompleterCheckbox.setChecked(SettingsCache::instance().getChatMentionCompleter()); - connect(&chatMentionCompleterCheckbox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), - SLOT(setChatMentionCompleter(QT_STATE_CHANGED_T))); + connect(&chatMentionCompleterCheckbox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setChatMentionCompleter); explainMessagesLabel.setTextInteractionFlags(Qt::LinksAccessibleByMouse); explainMessagesLabel.setOpenExternalLinks(true); ignoreUnregUsersMainChat.setChecked(SettingsCache::instance().getIgnoreUnregisteredUsers()); ignoreUnregUserMessages.setChecked(SettingsCache::instance().getIgnoreUnregisteredUserMessages()); - connect(&ignoreUnregUsersMainChat, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), - SLOT(setIgnoreUnregisteredUsers(QT_STATE_CHANGED_T))); - connect(&ignoreUnregUserMessages, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), - SLOT(setIgnoreUnregisteredUserMessages(QT_STATE_CHANGED_T))); + connect(&ignoreUnregUsersMainChat, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setIgnoreUnregisteredUsers); + connect(&ignoreUnregUserMessages, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setIgnoreUnregisteredUserMessages); invertMentionForeground.setChecked(SettingsCache::instance().getChatMentionForeground()); - connect(&invertMentionForeground, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), this, - SLOT(updateTextColor(QT_STATE_CHANGED_T))); + connect(&invertMentionForeground, &QCheckBox::QT_STATE_CHANGED, this, &MessagesSettingsPage::updateTextColor); invertHighlightForeground.setChecked(SettingsCache::instance().getChatHighlightForeground()); - connect(&invertHighlightForeground, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), this, - SLOT(updateTextHighlightColor(QT_STATE_CHANGED_T))); + connect(&invertHighlightForeground, &QCheckBox::QT_STATE_CHANGED, this, + &MessagesSettingsPage::updateTextHighlightColor); mentionColor = new QLineEdit(); mentionColor->setText(SettingsCache::instance().getChatMentionColor()); @@ -884,16 +895,15 @@ MessagesSettingsPage::MessagesSettingsPage() connect(mentionColor, SIGNAL(textChanged(QString)), this, SLOT(updateColor(QString))); messagePopups.setChecked(SettingsCache::instance().getShowMessagePopup()); - connect(&messagePopups, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), - SLOT(setShowMessagePopups(QT_STATE_CHANGED_T))); + connect(&messagePopups, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setShowMessagePopups); mentionPopups.setChecked(SettingsCache::instance().getShowMentionPopup()); - connect(&mentionPopups, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), - SLOT(setShowMentionPopups(QT_STATE_CHANGED_T))); + connect(&mentionPopups, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setShowMentionPopups); roomHistory.setChecked(SettingsCache::instance().getRoomHistory()); - connect(&roomHistory, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), - SLOT(setRoomHistory(QT_STATE_CHANGED_T))); + connect(&roomHistory, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), &SettingsCache::setRoomHistory); customAlertString = new QLineEdit(); customAlertString->setPlaceholderText(tr("Word1 Word2 Word3")); @@ -1095,8 +1105,8 @@ void MessagesSettingsPage::retranslateUi() SoundSettingsPage::SoundSettingsPage() { soundEnabledCheckBox.setChecked(SettingsCache::instance().getSoundEnabled()); - connect(&soundEnabledCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), - SLOT(setSoundEnabled(QT_STATE_CHANGED_T))); + connect(&soundEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setSoundEnabled); QString themeName = SettingsCache::instance().getSoundThemeName(); diff --git a/cockatrice/src/dialogs/dlg_settings.h b/cockatrice/src/dialogs/dlg_settings.h index 70915cc0e..fdd114274 100644 --- a/cockatrice/src/dialogs/dlg_settings.h +++ b/cockatrice/src/dialogs/dlg_settings.h @@ -113,7 +113,7 @@ class UserInterfaceSettingsPage : public AbstractSettingsPage { Q_OBJECT private slots: - void setNotificationEnabled(int); + void setNotificationEnabled(QT_STATE_CHANGED_T); private: QCheckBox notificationsEnabledCheckBox; @@ -174,6 +174,8 @@ private: QPushButton *updateNowButton; QLabel networkCacheLabel; QSpinBox networkCacheEdit; + QLabel networkRedirectCacheTtlLabel; + QSpinBox networkRedirectCacheTtlEdit; QSpinBox pixmapCacheEdit; QLabel pixmapCacheLabel; }; diff --git a/cockatrice/src/game/board/abstract_graphics_item.h b/cockatrice/src/game/board/abstract_graphics_item.h index 8fef7bb79..3461c35b3 100644 --- a/cockatrice/src/game/board/abstract_graphics_item.h +++ b/cockatrice/src/game/board/abstract_graphics_item.h @@ -13,6 +13,9 @@ enum GraphicsItemType typeOther = QGraphicsItem::UserType + 6 }; +/** + * Parent class of all objects that appear in a game. + */ class AbstractGraphicsItem : public QObject, public QGraphicsItem { Q_OBJECT diff --git a/cockatrice/src/game/cards/card_list.cpp b/cockatrice/src/game/cards/card_list.cpp index e2866e5d8..a4499a98c 100644 --- a/cockatrice/src/game/cards/card_list.cpp +++ b/cockatrice/src/game/cards/card_list.cpp @@ -3,61 +3,152 @@ #include "card_database.h" #include "card_item.h" +#include #include CardList::CardList(bool _contentsKnown) : QList(), contentsKnown(_contentsKnown) { } -CardItem *CardList::findCard(const int id, const bool remove, int *position) +/** + * @brief Finds the CardItem with the given id in the list. + * If contentsKnown is false, then this just returns the first element of the list. + * + * @param cardId The id of the card to find. + * + * @returns A pointer to the CardItem, or a nullptr if not found. + */ +CardItem *CardList::findCard(const int cardId) const { - if (!contentsKnown) { - if (empty()) - return 0; - CardItem *temp = at(0); - if (remove) - removeAt(0); - if (position) - *position = id; - return temp; - } else - for (int i = 0; i < size(); i++) { - CardItem *temp = at(i); - if (temp->getId() == id) { - if (remove) - removeAt(i); - if (position) - *position = i; - return temp; + if (!contentsKnown && !empty()) { + return at(0); + } else { + for (auto *cardItem : *this) { + if (cardItem->getId() == cardId) { + return cardItem; } } - return 0; + } + return nullptr; } -class CardList::compareFunctor +/** + * @brief sorts the list by using string comparison on properties extracted from the CardItem + * The cards are compared using each property in order. + * If two cards have the same value for a property, then the next property in the list is used. + * + * @param option the option to compare the cards by, in order of usage. + */ +void CardList::sortBy(const QList &option) { -private: - int flags; - -public: - explicit compareFunctor(int _flags) : flags(_flags) - { + // early return if we know we won't be sorting + if (option.isEmpty()) { + return; } - inline bool operator()(CardItem *a, CardItem *b) const - { - if (flags & SortByType) { - QString t1 = a->getInfo() ? a->getInfo()->getMainCardType() : QString(); - QString t2 = b->getInfo() ? b->getInfo()->getMainCardType() : QString(); - if ((t1 == t2) && (flags & SortByName)) - return a->getName() < b->getName(); - return t1 < t2; - } else - return a->getName() < b->getName(); - } -}; -void CardList::sort(int flags) -{ - compareFunctor cf(flags); - std::sort(begin(), end(), cf); + auto comparator = [&option](CardItem *a, CardItem *b) { + for (auto prop : option) { + auto extractor = getExtractorFor(prop); + QString t1 = extractor(a); + QString t2 = extractor(b); + if (t1 != t2) { + return t1 < t2; + } + } + return false; + }; + + std::sort(begin(), end(), comparator); } + +/** + * Creates a String for the card such that when sorting cards using that string, it will result in the + * following sort order: + * - Unrecognized colors + * - Land cards + * - Colorless cards + * - Monocolor cards, in wubrg order + * - Monocolor cards of any custom colors + * - 2C cards (no internal order) + * - 3C cards (no internal order) + * - 4C cards (no internal order) + * - 5C cards (no internal order) + * + * @param c The card info + * @param appendAtEnd For multicolor cards, whether to also append the entire color string at the end. + */ +static QString getColorSortString(CardInfoPtr c, bool appendAtEnd) +{ + QString colors = c->getColors(); + switch (colors.size()) { + case 0: { + if (c->getCardType().contains("Land")) { + return "a_land"; + } else { + return "b_colorless"; + } + } + case 1: + // force wubrg order + switch (colors.at(0).toLatin1()) { + case 'W': + return "c_W"; + case 'U': + return "d_U"; + case 'B': + return "e_B"; + case 'R': + return "f_R"; + case 'G': + return "g_G"; + default: + // handle any custom colors + return QString("h_%1").arg(colors.at(0)); + } + default: + return QString("i%1_%2").arg(colors.size()).arg(appendAtEnd ? colors : ""); + } +} + +/** + * @brief returns the function that extracts the given property from the CardItem. + */ +std::function CardList::getExtractorFor(SortOption option) +{ + switch (option) { + case NoSort: + return [](CardItem *) { return ""; }; + case SortByMainType: + return [](CardItem *c) { return c->getInfo() ? c->getInfo()->getMainCardType() : ""; }; + case SortByManaValue: + // getCmc returns the int as a string. We pad with 0's so that string comp also works on it + return [](CardItem *c) { return c->getInfo() ? c->getInfo()->getCmc().rightJustified(4, '0') : ""; }; + case SortByColorGrouping: + return [](CardItem *c) { return c->getInfo() ? getColorSortString(c->getInfo(), false) : ""; }; + case SortByName: + return [](CardItem *c) { return c->getName(); }; + case SortByType: + return [](CardItem *c) { return c->getInfo() ? c->getInfo()->getCardType() : ""; }; + case SortByManaCost: + return [](CardItem *c) { + auto info = c->getInfo(); + if (!info) + return QString(""); + + // calculation copied from CardDatabaseModel. + // we pad the cmc and also append the mana cost to the end so same cmc cards still have a sort order + return QString("%1%2").arg(info->getCmc(), 4, QChar('0')).arg(info->getManaCost()); + }; + case SortByColors: + return [](CardItem *c) { return c->getInfo() ? getColorSortString(c->getInfo(), true) : ""; }; + case SortByPt: + // do the same padding trick as above + return [](CardItem *c) { return c->getInfo() ? c->getInfo()->getPowTough().rightJustified(10, '0') : ""; }; + case SortBySet: + return [](CardItem *c) { return c->getInfo() ? c->getInfo()->getSetsNames() : ""; }; + } + + // this line should never be reached + qDebug() << "cardlist.cpp: Could not find extractor for SortOption" << option; + return [](CardItem *) { return ""; }; +} \ No newline at end of file diff --git a/cockatrice/src/game/cards/card_list.h b/cockatrice/src/game/cards/card_list.h index 1835ca96e..5b44764d5 100644 --- a/cockatrice/src/game/cards/card_list.h +++ b/cockatrice/src/game/cards/card_list.h @@ -7,25 +7,39 @@ class CardItem; class CardList : public QList { -private: - class compareFunctor; - protected: bool contentsKnown; public: - enum SortFlags + enum SortOption { - SortByName = 1, - SortByType = 2 + NoSort, + + // Options that are used by groupBy + // Should partition all cards into a reasonable number of buckets + SortByMainType, + SortByManaValue, + SortByColorGrouping, + + // Options that are used by sortBy + // We don't care about buckets; we want as many distinct values as possible. + SortByName, + SortByType, + SortByManaCost, + SortByColors, + SortByPt, + SortBySet }; CardList(bool _contentsKnown); - CardItem *findCard(const int id, const bool remove, int *position = NULL); + CardItem *findCard(const int cardId) const; bool getContentsKnown() const { return contentsKnown; } - void sort(int flags = SortByName); + + void sortBy(const QList &options); + + static std::function getExtractorFor(SortOption option); }; #endif diff --git a/cockatrice/src/game/player/player.cpp b/cockatrice/src/game/player/player.cpp index dd98305ff..71402fc61 100644 --- a/cockatrice/src/game/player/player.cpp +++ b/cockatrice/src/game/player/player.cpp @@ -1893,7 +1893,8 @@ void Player::setCardAttrHelper(const GameEventContext &context, CardItem *card, CardAttribute attribute, const QString &avalue, - bool allCards) + bool allCards, + EventProcessingOptions options) { if (card == nullptr) { return; @@ -1907,7 +1908,8 @@ void Player::setCardAttrHelper(const GameEventContext &context, if (!allCards) { emit logSetTapped(this, card, tapped); } - card->setTapped(tapped, !moveCardContext); + bool canAnimate = !options.testFlag(SKIP_TAP_ANIMATION) && !moveCardContext; + card->setTapped(tapped, canAnimate); } break; } @@ -2049,7 +2051,9 @@ void Player::eventCreateToken(const Event_CreateToken &event) zone->addCard(card, true, event.x(), event.y()); } -void Player::eventSetCardAttr(const Event_SetCardAttr &event, const GameEventContext &context) +void Player::eventSetCardAttr(const Event_SetCardAttr &event, + const GameEventContext &context, + EventProcessingOptions options) { CardZone *zone = zones.value(QString::fromStdString(event.zone_name()), 0); if (!zone) { @@ -2059,8 +2063,8 @@ void Player::eventSetCardAttr(const Event_SetCardAttr &event, const GameEventCon if (!event.has_card_id()) { const CardList &cards = zone->getCards(); for (int i = 0; i < cards.size(); ++i) { - setCardAttrHelper(context, cards.at(i), event.attribute(), QString::fromStdString(event.attr_value()), - true); + setCardAttrHelper(context, cards.at(i), event.attribute(), QString::fromStdString(event.attr_value()), true, + options); } if (event.attribute() == AttrTapped) { emit logSetTapped(this, nullptr, event.attr_value() == "1"); @@ -2071,7 +2075,7 @@ void Player::eventSetCardAttr(const Event_SetCardAttr &event, const GameEventCon qWarning() << "Player::eventSetCardAttr: card id=" << event.card_id() << "not found"; return; } - setCardAttrHelper(context, card, event.attribute(), QString::fromStdString(event.attr_value()), false); + setCardAttrHelper(context, card, event.attribute(), QString::fromStdString(event.attr_value()), false, options); } } @@ -2444,7 +2448,7 @@ void Player::processGameEvent(GameEvent::GameEventType type, eventCreateToken(event.GetExtension(Event_CreateToken::ext)); break; case GameEvent::SET_CARD_ATTR: - eventSetCardAttr(event.GetExtension(Event_SetCardAttr::ext), context); + eventSetCardAttr(event.GetExtension(Event_SetCardAttr::ext), context, options); break; case GameEvent::SET_CARD_COUNTER: eventSetCardCounter(event.GetExtension(Event_SetCardCounter::ext)); diff --git a/cockatrice/src/game/player/player.h b/cockatrice/src/game/player/player.h index 832e613b7..7f744a909 100644 --- a/cockatrice/src/game/player/player.h +++ b/cockatrice/src/game/player/player.h @@ -68,6 +68,9 @@ class ZoneViewZone; const int MAX_TOKENS_PER_DIALOG = 99; +/** + * The entire graphical area belonging to a single player. + */ class PlayerArea : public QObject, public QGraphicsItem { Q_OBJECT @@ -229,7 +232,8 @@ private slots: public: enum EventProcessingOption { - SKIP_REVEAL_WINDOW = 0x0001 + SKIP_REVEAL_WINDOW = 0x0001, + SKIP_TAP_ANIMATION = 0x0002 }; Q_DECLARE_FLAGS(EventProcessingOptions, EventProcessingOption) @@ -301,7 +305,8 @@ private: CardItem *card, CardAttribute attribute, const QString &avalue, - bool allCards); + bool allCards, + EventProcessingOptions options); void addRelatedCardActions(const CardItem *card, QMenu *cardMenu); void addRelatedCardView(const CardItem *card, QMenu *cardMenu); void createCard(const CardItem *sourceCard, @@ -328,7 +333,8 @@ private: void eventCreateArrow(const Event_CreateArrow &event); void eventDeleteArrow(const Event_DeleteArrow &event); void eventCreateToken(const Event_CreateToken &event); - void eventSetCardAttr(const Event_SetCardAttr &event, const GameEventContext &context); + void + eventSetCardAttr(const Event_SetCardAttr &event, const GameEventContext &context, EventProcessingOptions options); void eventSetCardCounter(const Event_SetCardCounter &event); void eventCreateCounter(const Event_CreateCounter &event); void eventSetCounter(const Event_SetCounter &event); diff --git a/cockatrice/src/game/zones/card_zone.cpp b/cockatrice/src/game/zones/card_zone.cpp index 13732f960..ad0175506 100644 --- a/cockatrice/src/game/zones/card_zone.cpp +++ b/cockatrice/src/game/zones/card_zone.cpp @@ -11,6 +11,14 @@ #include #include +/** + * @param _p the player that the zone belongs to + * @param _name internal name of the zone + * @param _isShufflable whether it makes sense to shuffle this zone by default after viewing it + * @param _contentsKnown whether the cards in the zone are known to the client + * @param parent the parent graphics object. + * @param _isView whether this zone is a view of another zone. Modifications to a view should modify the original + */ CardZone::CardZone(Player *_p, const QString &_name, bool _hasCardAttr, @@ -142,7 +150,7 @@ void CardZone::addCard(CardItem *card, bool reorganize, int x, int y) CardItem *CardZone::getCard(int cardId, const QString &cardName) { - CardItem *c = cards.findCard(cardId, false); + CardItem *c = cards.findCard(cardId); if (!c) { qDebug() << "CardZone::getCard: card id=" << cardId << "not found"; return 0; diff --git a/cockatrice/src/game/zones/card_zone.h b/cockatrice/src/game/zones/card_zone.h index c0ad27bc3..6d6cddd81 100644 --- a/cockatrice/src/game/zones/card_zone.h +++ b/cockatrice/src/game/zones/card_zone.h @@ -14,6 +14,12 @@ class QAction; class QPainter; class CardDragItem; +/** + * A zone in the game that can contain cards. + * This class contains methods to get and modify the cards that are contained inside this zone. + * + * The cards are stored as a list of `CardItem*`. + */ class CardZone : public AbstractGraphicsItem { Q_OBJECT diff --git a/cockatrice/src/game/zones/pile_zone.h b/cockatrice/src/game/zones/pile_zone.h index 5d28b3816..a66543ab3 100644 --- a/cockatrice/src/game/zones/pile_zone.h +++ b/cockatrice/src/game/zones/pile_zone.h @@ -3,6 +3,10 @@ #include "card_zone.h" +/** + * A CardZone where the cards are in a single pile instead of being laid out. + * Usually only top card is accessible by clicking. + */ class PileZone : public CardZone { Q_OBJECT diff --git a/cockatrice/src/game/zones/select_zone.h b/cockatrice/src/game/zones/select_zone.h index efec4bb02..ff0c12182 100644 --- a/cockatrice/src/game/zones/select_zone.h +++ b/cockatrice/src/game/zones/select_zone.h @@ -3,6 +3,9 @@ #include "card_zone.h" +/** + * A CardZone where the cards are laid out, with each card directly interactable by clicking. + */ class SelectZone : public CardZone { Q_OBJECT diff --git a/cockatrice/src/game/zones/view_zone.cpp b/cockatrice/src/game/zones/view_zone.cpp index 6d4908179..0187e8ee5 100644 --- a/cockatrice/src/game/zones/view_zone.cpp +++ b/cockatrice/src/game/zones/view_zone.cpp @@ -16,6 +16,13 @@ #include #include +/** + * @param _p the player that the cards are revealed to. + * @param _origZone the zone the cards were revealed from. + * @param _revealZone if false, the cards will be face down. + * @param _writeableRevealZone whether the player can interact with the revealed cards. + * @param parent the parent QGraphicsWidget containing the reveal zone + */ ZoneViewZone::ZoneViewZone(Player *_p, CardZone *_origZone, int _numberCards, @@ -24,7 +31,7 @@ ZoneViewZone::ZoneViewZone(Player *_p, QGraphicsItem *parent) : SelectZone(_p, _origZone->getName(), false, false, true, parent, true), bRect(QRectF()), minRows(0), numberCards(_numberCards), origZone(_origZone), revealZone(_revealZone), - writeableRevealZone(_writeableRevealZone), sortByName(false), sortByType(false) + writeableRevealZone(_writeableRevealZone), groupBy(CardList::NoSort), sortBy(CardList::NoSort) { if (!(revealZone && !writeableRevealZone)) { origZone->getViews().append(this); @@ -105,85 +112,127 @@ void ZoneViewZone::reorganizeCards() for (int i = 0; i < cardCount; ++i) cards[i]->setId(i); - int cols = qFloor(qSqrt((double)cardCount / 2)); - if (cols > 7) - cols = 7; - int rows = qCeil((double)cardCount / cols); - if (rows < 1) - rows = 1; - if (minRows == 0) - minRows = rows; - else if (rows < minRows) { - rows = minRows; - cols = qCeil((double)cardCount / minRows); - } - if (cols < 2) - cols = 2; - - qDebug() << "reorganizeCards: rows=" << rows << "cols=" << cols; - CardList cardsToDisplay(cards); - if (sortByName || sortByType) - cardsToDisplay.sort((sortByName ? CardList::SortByName : 0) | (sortByType ? CardList::SortByType : 0)); - int typeColumn = 0; - int longestRow = 0; - if (pileView && sortByType) { // we need sort by type enabled for the feature to work - int typeRow = 0; - QString lastCardType; - for (int i = 0; i < cardCount; i++) { - CardItem *c = cardsToDisplay.at(i); - QString cardType = c->getInfo() ? c->getInfo()->getMainCardType() : ""; + // sort cards + QList sortOptions; + if (groupBy != CardList::NoSort) { + sortOptions << groupBy; + } - if (i) { // if not the first card - if (cardType == lastCardType) - typeRow++; // add below current card - else { // if no match then move card to next column - typeColumn++; - typeRow = 0; - } - } + if (sortBy != CardList::NoSort) { + sortOptions << sortBy; - lastCardType = cardType; - qreal x = 7 + (typeColumn * CARD_WIDTH); - qreal y = typeRow * CARD_HEIGHT / 3; - c->setPos(x + 5, y + 5); - c->setRealZValue(i); - longestRow = qMax(typeRow, longestRow); - } - } else { - for (int i = 0; i < cardCount; i++) { - CardItem *c = cardsToDisplay.at(i); - qreal x = 7 + ((i / rows) * CARD_WIDTH); - qreal y = (i % rows) * CARD_HEIGHT / 3; - c->setPos(x + 5, y + 5); - c->setRealZValue(i); + // implicitly sort by name at the end so that cards with the same name appear together + if (sortBy != CardList::SortByName) { + sortOptions << CardList::SortByName; } } + cardsToDisplay.sortBy(sortOptions); + + // position cards + GridSize gridSize; + if (pileView) { + gridSize = positionCardsForDisplay(cardsToDisplay, groupBy); + } else { + gridSize = positionCardsForDisplay(cardsToDisplay); + } + + // determine bounding rect qreal aleft = 0; qreal atop = 0; - qreal awidth = (pileView && sortByType) ? qMax(typeColumn + 1, 3) * CARD_WIDTH + (CARD_WIDTH / 2) - : qMax(cols, 1) * CARD_WIDTH + (CARD_WIDTH / 2); - qreal aheight = (pileView && sortByType) ? (longestRow * CARD_HEIGHT) / 3 + CARD_HEIGHT * 1.3 - : (rows * CARD_HEIGHT) / 3 + CARD_HEIGHT * 1.3; + qreal awidth = gridSize.cols * CARD_WIDTH + (CARD_WIDTH / 2) + HORIZONTAL_PADDING; + qreal aheight = (gridSize.rows * CARD_HEIGHT) / 3 + CARD_HEIGHT * 1.3; optimumRect = QRectF(aleft, atop, awidth, aheight); updateGeometry(); emit optimumRectChanged(); } -void ZoneViewZone::setSortByName(int _sortByName) +/** + * @brief Sets the position of each card to the proper position for the view + * + * @param cards The cards to reposition. Will modify the cards in the list. + * @param pileOption Property used to group cards for the piles. Expects `cards` to be sorted by that property. Pass in + * NoSort to not make piles. + * + * @returns The number of rows and columns to display + */ +ZoneViewZone::GridSize ZoneViewZone::positionCardsForDisplay(CardList &cards, CardList::SortOption pileOption) { - sortByName = _sortByName; + int cardCount = cards.size(); + + if (pileOption != CardList::NoSort) { + int row = 0; + int col = 0; + int longestRow = 0; + + QString lastColumnProp; + + const auto extractor = CardList::getExtractorFor(pileOption); + + for (int i = 0; i < cardCount; i++) { + CardItem *c = cards.at(i); + QString columnProp = extractor(c); + + if (i) { // if not the first card + if (columnProp == lastColumnProp) + row++; // add below current card + else { // if no match then move card to next column + col++; + row = 0; + } + } + + lastColumnProp = columnProp; + qreal x = col * CARD_WIDTH; + qreal y = row * CARD_HEIGHT / 3; + c->setPos(HORIZONTAL_PADDING + x, VERTICAL_PADDING + y); + c->setRealZValue(i); + longestRow = qMax(row, longestRow); + } + + // +1 because the row/col variables used in the calculations are 0-indexed but + // GridSize expects the actual row/col count + return GridSize{longestRow + 1, qMax(col + 1, 3)}; + + } else { + int cols = qBound(1, qFloor(qSqrt((double)cardCount / 2)), 7); + int rows = qMax(qCeil((double)cardCount / cols), 1); + if (minRows == 0) { + minRows = rows; + } else if (rows < minRows) { + rows = minRows; + cols = qCeil((double)cardCount / minRows); + } + + if (cols < 2) + cols = 2; + + qDebug() << "reorganizeCards: rows=" << rows << "cols=" << cols; + + for (int i = 0; i < cardCount; i++) { + CardItem *c = cards.at(i); + qreal x = (i / rows) * CARD_WIDTH; + qreal y = (i % rows) * CARD_HEIGHT / 3; + c->setPos(HORIZONTAL_PADDING + x, VERTICAL_PADDING + y); + c->setRealZValue(i); + } + + return GridSize{rows, qMax(cols, 1)}; + } +} + +void ZoneViewZone::setGroupBy(CardList::SortOption _groupBy) +{ + groupBy = _groupBy; reorganizeCards(); } -void ZoneViewZone::setSortByType(int _sortByType) +void ZoneViewZone::setSortBy(CardList::SortOption _sortBy) { - sortByType = _sortByType; - if (!sortByType) - pileView = false; + sortBy = _sortBy; reorganizeCards(); } diff --git a/cockatrice/src/game/zones/view_zone.h b/cockatrice/src/game/zones/view_zone.h index a0e17b6c3..b1f5270b6 100644 --- a/cockatrice/src/game/zones/view_zone.h +++ b/cockatrice/src/game/zones/view_zone.h @@ -10,19 +10,39 @@ class Response; class ServerInfo_Card; class QGraphicsSceneWheelEvent; +/** + * A CardZone that is a view into another CardZone. + * If this zone is writable, then modifications to this zone will be reflected in the underlying zone. + * + * Most interactions with StackZones are handled through a zone view. + * For example, viewing the deck/graveyard/exile is handled through a view. + * + * Handles both limited reveals (eg. look at top X cards) and full zone reveals (eg. searching the deck). + */ class ZoneViewZone : public SelectZone, public QGraphicsLayoutItem { Q_OBJECT Q_INTERFACES(QGraphicsLayoutItem) private: + static constexpr int HORIZONTAL_PADDING = 12; + static constexpr int VERTICAL_PADDING = 5; + QRectF bRect, optimumRect; int minRows, numberCards; void handleDropEvent(const QList &dragItems, CardZone *startZone, const QPoint &dropPoint); CardZone *origZone; bool revealZone, writeableRevealZone; - bool sortByName, sortByType; + CardList::SortOption groupBy, sortBy; bool pileView; + struct GridSize + { + int rows; + int cols; + }; + + GridSize positionCardsForDisplay(CardList &cards, CardList::SortOption pileOption = CardList::NoSort); + public: ZoneViewZone(Player *_p, CardZone *_origZone, @@ -55,8 +75,8 @@ public: } void setWriteableRevealZone(bool _writeableRevealZone); public slots: - void setSortByName(int _sortByName); - void setSortByType(int _sortByType); + void setGroupBy(CardList::SortOption _groupBy); + void setSortBy(CardList::SortOption _sortBy); void setPileView(int _pileView); private slots: void zoneDumpReceived(const Response &r); diff --git a/cockatrice/src/game/zones/view_zone_widget.cpp b/cockatrice/src/game/zones/view_zone_widget.cpp index 974073a88..e8276898b 100644 --- a/cockatrice/src/game/zones/view_zone_widget.cpp +++ b/cockatrice/src/game/zones/view_zone_widget.cpp @@ -17,6 +17,15 @@ #include #include +/** + * @param _player the player the cards were revealed to. + * @param _origZone the zone the cards were revealed from. + * @param numberCards num of cards to reveal from the zone. Ex: scry the top 3 cards. + * Pass in a negative number to reveal the entire zone. + * -1 specifically will give the option to shuffle the zone upon closing the window. + * @param _revealZone if false, the cards will be face down. + * @param _writeableRevealZone whether the player can interact with the revealed cards. + */ ZoneViewWidget::ZoneViewWidget(Player *_player, CardZone *_origZone, int numberCards, @@ -31,22 +40,27 @@ ZoneViewWidget::ZoneViewWidget(Player *_player, setFlag(ItemIgnoresTransformations); QGraphicsLinearLayout *vbox = new QGraphicsLinearLayout(Qt::Vertical); - QGraphicsLinearLayout *hPilebox = 0; + // If the number is < 0, then it means that we can give the option to make the area sorted if (numberCards < 0) { - hPilebox = new QGraphicsLinearLayout(Qt::Horizontal); - QGraphicsLinearLayout *hFilterbox = new QGraphicsLinearLayout(Qt::Horizontal); + // top row + QGraphicsLinearLayout *hTopRow = new QGraphicsLinearLayout(Qt::Horizontal); - QGraphicsProxyWidget *sortByNameProxy = new QGraphicsProxyWidget; - sortByNameProxy->setWidget(&sortByNameCheckBox); - hFilterbox->addItem(sortByNameProxy); + // groupBy options + QGraphicsProxyWidget *groupBySelectorProxy = new QGraphicsProxyWidget; + groupBySelectorProxy->setWidget(&groupBySelector); + groupBySelectorProxy->setZValue(2000000008); + hTopRow->addItem(groupBySelectorProxy); - QGraphicsProxyWidget *sortByTypeProxy = new QGraphicsProxyWidget; - sortByTypeProxy->setWidget(&sortByTypeCheckBox); - hFilterbox->addItem(sortByTypeProxy); + // sortBy options + QGraphicsProxyWidget *sortBySelectorProxy = new QGraphicsProxyWidget; + sortBySelectorProxy->setWidget(&sortBySelector); + sortBySelectorProxy->setZValue(2000000007); + hTopRow->addItem(sortBySelectorProxy); - vbox->addItem(hFilterbox); + vbox->addItem(hTopRow); + // line QGraphicsProxyWidget *lineProxy = new QGraphicsProxyWidget; QFrame *line = new QFrame; line->setFrameShape(QFrame::HLine); @@ -54,19 +68,24 @@ ZoneViewWidget::ZoneViewWidget(Player *_player, lineProxy->setWidget(line); vbox->addItem(lineProxy); + // bottom row + QGraphicsLinearLayout *hBottomRow = new QGraphicsLinearLayout(Qt::Horizontal); + + // pile view options QGraphicsProxyWidget *pileViewProxy = new QGraphicsProxyWidget; pileViewProxy->setWidget(&pileViewCheckBox); - hPilebox->addItem(pileViewProxy); - } + hBottomRow->addItem(pileViewProxy); - if (_origZone->getIsShufflable() && (numberCards == -1)) { - shuffleCheckBox.setChecked(true); - QGraphicsProxyWidget *shuffleProxy = new QGraphicsProxyWidget; - shuffleProxy->setWidget(&shuffleCheckBox); - hPilebox->addItem(shuffleProxy); - } + // shuffle options + if (_origZone->getIsShufflable() && numberCards == -1) { + shuffleCheckBox.setChecked(true); + QGraphicsProxyWidget *shuffleProxy = new QGraphicsProxyWidget; + shuffleProxy->setWidget(&shuffleCheckBox); + hBottomRow->addItem(shuffleProxy); + } - vbox->addItem(hPilebox); + vbox->addItem(hBottomRow); + } extraHeight = vbox->sizeHint(Qt::PreferredSize).height(); resize(150, 150); @@ -93,42 +112,71 @@ ZoneViewWidget::ZoneViewWidget(Player *_player, connect(zone, SIGNAL(wheelEventReceived(QGraphicsSceneWheelEvent *)), scrollBarProxy, SLOT(recieveWheelEvent(QGraphicsSceneWheelEvent *))); - // numberCard is the num of cards we want to reveal from an area. Ex: scry the top 3 cards. - // If the number is < 0 then it means that we can make the area sorted and we dont care about the order. + retranslateUi(); + + // only wire up sort options after creating ZoneViewZone, since it segfaults otherwise. if (numberCards < 0) { - connect(&sortByNameCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), this, - SLOT(processSortByName(QT_STATE_CHANGED_T))); - connect(&sortByTypeCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), this, - SLOT(processSortByType(QT_STATE_CHANGED_T))); - connect(&pileViewCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), this, - SLOT(processSetPileView(QT_STATE_CHANGED_T))); - sortByNameCheckBox.setChecked(SettingsCache::instance().getZoneViewSortByName()); - sortByTypeCheckBox.setChecked(SettingsCache::instance().getZoneViewSortByType()); + connect(&groupBySelector, static_cast(&QComboBox::currentIndexChanged), this, + &ZoneViewWidget::processGroupBy); + connect(&sortBySelector, static_cast(&QComboBox::currentIndexChanged), this, + &ZoneViewWidget::processSortBy); + connect(&pileViewCheckBox, &QCheckBox::QT_STATE_CHANGED, this, &ZoneViewWidget::processSetPileView); + groupBySelector.setCurrentIndex(SettingsCache::instance().getZoneViewGroupByIndex()); + sortBySelector.setCurrentIndex(SettingsCache::instance().getZoneViewSortByIndex()); pileViewCheckBox.setChecked(SettingsCache::instance().getZoneViewPileView()); - if (!SettingsCache::instance().getZoneViewSortByType()) + + if (CardList::NoSort == static_cast(groupBySelector.currentData().toInt())) { pileViewCheckBox.setEnabled(false); + } } - retranslateUi(); setLayout(vbox); connect(zone, SIGNAL(optimumRectChanged()), this, SLOT(resizeToZoneContents())); connect(zone, SIGNAL(beingDeleted()), this, SLOT(zoneDeleted())); zone->initializeCards(cardList); + + // QLabel sizes aren't taken into account until the widget is rendered. + // Force refresh after 1ms to fix glitchy rendering with long QLabels. + auto *lastResizeBeforeVisibleTimer = new QTimer(this); + connect(lastResizeBeforeVisibleTimer, &QTimer::timeout, this, [=] { + resizeToZoneContents(); + disconnect(lastResizeBeforeVisibleTimer); + lastResizeBeforeVisibleTimer->deleteLater(); + }); + lastResizeBeforeVisibleTimer->setSingleShot(true); + lastResizeBeforeVisibleTimer->start(1); } -void ZoneViewWidget::processSortByType(QT_STATE_CHANGED_T value) +void ZoneViewWidget::processGroupBy(int index) { - pileViewCheckBox.setEnabled(value); - SettingsCache::instance().setZoneViewSortByType(value); - zone->setPileView(pileViewCheckBox.isChecked()); - zone->setSortByType(value); + auto option = static_cast(groupBySelector.itemData(index).toInt()); + SettingsCache::instance().setZoneViewGroupByIndex(index); + zone->setGroupBy(option); + + // disable pile view checkbox if we're not grouping by anything + pileViewCheckBox.setEnabled(option != CardList::NoSort); + + // reset sortBy if it has the same value as groupBy + if (option != CardList::NoSort && + option == static_cast(sortBySelector.currentData().toInt())) { + sortBySelector.setCurrentIndex(1); // set to SortByName + } } -void ZoneViewWidget::processSortByName(QT_STATE_CHANGED_T value) +void ZoneViewWidget::processSortBy(int index) { - SettingsCache::instance().setZoneViewSortByName(value); - zone->setSortByName(value); + auto option = static_cast(sortBySelector.itemData(index).toInt()); + + // set to SortByName instead if it has the same value as groupBy + if (option != CardList::NoSort && + option == static_cast(groupBySelector.currentData().toInt())) { + sortBySelector.setCurrentIndex(1); // set to SortByName + return; + } + + SettingsCache::instance().setZoneViewSortByIndex(index); + zone->setSortBy(option); } void ZoneViewWidget::processSetPileView(QT_STATE_CHANGED_T value) @@ -140,8 +188,30 @@ void ZoneViewWidget::processSetPileView(QT_STATE_CHANGED_T value) void ZoneViewWidget::retranslateUi() { setWindowTitle(zone->getTranslatedName(false, CaseNominative)); - sortByNameCheckBox.setText(tr("sort by name")); - sortByTypeCheckBox.setText(tr("sort by type")); + + { // We can't change the strings after they're put into the QComboBox, so this is our workaround + int oldIndex = groupBySelector.currentIndex(); + groupBySelector.clear(); + groupBySelector.addItem(tr("Group by ---"), CardList::NoSort); + groupBySelector.addItem(tr("Group by Type"), CardList::SortByMainType); + groupBySelector.addItem(tr("Group by Mana Value"), CardList::SortByManaValue); + groupBySelector.addItem(tr("Group by Color"), CardList::SortByColorGrouping); + groupBySelector.setCurrentIndex(oldIndex); + } + + { + int oldIndex = sortBySelector.currentIndex(); + sortBySelector.clear(); + sortBySelector.addItem(tr("Sort by ---"), CardList::NoSort); + sortBySelector.addItem(tr("Sort by Name"), CardList::SortByName); + sortBySelector.addItem(tr("Sort by Type"), CardList::SortByType); + sortBySelector.addItem(tr("Sort by Mana Cost"), CardList::SortByManaCost); + sortBySelector.addItem(tr("Sort by Colors"), CardList::SortByColors); + sortBySelector.addItem(tr("Sort by P/T"), CardList::SortByPt); + sortBySelector.addItem(tr("Sort by Set"), CardList::SortBySet); + sortBySelector.setCurrentIndex(oldIndex); + } + shuffleCheckBox.setText(tr("shuffle when closing")); pileViewCheckBox.setText(tr("pile view")); } diff --git a/cockatrice/src/game/zones/view_zone_widget.h b/cockatrice/src/game/zones/view_zone_widget.h index 43e92f425..bb5ec7128 100644 --- a/cockatrice/src/game/zones/view_zone_widget.h +++ b/cockatrice/src/game/zones/view_zone_widget.h @@ -4,6 +4,7 @@ #include "../../utility/macros.h" #include +#include #include #include @@ -14,7 +15,6 @@ class ZoneViewZone; class Player; class CardDatabase; class QScrollBar; -class QCheckBox; class GameScene; class ServerInfo_Card; class QGraphicsSceneMouseEvent; @@ -31,6 +31,12 @@ public slots: } }; +/** + * A QGraphicsWidget that holds a ZoneViewZone. + * + * Some zone views allow sorting. + * This widget will display the sort options when relevant, and forward the values of the options to the ZoneViewZone. + */ class ZoneViewWidget : public QGraphicsWidget { Q_OBJECT @@ -41,8 +47,8 @@ private: QPushButton *closeButton; QScrollBar *scrollBar; ScrollableGraphicsProxyWidget *scrollBarProxy; - QCheckBox sortByNameCheckBox; - QCheckBox sortByTypeCheckBox; + QComboBox groupBySelector; + QComboBox sortBySelector; QCheckBox shuffleCheckBox; QCheckBox pileViewCheckBox; @@ -52,8 +58,8 @@ private: signals: void closePressed(ZoneViewWidget *zv); private slots: - void processSortByType(QT_STATE_CHANGED_T value); - void processSortByName(QT_STATE_CHANGED_T value); + void processGroupBy(int value); + void processSortBy(int value); void processSetPileView(QT_STATE_CHANGED_T value); void resizeToZoneContents(); void handleScrollBarChange(int value); diff --git a/cockatrice/src/settings/cache_settings.cpp b/cockatrice/src/settings/cache_settings.cpp index 05de9fd48..3a75af782 100644 --- a/cockatrice/src/settings/cache_settings.cpp +++ b/cockatrice/src/settings/cache_settings.cpp @@ -221,6 +221,7 @@ SettingsCache::SettingsCache() pixmapCacheSize = PIXMAPCACHE_SIZE_DEFAULT; networkCacheSize = settings->value("personal/networkCacheSize", NETWORK_CACHE_SIZE_DEFAULT).toInt(); + redirectCacheTtl = settings->value("personal/redirectCacheTtl", NETWORK_REDIRECT_CACHE_TTL_DEFAULT).toInt(); picDownload = settings->value("personal/picturedownload", true).toBool(); @@ -251,8 +252,8 @@ SettingsCache::SettingsCache() chatMentionColor = settings->value("chat/mentioncolor", "A6120D").toString(); chatHighlightColor = settings->value("chat/highlightcolor", "A6120D").toString(); - zoneViewSortByName = settings->value("zoneview/sortbyname", true).toBool(); - zoneViewSortByType = settings->value("zoneview/sortbytype", true).toBool(); + zoneViewGroupByIndex = settings->value("zoneview/groupby", 1).toInt(); + zoneViewSortByIndex = settings->value("zoneview/sortby", 1).toInt(); zoneViewPileView = settings->value("zoneview/pileview", true).toBool(); soundEnabled = settings->value("sound/enabled", false).toBool(); @@ -581,16 +582,16 @@ void SettingsCache::setChatHighlightColor(const QString &_chatHighlightColor) settings->setValue("chat/highlightcolor", chatHighlightColor); } -void SettingsCache::setZoneViewSortByName(QT_STATE_CHANGED_T _zoneViewSortByName) +void SettingsCache::setZoneViewGroupByIndex(int _zoneViewGroupByIndex) { - zoneViewSortByName = static_cast(_zoneViewSortByName); - settings->setValue("zoneview/sortbyname", zoneViewSortByName); + zoneViewGroupByIndex = _zoneViewGroupByIndex; + settings->setValue("zoneview/groupby", zoneViewGroupByIndex); } -void SettingsCache::setZoneViewSortByType(QT_STATE_CHANGED_T _zoneViewSortByType) +void SettingsCache::setZoneViewSortByIndex(int _zoneViewSortByIndex) { - zoneViewSortByType = static_cast(_zoneViewSortByType); - settings->setValue("zoneview/sortbytype", zoneViewSortByType); + zoneViewSortByIndex = _zoneViewSortByIndex; + settings->setValue("zoneview/sortby", zoneViewSortByIndex); } void SettingsCache::setZoneViewPileView(QT_STATE_CHANGED_T _zoneViewPileView) @@ -657,6 +658,13 @@ void SettingsCache::setNetworkCacheSizeInMB(const int _networkCacheSize) emit networkCacheSizeChanged(networkCacheSize); } +void SettingsCache::setNetworkRedirectCacheTtl(const int _redirectCacheTtl) +{ + redirectCacheTtl = _redirectCacheTtl; + settings->setValue("personal/redirectCacheSize", redirectCacheTtl); + emit redirectCacheTtlChanged(redirectCacheTtl); +} + void SettingsCache::setClientID(const QString &_clientID) { clientID = _clientID; @@ -1030,6 +1038,7 @@ void SettingsCache::loadPaths() replaysPath = getSafeConfigPath("paths/replays", dataPath + "/replays/"); themesPath = getSafeConfigPath("paths/themes", dataPath + "/themes/"); picsPath = getSafeConfigPath("paths/pics", dataPath + "/pics/"); + redirectCachePath = getSafeConfigPath("paths/redirects", getCachePath() + "/redirects/"); // this has never been exposed as an user-configurable setting if (picsPath.endsWith("/")) { customPicsPath = getSafeConfigPath("paths/custompics", picsPath + "CUSTOM/"); diff --git a/cockatrice/src/settings/cache_settings.h b/cockatrice/src/settings/cache_settings.h index e0bf8b9ed..77e826412 100644 --- a/cockatrice/src/settings/cache_settings.h +++ b/cockatrice/src/settings/cache_settings.h @@ -16,16 +16,21 @@ class ReleaseChannel; -// size should be a multiple of 64 -#define PIXMAPCACHE_SIZE_DEFAULT 2047 +// In MB (Increments of 64) +#define PIXMAPCACHE_SIZE_DEFAULT 2048 #define PIXMAPCACHE_SIZE_MIN 64 -#define PIXMAPCACHE_SIZE_MAX 2047 +#define PIXMAPCACHE_SIZE_MAX 4096 // In MB constexpr int NETWORK_CACHE_SIZE_DEFAULT = 1024 * 4; // 4 GB constexpr int NETWORK_CACHE_SIZE_MIN = 1; // 1 MB constexpr int NETWORK_CACHE_SIZE_MAX = 1024 * 1024; // 1 TB +// In Days +#define NETWORK_REDIRECT_CACHE_TTL_DEFAULT 30 +#define NETWORK_REDIRECT_CACHE_TTL_MIN 1 +#define NETWORK_REDIRECT_CACHE_TTL_MAX 90 + #define DEFAULT_LANG_NAME "English" #define CLIENT_INFO_NOT_SET "notset" @@ -53,6 +58,7 @@ signals: void ignoreUnregisteredUserMessagesChanged(); void pixmapCacheSizeChanged(int newSizeInMBs); void networkCacheSizeChanged(int newSizeInMBs); + void redirectCacheTtlChanged(int newTtl); void masterVolumeChanged(int value); void chatMentionCompleterChanged(); void downloadSpoilerTimeIndexChanged(); @@ -73,8 +79,8 @@ private: QByteArray tokenDialogGeometry; QByteArray setsDialogGeometry; QString lang; - QString deckPath, replaysPath, picsPath, customPicsPath, cardDatabasePath, customCardDatabasePath, themesPath, - spoilerDatabasePath, tokenDatabasePath, themeName; + QString deckPath, replaysPath, picsPath, redirectCachePath, customPicsPath, cardDatabasePath, + customCardDatabasePath, themesPath, spoilerDatabasePath, tokenDatabasePath, themeName; bool notifyAboutUpdates; bool notifyAboutNewVersion; bool showTipsOnStartup; @@ -103,7 +109,8 @@ private: QString chatHighlightColor; bool chatMentionForeground; bool chatHighlightForeground; - bool zoneViewSortByName, zoneViewSortByType, zoneViewPileView; + int zoneViewSortByIndex, zoneViewGroupByIndex; + bool zoneViewPileView; bool soundEnabled; QString soundThemeName; bool ignoreUnregisteredUsers; @@ -116,6 +123,7 @@ private: bool useTearOffMenus; int pixmapCacheSize; int networkCacheSize; + int redirectCacheTtl; bool scaleCards; int verticalCardOverlapPercent; bool showMessagePopups; @@ -183,6 +191,10 @@ public: { return picsPath; } + QString getRedirectCachePath() const + { + return redirectCachePath; + } QString getCustomPicsPath() const { return customPicsPath; @@ -316,13 +328,19 @@ public: { return chatHighlightForeground; } - bool getZoneViewSortByName() const + /** + * Currently selected index for the `Group by X` QComboBox + */ + int getZoneViewGroupByIndex() const { - return zoneViewSortByName; + return zoneViewGroupByIndex; } - bool getZoneViewSortByType() const + /** + * Currently selected index for the `Sort by X` QComboBox + */ + int getZoneViewSortByIndex() const { - return zoneViewSortByType; + return zoneViewSortByIndex; } /** Returns if the view should be sorted into pile view. @@ -356,6 +374,10 @@ public: { return networkCacheSize; } + int getRedirectCacheTtl() const + { + return redirectCacheTtl; + } bool getScaleCards() const { return scaleCards; @@ -548,8 +570,8 @@ public slots: void setChatMentionCompleter(QT_STATE_CHANGED_T _chatMentionCompleter); void setChatMentionForeground(QT_STATE_CHANGED_T _chatMentionForeground); void setChatHighlightForeground(QT_STATE_CHANGED_T _chatHighlightForeground); - void setZoneViewSortByName(QT_STATE_CHANGED_T _zoneViewSortByName); - void setZoneViewSortByType(QT_STATE_CHANGED_T _zoneViewSortByType); + void setZoneViewGroupByIndex(const int _zoneViewGroupByIndex); + void setZoneViewSortByIndex(const int _zoneViewSortByIndex); void setZoneViewPileView(QT_STATE_CHANGED_T _zoneViewPileView); void setSoundEnabled(QT_STATE_CHANGED_T _soundEnabled); void setSoundThemeName(const QString &_soundThemeName); @@ -557,6 +579,7 @@ public slots: void setIgnoreUnregisteredUserMessages(QT_STATE_CHANGED_T _ignoreUnregisteredUserMessages); void setPixmapCacheSize(const int _pixmapCacheSize); void setNetworkCacheSizeInMB(const int _networkCacheSize); + void setNetworkRedirectCacheTtl(const int _redirectCacheTtl); void setCardScaling(const QT_STATE_CHANGED_T _scaleCards); void setStackCardOverlapPercent(const int _verticalCardOverlapPercent); void setShowMessagePopups(const QT_STATE_CHANGED_T _showMessagePopups); diff --git a/cockatrice/src/settings/download_settings.cpp b/cockatrice/src/settings/download_settings.cpp index 1af9d4f71..adbe26363 100644 --- a/cockatrice/src/settings/download_settings.cpp +++ b/cockatrice/src/settings/download_settings.cpp @@ -2,56 +2,28 @@ #include "settings_manager.h" +const QStringList DownloadSettings::DEFAULT_DOWNLOAD_URLS = { + "https://api.scryfall.com/cards/!set:uuid!?format=image&face=!prop:side!", + "https://api.scryfall.com/cards/multiverse/!set:muid!?format=image", + "https://gatherer.wizards.com/Handlers/Image.ashx?multiverseid=!set:muid!&type=card", + "https://gatherer.wizards.com/Handlers/Image.ashx?name=!name!&type=card"}; + DownloadSettings::DownloadSettings(const QString &settingPath, QObject *parent = nullptr) : SettingsManager(settingPath + "downloads.ini", parent) { - downloadURLs = getValue("urls", "downloads").value(); } -void DownloadSettings::setDownloadUrlAt(int index, const QString &url) +void DownloadSettings::setDownloadUrls(const QStringList &downloadURLs) { - downloadURLs.insert(index, url); setValue(QVariant::fromValue(downloadURLs), "urls", "downloads"); } -/** - * If reset or first run, this method contains the default URLs we will populate - */ QStringList DownloadSettings::getAllURLs() { - // First run, these will be empty - if (downloadURLs.count() == 0) { - populateDefaultURLs(); - } - - return downloadURLs; + return getValue("urls", "downloads").toStringList(); } -void DownloadSettings::populateDefaultURLs() +void DownloadSettings::resetToDefaultURLs() { - downloadURLs.clear(); - downloadURLs.append("https://api.scryfall.com/cards/!set:uuid!?format=image&face=!prop:side!"); - downloadURLs.append("https://api.scryfall.com/cards/multiverse/!set:muid!?format=image"); - downloadURLs.append("https://gatherer.wizards.com/Handlers/Image.ashx?multiverseid=!set:muid!&type=card"); - downloadURLs.append("https://gatherer.wizards.com/Handlers/Image.ashx?name=!name!&type=card"); - setValue(QVariant::fromValue(downloadURLs), "urls", "downloads"); + setValue(QVariant::fromValue(DEFAULT_DOWNLOAD_URLS), "urls", "downloads"); } - -QString DownloadSettings::getDownloadUrlAt(int index) -{ - if (0 <= index && index < downloadURLs.size()) { - return downloadURLs[index]; - } - - return ""; -} - -int DownloadSettings::getCount() -{ - return downloadURLs.size(); -} - -void DownloadSettings::clear() -{ - downloadURLs.clear(); -} \ No newline at end of file diff --git a/cockatrice/src/settings/download_settings.h b/cockatrice/src/settings/download_settings.h index 5916b0083..a961199b3 100644 --- a/cockatrice/src/settings/download_settings.h +++ b/cockatrice/src/settings/download_settings.h @@ -10,20 +10,14 @@ class DownloadSettings : public SettingsManager Q_OBJECT friend class SettingsCache; + static const QStringList DEFAULT_DOWNLOAD_URLS; + public: explicit DownloadSettings(const QString &, QObject *); QStringList getAllURLs(); - QString getDownloadUrlAt(int); - void setDownloadUrlAt(int, const QString &); - int getCount(); - void clear(); - -private: - QStringList downloadURLs; - -private: - void populateDefaultURLs(); + void setDownloadUrls(const QStringList &downloadURLs); + void resetToDefaultURLs(); }; #endif // COCKATRICE_DOWNLOADSETTINGS_H diff --git a/cockatrice/translations/cockatrice_nl.ts b/cockatrice/translations/cockatrice_nl.ts index ada657826..de57fce86 100644 --- a/cockatrice/translations/cockatrice_nl.ts +++ b/cockatrice/translations/cockatrice_nl.ts @@ -45,7 +45,7 @@ Open themes folder - Open thema folder + Open themafolder @@ -55,7 +55,7 @@ Display card names on cards having a picture - Kaartnamen altijd weergeven + Kaartnamen altijd weergeven op kaarten met een afbeelding @@ -7063,7 +7063,7 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties Replays - + Replays @@ -7749,12 +7749,12 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties Play/Pause - + Start/Stop Toggle Fast Forward - + Versnelling Schakelen \ No newline at end of file diff --git a/dbconverter/src/mocks.cpp b/dbconverter/src/mocks.cpp index 37c1424dc..e13db95ff 100644 --- a/dbconverter/src/mocks.cpp +++ b/dbconverter/src/mocks.cpp @@ -184,10 +184,10 @@ void SettingsCache::setChatMentionColor(const QString & /* _chatMentionColor */) void SettingsCache::setChatHighlightColor(const QString & /* _chatHighlightColor */) { } -void SettingsCache::setZoneViewSortByName(QT_STATE_CHANGED_T /* _zoneViewSortByName */) +void SettingsCache::setZoneViewGroupByIndex(int /* _zoneViewGroupByIndex */) { } -void SettingsCache::setZoneViewSortByType(QT_STATE_CHANGED_T /* _zoneViewSortByType */) +void SettingsCache::setZoneViewSortByIndex(int /* _zoneViewSortByIndex */) { } void SettingsCache::setZoneViewPileView(QT_STATE_CHANGED_T /* _zoneViewPileView */) @@ -220,6 +220,9 @@ void SettingsCache::setPixmapCacheSize(const int /* _pixmapCacheSize */) void SettingsCache::setNetworkCacheSizeInMB(const int /* _networkCacheSize */) { } +void SettingsCache::setNetworkRedirectCacheTtl(const int /* _redirectCacheTtl */) +{ +} void SettingsCache::setClientID(const QString & /* _clientID */) { } diff --git a/tests/carddatabase/mocks.cpp b/tests/carddatabase/mocks.cpp index 112fc9416..0eed998b3 100644 --- a/tests/carddatabase/mocks.cpp +++ b/tests/carddatabase/mocks.cpp @@ -188,10 +188,10 @@ void SettingsCache::setChatMentionColor(const QString & /* _chatMentionColor */) void SettingsCache::setChatHighlightColor(const QString & /* _chatHighlightColor */) { } -void SettingsCache::setZoneViewSortByName(QT_STATE_CHANGED_T /* _zoneViewSortByName */) +void SettingsCache::setZoneViewGroupByIndex(int /* _zoneViewGroupByIndex */) { } -void SettingsCache::setZoneViewSortByType(QT_STATE_CHANGED_T /* _zoneViewSortByType */) +void SettingsCache::setZoneViewSortByIndex(int /* _zoneViewSortByIndex */) { } void SettingsCache::setZoneViewPileView(QT_STATE_CHANGED_T /* _zoneViewPileView */) @@ -224,6 +224,9 @@ void SettingsCache::setPixmapCacheSize(const int /* _pixmapCacheSize */) void SettingsCache::setNetworkCacheSizeInMB(const int /* _networkCacheSize */) { } +void SettingsCache::setNetworkRedirectCacheTtl(const int /* _redirectCacheTtl */) +{ +} void SettingsCache::setClientID(const QString & /* _clientID */) { } diff --git a/webclient/package-lock.json b/webclient/package-lock.json index c0305729d..2dc6ee7f1 100644 --- a/webclient/package-lock.json +++ b/webclient/package-lock.json @@ -7006,9 +7006,9 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -25082,9 +25082,9 @@ } }, "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "requires": { "path-key": "^3.1.0", "shebang-command": "^2.0.0",