diff --git a/cockatrice/cockatrice.qrc b/cockatrice/cockatrice.qrc
index 37fb145f0..00f9f0284 100644
--- a/cockatrice/cockatrice.qrc
+++ b/cockatrice/cockatrice.qrc
@@ -69,6 +69,7 @@
resources/config/interface.svg
resources/config/messages.svg
resources/config/deckeditor.svg
+ resources/config/storage.svg
resources/config/shorcuts.svg
resources/config/sound.svg
resources/config/debug.ini
diff --git a/cockatrice/resources/config/storage.svg b/cockatrice/resources/config/storage.svg
new file mode 100644
index 000000000..de85228dc
--- /dev/null
+++ b/cockatrice/resources/config/storage.svg
@@ -0,0 +1,799 @@
+
+
+
+
diff --git a/cockatrice/src/client/settings/cache_settings.cpp b/cockatrice/src/client/settings/cache_settings.cpp
index 779522b72..64416e5ee 100644
--- a/cockatrice/src/client/settings/cache_settings.cpp
+++ b/cockatrice/src/client/settings/cache_settings.cpp
@@ -1,5 +1,7 @@
#include "cache_settings.h"
+#include "../../interface/card_picture_loader/card_picture_loader_cache_method.h"
+#include "../../interface/card_picture_loader/card_picture_loader_local_schemes.h"
#include "../network/update/client/release_channel.h"
#include "card_counter_settings.h"
#include "version_string.h"
@@ -278,6 +280,16 @@ SettingsCache::SettingsCache()
networkCacheSize = settings->value("personal/networkCacheSize", NETWORK_CACHE_SIZE_DEFAULT).toInt();
redirectCacheTtl = settings->value("personal/redirectCacheTtl", NETWORK_REDIRECT_CACHE_TTL_DEFAULT).toInt();
+ cardPictureLoaderCacheMethod =
+ settings
+ ->value("personal/cardPictureLoaderCacheMethod",
+ static_cast(CardPictureLoaderCacheMethod::CacheMethod::NETWORK_CACHE))
+ .toInt();
+ localCardImageStorageNamingScheme =
+ settings
+ ->value("personal/localCardImageStorageNamingScheme",
+ static_cast(CardPictureLoaderLocalSchemes::NamingScheme::Set_Folder_Name_Set_Collector))
+ .toInt();
picDownload = settings->value("personal/picturedownload", true).toBool();
showStatusBar = settings->value("personal/showStatusBar", false).toBool();
@@ -1112,6 +1124,13 @@ void SettingsCache::setPixmapCacheSize(const int _pixmapCacheSize)
emit pixmapCacheSizeChanged(pixmapCacheSize);
}
+void SettingsCache::setCardImageCacheMethod(const CardPictureLoaderCacheMethod::CacheMethod _cardImageCachingMethod)
+{
+ cardPictureLoaderCacheMethod = static_cast(_cardImageCachingMethod);
+ settings->setValue("personal/cardPictureLoaderCacheMethod", cardPictureLoaderCacheMethod);
+ emit cardPictureLoaderCacheMethodChanged(cardPictureLoaderCacheMethod);
+}
+
void SettingsCache::setNetworkCacheSizeInMB(const int _networkCacheSize)
{
networkCacheSize = _networkCacheSize;
@@ -1126,6 +1145,14 @@ void SettingsCache::setNetworkRedirectCacheTtl(const int _redirectCacheTtl)
emit redirectCacheTtlChanged(redirectCacheTtl);
}
+void SettingsCache::setLocalCardImageStorageNamingScheme(
+ const CardPictureLoaderLocalSchemes::NamingScheme _localCardImageStorageNamingScheme)
+{
+ localCardImageStorageNamingScheme = static_cast(_localCardImageStorageNamingScheme);
+ settings->setValue("personal/localCardImageStorageNamingScheme", localCardImageStorageNamingScheme);
+ emit localCardImageStorageNamingSchemeChanged(localCardImageStorageNamingScheme);
+}
+
void SettingsCache::setClientID(const QString &_clientID)
{
clientID = _clientID;
diff --git a/cockatrice/src/client/settings/cache_settings.h b/cockatrice/src/client/settings/cache_settings.h
index 0cd5ceb68..90aa8eedb 100644
--- a/cockatrice/src/client/settings/cache_settings.h
+++ b/cockatrice/src/client/settings/cache_settings.h
@@ -7,6 +7,8 @@
#ifndef SETTINGSCACHE_H
#define SETTINGSCACHE_H
+#include "../../interface/card_picture_loader/card_picture_loader_cache_method.h"
+#include "../../interface/card_picture_loader/card_picture_loader_local_schemes.h"
#include "shortcuts_settings.h"
#include
@@ -184,6 +186,8 @@ signals:
void pixmapCacheSizeChanged(int newSizeInMBs);
void networkCacheSizeChanged(int newSizeInMBs);
void redirectCacheTtlChanged(int newTtl);
+ void cardPictureLoaderCacheMethodChanged(int cardPictureLoaderCacheMethod);
+ void localCardImageStorageNamingSchemeChanged(int localCardImageStorageNamingScheme);
void masterVolumeChanged(int value);
void chatMentionCompleterChanged();
void downloadSpoilerTimeIndexChanged();
@@ -303,6 +307,8 @@ private:
int pixmapCacheSize;
int networkCacheSize;
int redirectCacheTtl;
+ int cardPictureLoaderCacheMethod;
+ int localCardImageStorageNamingScheme;
bool scaleCards;
int verticalCardOverlapPercent;
bool showMessagePopups;
@@ -786,6 +792,10 @@ public:
{
return pixmapCacheSize;
}
+ [[nodiscard]] CardPictureLoaderCacheMethod::CacheMethod getCardPictureLoaderCacheMethod() const
+ {
+ return static_cast(cardPictureLoaderCacheMethod);
+ }
[[nodiscard]] int getNetworkCacheSizeInMB() const
{
return networkCacheSize;
@@ -794,6 +804,10 @@ public:
{
return redirectCacheTtl;
}
+ [[nodiscard]] CardPictureLoaderLocalSchemes::NamingScheme getLocalCardImageStorageNamingScheme() const
+ {
+ return static_cast(localCardImageStorageNamingScheme);
+ }
[[nodiscard]] bool getScaleCards() const
{
return scaleCards;
@@ -1098,8 +1112,11 @@ public slots:
void setIgnoreUnregisteredUsers(QT_STATE_CHANGED_T _ignoreUnregisteredUsers);
void setIgnoreUnregisteredUserMessages(QT_STATE_CHANGED_T _ignoreUnregisteredUserMessages);
void setPixmapCacheSize(const int _pixmapCacheSize);
+ void setCardImageCacheMethod(CardPictureLoaderCacheMethod::CacheMethod _cardImageCachingMethod);
void setNetworkCacheSizeInMB(const int _networkCacheSize);
void setNetworkRedirectCacheTtl(const int _redirectCacheTtl);
+ void setLocalCardImageStorageNamingScheme(
+ const CardPictureLoaderLocalSchemes::NamingScheme _localCardImageStorageNamingScheme);
void setCardScaling(const QT_STATE_CHANGED_T _scaleCards);
void setStackCardOverlapPercent(const int _verticalCardOverlapPercent);
void setShowMessagePopups(const QT_STATE_CHANGED_T _showMessagePopups);
diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader.cpp b/cockatrice/src/interface/card_picture_loader/card_picture_loader.cpp
index 2cd28c4d3..bd427a6c8 100644
--- a/cockatrice/src/interface/card_picture_loader/card_picture_loader.cpp
+++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader.cpp
@@ -150,9 +150,10 @@ void CardPictureLoader::getPixmap(QPixmap &pixmap, const ExactCard &card, QSize
void CardPictureLoader::imageLoaded(const ExactCard &card, const QImage &image)
{
+ QPixmap finalPixmap;
+
if (image.isNull()) {
qCDebug(CardPictureLoaderLog) << "Caching NULL pixmap for" << card.getName();
- QPixmapCache::insert(card.getPixmapCacheKey(), QPixmap());
} else {
if (card.getInfo().getUiAttributes().upsideDownArt) {
#if (QT_VERSION >= QT_VERSION_CHECK(6, 9, 0))
@@ -160,12 +161,19 @@ void CardPictureLoader::imageLoaded(const ExactCard &card, const QImage &image)
#else
QImage mirrorImage = image.mirrored(true, true);
#endif
- QPixmapCache::insert(card.getPixmapCacheKey(), QPixmap::fromImage(mirrorImage));
+ finalPixmap = QPixmap::fromImage(mirrorImage);
} else {
- QPixmapCache::insert(card.getPixmapCacheKey(), QPixmap::fromImage(image));
+ finalPixmap = QPixmap::fromImage(image);
}
}
+ QPixmapCache::insert(card.getPixmapCacheKey(), finalPixmap);
+
+ if (SettingsCache::instance().getCardPictureLoaderCacheMethod() ==
+ CardPictureLoaderCacheMethod::CacheMethod::FILESYSTEM_CACHE) {
+ saveCardImageToLocalStorage(card, finalPixmap);
+ }
+
// imageLoaded should only be reached if the exactCard isn't already in cache.
// (plus there's a deduplication mechanism in CardPictureLoaderWorker)
// It should be safe to connect the CardInfo here without worrying about redundant connections.
@@ -175,6 +183,88 @@ void CardPictureLoader::imageLoaded(const ExactCard &card, const QImage &image)
card.emitPixmapUpdated();
}
+void CardPictureLoader::saveCardImageToLocalStorage(const ExactCard &card, const QPixmap &pixmap)
+{
+ if (pixmap.isNull() || !card) {
+ return;
+ }
+
+ const QString picsRoot = SettingsCache::instance().getPicsPath();
+ CardPictureLoaderLocalSchemes::NamingScheme scheme =
+ SettingsCache::instance().getLocalCardImageStorageNamingScheme();
+
+ QString pattern;
+
+ for (const auto &s : CardPictureLoaderLocalSchemes::exportSchemes()) {
+ if (s.id == scheme) {
+ pattern = s.pattern;
+ break;
+ }
+ }
+
+ if (picsRoot.isEmpty() || pattern.isEmpty()) {
+ return;
+ }
+
+ // Base directory: /downloadedPics
+ QDir baseDir(picsRoot);
+ if (!baseDir.exists("downloadedPics")) {
+ baseDir.mkpath("downloadedPics");
+ }
+ baseDir.cd("downloadedPics");
+
+ // Collect card metadata
+ const QString cardName = card.getInfo().getCorrectedName();
+
+ QString setName;
+ QString collectorNumber;
+ QString uuid;
+
+ PrintingInfo printing = card.getPrinting();
+ if (printing.getSet()) {
+ setName = printing.getSet()->getCorrectedShortName();
+ collectorNumber = printing.getProperty("num");
+ uuid = printing.getUuid();
+ }
+
+ // Build path from scheme
+ QString relativePath =
+ CardPictureLoaderLocalSchemes::expandPattern(pattern, cardName, setName, collectorNumber, uuid);
+
+ if (relativePath.isEmpty()) {
+ return;
+ }
+
+ // append extension
+ relativePath += ".png";
+
+ // Normalize slashes
+ relativePath = QDir::cleanPath(relativePath);
+
+ QFileInfo outInfo(baseDir.filePath(relativePath));
+
+ // Do not overwrite existing files
+ if (outInfo.exists()) {
+ return;
+ }
+
+ QDir outDir = outInfo.dir();
+
+ // Ensure directory exists
+ if (!outDir.exists()) {
+ if (!baseDir.mkpath(outDir.path())) {
+ qCWarning(CardPictureLoaderLog) << "Failed to create directory for downloaded card image:" << outDir.path();
+ return;
+ }
+ }
+
+ // Save image
+ QImage image = pixmap.toImage();
+ if (!image.save(outInfo.absoluteFilePath(), "PNG")) {
+ qCWarning(CardPictureLoaderLog) << "Failed to save card image to" << outInfo.absoluteFilePath();
+ }
+}
+
void CardPictureLoader::clearPixmapCache()
{
QPixmapCache::clear();
diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader.h b/cockatrice/src/interface/card_picture_loader/card_picture_loader.h
index 4000fd99a..0c114ae92 100644
--- a/cockatrice/src/interface/card_picture_loader/card_picture_loader.h
+++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader.h
@@ -117,6 +117,7 @@ public slots:
* @param image Loaded QImage.
*/
void imageLoaded(const ExactCard &card, const QImage &image);
+ void saveCardImageToLocalStorage(const ExactCard &card, const QPixmap &pixmap);
private slots:
/**
diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader_cache_method.h b/cockatrice/src/interface/card_picture_loader/card_picture_loader_cache_method.h
new file mode 100644
index 000000000..54977e249
--- /dev/null
+++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader_cache_method.h
@@ -0,0 +1,31 @@
+#ifndef COCKATRICE_CARD_PICTURE_LOADER_CACHE_METHOD_H
+#define COCKATRICE_CARD_PICTURE_LOADER_CACHE_METHOD_H
+#include
+#include
+#include
+
+namespace CardPictureLoaderCacheMethod
+{
+enum class CacheMethod
+{
+ NETWORK_CACHE,
+ FILESYSTEM_CACHE
+};
+
+struct CacheMethodInfo
+{
+ CacheMethod id;
+ QString displayName;
+};
+
+static inline const QList methods()
+{
+ static QList all = {
+ {CacheMethod::NETWORK_CACHE, QCoreApplication::translate("CardPictureLoaderCacheMethod", "Network Cache")},
+ {CacheMethod::FILESYSTEM_CACHE, QCoreApplication::translate("CardPictureLoaderCacheMethod", "Filesystem")},
+ };
+ return all;
+}
+} // namespace CardPictureLoaderCacheMethod
+
+#endif // COCKATRICE_CARD_PICTURE_LOADER_CACHE_METHOD_H
diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader_local.cpp b/cockatrice/src/interface/card_picture_loader/card_picture_loader_local.cpp
index dd00e6e1d..bb10d1c42 100644
--- a/cockatrice/src/interface/card_picture_loader/card_picture_loader_local.cpp
+++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader_local.cpp
@@ -1,6 +1,7 @@
#include "card_picture_loader_local.h"
#include "../../client/settings/cache_settings.h"
+#include "card_picture_loader_local_schemes.h"
#include "card_picture_to_load.h"
#include
@@ -77,26 +78,8 @@ QImage CardPictureLoaderLocal::tryLoadCardImageFromDisk(const QString &setName,
imgReader.setDecideFormatFromContent(true);
// Most-to-least specific, these will fall through in order.
- QStringList nameVariants;
-
- // cardName_providerId
- if (!providerId.isEmpty()) {
- nameVariants << QString("%1-%2").arg(correctedCardName, providerId)
- << QString("%1_%2").arg(correctedCardName, providerId);
- }
- // cardName_setName_collectorNumber & setName-collectorNumber-cardName
- if (!setName.isEmpty() && !collectorNumber.isEmpty()) {
- nameVariants << QString("%1_%2_%3").arg(correctedCardName, setName, collectorNumber)
- << QString("%1-%2-%3").arg(setName, collectorNumber, correctedCardName);
- }
- // cardName_setName
- if (!setName.isEmpty()) {
- nameVariants << QString("%1_%2").arg(correctedCardName, setName)
- << QString("%1-%2").arg(setName, correctedCardName);
- }
-
- // cardName
- nameVariants << correctedCardName;
+ QStringList nameVariants =
+ CardPictureLoaderLocalSchemes::generateImportVariants(correctedCardName, setName, collectorNumber, providerId);
for (const QString &nameVariant : nameVariants) {
if (nameVariant.isEmpty()) {
diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader_local_schemes.h b/cockatrice/src/interface/card_picture_loader/card_picture_loader_local_schemes.h
new file mode 100644
index 000000000..cad7d2d5f
--- /dev/null
+++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader_local_schemes.h
@@ -0,0 +1,109 @@
+#ifndef COCKATRICE_CARD_PICTURE_LOADER_LOCAL_SCHEMES_H
+#define COCKATRICE_CARD_PICTURE_LOADER_LOCAL_SCHEMES_H
+
+#include
+#include
+#include
+#include
+
+namespace CardPictureLoaderLocalSchemes
+{
+
+enum class NamingScheme
+{
+ NameOnly,
+ Name_Set,
+ Name_Set_Collector,
+ Set_Collector_Name,
+ Name_ProviderId,
+ Set_Folder_Name_ProviderId,
+ Set_Folder_Name_Set_Collector
+};
+
+struct NamingSchemeInfo
+{
+ NamingScheme id;
+ QString displayName;
+ QString pattern;
+};
+
+inline const QList &importSchemes()
+{
+ static QList list = {
+ {NamingScheme::Name_ProviderId, "Card Name + Provider ID", "{name}_{providerId}"},
+ {NamingScheme::Name_Set_Collector, "Card Name + Set + Collector", "{name}_{set}_{collector}"},
+ {NamingScheme::Set_Collector_Name, "Set + Collector + Card Name", "{set}_{collector}_{name}"},
+ {NamingScheme::Name_Set, "Card Name + Set", "{name}_{set}"},
+ {NamingScheme::NameOnly, "Card Name", "{name}"},
+ };
+ return list;
+}
+
+inline const QList &exportSchemes()
+{
+ static QList list = {
+ {NamingScheme::Set_Folder_Name_ProviderId, "Set Folder / Name + Provider ID", "{set}/{name}_{providerId}"},
+ {NamingScheme::Set_Folder_Name_Set_Collector, "Set Folder / Name + Set Name + Collector",
+ "{set}/{name}_{set}_{collector}"},
+ {NamingScheme::Name_ProviderId, "Card Name + Provider ID", "{name}_{providerId}"},
+ {NamingScheme::Name_Set_Collector, "Card Name + Set + Collector", "{name}_{set}_{collector}"},
+ {NamingScheme::Set_Collector_Name, "Set + Collector + Card Name", "{set}_{collector}_{name}"},
+ };
+ return list;
+}
+
+inline QString expandPattern(const QString &pattern,
+ const QString &name,
+ const QString &set,
+ const QString &collector,
+ const QString &providerId)
+{
+ QString result = pattern;
+
+ auto replaceIfPresent = [&](const QString &token, const QString &value) -> bool {
+ if (!result.contains(token))
+ return true;
+
+ if (value.isEmpty())
+ return false;
+
+ result.replace(token, value);
+ return true;
+ };
+
+ if (!replaceIfPresent("{name}", name))
+ return {};
+ if (!replaceIfPresent("{set}", set))
+ return {};
+ if (!replaceIfPresent("{collector}", collector))
+ return {};
+ if (!replaceIfPresent("{providerId}", providerId))
+ return {};
+
+ return result;
+}
+
+inline QStringList
+generateImportVariants(const QString &name, const QString &set, const QString &collector, const QString &providerId)
+{
+ QStringList variants;
+ const QStringList separators = {"_", "-"};
+
+ for (const auto &scheme : importSchemes()) {
+ for (const QString &sep : separators) {
+
+ QString pattern = scheme.pattern;
+ pattern.replace("_", sep);
+
+ QString v = expandPattern(pattern, name, set, collector, providerId);
+ if (!v.isEmpty())
+ variants << v;
+ }
+ }
+
+ return variants;
+}
+
+} // namespace CardPictureLoaderLocalSchemes
+
+#endif // COCKATRICE_CARD_PICTURE_LOADER_LOCAL_SCHEMES_H
\ No newline at end of file
diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker.cpp b/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker.cpp
index a246d74f2..a0f000139 100644
--- a/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker.cpp
+++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker.cpp
@@ -26,10 +26,14 @@ CardPictureLoaderWorker::CardPictureLoaderWorker()
cache->setCacheDirectory(SettingsCache::instance().getNetworkCachePath());
cache->setMaximumCacheSize(1024L * 1024L *
static_cast(SettingsCache::instance().getNetworkCacheSizeInMB()));
- // Note: the settings is in MB, but QNetworkDiskCache uses bytes
- connect(&SettingsCache::instance(), &SettingsCache::networkCacheSizeChanged, this,
- [this](int newSizeInMB) { cache->setMaximumCacheSize(1024L * 1024L * static_cast(newSizeInMB)); });
+
+ connect(&SettingsCache::instance(), &SettingsCache::networkCacheSizeChanged, cache, [this](int newSizeInMB) {
+ if (cache)
+ cache->setMaximumCacheSize(1024L * 1024L * static_cast(newSizeInMB));
+ });
+
networkManager->setCache(cache);
+
// Use a ManualRedirectPolicy since we keep track of redirects in picDownloadFinished
// We can't use NoLessSafeRedirectPolicy because it is not applied with AlwaysCache
networkManager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy);
@@ -65,14 +69,19 @@ void CardPictureLoaderWorker::queueRequest(const QUrl &url, CardPictureLoaderWor
QUrl cachedRedirect = getCachedRedirect(url);
if (!cachedRedirect.isEmpty()) {
queueRequest(cachedRedirect, worker);
- } else if (cache->metaData(url).isValid()) {
- // If we hit a cached url, we get to make the request for free, since it won't contribute towards the rate-limit
- makeRequest(url, worker);
- } else {
- requestLoadQueue.append(qMakePair(url, worker));
- emit imageRequestQueued(url, worker->cardToDownload.getCard(), worker->cardToDownload.getSetName());
- processQueuedRequests();
+ return;
}
+ if (SettingsCache::instance().getCardPictureLoaderCacheMethod() ==
+ CardPictureLoaderCacheMethod::CacheMethod::NETWORK_CACHE &&
+ cache->metaData(url).isValid()) {
+ // If we hit a cached url, we get to make the request for free, since it won't contribute towards the
+ // rate-limit
+ makeRequest(url, worker);
+ return;
+ }
+ requestLoadQueue.append(qMakePair(url, worker));
+ emit imageRequestQueued(url, worker->cardToDownload.getCard(), worker->cardToDownload.getSetName());
+ processQueuedRequests();
}
QNetworkReply *CardPictureLoaderWorker::makeRequest(const QUrl &url, CardPictureLoaderWorkerWork *worker)
@@ -87,9 +96,12 @@ QNetworkReply *CardPictureLoaderWorker::makeRequest(const QUrl &url, CardPicture
QNetworkRequest req(url);
req.setHeader(QNetworkRequest::UserAgentHeader, QString("Cockatrice %1").arg(VERSION_STRING));
req.setRawHeader("Accept", "image/avif,image/webp,image/apng,image/,/*;q=0.8");
- if (!picDownload) {
- req.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysCache);
- }
+
+ bool useNetworkCache = !picDownload && SettingsCache::instance().getCardPictureLoaderCacheMethod() ==
+ CardPictureLoaderCacheMethod::CacheMethod::NETWORK_CACHE;
+
+ req.setAttribute(QNetworkRequest::CacheLoadControlAttribute,
+ useNetworkCache ? QNetworkRequest::AlwaysCache : QNetworkRequest::AlwaysNetwork);
QNetworkReply *reply = networkManager->get(req);
diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp
index d6b5aa213..7da0a9207 100644
--- a/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp
+++ b/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp
@@ -1013,8 +1013,6 @@ DeckEditorSettingsPage::DeckEditorSettingsPage()
urlLinkLabel.setTextInteractionFlags(Qt::LinksAccessibleByMouse);
urlLinkLabel.setOpenExternalLinks(true);
- connect(&clearDownloadedPicsButton, &QPushButton::clicked, this,
- &DeckEditorSettingsPage::clearDownloadedPicsButtonClicked);
connect(&resetDownloadURLs, &QPushButton::clicked, this, &DeckEditorSettingsPage::resetDownloadedURLsButtonClicked);
auto *lpGeneralGrid = new QGridLayout;
@@ -1066,49 +1064,11 @@ DeckEditorSettingsPage::DeckEditorSettingsPage()
urlListLayout->addWidget(urlToolBar);
urlListLayout->addWidget(urlList);
- // pixmap cache
- pixmapCacheEdit.setMinimum(PIXMAPCACHE_SIZE_MIN);
- // 2047 is the max value to avoid overflowing of QPixmapCache::setCacheLimit(int size)
- pixmapCacheEdit.setMaximum(PIXMAPCACHE_SIZE_MAX);
- pixmapCacheEdit.setSingleStep(64);
- pixmapCacheEdit.setValue(SettingsCache::instance().getPixmapCacheSize());
- pixmapCacheEdit.setSuffix(" MB");
-
- networkCacheEdit.setMinimum(NETWORK_CACHE_SIZE_MIN);
- networkCacheEdit.setMaximum(NETWORK_CACHE_SIZE_MAX);
- networkCacheEdit.setSingleStep(1);
- networkCacheEdit.setValue(SettingsCache::instance().getNetworkCacheSizeInMB());
- networkCacheEdit.setSuffix(" MB");
-
- networkRedirectCacheTtlEdit.setMinimum(NETWORK_REDIRECT_CACHE_TTL_MIN);
- networkRedirectCacheTtlEdit.setMaximum(NETWORK_REDIRECT_CACHE_TTL_MAX);
- networkRedirectCacheTtlEdit.setSingleStep(1);
- networkRedirectCacheTtlEdit.setValue(SettingsCache::instance().getRedirectCacheTtl());
-
- auto networkCacheLayout = new QHBoxLayout;
- networkCacheLayout->addStretch();
- networkCacheLayout->addWidget(&networkCacheLabel);
- networkCacheLayout->addWidget(&networkCacheEdit);
-
- auto networkRedirectCacheLayout = new QHBoxLayout;
- networkRedirectCacheLayout->addStretch();
- networkRedirectCacheLayout->addWidget(&networkRedirectCacheTtlLabel);
- networkRedirectCacheLayout->addWidget(&networkRedirectCacheTtlEdit);
-
- auto pixmapCacheLayout = new QHBoxLayout;
- pixmapCacheLayout->addStretch();
- pixmapCacheLayout->addWidget(&pixmapCacheLabel);
- pixmapCacheLayout->addWidget(&pixmapCacheEdit);
-
// Top Layout
lpGeneralGrid->addWidget(&picDownloadCheckBox, 0, 0);
lpGeneralGrid->addWidget(&resetDownloadURLs, 0, 1);
lpGeneralGrid->addLayout(urlListLayout, 1, 0, 1, 2);
- lpGeneralGrid->addLayout(networkCacheLayout, 2, 1);
- lpGeneralGrid->addLayout(networkRedirectCacheLayout, 3, 0);
- lpGeneralGrid->addLayout(pixmapCacheLayout, 3, 1);
- lpGeneralGrid->addWidget(&urlLinkLabel, 5, 0);
- lpGeneralGrid->addWidget(&clearDownloadedPicsButton, 5, 1);
+ lpGeneralGrid->addWidget(&urlLinkLabel, 4, 0);
// Spoiler Layout
lpSpoilerGrid->addWidget(&mcDownloadSpoilersCheckBox, 0, 0);
@@ -1123,12 +1083,6 @@ DeckEditorSettingsPage::DeckEditorSettingsPage()
connect(&mcDownloadSpoilersCheckBox, &QCheckBox::toggled, &SettingsCache::instance(),
&SettingsCache::setDownloadSpoilerStatus);
connect(&mcDownloadSpoilersCheckBox, &QCheckBox::toggled, this, &DeckEditorSettingsPage::setSpoilersEnabled);
- connect(&pixmapCacheEdit, qOverload(&QSpinBox::valueChanged), &SettingsCache::instance(),
- &SettingsCache::setPixmapCacheSize);
- connect(&networkCacheEdit, qOverload(&QSpinBox::valueChanged), &SettingsCache::instance(),
- &SettingsCache::setNetworkCacheSizeInMB);
- connect(&networkRedirectCacheTtlEdit, qOverload(&QSpinBox::valueChanged), &SettingsCache::instance(),
- &SettingsCache::setNetworkRedirectCacheTtl);
mpGeneralGroupBox = new QGroupBox;
mpGeneralGroupBox->setLayout(lpGeneralGrid);
@@ -1154,46 +1108,6 @@ void DeckEditorSettingsPage::resetDownloadedURLsButtonClicked()
QMessageBox::information(this, tr("Success"), tr("Download URLs have been reset."));
}
-void DeckEditorSettingsPage::clearDownloadedPicsButtonClicked()
-{
- CardPictureLoader::clearNetworkCache();
-
- // These are not used anymore, but we don't delete them automatically, so
- // we should do it here lest we leave pictures hanging around on users'
- // machines.
- QString picsPath = SettingsCache::instance().getPicsPath() + "/downloadedPics/";
- QStringList dirs = QDir(picsPath).entryList(QDir::AllDirs | QDir::NoDotAndDotDot);
- bool outerSuccessRemove = true;
- for (const auto &dir : dirs) {
- QString currentPath = picsPath + dir + "/";
- QStringList files = QDir(currentPath).entryList(QDir::Files);
- bool innerSuccessRemove = true;
- for (int j = 0; j < files.length(); j++) {
- if (!QDir(currentPath).remove(files.at(j))) {
- qInfo() << "Failed to remove " + currentPath.toUtf8() + files.at(j).toUtf8();
- outerSuccessRemove = false;
- innerSuccessRemove = false;
- }
- qInfo() << "Removed " << currentPath << files.at(j);
- }
-
- if (innerSuccessRemove) {
- bool success = QDir(picsPath).rmdir(dir);
- if (!success) {
- qInfo() << "Failed to remove inner directory" << picsPath;
- } else {
- qInfo() << "Removed" << currentPath;
- }
- }
- }
- if (outerSuccessRemove) {
- QMessageBox::information(this, tr("Success"), tr("Downloaded card pictures have been reset."));
- QDir(SettingsCache::instance().getPicsPath()).rmdir("downloadedPics");
- } else {
- QMessageBox::critical(this, tr("Error"), tr("One or more downloaded card pictures could not be cleared."));
- }
-}
-
void DeckEditorSettingsPage::actAddURL()
{
bool ok;
@@ -1312,18 +1226,246 @@ void DeckEditorSettingsPage::retranslateUi()
tr("Do not close settings until manual update is complete"));
picDownloadCheckBox.setText(tr("Download card pictures on the fly"));
urlLinkLabel.setText(QString("%2").arg(WIKI_CUSTOM_PIC_URL).arg(tr("How to add a custom URL")));
- clearDownloadedPicsButton.setText(tr("Delete Downloaded Images"));
resetDownloadURLs.setText(tr("Reset Download URLs"));
+ updateNowButton->setText(tr("Update Spoilers"));
+ aAdd->setText(tr("Add New URL"));
+ aEdit->setText(tr("Edit URL"));
+ aRemove->setText(tr("Remove URL"));
+}
+
+StorageSettingsPage::StorageSettingsPage()
+{
+ auto *lpNetworkCacheGrid = new QGridLayout;
+ auto *lpImageBackupGrid = new QGridLayout;
+ auto *lpPixmapCacheGrid = new QGridLayout;
+
+ networkCacheExplainerLabel.setWordWrap(true);
+ imageBackupExplainerLabel.setWordWrap(true);
+ pixmapCacheExplainerLabel.setWordWrap(true);
+
+ connect(&clearDownloadedPicsButton, &QPushButton::clicked, this,
+ &StorageSettingsPage::clearDownloadedPicsButtonClicked);
+
+ connect(&clearPixmapCacheButton, &QPushButton::clicked, this, &StorageSettingsPage::clearPixmapCacheButtonClicked);
+
+ // pixmap cache
+ pixmapCacheEdit.setMinimum(PIXMAPCACHE_SIZE_MIN);
+ // 2047 is the max value to avoid overflowing of QPixmapCache::setCacheLimit(int size)
+ pixmapCacheEdit.setMaximum(PIXMAPCACHE_SIZE_MAX);
+ pixmapCacheEdit.setSingleStep(64);
+ pixmapCacheEdit.setValue(SettingsCache::instance().getPixmapCacheSize());
+ pixmapCacheEdit.setSuffix(" MB");
+
+ // Caching method
+
+ cardPictureLoaderCacheMethodComboBox = new QComboBox;
+ for (auto method : CardPictureLoaderCacheMethod::methods()) {
+ cardPictureLoaderCacheMethodComboBox->addItem(method.displayName, static_cast(method.id));
+ }
+
+ int currentCacheMethod = static_cast(SettingsCache::instance().getCardPictureLoaderCacheMethod());
+
+ int currentIndex = cardPictureLoaderCacheMethodComboBox->findData(currentCacheMethod);
+ if (currentIndex >= 0) {
+ cardPictureLoaderCacheMethodComboBox->setCurrentIndex(currentIndex);
+ }
+
+ connect(cardPictureLoaderCacheMethodComboBox, qOverload(&QComboBox::currentIndexChanged), this,
+ [this](int index) {
+ auto cacheMethod = static_cast(
+ cardPictureLoaderCacheMethodComboBox->itemData(index).toInt());
+
+ bool useNetworkCache = (cacheMethod == CardPictureLoaderCacheMethod::CacheMethod::NETWORK_CACHE);
+
+ if (useNetworkCache) {
+ clearImageBackupsButtonClicked();
+ } else {
+ clearDownloadedPicsButtonClicked();
+ }
+
+ mpNetworkCacheGroupBox->setEnabled(useNetworkCache);
+ mpImageBackupGroupBox->setEnabled(!useNetworkCache);
+
+ SettingsCache::instance().setCardImageCacheMethod(cacheMethod);
+ });
+
+ // Network Cache
+
+ networkCacheEdit.setMinimum(NETWORK_CACHE_SIZE_MIN);
+ networkCacheEdit.setMaximum(NETWORK_CACHE_SIZE_MAX);
+ networkCacheEdit.setSingleStep(1);
+ networkCacheEdit.setValue(SettingsCache::instance().getNetworkCacheSizeInMB());
+ networkCacheEdit.setSuffix(" MB");
+
+ networkRedirectCacheTtlEdit.setMinimum(NETWORK_REDIRECT_CACHE_TTL_MIN);
+ networkRedirectCacheTtlEdit.setMaximum(NETWORK_REDIRECT_CACHE_TTL_MAX);
+ networkRedirectCacheTtlEdit.setSingleStep(1);
+ networkRedirectCacheTtlEdit.setValue(SettingsCache::instance().getRedirectCacheTtl());
+
+ // Image Backup
+ localCardImageStorageNamingSchemeComboBox = new QComboBox;
+ for (const auto &scheme : CardPictureLoaderLocalSchemes::exportSchemes()) {
+ localCardImageStorageNamingSchemeComboBox->addItem(scheme.displayName, static_cast(scheme.id));
+ }
+
+ int current = static_cast(SettingsCache::instance().getLocalCardImageStorageNamingScheme());
+
+ int index = localCardImageStorageNamingSchemeComboBox->findData(current);
+ if (index >= 0) {
+ localCardImageStorageNamingSchemeComboBox->setCurrentIndex(index);
+ }
+
+ connect(localCardImageStorageNamingSchemeComboBox, qOverload(&QComboBox::currentIndexChanged), this,
+ [this](int index) {
+ auto scheme = static_cast(
+ localCardImageStorageNamingSchemeComboBox->itemData(index).toInt());
+ SettingsCache::instance().setLocalCardImageStorageNamingScheme(scheme);
+ });
+
+ connect(&clearBackupsButton, &QPushButton::clicked, this, &StorageSettingsPage::clearImageBackupsButtonClicked);
+
+ auto cacheMethodLayout = new QHBoxLayout;
+ cacheMethodLayout->addWidget(&cardPictureLoaderCacheMethodLabel);
+ cacheMethodLayout->addWidget(cardPictureLoaderCacheMethodComboBox);
+
+ auto networkCacheLayout = new QHBoxLayout;
+ networkCacheLayout->addWidget(&clearDownloadedPicsButton);
+ networkCacheLayout->addStretch();
+ networkCacheLayout->addWidget(&networkCacheLabel);
+ networkCacheLayout->addWidget(&networkCacheEdit);
+
+ auto networkRedirectCacheLayout = new QHBoxLayout;
+ networkRedirectCacheLayout->addStretch();
+ networkRedirectCacheLayout->addWidget(&networkRedirectCacheTtlLabel);
+ networkRedirectCacheLayout->addWidget(&networkRedirectCacheTtlEdit);
+
+ auto pixmapCacheLayout = new QHBoxLayout;
+ pixmapCacheLayout->addWidget(&clearPixmapCacheButton);
+ pixmapCacheLayout->addStretch();
+ pixmapCacheLayout->addWidget(&pixmapCacheLabel);
+ pixmapCacheLayout->addWidget(&pixmapCacheEdit);
+
+ lpNetworkCacheGrid->addWidget(&networkCacheExplainerLabel, 0, 0);
+ lpNetworkCacheGrid->addLayout(networkCacheLayout, 1, 0);
+ lpNetworkCacheGrid->addLayout(networkRedirectCacheLayout, 2, 0);
+
+ // Image Backup Layout
+ lpImageBackupGrid->addWidget(&imageBackupExplainerLabel, 0, 0, 1, 2);
+ lpImageBackupGrid->addWidget(&localCardImageStorageNamingSchemeLabel, 1, 0);
+ lpImageBackupGrid->addWidget(localCardImageStorageNamingSchemeComboBox, 1, 1);
+ lpImageBackupGrid->addWidget(&clearBackupsButton, 2, 0);
+
+ lpPixmapCacheGrid->addWidget(&pixmapCacheExplainerLabel, 0, 0);
+ lpPixmapCacheGrid->addLayout(pixmapCacheLayout, 1, 0);
+
+ connect(&pixmapCacheEdit, qOverload(&QSpinBox::valueChanged), &SettingsCache::instance(),
+ &SettingsCache::setPixmapCacheSize);
+ connect(&networkCacheEdit, qOverload(&QSpinBox::valueChanged), &SettingsCache::instance(),
+ &SettingsCache::setNetworkCacheSizeInMB);
+ connect(&networkRedirectCacheTtlEdit, qOverload(&QSpinBox::valueChanged), &SettingsCache::instance(),
+ &SettingsCache::setNetworkRedirectCacheTtl);
+
+ mpCacheMethodGroupBox = new QGroupBox;
+ mpCacheMethodGroupBox->setLayout(cacheMethodLayout);
+
+ mpNetworkCacheGroupBox = new QGroupBox;
+ mpNetworkCacheGroupBox->setLayout(lpNetworkCacheGrid);
+
+ mpImageBackupGroupBox = new QGroupBox;
+ mpImageBackupGroupBox->setLayout(lpImageBackupGrid);
+
+ mpPixmapCacheGroupBox = new QGroupBox;
+ mpPixmapCacheGroupBox->setLayout(lpPixmapCacheGrid);
+
+ auto *lpMainLayout = new QVBoxLayout;
+
+ lpMainLayout->addWidget(mpCacheMethodGroupBox);
+ lpMainLayout->addWidget(mpNetworkCacheGroupBox);
+ lpMainLayout->addWidget(mpImageBackupGroupBox);
+ lpMainLayout->addWidget(mpPixmapCacheGroupBox);
+ lpMainLayout->addStretch();
+
+ setLayout(lpMainLayout);
+
+ bool useNetworkCache = SettingsCache::instance().getCardPictureLoaderCacheMethod() ==
+ CardPictureLoaderCacheMethod::CacheMethod::NETWORK_CACHE;
+
+ mpNetworkCacheGroupBox->setEnabled(useNetworkCache);
+ mpImageBackupGroupBox->setEnabled(!useNetworkCache);
+
+ connect(&SettingsCache::instance(), &SettingsCache::langChanged, this, &StorageSettingsPage::retranslateUi);
+ retranslateUi();
+}
+
+void StorageSettingsPage::clearDownloadedPicsButtonClicked()
+{
+ CardPictureLoader::clearNetworkCache();
+ CardPictureLoader::clearPixmapCache();
+ QMessageBox::information(this, tr("Success"), tr("Cached card pictures have been reset."));
+}
+
+void StorageSettingsPage::clearImageBackupsButtonClicked()
+{
+ QString picsPath = SettingsCache::instance().getPicsPath() + "/downloadedPics";
+
+ QDir dir(picsPath);
+ bool success = dir.removeRecursively();
+
+ CardPictureLoader::clearPixmapCache();
+
+ if (success) {
+ QMessageBox::information(this, tr("Success"), tr("Downloaded card pictures have been reset."));
+ } else {
+ QMessageBox::critical(this, tr("Error"), tr("One or more downloaded card pictures could not be cleared."));
+ }
+}
+
+void StorageSettingsPage::clearPixmapCacheButtonClicked()
+{
+ CardPictureLoader::clearPixmapCache();
+ QMessageBox::information(this, tr("Success"), tr("In-memory (currently loaded) card pictures have been reset."));
+}
+
+void StorageSettingsPage::retranslateUi()
+{
+ cardPictureLoaderCacheMethodLabel.setText(tr("Card Picture Loader Caching Method:"));
+
+ networkCacheExplainerLabel.setText(
+ tr("The network cache is the preferred way of storing images. Downloaded images "
+ "are stored here until the size of the cache exceeds the configured size. Cockatrice automatically monitors "
+ "this cache and deletes the least recently seen card images to ensure the cache does not exceed the "
+ "configured size."));
+ imageBackupExplainerLabel.setText(
+ tr("Writing card images directly to a folder on your hard drive is another way "
+ "of storing images. This does not change how Cockatrice accesses or downloads "
+ "images. Cockatrice will NOT automatically monitor and clear this folder, so if you enable this option, it "
+ "is up to you to ensure sufficient available space. It should also be noted that if a provider outage "
+ "causes you to download the wrong picture (i.e. wrong printing) you will be stuck with it until you "
+ "manually delete the file, as opposed to using the network cache, which automatically rotates and thus "
+ "correct errors after a while."));
+ pixmapCacheExplainerLabel.setText(
+ tr("This is the in-memory picture cache used by the application at runtime. It determines how much memory "
+ "(RAM) Cockatrice can use before it has to fetch card images from the hard disk again. Increasing this will "
+ "allow more card images to be displayed at once but shouldn't be necessary. Clearing this will make "
+ "Cockatrice reload all images from the network cache or the disk."));
+
+ clearDownloadedPicsButton.setText(tr("Delete Cached Images"));
+ clearBackupsButton.setText(tr("Delete Saved Images"));
+ clearPixmapCacheButton.setText(tr("Clear In-Memory Images"));
+
+ mpCacheMethodGroupBox->setTitle(tr("Card Picture Loader Cache Method"));
+ mpNetworkCacheGroupBox->setTitle(tr("Network Cache"));
+ mpImageBackupGroupBox->setTitle(tr("Filesystem"));
+ mpPixmapCacheGroupBox->setTitle(tr("In-Memory Picture Cache"));
+
networkCacheLabel.setText(tr("Network Cache Size:"));
networkCacheEdit.setToolTip(tr("On-disk cache for downloaded pictures"));
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"));
- updateNowButton->setText(tr("Update Spoilers"));
- aAdd->setText(tr("Add New URL"));
- aEdit->setText(tr("Edit URL"));
- aRemove->setText(tr("Remove URL"));
+ localCardImageStorageNamingSchemeLabel.setText(tr("Naming scheme:"));
+
networkRedirectCacheTtlEdit.setSuffix(" " + tr("Day(s)"));
}
@@ -1803,6 +1945,7 @@ DlgSettings::DlgSettings(QWidget *parent) : QDialog(parent)
pagesWidget->addWidget(makeScrollable(new AppearanceSettingsPage));
pagesWidget->addWidget(makeScrollable(new UserInterfaceSettingsPage));
pagesWidget->addWidget(new DeckEditorSettingsPage);
+ pagesWidget->addWidget(makeScrollable(new StorageSettingsPage));
pagesWidget->addWidget(new MessagesSettingsPage);
pagesWidget->addWidget(new SoundSettingsPage);
pagesWidget->addWidget(new ShortcutSettingsPage);
@@ -1851,6 +1994,11 @@ void DlgSettings::createIcons()
deckEditorButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
deckEditorButton->setIcon(QPixmap("theme:config/deckeditor"));
+ storageButton = new QListWidgetItem(contentsWidget);
+ storageButton->setTextAlignment(Qt::AlignHCenter);
+ storageButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
+ storageButton->setIcon(QPixmap("theme:config/storage"));
+
messagesButton = new QListWidgetItem(contentsWidget);
messagesButton->setTextAlignment(Qt::AlignHCenter);
messagesButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
@@ -1975,6 +2123,7 @@ void DlgSettings::retranslateUi()
generalButton->setText(tr("General"));
appearanceButton->setText(tr("Appearance"));
userInterfaceButton->setText(tr("User Interface"));
+ storageButton->setText(tr("Storage"));
deckEditorButton->setText(tr("Card Sources"));
messagesButton->setText(tr("Chat"));
soundButton->setText(tr("Sound"));
diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_settings.h b/cockatrice/src/interface/widgets/dialogs/dlg_settings.h
index b655a30bc..42268e997 100644
--- a/cockatrice/src/interface/widgets/dialogs/dlg_settings.h
+++ b/cockatrice/src/interface/widgets/dialogs/dlg_settings.h
@@ -213,11 +213,9 @@ private slots:
void actAddURL();
void actRemoveURL();
void actEditURL();
- void clearDownloadedPicsButtonClicked();
void resetDownloadedURLsButtonClicked();
private:
- QPushButton clearDownloadedPicsButton;
QPushButton resetDownloadURLs;
QLabel urlLinkLabel;
QCheckBox picDownloadCheckBox;
@@ -227,18 +225,51 @@ private:
QLabel msDownloadSpoilersLabel;
QGroupBox *mpGeneralGroupBox;
QGroupBox *mpSpoilerGroupBox;
+
QLineEdit *mpSpoilerSavePathLineEdit;
QLabel mcSpoilerSaveLabel;
QLabel lastUpdatedLabel;
QLabel infoOnSpoilersLabel;
QPushButton *mpSpoilerPathButton;
QPushButton *updateNowButton;
+};
+
+class StorageSettingsPage : public AbstractSettingsPage
+{
+ Q_OBJECT
+public:
+ StorageSettingsPage();
+ void retranslateUi() override;
+
+private slots:
+ void clearDownloadedPicsButtonClicked();
+ void clearImageBackupsButtonClicked();
+ void clearPixmapCacheButtonClicked();
+
+private:
+ QPushButton clearDownloadedPicsButton;
+ QPushButton clearBackupsButton;
+ QPushButton clearPixmapCacheButton;
+
+ QGroupBox *mpCacheMethodGroupBox;
+ QGroupBox *mpNetworkCacheGroupBox;
+ QGroupBox *mpImageBackupGroupBox;
+ QGroupBox *mpPixmapCacheGroupBox;
+
+ QLabel networkCacheExplainerLabel;
+ QLabel imageBackupExplainerLabel;
+ QLabel pixmapCacheExplainerLabel;
+
+ QLabel cardPictureLoaderCacheMethodLabel;
+ QComboBox *cardPictureLoaderCacheMethodComboBox;
QLabel networkCacheLabel;
QSpinBox networkCacheEdit;
QLabel networkRedirectCacheTtlLabel;
QSpinBox networkRedirectCacheTtlEdit;
QSpinBox pixmapCacheEdit;
QLabel pixmapCacheLabel;
+ QLabel localCardImageStorageNamingSchemeLabel;
+ QComboBox *localCardImageStorageNamingSchemeComboBox;
};
class MessagesSettingsPage : public AbstractSettingsPage
@@ -353,8 +384,8 @@ private slots:
private:
QListWidget *contentsWidget;
QStackedWidget *pagesWidget;
- QListWidgetItem *generalButton, *appearanceButton, *userInterfaceButton, *deckEditorButton, *messagesButton,
- *soundButton, *shortcutsButton;
+ QListWidgetItem *generalButton, *appearanceButton, *userInterfaceButton, *deckEditorButton, *storageButton,
+ *messagesButton, *soundButton, *shortcutsButton;
void createIcons();
void retranslateUi();