Merge branch 'master' into pr/5097

This commit is contained in:
tooomm 2024-12-01 14:38:21 +01:00
commit dc61357a31
40 changed files with 886 additions and 404 deletions

View file

@ -4,13 +4,13 @@
git push -d origin --REPLACE-WITH-BETA-LIST-- git push -d origin --REPLACE-WITH-BETA-LIST--
--> -->
<!-- This list of binaries should be updated every time the ci is changed to <!-- This list of binaries should be updated every time the CI is changed to
include different targets --> include different targets -->
<pre> <pre>
<b>Pre-compiled binaries we serve:</b> <b>Pre-compiled binaries we serve:</b>
- <kbd>Windows 10+</kbd> - <kbd>Windows 10+</kbd>
- <kbd>Windows 7+</kbd> - <kbd>Windows 7+</kbd>
- <kbd>macOS 13+</kbd> ("Ventura") / Apple M - <kbd>macOS 14+</kbd> ("Sonoma") / Apple M
- <kbd>macOS 13+</kbd> ("Ventura") / Intel - <kbd>macOS 13+</kbd> ("Ventura") / Intel
- <kbd>Ubuntu 24.04 LTS</kbd> ("Noble Numbat") - <kbd>Ubuntu 24.04 LTS</kbd> ("Noble Numbat")
- <kbd>Ubuntu 22.04 LTS</kbd> ("Jammy Jellyfish") - <kbd>Ubuntu 22.04 LTS</kbd> ("Jammy Jellyfish")
@ -19,8 +19,9 @@ include different targets -->
- <kbd>Debian 11</kbd> ("Bullseye") - <kbd>Debian 11</kbd> ("Bullseye")
- <kbd>Fedora 41</kbd> - <kbd>Fedora 41</kbd>
- <kbd>Fedora 40</kbd> - <kbd>Fedora 40</kbd>
<kbd>We are also packaged in Arch Linux's official "extra" repository, courtesy of @FFY00</kbd></i>
<kbd>General Linux support is available via a flatpak package (Flathub)</kbd></i> <i>We are also packaged in <kbd>Arch Linux</kbd>'s official "extra" repository, courtesy of @FFY00</i>
<i>General Linux support is available via a <kbd>flatpak</kbd> package (Flathub)</i>
</pre> </pre>
@ -28,22 +29,24 @@ include different targets -->
We're pleased to announce the newest official release: <kbd>--REPLACE-WITH-RELEASE-TITLE--</kbd> We're pleased to announce the newest official release: <kbd>--REPLACE-WITH-RELEASE-TITLE--</kbd>
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 ## Upgrading Cockatrice
<!-- this optional section puts a warning banner for problems with updating <!-- this optional section puts a warning banner for problems with updating
> ⚠️ **With this release, we no longer provide a ready-to-install binary for:** > [!IMPORTANT]
> **With this release, we no longer provide a ready-to-install binary for:**
> --DEPRECATED-OS-HERE-- > --DEPRECATED-OS-HERE--
--> -->
- Run the internal software updater: <kbd>Help → Check for Client Updates</kbd> Run the internal software updater: <kbd>Help → Check for Client Updates</kbd>
Don't forget to update your card database right after! (<kbd>Help → Check for Card Updates...</kbd>) Don't forget to update your card database right after! (<kbd>Help → Check for Card Updates...</kbd>)
@ -60,14 +63,14 @@ Remove empty headers when done.
--> -->
<!-- Highlights of the release --> <!-- Highlights of the release -->
### ⚠️ Important: ### 🔖 Highlights:
### ✨ New Features: ### ✨ New Features:
### 🐛 Fixed Bugs / Resolved issues: ### 🐛 Fixed Bugs / Resolved Issues:
<!-- Complete list of changes (foldable) --> <!-- Complete list of changes (foldable) -->
<details> <details>
<summary> <summary>
📘 <b>Show all changes</b> (--REPLACE-WITH-COMMIT-COUNT-- commits) <b>Show all changes</b> (--REPLACE-WITH-COMMIT-COUNT-- commits)
</summary> </summary>
### User Interface ### User Interface
@ -88,5 +91,6 @@ Remove empty headers when done.
## Special Thanks ## Special Thanks
<!-- Personalise this a bit! --> <!-- Personalise this a bit! -->
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! 🙏
<!-- We'd like to especially recognize @ZeldaZach, --ADD-CONTRIBUTORS-HERE-- for their help in preparing so many amazing new features for the user base. --> <!-- We'd like to especially recognize @ZeldaZach, --ADD-CONTRIBUTORS-HERE-- for their help in preparing so many amazing new features for the user base. -->

View file

@ -212,7 +212,7 @@ jobs:
- target: 14 - target: 14
soc: Apple soc: Apple
os: macos-14 os: macos-14
xcode: "14.3.1" xcode: "15.4"
type: Release type: Release
core_count: 3 core_count: 3
make_package: 1 make_package: 1
@ -220,7 +220,7 @@ jobs:
- target: 14 - target: 14
soc: Apple soc: Apple
os: macos-14 os: macos-14
xcode: "14.3.1" xcode: "15.4"
type: Debug type: Debug
core_count: 3 core_count: 3

View file

@ -19,7 +19,7 @@ function(get_commit_id)
PARENT_SCOPE PARENT_SCOPE
) )
set(PROJECT_VERSION_LABEL set(PROJECT_VERSION_LABEL
"custom(${GIT_COM_ID})" "custom-${GIT_COM_ID}"
PARENT_SCOPE PARENT_SCOPE
) )
endfunction() endfunction()

View file

@ -162,6 +162,10 @@ void ReplayTimelineWidget::processNewEvents(PlaybackMode playbackMode)
if (playbackMode == BACKWARD_SKIP || currentTime - replayTimeline[currentEvent] > BIG_SKIP_MS) if (playbackMode == BACKWARD_SKIP || currentTime - replayTimeline[currentEvent] > BIG_SKIP_MS)
options |= Player::EventProcessingOption::SKIP_REVEAL_WINDOW; 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); emit processNextEvent(options);
++currentEvent; ++currentEvent;
} }

View file

@ -748,9 +748,7 @@ bool TabDeckEditor::confirmClose()
{ {
if (modified) { if (modified) {
tabSupervisor->setCurrentWidget(this); tabSupervisor->setCurrentWidget(this);
QMessageBox::StandardButton ret = QMessageBox::warning( int ret = createSaveConfirmationWindow()->exec();
this, tr("Are you sure?"), tr("The decklist has been modified.\nDo you want to save the changes?"),
QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
if (ret == QMessageBox::Save) if (ret == QMessageBox::Save)
return actSaveDeck(); return actSaveDeck();
else if (ret == QMessageBox::Cancel) else if (ret == QMessageBox::Cancel)
@ -767,13 +765,16 @@ void TabDeckEditor::closeRequest()
void TabDeckEditor::actNewDeck() void TabDeckEditor::actNewDeck()
{ {
if (SettingsCache::instance().getOpenDeckInNewTab()) { auto deckOpenLocation = confirmOpen(false);
emit openDeckEditor(nullptr);
if (deckOpenLocation == CANCELLED) {
return; return;
} }
if (!confirmClose()) if (deckOpenLocation == NEW_TAB) {
emit openDeckEditor(nullptr);
return; return;
}
deckModel->cleanList(); deckModel->cleanList();
nameEdit->setText(QString()); nameEdit->setText(QString());
@ -785,10 +786,11 @@ void TabDeckEditor::actNewDeck()
void TabDeckEditor::actLoadDeck() void TabDeckEditor::actLoadDeck()
{ {
bool openInNewTab = SettingsCache::instance().getOpenDeckInNewTab() && !isBlankNewDeck(); auto deckOpenLocation = confirmOpen();
if (!openInNewTab && !confirmClose()) if (deckOpenLocation == CANCELLED) {
return; return;
}
QFileDialog dialog(this, tr("Load deck")); QFileDialog dialog(this, tr("Load deck"));
dialog.setDirectory(SettingsCache::instance().getDeckPath()); dialog.setDirectory(SettingsCache::instance().getDeckPath());
@ -801,7 +803,7 @@ void TabDeckEditor::actLoadDeck()
auto *l = new DeckLoader; auto *l = new DeckLoader;
if (l->loadFromFile(fileName, fmt)) { if (l->loadFromFile(fileName, fmt)) {
if (openInNewTab) { if (deckOpenLocation == NEW_TAB) {
emit openDeckEditor(l); emit openDeckEditor(l);
} else { } else {
setSaveStatus(false); setSaveStatus(false);
@ -878,16 +880,17 @@ bool TabDeckEditor::actSaveDeckAs()
void TabDeckEditor::actLoadDeckFromClipboard() void TabDeckEditor::actLoadDeckFromClipboard()
{ {
bool openInNewTab = SettingsCache::instance().getOpenDeckInNewTab() && !isBlankNewDeck(); auto deckOpenLocation = confirmOpen();
if (!openInNewTab && !confirmClose()) if (deckOpenLocation == CANCELLED) {
return; return;
}
DlgLoadDeckFromClipboard dlg(this); DlgLoadDeckFromClipboard dlg(this);
if (!dlg.exec()) if (!dlg.exec())
return; return;
if (openInNewTab) { if (deckOpenLocation == NEW_TAB) {
emit openDeckEditor(dlg.getDeckList()); emit openDeckEditor(dlg.getDeckList());
} else { } else {
setDeck(dlg.getDeckList()); setDeck(dlg.getDeckList());
@ -987,6 +990,70 @@ void TabDeckEditor::recursiveExpand(const QModelIndex &index)
deckView->expand(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. * @brief Returns true if this tab is a blank newly opened tab, as if it was just created with the `New Deck` action.
*/ */

View file

@ -22,6 +22,7 @@ class Response;
class FilterTreeModel; class FilterTreeModel;
class FilterBuilder; class FilterBuilder;
class QGroupBox; class QGroupBox;
class QMessageBox;
class QHBoxLayout; class QHBoxLayout;
class QVBoxLayout; class QVBoxLayout;
class QPushButton; class QPushButton;
@ -100,6 +101,19 @@ private slots:
void showSearchSyntaxHelp(); void showSearchSyntaxHelp();
private: 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; bool isBlankNewDeck() const;
CardInfoPtr currentCardInfo() const; CardInfoPtr currentCardInfo() const;
void addCardHelper(QString zoneName); void addCardHelper(QString zoneName);

View file

@ -648,8 +648,7 @@ void TabGame::replayRewind()
messageLog->clearChat(); messageLog->clearChat();
// reset phase markers // reset phase markers
currentPhase = -1; setActivePhase(-1);
phasesToolbar->reset();
} }
void TabGame::incrementGameTime() void TabGame::incrementGameTime()

View file

@ -86,23 +86,6 @@ void PhaseButton::updateAnimation()
update(); 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*/) void PhaseButton::mousePressEvent(QGraphicsSceneMouseEvent * /*event*/)
{ {
emit clicked(); emit clicked();
@ -268,16 +251,6 @@ void PhasesToolbar::phaseButtonClicked()
emit sendGameCommand(cmd, -1); 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() void PhasesToolbar::actNextTurn()
{ {
emit sendGameCommand(Command_NextTurn(), -1); emit sendGameCommand(Command_NextTurn(), -1);

View file

@ -45,7 +45,6 @@ public:
{ {
return active; return active;
} }
void reset();
void triggerDoubleClickAction(); void triggerDoubleClickAction();
signals: signals:
void clicked(); void clicked();
@ -86,7 +85,6 @@ public:
public slots: public slots:
void setActivePhase(int phase); void setActivePhase(int phase);
void triggerPhaseAction(int phase); void triggerPhaseAction(int phase);
void reset();
private slots: private slots:
void phaseButtonClicked(); void phaseButtonClicked();
void actNextTurn(); void actNextTurn();

View file

@ -1,17 +1,14 @@
#include "picture_loader.h" #include "picture_loader.h"
#include "../../game/cards/card_database.h"
#include "../../game/cards/card_database_manager.h" #include "../../game/cards/card_database_manager.h"
#include "../../settings/cache_settings.h" #include "../../settings/cache_settings.h"
#include "theme_manager.h"
#include <QApplication> #include <QApplication>
#include <QBuffer> #include <QBuffer>
#include <QCryptographicHash> #include <QCryptographicHash>
#include <QDebug> #include <QDebug>
#include <QDir>
#include <QDirIterator> #include <QDirIterator>
#include <QFile> #include <QFileInfo>
#include <QImageReader> #include <QImageReader>
#include <QMovie> #include <QMovie>
#include <QNetworkAccessManager> #include <QNetworkAccessManager>
@ -23,7 +20,6 @@
#include <QRegularExpression> #include <QRegularExpression>
#include <QScreen> #include <QScreen>
#include <QSet> #include <QSet>
#include <QSvgRenderer>
#include <QThread> #include <QThread>
#include <QUrl> #include <QUrl>
#include <algorithm> #include <algorithm>
@ -136,6 +132,8 @@ PictureLoaderWorker::PictureLoaderWorker()
#endif #endif
auto cache = new QNetworkDiskCache(this); auto cache = new QNetworkDiskCache(this);
cache->setCacheDirectory(SettingsCache::instance().getNetworkCachePath()); cache->setCacheDirectory(SettingsCache::instance().getNetworkCachePath());
cache->setMaximumCacheSize(1024L * 1024L *
static_cast<qint64>(SettingsCache::instance().getNetworkCacheSizeInMB()));
// Note: the settings is in MB, but QNetworkDiskCache uses bytes // Note: the settings is in MB, but QNetworkDiskCache uses bytes
connect(&SettingsCache::instance(), &SettingsCache::networkCacheSizeChanged, cache, connect(&SettingsCache::instance(), &SettingsCache::networkCacheSizeChanged, cache,
[cache](int newSizeInMB) { cache->setMaximumCacheSize(1024L * 1024L * static_cast<qint64>(newSizeInMB)); }); [cache](int newSizeInMB) { cache->setMaximumCacheSize(1024L * 1024L * static_cast<qint64>(newSizeInMB)); });
@ -145,6 +143,13 @@ PictureLoaderWorker::PictureLoaderWorker()
networkManager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy); networkManager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy);
connect(networkManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(picDownloadFinished(QNetworkReply *))); 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 = new QThread;
pictureLoaderThread->start(QThread::LowPriority); pictureLoaderThread->start(QThread::LowPriority);
moveToThread(pictureLoaderThread); moveToThread(pictureLoaderThread);
@ -447,11 +452,104 @@ bool PictureLoaderWorker::imageIsBlackListed(const QByteArray &picData)
QNetworkReply *PictureLoaderWorker::makeRequest(const QUrl &url) 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); QNetworkRequest req(url);
if (!picDownload) { if (!picDownload) {
req.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysCache); 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<int>(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) void PictureLoaderWorker::picDownloadFinished(QNetworkReply *reply)

View file

@ -11,6 +11,12 @@ class QNetworkAccessManager;
class QNetworkReply; class QNetworkReply;
class QThread; 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 class PictureToLoad
{ {
private: private:
@ -83,6 +89,9 @@ private:
QList<PictureToLoad> loadQueue; QList<PictureToLoad> loadQueue;
QMutex mutex; QMutex mutex;
QNetworkAccessManager *networkManager; QNetworkAccessManager *networkManager;
QHash<QUrl, QPair<QUrl, QDateTime>> redirectCache; // Stores redirect and timestamp
QString cacheFilePath; // Path to persistent storage
static constexpr int CacheTTLInDays = 30; // TODO: Make user configurable
QList<PictureToLoad> cardsToDownload; QList<PictureToLoad> cardsToDownload;
PictureToLoad cardBeingLoaded; PictureToLoad cardBeingLoaded;
PictureToLoad cardBeingDownloaded; PictureToLoad cardBeingDownloaded;
@ -91,6 +100,12 @@ private:
bool cardImageExistsOnDisk(QString &setName, QString &correctedCardName); bool cardImageExistsOnDisk(QString &setName, QString &correctedCardName);
bool imageIsBlackListed(const QByteArray &); bool imageIsBlackListed(const QByteArray &);
QNetworkReply *makeRequest(const QUrl &url); 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: private slots:
void picDownloadFinished(QNetworkReply *reply); void picDownloadFinished(QNetworkReply *reply);
void picDownloadFailed(); void picDownloadFailed();

View file

@ -666,6 +666,7 @@ void MainWindow::retranslateUi()
aOpenCustomFolder->setText(tr("Open custom image folder")); aOpenCustomFolder->setText(tr("Open custom image folder"));
aOpenCustomsetsFolder->setText(tr("Open custom sets folder")); aOpenCustomsetsFolder->setText(tr("Open custom sets folder"));
aAddCustomSet->setText(tr("Add custom sets/cards")); aAddCustomSet->setText(tr("Add custom sets/cards"));
aReloadCardDatabase->setText(tr("Reload card database"));
helpMenu->setTitle(tr("&Help")); helpMenu->setTitle(tr("&Help"));
aAbout->setText(tr("&About Cockatrice")); aAbout->setText(tr("&About Cockatrice"));
@ -714,6 +715,8 @@ void MainWindow::createActions()
connect(aOpenCustomsetsFolder, SIGNAL(triggered()), this, SLOT(actOpenCustomsetsFolder())); connect(aOpenCustomsetsFolder, SIGNAL(triggered()), this, SLOT(actOpenCustomsetsFolder()));
aAddCustomSet = new QAction(QString(), this); aAddCustomSet = new QAction(QString(), this);
connect(aAddCustomSet, SIGNAL(triggered()), this, SLOT(actAddCustomSet())); connect(aAddCustomSet, SIGNAL(triggered()), this, SLOT(actAddCustomSet()));
aReloadCardDatabase = new QAction(QString(), this);
connect(aReloadCardDatabase, SIGNAL(triggered()), this, SLOT(actReloadCardDatabase()));
aAbout = new QAction(this); aAbout = new QAction(this);
connect(aAbout, SIGNAL(triggered()), this, SLOT(actAbout())); connect(aAbout, SIGNAL(triggered()), this, SLOT(actAbout()));
@ -788,6 +791,8 @@ void MainWindow::createMenus()
dbMenu->addAction(aOpenCustomFolder); dbMenu->addAction(aOpenCustomFolder);
dbMenu->addAction(aOpenCustomsetsFolder); dbMenu->addAction(aOpenCustomsetsFolder);
dbMenu->addAction(aAddCustomSet); dbMenu->addAction(aAddCustomSet);
dbMenu->addSeparator();
dbMenu->addAction(aReloadCardDatabase);
helpMenu = menuBar()->addMenu(QString()); helpMenu = menuBar()->addMenu(QString());
helpMenu->addAction(aAbout); helpMenu->addAction(aAbout);
@ -878,6 +883,7 @@ void MainWindow::startupConfigCheck()
// no config found, 99% new clean install // no config found, 99% new clean install
qDebug() << "Startup: old client version empty, assuming first start after clean install"; qDebug() << "Startup: old client version empty, assuming first start after clean install";
alertForcedOracleRun(VERSION_STRING, false); alertForcedOracleRun(VERSION_STRING, false);
SettingsCache::instance().downloads().resetToDefaultURLs(); // populate the download urls
SettingsCache::instance().setClientVersion(VERSION_STRING); SettingsCache::instance().setClientVersion(VERSION_STRING);
} else if (SettingsCache::instance().getClientVersion() != VERSION_STRING) { } else if (SettingsCache::instance().getClientVersion() != VERSION_STRING) {
// config found, from another (presumably older) version // config found, from another (presumably older) version
@ -1324,6 +1330,11 @@ int MainWindow::getNextCustomSetPrefix(QDir dataDir)
return maxIndex + 1; return maxIndex + 1;
} }
void MainWindow::actReloadCardDatabase()
{
const auto reloadOk1 = QtConcurrent::run([] { CardDatabaseManager::getInstance()->loadCardDatabases(); });
}
void MainWindow::actManageSets() void MainWindow::actManageSets()
{ {
wndSets = new WndSets(this); wndSets = new WndSets(this);

View file

@ -98,6 +98,7 @@ private slots:
void actOpenCustomFolder(); void actOpenCustomFolder();
void actOpenCustomsetsFolder(); void actOpenCustomsetsFolder();
void actAddCustomSet(); void actAddCustomSet();
void actReloadCardDatabase();
void actManageSets(); void actManageSets();
void actEditTokens(); void actEditTokens();
@ -125,7 +126,7 @@ private:
QMenu *cockatriceMenu, *dbMenu, *helpMenu, *trayIconMenu; QMenu *cockatriceMenu, *dbMenu, *helpMenu, *trayIconMenu;
QAction *aConnect, *aDisconnect, *aSinglePlayer, *aWatchReplay, *aDeckEditor, *aFullScreen, *aSettings, *aExit, QAction *aConnect, *aDisconnect, *aSinglePlayer, *aWatchReplay, *aDeckEditor, *aFullScreen, *aSettings, *aExit,
*aAbout, *aTips, *aCheckCardUpdates, *aRegister, *aForgotPassword, *aUpdate, *aViewLog, *aManageSets, *aAbout, *aTips, *aCheckCardUpdates, *aRegister, *aForgotPassword, *aUpdate, *aViewLog, *aManageSets,
*aEditTokens, *aOpenCustomFolder, *aOpenCustomsetsFolder, *aAddCustomSet, *aShow; *aEditTokens, *aOpenCustomFolder, *aOpenCustomsetsFolder, *aAddCustomSet, *aReloadCardDatabase, *aShow;
TabSupervisor *tabSupervisor; TabSupervisor *tabSupervisor;
WndSets *wndSets; WndSets *wndSets;
RemoteClient *client; RemoteClient *client;

View file

@ -80,8 +80,7 @@ DlgConnect::DlgConnect(QWidget *parent) : QDialog(parent)
autoConnectCheckBox->setEnabled(false); autoConnectCheckBox->setEnabled(false);
} }
connect(savePasswordCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), this, connect(savePasswordCheckBox, &QCheckBox::QT_STATE_CHANGED, this, &DlgConnect::passwordSaved);
SLOT(passwordSaved(QT_STATE_CHANGED_T)));
serverIssuesLabel = serverIssuesLabel =
new QLabel(tr("If you have any trouble connecting or registering then contact the server staff for help!")); new QLabel(tr("If you have any trouble connecting or registering then contact the server staff for help!"));

View file

@ -81,8 +81,7 @@ void DlgCreateGame::sharedCtor()
spectatorsAllowedCheckBox = new QCheckBox(tr("&Spectators can watch")); spectatorsAllowedCheckBox = new QCheckBox(tr("&Spectators can watch"));
spectatorsAllowedCheckBox->setChecked(true); spectatorsAllowedCheckBox->setChecked(true);
connect(spectatorsAllowedCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), this, connect(spectatorsAllowedCheckBox, &QCheckBox::QT_STATE_CHANGED, this, &DlgCreateGame::spectatorsAllowedChanged);
SLOT(spectatorsAllowedChanged(QT_STATE_CHANGED_T)));
spectatorsNeedPasswordCheckBox = new QCheckBox(tr("Spectators &need a password to watch")); spectatorsNeedPasswordCheckBox = new QCheckBox(tr("Spectators &need a password to watch"));
spectatorsCanTalkCheckBox = new QCheckBox(tr("Spectators can &chat")); spectatorsCanTalkCheckBox = new QCheckBox(tr("Spectators can &chat"));
spectatorsSeeEverythingCheckBox = new QCheckBox(tr("Spectators can see &hands")); spectatorsSeeEverythingCheckBox = new QCheckBox(tr("Spectators can see &hands"));

View file

@ -276,7 +276,7 @@ int DlgFilterGames::getMaxPlayersFilterMax() const
return maxPlayersFilterMaxSpinBox->value(); return maxPlayersFilterMaxSpinBox->value();
} }
const QTime &DlgFilterGames::getMaxGameAge() const QTime DlgFilterGames::getMaxGameAge() const
{ {
int index = maxGameAgeComboBox->currentIndex(); int index = maxGameAgeComboBox->currentIndex();
if (index < 0 || index >= gameAgeMap.size()) { // index is out of bounds if (index < 0 || index >= gameAgeMap.size()) { // index is out of bounds

View file

@ -67,7 +67,7 @@ public:
int getMaxPlayersFilterMin() const; int getMaxPlayersFilterMin() const;
int getMaxPlayersFilterMax() const; int getMaxPlayersFilterMax() const;
void setMaxPlayersFilter(int _maxPlayersFilterMin, int _maxPlayersFilterMax); void setMaxPlayersFilter(int _maxPlayersFilterMin, int _maxPlayersFilterMax);
const QTime &getMaxGameAge() const; QTime getMaxGameAge() const;
const QMap<QTime, QString> gameAgeMap; const QMap<QTime, QString> gameAgeMap;
bool getShowOnlyIfSpectatorsCanWatch() const; bool getShowOnlyIfSpectatorsCanWatch() const;
bool getShowSpectatorPasswordProtected() const; bool getShowSpectatorPasswordProtected() const;

View file

@ -74,10 +74,9 @@ GeneralSettingsPage::GeneralSettingsPage()
connect(&languageBox, SIGNAL(currentIndexChanged(int)), this, SLOT(languageBoxChanged(int))); connect(&languageBox, SIGNAL(currentIndexChanged(int)), this, SLOT(languageBoxChanged(int)));
connect(&updateReleaseChannelBox, SIGNAL(currentIndexChanged(int)), &settings, SLOT(setUpdateReleaseChannel(int))); connect(&updateReleaseChannelBox, SIGNAL(currentIndexChanged(int)), &settings, SLOT(setUpdateReleaseChannel(int)));
connect(&updateNotificationCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &settings, connect(&updateNotificationCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, &SettingsCache::setNotifyAboutUpdate);
SLOT(setNotifyAboutUpdate(QT_STATE_CHANGED_T))); connect(&newVersionOracleCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings,
connect(&newVersionOracleCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &settings, &SettingsCache::setNotifyAboutNewVersion);
SLOT(setNotifyAboutNewVersion(QT_STATE_CHANGED_T)));
connect(&showTipsOnStartup, SIGNAL(clicked(bool)), &settings, SLOT(setShowTipsOnStartup(bool))); connect(&showTipsOnStartup, SIGNAL(clicked(bool)), &settings, SLOT(setShowTipsOnStartup(bool)));
auto *personalGrid = new QGridLayout; auto *personalGrid = new QGridLayout;
@ -324,12 +323,10 @@ AppearanceSettingsPage::AppearanceSettingsPage()
themeGroupBox->setLayout(themeGrid); themeGroupBox->setLayout(themeGrid);
displayCardNamesCheckBox.setChecked(settings.getDisplayCardNames()); displayCardNamesCheckBox.setChecked(settings.getDisplayCardNames());
connect(&displayCardNamesCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &settings, connect(&displayCardNamesCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, &SettingsCache::setDisplayCardNames);
SLOT(setDisplayCardNames(QT_STATE_CHANGED_T)));
cardScalingCheckBox.setChecked(settings.getScaleCards()); cardScalingCheckBox.setChecked(settings.getScaleCards());
connect(&cardScalingCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &settings, connect(&cardScalingCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, &SettingsCache::setCardScaling);
SLOT(setCardScaling(QT_STATE_CHANGED_T)));
verticalCardOverlapPercentBox.setValue(settings.getStackCardOverlapPercent()); verticalCardOverlapPercentBox.setValue(settings.getStackCardOverlapPercent());
verticalCardOverlapPercentBox.setRange(0, 80); verticalCardOverlapPercentBox.setRange(0, 80);
@ -346,12 +343,10 @@ AppearanceSettingsPage::AppearanceSettingsPage()
cardsGroupBox->setLayout(cardsGrid); cardsGroupBox->setLayout(cardsGrid);
horizontalHandCheckBox.setChecked(settings.getHorizontalHand()); horizontalHandCheckBox.setChecked(settings.getHorizontalHand());
connect(&horizontalHandCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &settings, connect(&horizontalHandCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, &SettingsCache::setHorizontalHand);
SLOT(setHorizontalHand(QT_STATE_CHANGED_T)));
leftJustifiedHandCheckBox.setChecked(settings.getLeftJustified()); leftJustifiedHandCheckBox.setChecked(settings.getLeftJustified());
connect(&leftJustifiedHandCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &settings, connect(&leftJustifiedHandCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, &SettingsCache::setLeftJustified);
SLOT(setLeftJustified(QT_STATE_CHANGED_T)));
auto *handGrid = new QGridLayout; auto *handGrid = new QGridLayout;
handGrid->addWidget(&horizontalHandCheckBox, 0, 0, 1, 2); handGrid->addWidget(&horizontalHandCheckBox, 0, 0, 1, 2);
@ -361,8 +356,8 @@ AppearanceSettingsPage::AppearanceSettingsPage()
handGroupBox->setLayout(handGrid); handGroupBox->setLayout(handGrid);
invertVerticalCoordinateCheckBox.setChecked(settings.getInvertVerticalCoordinate()); invertVerticalCoordinateCheckBox.setChecked(settings.getInvertVerticalCoordinate());
connect(&invertVerticalCoordinateCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &settings, connect(&invertVerticalCoordinateCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings,
SLOT(setInvertVerticalCoordinate(QT_STATE_CHANGED_T))); &SettingsCache::setInvertVerticalCoordinate);
minPlayersForMultiColumnLayoutEdit.setMinimum(2); minPlayersForMultiColumnLayoutEdit.setMinimum(2);
minPlayersForMultiColumnLayoutEdit.setValue(settings.getMinPlayersForMultiColumnLayout()); minPlayersForMultiColumnLayoutEdit.setValue(settings.getMinPlayersForMultiColumnLayout());
@ -442,37 +437,37 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage()
{ {
// general settings and notification settings // general settings and notification settings
notificationsEnabledCheckBox.setChecked(SettingsCache::instance().getNotificationsEnabled()); notificationsEnabledCheckBox.setChecked(SettingsCache::instance().getNotificationsEnabled());
connect(&notificationsEnabledCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), connect(&notificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(),
SLOT(setNotificationsEnabled(QT_STATE_CHANGED_T))); &SettingsCache::setNotificationsEnabled);
connect(&notificationsEnabledCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), this, connect(&notificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, this,
SLOT(setNotificationEnabled(QT_STATE_CHANGED_T))); &UserInterfaceSettingsPage::setNotificationEnabled);
specNotificationsEnabledCheckBox.setChecked(SettingsCache::instance().getSpectatorNotificationsEnabled()); specNotificationsEnabledCheckBox.setChecked(SettingsCache::instance().getSpectatorNotificationsEnabled());
specNotificationsEnabledCheckBox.setEnabled(SettingsCache::instance().getNotificationsEnabled()); specNotificationsEnabledCheckBox.setEnabled(SettingsCache::instance().getNotificationsEnabled());
connect(&specNotificationsEnabledCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), connect(&specNotificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(),
SLOT(setSpectatorNotificationsEnabled(QT_STATE_CHANGED_T))); &SettingsCache::setSpectatorNotificationsEnabled);
buddyConnectNotificationsEnabledCheckBox.setChecked( buddyConnectNotificationsEnabledCheckBox.setChecked(
SettingsCache::instance().getBuddyConnectNotificationsEnabled()); SettingsCache::instance().getBuddyConnectNotificationsEnabled());
buddyConnectNotificationsEnabledCheckBox.setEnabled(SettingsCache::instance().getNotificationsEnabled()); buddyConnectNotificationsEnabledCheckBox.setEnabled(SettingsCache::instance().getNotificationsEnabled());
connect(&buddyConnectNotificationsEnabledCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), connect(&buddyConnectNotificationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(),
&SettingsCache::instance(), SLOT(setBuddyConnectNotificationsEnabled(QT_STATE_CHANGED_T))); &SettingsCache::setBuddyConnectNotificationsEnabled);
doubleClickToPlayCheckBox.setChecked(SettingsCache::instance().getDoubleClickToPlay()); doubleClickToPlayCheckBox.setChecked(SettingsCache::instance().getDoubleClickToPlay());
connect(&doubleClickToPlayCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), connect(&doubleClickToPlayCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(),
SLOT(setDoubleClickToPlay(QT_STATE_CHANGED_T))); &SettingsCache::setDoubleClickToPlay);
playToStackCheckBox.setChecked(SettingsCache::instance().getPlayToStack()); playToStackCheckBox.setChecked(SettingsCache::instance().getPlayToStack());
connect(&playToStackCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), connect(&playToStackCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(),
SLOT(setPlayToStack(QT_STATE_CHANGED_T))); &SettingsCache::setPlayToStack);
annotateTokensCheckBox.setChecked(SettingsCache::instance().getAnnotateTokens()); annotateTokensCheckBox.setChecked(SettingsCache::instance().getAnnotateTokens());
connect(&annotateTokensCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), connect(&annotateTokensCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(),
SLOT(setAnnotateTokens(QT_STATE_CHANGED_T))); &SettingsCache::setAnnotateTokens);
useTearOffMenusCheckBox.setChecked(SettingsCache::instance().getUseTearOffMenus()); useTearOffMenusCheckBox.setChecked(SettingsCache::instance().getUseTearOffMenus());
connect(&useTearOffMenusCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), 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; auto *generalGrid = new QGridLayout;
generalGrid->addWidget(&doubleClickToPlayCheckBox, 0, 0); generalGrid->addWidget(&doubleClickToPlayCheckBox, 0, 0);
@ -493,8 +488,8 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage()
// animation settings // animation settings
tapAnimationCheckBox.setChecked(SettingsCache::instance().getTapAnimation()); tapAnimationCheckBox.setChecked(SettingsCache::instance().getTapAnimation());
connect(&tapAnimationCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), connect(&tapAnimationCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(),
SLOT(setTapAnimation(QT_STATE_CHANGED_T))); &SettingsCache::setTapAnimation);
auto *animationGrid = new QGridLayout; auto *animationGrid = new QGridLayout;
animationGrid->addWidget(&tapAnimationCheckBox, 0, 0); animationGrid->addWidget(&tapAnimationCheckBox, 0, 0);
@ -504,8 +499,8 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage()
// deck editor settings // deck editor settings
openDeckInNewTabCheckBox.setChecked(SettingsCache::instance().getOpenDeckInNewTab()); openDeckInNewTabCheckBox.setChecked(SettingsCache::instance().getOpenDeckInNewTab());
connect(&openDeckInNewTabCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), connect(&openDeckInNewTabCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(),
SLOT(setOpenDeckInNewTab(QT_STATE_CHANGED_T))); &SettingsCache::setOpenDeckInNewTab);
auto *deckEditorGrid = new QGridLayout; auto *deckEditorGrid = new QGridLayout;
deckEditorGrid->addWidget(&openDeckInNewTabCheckBox, 0, 0); deckEditorGrid->addWidget(&openDeckInNewTabCheckBox, 0, 0);
@ -524,7 +519,7 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage()
setLayout(mainLayout); setLayout(mainLayout);
} }
void UserInterfaceSettingsPage::setNotificationEnabled(int i) void UserInterfaceSettingsPage::setNotificationEnabled(QT_STATE_CHANGED_T i)
{ {
specNotificationsEnabledCheckBox.setEnabled(i != 0); specNotificationsEnabledCheckBox.setEnabled(i != 0);
buddyConnectNotificationsEnabledCheckBox.setEnabled(i != 0); buddyConnectNotificationsEnabledCheckBox.setEnabled(i != 0);
@ -554,8 +549,8 @@ void UserInterfaceSettingsPage::retranslateUi()
DeckEditorSettingsPage::DeckEditorSettingsPage() DeckEditorSettingsPage::DeckEditorSettingsPage()
{ {
picDownloadCheckBox.setChecked(SettingsCache::instance().getPicDownload()); picDownloadCheckBox.setChecked(SettingsCache::instance().getPicDownload());
connect(&picDownloadCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), connect(&picDownloadCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(),
SLOT(setPicDownload(QT_STATE_CHANGED_T))); &SettingsCache::setPicDownload);
urlLinkLabel.setTextInteractionFlags(Qt::LinksAccessibleByMouse); urlLinkLabel.setTextInteractionFlags(Qt::LinksAccessibleByMouse);
urlLinkLabel.setOpenExternalLinks(true); urlLinkLabel.setOpenExternalLinks(true);
@ -588,8 +583,7 @@ DeckEditorSettingsPage::DeckEditorSettingsPage()
connect(urlList->model(), SIGNAL(rowsMoved(const QModelIndex, int, int, const QModelIndex, int)), this, connect(urlList->model(), SIGNAL(rowsMoved(const QModelIndex, int, int, const QModelIndex, int)), this,
SLOT(urlListChanged(const QModelIndex, int, int, const QModelIndex, int))); SLOT(urlListChanged(const QModelIndex, int, int, const QModelIndex, int)));
for (int i = 0; i < SettingsCache::instance().downloads().getCount(); i++) urlList->addItems(SettingsCache::instance().downloads().getAllURLs());
urlList->addItem(SettingsCache::instance().downloads().getDownloadUrlAt(i));
auto aAdd = new QAction(this); auto aAdd = new QAction(this);
aAdd->setIcon(QPixmap("theme:icons/increment")); aAdd->setIcon(QPixmap("theme:icons/increment"));
@ -604,16 +598,16 @@ DeckEditorSettingsPage::DeckEditorSettingsPage()
aRemove->setToolTip(tr("Remove URL")); aRemove->setToolTip(tr("Remove URL"));
connect(aRemove, SIGNAL(triggered()), this, SLOT(actRemoveURL())); connect(aRemove, SIGNAL(triggered()), this, SLOT(actRemoveURL()));
auto *messageToolBar = new QToolBar; auto *urlToolBar = new QToolBar;
messageToolBar->setOrientation(Qt::Vertical); urlToolBar->setOrientation(Qt::Vertical);
messageToolBar->addAction(aAdd); urlToolBar->addAction(aAdd);
messageToolBar->addAction(aRemove); urlToolBar->addAction(aRemove);
messageToolBar->addAction(aEdit); urlToolBar->addAction(aEdit);
messageToolBar->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); urlToolBar->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
auto *messageListLayout = new QHBoxLayout; auto *urlListLayout = new QHBoxLayout;
messageListLayout->addWidget(messageToolBar); urlListLayout->addWidget(urlToolBar);
messageListLayout->addWidget(urlList); urlListLayout->addWidget(urlList);
// pixmap cache // pixmap cache
pixmapCacheEdit.setMinimum(PIXMAPCACHE_SIZE_MIN); pixmapCacheEdit.setMinimum(PIXMAPCACHE_SIZE_MIN);
@ -629,11 +623,22 @@ DeckEditorSettingsPage::DeckEditorSettingsPage()
networkCacheEdit.setValue(SettingsCache::instance().getNetworkCacheSizeInMB()); networkCacheEdit.setValue(SettingsCache::instance().getNetworkCacheSizeInMB());
networkCacheEdit.setSuffix(" MB"); 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; auto networkCacheLayout = new QHBoxLayout;
networkCacheLayout->addStretch(); networkCacheLayout->addStretch();
networkCacheLayout->addWidget(&networkCacheLabel); networkCacheLayout->addWidget(&networkCacheLabel);
networkCacheLayout->addWidget(&networkCacheEdit); networkCacheLayout->addWidget(&networkCacheEdit);
auto networkRedirectCacheLayout = new QHBoxLayout;
networkRedirectCacheLayout->addStretch();
networkRedirectCacheLayout->addWidget(&networkRedirectCacheTtlLabel);
networkRedirectCacheLayout->addWidget(&networkRedirectCacheTtlEdit);
auto pixmapCacheLayout = new QHBoxLayout; auto pixmapCacheLayout = new QHBoxLayout;
pixmapCacheLayout->addStretch(); pixmapCacheLayout->addStretch();
pixmapCacheLayout->addWidget(&pixmapCacheLabel); pixmapCacheLayout->addWidget(&pixmapCacheLabel);
@ -642,11 +647,12 @@ DeckEditorSettingsPage::DeckEditorSettingsPage()
// Top Layout // Top Layout
lpGeneralGrid->addWidget(&picDownloadCheckBox, 0, 0); lpGeneralGrid->addWidget(&picDownloadCheckBox, 0, 0);
lpGeneralGrid->addWidget(&resetDownloadURLs, 0, 1); 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(networkCacheLayout, 2, 0, 1, 2);
lpGeneralGrid->addLayout(pixmapCacheLayout, 3, 0, 1, 2); lpGeneralGrid->addLayout(pixmapCacheLayout, 3, 0, 1, 2);
lpGeneralGrid->addWidget(&urlLinkLabel, 4, 0); lpGeneralGrid->addLayout(networkRedirectCacheLayout, 4, 0, 1, 2);
lpGeneralGrid->addWidget(&clearDownloadedPicsButton, 4, 1); lpGeneralGrid->addWidget(&urlLinkLabel, 5, 0);
lpGeneralGrid->addWidget(&clearDownloadedPicsButton, 5, 1);
// Spoiler Layout // Spoiler Layout
lpSpoilerGrid->addWidget(&mcDownloadSpoilersCheckBox, 0, 0); lpSpoilerGrid->addWidget(&mcDownloadSpoilersCheckBox, 0, 0);
@ -657,13 +663,15 @@ DeckEditorSettingsPage::DeckEditorSettingsPage()
lpSpoilerGrid->addWidget(updateNowButton, 2, 1); lpSpoilerGrid->addWidget(updateNowButton, 2, 1);
lpSpoilerGrid->addWidget(&infoOnSpoilersLabel, 3, 0, 1, 3, Qt::AlignTop); 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(), connect(&mcDownloadSpoilersCheckBox, SIGNAL(toggled(bool)), &SettingsCache::instance(),
SLOT(setDownloadSpoilerStatus(bool))); SLOT(setDownloadSpoilerStatus(bool)));
connect(&mcDownloadSpoilersCheckBox, SIGNAL(toggled(bool)), this, SLOT(setSpoilersEnabled(bool))); connect(&mcDownloadSpoilersCheckBox, SIGNAL(toggled(bool)), this, SLOT(setSpoilersEnabled(bool)));
connect(&pixmapCacheEdit, SIGNAL(valueChanged(int)), &SettingsCache::instance(), SLOT(setPixmapCacheSize(int))); connect(&pixmapCacheEdit, SIGNAL(valueChanged(int)), &SettingsCache::instance(), SLOT(setPixmapCacheSize(int)));
connect(&networkCacheEdit, SIGNAL(valueChanged(int)), &SettingsCache::instance(), connect(&networkCacheEdit, SIGNAL(valueChanged(int)), &SettingsCache::instance(),
SLOT(setNetworkCacheSizeInMB(int))); SLOT(setNetworkCacheSizeInMB(int)));
connect(&networkRedirectCacheTtlEdit, SIGNAL(valueChanged(int)), &SettingsCache::instance(),
SLOT(setNetworkRedirectCacheTtl(int)));
mpGeneralGroupBox = new QGroupBox; mpGeneralGroupBox = new QGroupBox;
mpGeneralGroupBox->setLayout(lpGeneralGrid); mpGeneralGroupBox->setLayout(lpGeneralGrid);
@ -680,7 +688,7 @@ DeckEditorSettingsPage::DeckEditorSettingsPage()
void DeckEditorSettingsPage::resetDownloadedURLsButtonClicked() void DeckEditorSettingsPage::resetDownloadedURLsButtonClicked()
{ {
SettingsCache::instance().downloads().clear(); SettingsCache::instance().downloads().resetToDefaultURLs();
urlList->clear(); urlList->clear();
urlList->addItems(SettingsCache::instance().downloads().getAllURLs()); urlList->addItems(SettingsCache::instance().downloads().getAllURLs());
QMessageBox::information(this, tr("Success"), tr("Download URLs have been reset.")); QMessageBox::information(this, tr("Success"), tr("Download URLs have been reset."));
@ -760,11 +768,13 @@ void DeckEditorSettingsPage::actEditURL()
void DeckEditorSettingsPage::storeSettings() void DeckEditorSettingsPage::storeSettings()
{ {
qInfo() << "URL Priority Reset"; qInfo() << "URL Priority Reset";
SettingsCache::instance().downloads().clear();
QStringList downloadUrls;
for (int i = 0; i < urlList->count(); i++) { for (int i = 0; i < urlList->count(); i++) {
qInfo() << "Priority" << i << ":" << urlList->item(i)->text(); 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) void DeckEditorSettingsPage::urlListChanged(const QModelIndex &, int, int, const QModelIndex &, int)
@ -844,39 +854,40 @@ void DeckEditorSettingsPage::retranslateUi()
urlLinkLabel.setText(QString("<a href='%1'>%2</a>").arg(WIKI_CUSTOM_PIC_URL).arg(tr("How to add a custom URL"))); urlLinkLabel.setText(QString("<a href='%1'>%2</a>").arg(WIKI_CUSTOM_PIC_URL).arg(tr("How to add a custom URL")));
clearDownloadedPicsButton.setText(tr("Delete Downloaded Images")); clearDownloadedPicsButton.setText(tr("Delete Downloaded Images"));
resetDownloadURLs.setText(tr("Reset Download URLs")); 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")); 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")); pixmapCacheEdit.setToolTip(tr("In-memory cache for pictures not currently on screen"));
} }
MessagesSettingsPage::MessagesSettingsPage() MessagesSettingsPage::MessagesSettingsPage()
{ {
chatMentionCheckBox.setChecked(SettingsCache::instance().getChatMention()); chatMentionCheckBox.setChecked(SettingsCache::instance().getChatMention());
connect(&chatMentionCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), connect(&chatMentionCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(),
SLOT(setChatMention(QT_STATE_CHANGED_T))); &SettingsCache::setChatMention);
chatMentionCompleterCheckbox.setChecked(SettingsCache::instance().getChatMentionCompleter()); chatMentionCompleterCheckbox.setChecked(SettingsCache::instance().getChatMentionCompleter());
connect(&chatMentionCompleterCheckbox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), connect(&chatMentionCompleterCheckbox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(),
SLOT(setChatMentionCompleter(QT_STATE_CHANGED_T))); &SettingsCache::setChatMentionCompleter);
explainMessagesLabel.setTextInteractionFlags(Qt::LinksAccessibleByMouse); explainMessagesLabel.setTextInteractionFlags(Qt::LinksAccessibleByMouse);
explainMessagesLabel.setOpenExternalLinks(true); explainMessagesLabel.setOpenExternalLinks(true);
ignoreUnregUsersMainChat.setChecked(SettingsCache::instance().getIgnoreUnregisteredUsers()); ignoreUnregUsersMainChat.setChecked(SettingsCache::instance().getIgnoreUnregisteredUsers());
ignoreUnregUserMessages.setChecked(SettingsCache::instance().getIgnoreUnregisteredUserMessages()); ignoreUnregUserMessages.setChecked(SettingsCache::instance().getIgnoreUnregisteredUserMessages());
connect(&ignoreUnregUsersMainChat, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), connect(&ignoreUnregUsersMainChat, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(),
SLOT(setIgnoreUnregisteredUsers(QT_STATE_CHANGED_T))); &SettingsCache::setIgnoreUnregisteredUsers);
connect(&ignoreUnregUserMessages, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), connect(&ignoreUnregUserMessages, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(),
SLOT(setIgnoreUnregisteredUserMessages(QT_STATE_CHANGED_T))); &SettingsCache::setIgnoreUnregisteredUserMessages);
invertMentionForeground.setChecked(SettingsCache::instance().getChatMentionForeground()); invertMentionForeground.setChecked(SettingsCache::instance().getChatMentionForeground());
connect(&invertMentionForeground, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), this, connect(&invertMentionForeground, &QCheckBox::QT_STATE_CHANGED, this, &MessagesSettingsPage::updateTextColor);
SLOT(updateTextColor(QT_STATE_CHANGED_T)));
invertHighlightForeground.setChecked(SettingsCache::instance().getChatHighlightForeground()); invertHighlightForeground.setChecked(SettingsCache::instance().getChatHighlightForeground());
connect(&invertHighlightForeground, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), this, connect(&invertHighlightForeground, &QCheckBox::QT_STATE_CHANGED, this,
SLOT(updateTextHighlightColor(QT_STATE_CHANGED_T))); &MessagesSettingsPage::updateTextHighlightColor);
mentionColor = new QLineEdit(); mentionColor = new QLineEdit();
mentionColor->setText(SettingsCache::instance().getChatMentionColor()); mentionColor->setText(SettingsCache::instance().getChatMentionColor());
@ -884,16 +895,15 @@ MessagesSettingsPage::MessagesSettingsPage()
connect(mentionColor, SIGNAL(textChanged(QString)), this, SLOT(updateColor(QString))); connect(mentionColor, SIGNAL(textChanged(QString)), this, SLOT(updateColor(QString)));
messagePopups.setChecked(SettingsCache::instance().getShowMessagePopup()); messagePopups.setChecked(SettingsCache::instance().getShowMessagePopup());
connect(&messagePopups, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), connect(&messagePopups, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(),
SLOT(setShowMessagePopups(QT_STATE_CHANGED_T))); &SettingsCache::setShowMessagePopups);
mentionPopups.setChecked(SettingsCache::instance().getShowMentionPopup()); mentionPopups.setChecked(SettingsCache::instance().getShowMentionPopup());
connect(&mentionPopups, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), connect(&mentionPopups, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(),
SLOT(setShowMentionPopups(QT_STATE_CHANGED_T))); &SettingsCache::setShowMentionPopups);
roomHistory.setChecked(SettingsCache::instance().getRoomHistory()); roomHistory.setChecked(SettingsCache::instance().getRoomHistory());
connect(&roomHistory, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), connect(&roomHistory, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), &SettingsCache::setRoomHistory);
SLOT(setRoomHistory(QT_STATE_CHANGED_T)));
customAlertString = new QLineEdit(); customAlertString = new QLineEdit();
customAlertString->setPlaceholderText(tr("Word1 Word2 Word3")); customAlertString->setPlaceholderText(tr("Word1 Word2 Word3"));
@ -1095,8 +1105,8 @@ void MessagesSettingsPage::retranslateUi()
SoundSettingsPage::SoundSettingsPage() SoundSettingsPage::SoundSettingsPage()
{ {
soundEnabledCheckBox.setChecked(SettingsCache::instance().getSoundEnabled()); soundEnabledCheckBox.setChecked(SettingsCache::instance().getSoundEnabled());
connect(&soundEnabledCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), &SettingsCache::instance(), connect(&soundEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(),
SLOT(setSoundEnabled(QT_STATE_CHANGED_T))); &SettingsCache::setSoundEnabled);
QString themeName = SettingsCache::instance().getSoundThemeName(); QString themeName = SettingsCache::instance().getSoundThemeName();

View file

@ -113,7 +113,7 @@ class UserInterfaceSettingsPage : public AbstractSettingsPage
{ {
Q_OBJECT Q_OBJECT
private slots: private slots:
void setNotificationEnabled(int); void setNotificationEnabled(QT_STATE_CHANGED_T);
private: private:
QCheckBox notificationsEnabledCheckBox; QCheckBox notificationsEnabledCheckBox;
@ -174,6 +174,8 @@ private:
QPushButton *updateNowButton; QPushButton *updateNowButton;
QLabel networkCacheLabel; QLabel networkCacheLabel;
QSpinBox networkCacheEdit; QSpinBox networkCacheEdit;
QLabel networkRedirectCacheTtlLabel;
QSpinBox networkRedirectCacheTtlEdit;
QSpinBox pixmapCacheEdit; QSpinBox pixmapCacheEdit;
QLabel pixmapCacheLabel; QLabel pixmapCacheLabel;
}; };

View file

@ -13,6 +13,9 @@ enum GraphicsItemType
typeOther = QGraphicsItem::UserType + 6 typeOther = QGraphicsItem::UserType + 6
}; };
/**
* Parent class of all objects that appear in a game.
*/
class AbstractGraphicsItem : public QObject, public QGraphicsItem class AbstractGraphicsItem : public QObject, public QGraphicsItem
{ {
Q_OBJECT Q_OBJECT

View file

@ -3,61 +3,152 @@
#include "card_database.h" #include "card_database.h"
#include "card_item.h" #include "card_item.h"
#include <QDebug>
#include <algorithm> #include <algorithm>
CardList::CardList(bool _contentsKnown) : QList<CardItem *>(), contentsKnown(_contentsKnown) CardList::CardList(bool _contentsKnown) : QList<CardItem *>(), 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 (!contentsKnown && !empty()) {
if (empty()) return at(0);
return 0; } else {
CardItem *temp = at(0); for (auto *cardItem : *this) {
if (remove) if (cardItem->getId() == cardId) {
removeAt(0); return cardItem;
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;
} }
} }
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<SortOption> &option)
{ {
private: // early return if we know we won't be sorting
int flags; if (option.isEmpty()) {
return;
public:
explicit compareFunctor(int _flags) : flags(_flags)
{
} }
inline bool operator()(CardItem *a, CardItem *b) const
{ auto comparator = [&option](CardItem *a, CardItem *b) {
if (flags & SortByType) { for (auto prop : option) {
QString t1 = a->getInfo() ? a->getInfo()->getMainCardType() : QString(); auto extractor = getExtractorFor(prop);
QString t2 = b->getInfo() ? b->getInfo()->getMainCardType() : QString(); QString t1 = extractor(a);
if ((t1 == t2) && (flags & SortByName)) QString t2 = extractor(b);
return a->getName() < b->getName(); if (t1 != t2) {
return t1 < t2; return t1 < t2;
} else
return a->getName() < b->getName();
} }
}; }
return false;
};
void CardList::sort(int flags) std::sort(begin(), end(), comparator);
{ }
compareFunctor cf(flags);
std::sort(begin(), end(), cf); /**
* 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<QString(CardItem *)> 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 ""; };
} }

View file

@ -7,25 +7,39 @@ class CardItem;
class CardList : public QList<CardItem *> class CardList : public QList<CardItem *>
{ {
private:
class compareFunctor;
protected: protected:
bool contentsKnown; bool contentsKnown;
public: public:
enum SortFlags enum SortOption
{ {
SortByName = 1, NoSort,
SortByType = 2
// 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); CardList(bool _contentsKnown);
CardItem *findCard(const int id, const bool remove, int *position = NULL); CardItem *findCard(const int cardId) const;
bool getContentsKnown() const bool getContentsKnown() const
{ {
return contentsKnown; return contentsKnown;
} }
void sort(int flags = SortByName);
void sortBy(const QList<SortOption> &options);
static std::function<QString(CardItem *)> getExtractorFor(SortOption option);
}; };
#endif #endif

View file

@ -1893,7 +1893,8 @@ void Player::setCardAttrHelper(const GameEventContext &context,
CardItem *card, CardItem *card,
CardAttribute attribute, CardAttribute attribute,
const QString &avalue, const QString &avalue,
bool allCards) bool allCards,
EventProcessingOptions options)
{ {
if (card == nullptr) { if (card == nullptr) {
return; return;
@ -1907,7 +1908,8 @@ void Player::setCardAttrHelper(const GameEventContext &context,
if (!allCards) { if (!allCards) {
emit logSetTapped(this, card, tapped); emit logSetTapped(this, card, tapped);
} }
card->setTapped(tapped, !moveCardContext); bool canAnimate = !options.testFlag(SKIP_TAP_ANIMATION) && !moveCardContext;
card->setTapped(tapped, canAnimate);
} }
break; break;
} }
@ -2049,7 +2051,9 @@ void Player::eventCreateToken(const Event_CreateToken &event)
zone->addCard(card, true, event.x(), event.y()); 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); CardZone *zone = zones.value(QString::fromStdString(event.zone_name()), 0);
if (!zone) { if (!zone) {
@ -2059,8 +2063,8 @@ void Player::eventSetCardAttr(const Event_SetCardAttr &event, const GameEventCon
if (!event.has_card_id()) { if (!event.has_card_id()) {
const CardList &cards = zone->getCards(); const CardList &cards = zone->getCards();
for (int i = 0; i < cards.size(); ++i) { for (int i = 0; i < cards.size(); ++i) {
setCardAttrHelper(context, cards.at(i), event.attribute(), QString::fromStdString(event.attr_value()), setCardAttrHelper(context, cards.at(i), event.attribute(), QString::fromStdString(event.attr_value()), true,
true); options);
} }
if (event.attribute() == AttrTapped) { if (event.attribute() == AttrTapped) {
emit logSetTapped(this, nullptr, event.attr_value() == "1"); 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"; qWarning() << "Player::eventSetCardAttr: card id=" << event.card_id() << "not found";
return; 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)); eventCreateToken(event.GetExtension(Event_CreateToken::ext));
break; break;
case GameEvent::SET_CARD_ATTR: case GameEvent::SET_CARD_ATTR:
eventSetCardAttr(event.GetExtension(Event_SetCardAttr::ext), context); eventSetCardAttr(event.GetExtension(Event_SetCardAttr::ext), context, options);
break; break;
case GameEvent::SET_CARD_COUNTER: case GameEvent::SET_CARD_COUNTER:
eventSetCardCounter(event.GetExtension(Event_SetCardCounter::ext)); eventSetCardCounter(event.GetExtension(Event_SetCardCounter::ext));

View file

@ -68,6 +68,9 @@ class ZoneViewZone;
const int MAX_TOKENS_PER_DIALOG = 99; const int MAX_TOKENS_PER_DIALOG = 99;
/**
* The entire graphical area belonging to a single player.
*/
class PlayerArea : public QObject, public QGraphicsItem class PlayerArea : public QObject, public QGraphicsItem
{ {
Q_OBJECT Q_OBJECT
@ -229,7 +232,8 @@ private slots:
public: public:
enum EventProcessingOption enum EventProcessingOption
{ {
SKIP_REVEAL_WINDOW = 0x0001 SKIP_REVEAL_WINDOW = 0x0001,
SKIP_TAP_ANIMATION = 0x0002
}; };
Q_DECLARE_FLAGS(EventProcessingOptions, EventProcessingOption) Q_DECLARE_FLAGS(EventProcessingOptions, EventProcessingOption)
@ -301,7 +305,8 @@ private:
CardItem *card, CardItem *card,
CardAttribute attribute, CardAttribute attribute,
const QString &avalue, const QString &avalue,
bool allCards); bool allCards,
EventProcessingOptions options);
void addRelatedCardActions(const CardItem *card, QMenu *cardMenu); void addRelatedCardActions(const CardItem *card, QMenu *cardMenu);
void addRelatedCardView(const CardItem *card, QMenu *cardMenu); void addRelatedCardView(const CardItem *card, QMenu *cardMenu);
void createCard(const CardItem *sourceCard, void createCard(const CardItem *sourceCard,
@ -328,7 +333,8 @@ private:
void eventCreateArrow(const Event_CreateArrow &event); void eventCreateArrow(const Event_CreateArrow &event);
void eventDeleteArrow(const Event_DeleteArrow &event); void eventDeleteArrow(const Event_DeleteArrow &event);
void eventCreateToken(const Event_CreateToken &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 eventSetCardCounter(const Event_SetCardCounter &event);
void eventCreateCounter(const Event_CreateCounter &event); void eventCreateCounter(const Event_CreateCounter &event);
void eventSetCounter(const Event_SetCounter &event); void eventSetCounter(const Event_SetCounter &event);

View file

@ -11,6 +11,14 @@
#include <QGraphicsSceneMouseEvent> #include <QGraphicsSceneMouseEvent>
#include <QMenu> #include <QMenu>
/**
* @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, CardZone::CardZone(Player *_p,
const QString &_name, const QString &_name,
bool _hasCardAttr, 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 *CardZone::getCard(int cardId, const QString &cardName)
{ {
CardItem *c = cards.findCard(cardId, false); CardItem *c = cards.findCard(cardId);
if (!c) { if (!c) {
qDebug() << "CardZone::getCard: card id=" << cardId << "not found"; qDebug() << "CardZone::getCard: card id=" << cardId << "not found";
return 0; return 0;

View file

@ -14,6 +14,12 @@ class QAction;
class QPainter; class QPainter;
class CardDragItem; 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 class CardZone : public AbstractGraphicsItem
{ {
Q_OBJECT Q_OBJECT

View file

@ -3,6 +3,10 @@
#include "card_zone.h" #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 class PileZone : public CardZone
{ {
Q_OBJECT Q_OBJECT

View file

@ -3,6 +3,9 @@
#include "card_zone.h" #include "card_zone.h"
/**
* A CardZone where the cards are laid out, with each card directly interactable by clicking.
*/
class SelectZone : public CardZone class SelectZone : public CardZone
{ {
Q_OBJECT Q_OBJECT

View file

@ -16,6 +16,13 @@
#include <QPainter> #include <QPainter>
#include <QtMath> #include <QtMath>
/**
* @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, ZoneViewZone::ZoneViewZone(Player *_p,
CardZone *_origZone, CardZone *_origZone,
int _numberCards, int _numberCards,
@ -24,7 +31,7 @@ ZoneViewZone::ZoneViewZone(Player *_p,
QGraphicsItem *parent) QGraphicsItem *parent)
: SelectZone(_p, _origZone->getName(), false, false, true, parent, true), bRect(QRectF()), minRows(0), : SelectZone(_p, _origZone->getName(), false, false, true, parent, true), bRect(QRectF()), minRows(0),
numberCards(_numberCards), origZone(_origZone), revealZone(_revealZone), numberCards(_numberCards), origZone(_origZone), revealZone(_revealZone),
writeableRevealZone(_writeableRevealZone), sortByName(false), sortByType(false) writeableRevealZone(_writeableRevealZone), groupBy(CardList::NoSort), sortBy(CardList::NoSort)
{ {
if (!(revealZone && !writeableRevealZone)) { if (!(revealZone && !writeableRevealZone)) {
origZone->getViews().append(this); origZone->getViews().append(this);
@ -105,85 +112,127 @@ void ZoneViewZone::reorganizeCards()
for (int i = 0; i < cardCount; ++i) for (int i = 0; i < cardCount; ++i)
cards[i]->setId(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); CardList cardsToDisplay(cards);
if (sortByName || sortByType)
cardsToDisplay.sort((sortByName ? CardList::SortByName : 0) | (sortByType ? CardList::SortByType : 0));
int typeColumn = 0; // sort cards
int longestRow = 0; QList<CardList::SortOption> sortOptions;
if (pileView && sortByType) { // we need sort by type enabled for the feature to work if (groupBy != CardList::NoSort) {
int typeRow = 0; sortOptions << groupBy;
QString lastCardType; }
for (int i = 0; i < cardCount; i++) {
CardItem *c = cardsToDisplay.at(i);
QString cardType = c->getInfo() ? c->getInfo()->getMainCardType() : "";
if (i) { // if not the first card if (sortBy != CardList::NoSort) {
if (cardType == lastCardType) sortOptions << sortBy;
typeRow++; // add below current card
else { // if no match then move card to next column // implicitly sort by name at the end so that cards with the same name appear together
typeColumn++; if (sortBy != CardList::SortByName) {
typeRow = 0; sortOptions << CardList::SortByName;
} }
} }
lastCardType = cardType; cardsToDisplay.sortBy(sortOptions);
qreal x = 7 + (typeColumn * CARD_WIDTH);
qreal y = typeRow * CARD_HEIGHT / 3; // position cards
c->setPos(x + 5, y + 5); GridSize gridSize;
c->setRealZValue(i); if (pileView) {
longestRow = qMax(typeRow, longestRow); gridSize = positionCardsForDisplay(cardsToDisplay, groupBy);
}
} else { } else {
for (int i = 0; i < cardCount; i++) { gridSize = positionCardsForDisplay(cardsToDisplay);
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);
}
} }
// determine bounding rect
qreal aleft = 0; qreal aleft = 0;
qreal atop = 0; qreal atop = 0;
qreal awidth = (pileView && sortByType) ? qMax(typeColumn + 1, 3) * CARD_WIDTH + (CARD_WIDTH / 2) qreal awidth = gridSize.cols * CARD_WIDTH + (CARD_WIDTH / 2) + HORIZONTAL_PADDING;
: qMax(cols, 1) * CARD_WIDTH + (CARD_WIDTH / 2); qreal aheight = (gridSize.rows * CARD_HEIGHT) / 3 + CARD_HEIGHT * 1.3;
qreal aheight = (pileView && sortByType) ? (longestRow * CARD_HEIGHT) / 3 + CARD_HEIGHT * 1.3
: (rows * CARD_HEIGHT) / 3 + CARD_HEIGHT * 1.3;
optimumRect = QRectF(aleft, atop, awidth, aheight); optimumRect = QRectF(aleft, atop, awidth, aheight);
updateGeometry(); updateGeometry();
emit optimumRectChanged(); 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(); reorganizeCards();
} }
void ZoneViewZone::setSortByType(int _sortByType) void ZoneViewZone::setSortBy(CardList::SortOption _sortBy)
{ {
sortByType = _sortByType; sortBy = _sortBy;
if (!sortByType)
pileView = false;
reorganizeCards(); reorganizeCards();
} }

View file

@ -10,19 +10,39 @@ class Response;
class ServerInfo_Card; class ServerInfo_Card;
class QGraphicsSceneWheelEvent; 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 class ZoneViewZone : public SelectZone, public QGraphicsLayoutItem
{ {
Q_OBJECT Q_OBJECT
Q_INTERFACES(QGraphicsLayoutItem) Q_INTERFACES(QGraphicsLayoutItem)
private: private:
static constexpr int HORIZONTAL_PADDING = 12;
static constexpr int VERTICAL_PADDING = 5;
QRectF bRect, optimumRect; QRectF bRect, optimumRect;
int minRows, numberCards; int minRows, numberCards;
void handleDropEvent(const QList<CardDragItem *> &dragItems, CardZone *startZone, const QPoint &dropPoint); void handleDropEvent(const QList<CardDragItem *> &dragItems, CardZone *startZone, const QPoint &dropPoint);
CardZone *origZone; CardZone *origZone;
bool revealZone, writeableRevealZone; bool revealZone, writeableRevealZone;
bool sortByName, sortByType; CardList::SortOption groupBy, sortBy;
bool pileView; bool pileView;
struct GridSize
{
int rows;
int cols;
};
GridSize positionCardsForDisplay(CardList &cards, CardList::SortOption pileOption = CardList::NoSort);
public: public:
ZoneViewZone(Player *_p, ZoneViewZone(Player *_p,
CardZone *_origZone, CardZone *_origZone,
@ -55,8 +75,8 @@ public:
} }
void setWriteableRevealZone(bool _writeableRevealZone); void setWriteableRevealZone(bool _writeableRevealZone);
public slots: public slots:
void setSortByName(int _sortByName); void setGroupBy(CardList::SortOption _groupBy);
void setSortByType(int _sortByType); void setSortBy(CardList::SortOption _sortBy);
void setPileView(int _pileView); void setPileView(int _pileView);
private slots: private slots:
void zoneDumpReceived(const Response &r); void zoneDumpReceived(const Response &r);

View file

@ -17,6 +17,15 @@
#include <QStyleOption> #include <QStyleOption>
#include <QStyleOptionTitleBar> #include <QStyleOptionTitleBar>
/**
* @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, ZoneViewWidget::ZoneViewWidget(Player *_player,
CardZone *_origZone, CardZone *_origZone,
int numberCards, int numberCards,
@ -31,22 +40,27 @@ ZoneViewWidget::ZoneViewWidget(Player *_player,
setFlag(ItemIgnoresTransformations); setFlag(ItemIgnoresTransformations);
QGraphicsLinearLayout *vbox = new QGraphicsLinearLayout(Qt::Vertical); 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) { if (numberCards < 0) {
hPilebox = new QGraphicsLinearLayout(Qt::Horizontal); // top row
QGraphicsLinearLayout *hFilterbox = new QGraphicsLinearLayout(Qt::Horizontal); QGraphicsLinearLayout *hTopRow = new QGraphicsLinearLayout(Qt::Horizontal);
QGraphicsProxyWidget *sortByNameProxy = new QGraphicsProxyWidget; // groupBy options
sortByNameProxy->setWidget(&sortByNameCheckBox); QGraphicsProxyWidget *groupBySelectorProxy = new QGraphicsProxyWidget;
hFilterbox->addItem(sortByNameProxy); groupBySelectorProxy->setWidget(&groupBySelector);
groupBySelectorProxy->setZValue(2000000008);
hTopRow->addItem(groupBySelectorProxy);
QGraphicsProxyWidget *sortByTypeProxy = new QGraphicsProxyWidget; // sortBy options
sortByTypeProxy->setWidget(&sortByTypeCheckBox); QGraphicsProxyWidget *sortBySelectorProxy = new QGraphicsProxyWidget;
hFilterbox->addItem(sortByTypeProxy); sortBySelectorProxy->setWidget(&sortBySelector);
sortBySelectorProxy->setZValue(2000000007);
hTopRow->addItem(sortBySelectorProxy);
vbox->addItem(hFilterbox); vbox->addItem(hTopRow);
// line
QGraphicsProxyWidget *lineProxy = new QGraphicsProxyWidget; QGraphicsProxyWidget *lineProxy = new QGraphicsProxyWidget;
QFrame *line = new QFrame; QFrame *line = new QFrame;
line->setFrameShape(QFrame::HLine); line->setFrameShape(QFrame::HLine);
@ -54,19 +68,24 @@ ZoneViewWidget::ZoneViewWidget(Player *_player,
lineProxy->setWidget(line); lineProxy->setWidget(line);
vbox->addItem(lineProxy); vbox->addItem(lineProxy);
// bottom row
QGraphicsLinearLayout *hBottomRow = new QGraphicsLinearLayout(Qt::Horizontal);
// pile view options
QGraphicsProxyWidget *pileViewProxy = new QGraphicsProxyWidget; QGraphicsProxyWidget *pileViewProxy = new QGraphicsProxyWidget;
pileViewProxy->setWidget(&pileViewCheckBox); pileViewProxy->setWidget(&pileViewCheckBox);
hPilebox->addItem(pileViewProxy); hBottomRow->addItem(pileViewProxy);
}
if (_origZone->getIsShufflable() && (numberCards == -1)) { // shuffle options
if (_origZone->getIsShufflable() && numberCards == -1) {
shuffleCheckBox.setChecked(true); shuffleCheckBox.setChecked(true);
QGraphicsProxyWidget *shuffleProxy = new QGraphicsProxyWidget; QGraphicsProxyWidget *shuffleProxy = new QGraphicsProxyWidget;
shuffleProxy->setWidget(&shuffleCheckBox); shuffleProxy->setWidget(&shuffleCheckBox);
hPilebox->addItem(shuffleProxy); hBottomRow->addItem(shuffleProxy);
} }
vbox->addItem(hPilebox); vbox->addItem(hBottomRow);
}
extraHeight = vbox->sizeHint(Qt::PreferredSize).height(); extraHeight = vbox->sizeHint(Qt::PreferredSize).height();
resize(150, 150); resize(150, 150);
@ -93,42 +112,71 @@ ZoneViewWidget::ZoneViewWidget(Player *_player,
connect(zone, SIGNAL(wheelEventReceived(QGraphicsSceneWheelEvent *)), scrollBarProxy, connect(zone, SIGNAL(wheelEventReceived(QGraphicsSceneWheelEvent *)), scrollBarProxy,
SLOT(recieveWheelEvent(QGraphicsSceneWheelEvent *))); SLOT(recieveWheelEvent(QGraphicsSceneWheelEvent *)));
// numberCard is the num of cards we want to reveal from an area. Ex: scry the top 3 cards. retranslateUi();
// If the number is < 0 then it means that we can make the area sorted and we dont care about the order.
// only wire up sort options after creating ZoneViewZone, since it segfaults otherwise.
if (numberCards < 0) { if (numberCards < 0) {
connect(&sortByNameCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), this, connect(&groupBySelector, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
SLOT(processSortByName(QT_STATE_CHANGED_T))); &ZoneViewWidget::processGroupBy);
connect(&sortByTypeCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), this, connect(&sortBySelector, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
SLOT(processSortByType(QT_STATE_CHANGED_T))); &ZoneViewWidget::processSortBy);
connect(&pileViewCheckBox, SIGNAL(QT_STATE_CHANGED(QT_STATE_CHANGED_T)), this, connect(&pileViewCheckBox, &QCheckBox::QT_STATE_CHANGED, this, &ZoneViewWidget::processSetPileView);
SLOT(processSetPileView(QT_STATE_CHANGED_T))); groupBySelector.setCurrentIndex(SettingsCache::instance().getZoneViewGroupByIndex());
sortByNameCheckBox.setChecked(SettingsCache::instance().getZoneViewSortByName()); sortBySelector.setCurrentIndex(SettingsCache::instance().getZoneViewSortByIndex());
sortByTypeCheckBox.setChecked(SettingsCache::instance().getZoneViewSortByType());
pileViewCheckBox.setChecked(SettingsCache::instance().getZoneViewPileView()); pileViewCheckBox.setChecked(SettingsCache::instance().getZoneViewPileView());
if (!SettingsCache::instance().getZoneViewSortByType())
if (CardList::NoSort == static_cast<CardList::SortOption>(groupBySelector.currentData().toInt())) {
pileViewCheckBox.setEnabled(false); pileViewCheckBox.setEnabled(false);
} }
}
retranslateUi();
setLayout(vbox); setLayout(vbox);
connect(zone, SIGNAL(optimumRectChanged()), this, SLOT(resizeToZoneContents())); connect(zone, SIGNAL(optimumRectChanged()), this, SLOT(resizeToZoneContents()));
connect(zone, SIGNAL(beingDeleted()), this, SLOT(zoneDeleted())); connect(zone, SIGNAL(beingDeleted()), this, SLOT(zoneDeleted()));
zone->initializeCards(cardList); 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); auto option = static_cast<CardList::SortOption>(groupBySelector.itemData(index).toInt());
SettingsCache::instance().setZoneViewSortByType(value); SettingsCache::instance().setZoneViewGroupByIndex(index);
zone->setPileView(pileViewCheckBox.isChecked()); zone->setGroupBy(option);
zone->setSortByType(value);
// 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<CardList::SortOption>(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); auto option = static_cast<CardList::SortOption>(sortBySelector.itemData(index).toInt());
zone->setSortByName(value);
// set to SortByName instead if it has the same value as groupBy
if (option != CardList::NoSort &&
option == static_cast<CardList::SortOption>(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) void ZoneViewWidget::processSetPileView(QT_STATE_CHANGED_T value)
@ -140,8 +188,30 @@ void ZoneViewWidget::processSetPileView(QT_STATE_CHANGED_T value)
void ZoneViewWidget::retranslateUi() void ZoneViewWidget::retranslateUi()
{ {
setWindowTitle(zone->getTranslatedName(false, CaseNominative)); 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")); shuffleCheckBox.setText(tr("shuffle when closing"));
pileViewCheckBox.setText(tr("pile view")); pileViewCheckBox.setText(tr("pile view"));
} }

View file

@ -4,6 +4,7 @@
#include "../../utility/macros.h" #include "../../utility/macros.h"
#include <QCheckBox> #include <QCheckBox>
#include <QComboBox>
#include <QGraphicsProxyWidget> #include <QGraphicsProxyWidget>
#include <QGraphicsWidget> #include <QGraphicsWidget>
@ -14,7 +15,6 @@ class ZoneViewZone;
class Player; class Player;
class CardDatabase; class CardDatabase;
class QScrollBar; class QScrollBar;
class QCheckBox;
class GameScene; class GameScene;
class ServerInfo_Card; class ServerInfo_Card;
class QGraphicsSceneMouseEvent; 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 class ZoneViewWidget : public QGraphicsWidget
{ {
Q_OBJECT Q_OBJECT
@ -41,8 +47,8 @@ private:
QPushButton *closeButton; QPushButton *closeButton;
QScrollBar *scrollBar; QScrollBar *scrollBar;
ScrollableGraphicsProxyWidget *scrollBarProxy; ScrollableGraphicsProxyWidget *scrollBarProxy;
QCheckBox sortByNameCheckBox; QComboBox groupBySelector;
QCheckBox sortByTypeCheckBox; QComboBox sortBySelector;
QCheckBox shuffleCheckBox; QCheckBox shuffleCheckBox;
QCheckBox pileViewCheckBox; QCheckBox pileViewCheckBox;
@ -52,8 +58,8 @@ private:
signals: signals:
void closePressed(ZoneViewWidget *zv); void closePressed(ZoneViewWidget *zv);
private slots: private slots:
void processSortByType(QT_STATE_CHANGED_T value); void processGroupBy(int value);
void processSortByName(QT_STATE_CHANGED_T value); void processSortBy(int value);
void processSetPileView(QT_STATE_CHANGED_T value); void processSetPileView(QT_STATE_CHANGED_T value);
void resizeToZoneContents(); void resizeToZoneContents();
void handleScrollBarChange(int value); void handleScrollBarChange(int value);

View file

@ -221,6 +221,7 @@ SettingsCache::SettingsCache()
pixmapCacheSize = PIXMAPCACHE_SIZE_DEFAULT; pixmapCacheSize = PIXMAPCACHE_SIZE_DEFAULT;
networkCacheSize = settings->value("personal/networkCacheSize", NETWORK_CACHE_SIZE_DEFAULT).toInt(); 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(); picDownload = settings->value("personal/picturedownload", true).toBool();
@ -251,8 +252,8 @@ SettingsCache::SettingsCache()
chatMentionColor = settings->value("chat/mentioncolor", "A6120D").toString(); chatMentionColor = settings->value("chat/mentioncolor", "A6120D").toString();
chatHighlightColor = settings->value("chat/highlightcolor", "A6120D").toString(); chatHighlightColor = settings->value("chat/highlightcolor", "A6120D").toString();
zoneViewSortByName = settings->value("zoneview/sortbyname", true).toBool(); zoneViewGroupByIndex = settings->value("zoneview/groupby", 1).toInt();
zoneViewSortByType = settings->value("zoneview/sortbytype", true).toBool(); zoneViewSortByIndex = settings->value("zoneview/sortby", 1).toInt();
zoneViewPileView = settings->value("zoneview/pileview", true).toBool(); zoneViewPileView = settings->value("zoneview/pileview", true).toBool();
soundEnabled = settings->value("sound/enabled", false).toBool(); soundEnabled = settings->value("sound/enabled", false).toBool();
@ -581,16 +582,16 @@ void SettingsCache::setChatHighlightColor(const QString &_chatHighlightColor)
settings->setValue("chat/highlightcolor", chatHighlightColor); settings->setValue("chat/highlightcolor", chatHighlightColor);
} }
void SettingsCache::setZoneViewSortByName(QT_STATE_CHANGED_T _zoneViewSortByName) void SettingsCache::setZoneViewGroupByIndex(int _zoneViewGroupByIndex)
{ {
zoneViewSortByName = static_cast<bool>(_zoneViewSortByName); zoneViewGroupByIndex = _zoneViewGroupByIndex;
settings->setValue("zoneview/sortbyname", zoneViewSortByName); settings->setValue("zoneview/groupby", zoneViewGroupByIndex);
} }
void SettingsCache::setZoneViewSortByType(QT_STATE_CHANGED_T _zoneViewSortByType) void SettingsCache::setZoneViewSortByIndex(int _zoneViewSortByIndex)
{ {
zoneViewSortByType = static_cast<bool>(_zoneViewSortByType); zoneViewSortByIndex = _zoneViewSortByIndex;
settings->setValue("zoneview/sortbytype", zoneViewSortByType); settings->setValue("zoneview/sortby", zoneViewSortByIndex);
} }
void SettingsCache::setZoneViewPileView(QT_STATE_CHANGED_T _zoneViewPileView) void SettingsCache::setZoneViewPileView(QT_STATE_CHANGED_T _zoneViewPileView)
@ -657,6 +658,13 @@ void SettingsCache::setNetworkCacheSizeInMB(const int _networkCacheSize)
emit networkCacheSizeChanged(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) void SettingsCache::setClientID(const QString &_clientID)
{ {
clientID = _clientID; clientID = _clientID;
@ -1030,6 +1038,7 @@ void SettingsCache::loadPaths()
replaysPath = getSafeConfigPath("paths/replays", dataPath + "/replays/"); replaysPath = getSafeConfigPath("paths/replays", dataPath + "/replays/");
themesPath = getSafeConfigPath("paths/themes", dataPath + "/themes/"); themesPath = getSafeConfigPath("paths/themes", dataPath + "/themes/");
picsPath = getSafeConfigPath("paths/pics", dataPath + "/pics/"); picsPath = getSafeConfigPath("paths/pics", dataPath + "/pics/");
redirectCachePath = getSafeConfigPath("paths/redirects", getCachePath() + "/redirects/");
// this has never been exposed as an user-configurable setting // this has never been exposed as an user-configurable setting
if (picsPath.endsWith("/")) { if (picsPath.endsWith("/")) {
customPicsPath = getSafeConfigPath("paths/custompics", picsPath + "CUSTOM/"); customPicsPath = getSafeConfigPath("paths/custompics", picsPath + "CUSTOM/");

View file

@ -16,16 +16,21 @@
class ReleaseChannel; class ReleaseChannel;
// size should be a multiple of 64 // In MB (Increments of 64)
#define PIXMAPCACHE_SIZE_DEFAULT 2047 #define PIXMAPCACHE_SIZE_DEFAULT 2048
#define PIXMAPCACHE_SIZE_MIN 64 #define PIXMAPCACHE_SIZE_MIN 64
#define PIXMAPCACHE_SIZE_MAX 2047 #define PIXMAPCACHE_SIZE_MAX 4096
// In MB // In MB
constexpr int NETWORK_CACHE_SIZE_DEFAULT = 1024 * 4; // 4 GB constexpr int NETWORK_CACHE_SIZE_DEFAULT = 1024 * 4; // 4 GB
constexpr int NETWORK_CACHE_SIZE_MIN = 1; // 1 MB constexpr int NETWORK_CACHE_SIZE_MIN = 1; // 1 MB
constexpr int NETWORK_CACHE_SIZE_MAX = 1024 * 1024; // 1 TB 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 DEFAULT_LANG_NAME "English"
#define CLIENT_INFO_NOT_SET "notset" #define CLIENT_INFO_NOT_SET "notset"
@ -53,6 +58,7 @@ signals:
void ignoreUnregisteredUserMessagesChanged(); void ignoreUnregisteredUserMessagesChanged();
void pixmapCacheSizeChanged(int newSizeInMBs); void pixmapCacheSizeChanged(int newSizeInMBs);
void networkCacheSizeChanged(int newSizeInMBs); void networkCacheSizeChanged(int newSizeInMBs);
void redirectCacheTtlChanged(int newTtl);
void masterVolumeChanged(int value); void masterVolumeChanged(int value);
void chatMentionCompleterChanged(); void chatMentionCompleterChanged();
void downloadSpoilerTimeIndexChanged(); void downloadSpoilerTimeIndexChanged();
@ -73,8 +79,8 @@ private:
QByteArray tokenDialogGeometry; QByteArray tokenDialogGeometry;
QByteArray setsDialogGeometry; QByteArray setsDialogGeometry;
QString lang; QString lang;
QString deckPath, replaysPath, picsPath, customPicsPath, cardDatabasePath, customCardDatabasePath, themesPath, QString deckPath, replaysPath, picsPath, redirectCachePath, customPicsPath, cardDatabasePath,
spoilerDatabasePath, tokenDatabasePath, themeName; customCardDatabasePath, themesPath, spoilerDatabasePath, tokenDatabasePath, themeName;
bool notifyAboutUpdates; bool notifyAboutUpdates;
bool notifyAboutNewVersion; bool notifyAboutNewVersion;
bool showTipsOnStartup; bool showTipsOnStartup;
@ -103,7 +109,8 @@ private:
QString chatHighlightColor; QString chatHighlightColor;
bool chatMentionForeground; bool chatMentionForeground;
bool chatHighlightForeground; bool chatHighlightForeground;
bool zoneViewSortByName, zoneViewSortByType, zoneViewPileView; int zoneViewSortByIndex, zoneViewGroupByIndex;
bool zoneViewPileView;
bool soundEnabled; bool soundEnabled;
QString soundThemeName; QString soundThemeName;
bool ignoreUnregisteredUsers; bool ignoreUnregisteredUsers;
@ -116,6 +123,7 @@ private:
bool useTearOffMenus; bool useTearOffMenus;
int pixmapCacheSize; int pixmapCacheSize;
int networkCacheSize; int networkCacheSize;
int redirectCacheTtl;
bool scaleCards; bool scaleCards;
int verticalCardOverlapPercent; int verticalCardOverlapPercent;
bool showMessagePopups; bool showMessagePopups;
@ -183,6 +191,10 @@ public:
{ {
return picsPath; return picsPath;
} }
QString getRedirectCachePath() const
{
return redirectCachePath;
}
QString getCustomPicsPath() const QString getCustomPicsPath() const
{ {
return customPicsPath; return customPicsPath;
@ -316,13 +328,19 @@ public:
{ {
return chatHighlightForeground; 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. Returns if the view should be sorted into pile view.
@ -356,6 +374,10 @@ public:
{ {
return networkCacheSize; return networkCacheSize;
} }
int getRedirectCacheTtl() const
{
return redirectCacheTtl;
}
bool getScaleCards() const bool getScaleCards() const
{ {
return scaleCards; return scaleCards;
@ -548,8 +570,8 @@ public slots:
void setChatMentionCompleter(QT_STATE_CHANGED_T _chatMentionCompleter); void setChatMentionCompleter(QT_STATE_CHANGED_T _chatMentionCompleter);
void setChatMentionForeground(QT_STATE_CHANGED_T _chatMentionForeground); void setChatMentionForeground(QT_STATE_CHANGED_T _chatMentionForeground);
void setChatHighlightForeground(QT_STATE_CHANGED_T _chatHighlightForeground); void setChatHighlightForeground(QT_STATE_CHANGED_T _chatHighlightForeground);
void setZoneViewSortByName(QT_STATE_CHANGED_T _zoneViewSortByName); void setZoneViewGroupByIndex(const int _zoneViewGroupByIndex);
void setZoneViewSortByType(QT_STATE_CHANGED_T _zoneViewSortByType); void setZoneViewSortByIndex(const int _zoneViewSortByIndex);
void setZoneViewPileView(QT_STATE_CHANGED_T _zoneViewPileView); void setZoneViewPileView(QT_STATE_CHANGED_T _zoneViewPileView);
void setSoundEnabled(QT_STATE_CHANGED_T _soundEnabled); void setSoundEnabled(QT_STATE_CHANGED_T _soundEnabled);
void setSoundThemeName(const QString &_soundThemeName); void setSoundThemeName(const QString &_soundThemeName);
@ -557,6 +579,7 @@ public slots:
void setIgnoreUnregisteredUserMessages(QT_STATE_CHANGED_T _ignoreUnregisteredUserMessages); void setIgnoreUnregisteredUserMessages(QT_STATE_CHANGED_T _ignoreUnregisteredUserMessages);
void setPixmapCacheSize(const int _pixmapCacheSize); void setPixmapCacheSize(const int _pixmapCacheSize);
void setNetworkCacheSizeInMB(const int _networkCacheSize); void setNetworkCacheSizeInMB(const int _networkCacheSize);
void setNetworkRedirectCacheTtl(const int _redirectCacheTtl);
void setCardScaling(const QT_STATE_CHANGED_T _scaleCards); void setCardScaling(const QT_STATE_CHANGED_T _scaleCards);
void setStackCardOverlapPercent(const int _verticalCardOverlapPercent); void setStackCardOverlapPercent(const int _verticalCardOverlapPercent);
void setShowMessagePopups(const QT_STATE_CHANGED_T _showMessagePopups); void setShowMessagePopups(const QT_STATE_CHANGED_T _showMessagePopups);

View file

@ -2,56 +2,28 @@
#include "settings_manager.h" #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) DownloadSettings::DownloadSettings(const QString &settingPath, QObject *parent = nullptr)
: SettingsManager(settingPath + "downloads.ini", parent) : SettingsManager(settingPath + "downloads.ini", parent)
{ {
downloadURLs = getValue("urls", "downloads").value<QStringList>();
} }
void DownloadSettings::setDownloadUrlAt(int index, const QString &url) void DownloadSettings::setDownloadUrls(const QStringList &downloadURLs)
{ {
downloadURLs.insert(index, url);
setValue(QVariant::fromValue(downloadURLs), "urls", "downloads"); setValue(QVariant::fromValue(downloadURLs), "urls", "downloads");
} }
/**
* If reset or first run, this method contains the default URLs we will populate
*/
QStringList DownloadSettings::getAllURLs() QStringList DownloadSettings::getAllURLs()
{ {
// First run, these will be empty return getValue("urls", "downloads").toStringList();
if (downloadURLs.count() == 0) {
populateDefaultURLs();
}
return downloadURLs;
} }
void DownloadSettings::populateDefaultURLs() void DownloadSettings::resetToDefaultURLs()
{ {
downloadURLs.clear(); setValue(QVariant::fromValue(DEFAULT_DOWNLOAD_URLS), "urls", "downloads");
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");
}
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();
} }

View file

@ -10,20 +10,14 @@ class DownloadSettings : public SettingsManager
Q_OBJECT Q_OBJECT
friend class SettingsCache; friend class SettingsCache;
static const QStringList DEFAULT_DOWNLOAD_URLS;
public: public:
explicit DownloadSettings(const QString &, QObject *); explicit DownloadSettings(const QString &, QObject *);
QStringList getAllURLs(); QStringList getAllURLs();
QString getDownloadUrlAt(int); void setDownloadUrls(const QStringList &downloadURLs);
void setDownloadUrlAt(int, const QString &); void resetToDefaultURLs();
int getCount();
void clear();
private:
QStringList downloadURLs;
private:
void populateDefaultURLs();
}; };
#endif // COCKATRICE_DOWNLOADSETTINGS_H #endif // COCKATRICE_DOWNLOADSETTINGS_H

View file

@ -45,7 +45,7 @@
<message> <message>
<location filename="src/dialogs/dlg_settings.cpp" line="416"/> <location filename="src/dialogs/dlg_settings.cpp" line="416"/>
<source>Open themes folder</source> <source>Open themes folder</source>
<translation>Open thema folder</translation> <translation>Open themafolder</translation>
</message> </message>
<message> <message>
<location filename="src/dialogs/dlg_settings.cpp" line="418"/> <location filename="src/dialogs/dlg_settings.cpp" line="418"/>
@ -55,7 +55,7 @@
<message> <message>
<location filename="src/dialogs/dlg_settings.cpp" line="419"/> <location filename="src/dialogs/dlg_settings.cpp" line="419"/>
<source>Display card names on cards having a picture</source> <source>Display card names on cards having a picture</source>
<translation>Kaartnamen altijd weergeven</translation> <translation>Kaartnamen altijd weergeven op kaarten met een afbeelding</translation>
</message> </message>
<message> <message>
<location filename="src/dialogs/dlg_settings.cpp" line="420"/> <location filename="src/dialogs/dlg_settings.cpp" line="420"/>
@ -7063,7 +7063,7 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties
<message> <message>
<location filename="src/settings/shortcuts_settings.h" line="73"/> <location filename="src/settings/shortcuts_settings.h" line="73"/>
<source>Replays</source> <source>Replays</source>
<translation type="unfinished"/> <translation>Replays</translation>
</message> </message>
<message> <message>
<location filename="src/settings/shortcuts_settings.h" line="143"/> <location filename="src/settings/shortcuts_settings.h" line="143"/>
@ -7749,12 +7749,12 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties
<message> <message>
<location filename="src/settings/shortcuts_settings.h" line="593"/> <location filename="src/settings/shortcuts_settings.h" line="593"/>
<source>Play/Pause</source> <source>Play/Pause</source>
<translation type="unfinished"/> <translation>Start/Stop</translation>
</message> </message>
<message> <message>
<location filename="src/settings/shortcuts_settings.h" line="596"/> <location filename="src/settings/shortcuts_settings.h" line="596"/>
<source>Toggle Fast Forward</source> <source>Toggle Fast Forward</source>
<translation type="unfinished"/> <translation>Versnelling Schakelen</translation>
</message> </message>
</context> </context>
</TS> </TS>

View file

@ -184,10 +184,10 @@ void SettingsCache::setChatMentionColor(const QString & /* _chatMentionColor */)
void SettingsCache::setChatHighlightColor(const QString & /* _chatHighlightColor */) 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 */) 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::setNetworkCacheSizeInMB(const int /* _networkCacheSize */)
{ {
} }
void SettingsCache::setNetworkRedirectCacheTtl(const int /* _redirectCacheTtl */)
{
}
void SettingsCache::setClientID(const QString & /* _clientID */) void SettingsCache::setClientID(const QString & /* _clientID */)
{ {
} }

View file

@ -188,10 +188,10 @@ void SettingsCache::setChatMentionColor(const QString & /* _chatMentionColor */)
void SettingsCache::setChatHighlightColor(const QString & /* _chatHighlightColor */) 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 */) 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::setNetworkCacheSizeInMB(const int /* _networkCacheSize */)
{ {
} }
void SettingsCache::setNetworkRedirectCacheTtl(const int /* _redirectCacheTtl */)
{
}
void SettingsCache::setClientID(const QString & /* _clientID */) void SettingsCache::setClientID(const QString & /* _clientID */)
{ {
} }

View file

@ -7006,9 +7006,9 @@
} }
}, },
"node_modules/cross-spawn": { "node_modules/cross-spawn": {
"version": "7.0.3", "version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dependencies": { "dependencies": {
"path-key": "^3.1.0", "path-key": "^3.1.0",
"shebang-command": "^2.0.0", "shebang-command": "^2.0.0",
@ -25082,9 +25082,9 @@
} }
}, },
"cross-spawn": { "cross-spawn": {
"version": "7.0.3", "version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"requires": { "requires": {
"path-key": "^3.1.0", "path-key": "^3.1.0",
"shebang-command": "^2.0.0", "shebang-command": "^2.0.0",