Merge branch 'master' into fix-compiler-warnings

This commit is contained in:
BruebachL 2025-11-16 01:39:05 +01:00 committed by GitHub
commit b88dc38278
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
103 changed files with 2317 additions and 1431 deletions

View file

@ -991,7 +991,7 @@ WARN_LOGFILE =
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
# Note: If this tag is empty the current directory is searched.
INPUT = cockatrice doc/doxygen-groups libcockatrice_card libcockatrice_deck_list libcockatrice_filters libcockatrice_interfaces libcockatrice_models libcockatrice_network libcockatrice_protocol libcockatrice_rng libcockatrice_settings libcockatrice_utility
INPUT = cockatrice doc/doxygen-extra-pages doc/doxygen-groups libcockatrice_card libcockatrice_deck_list libcockatrice_filters libcockatrice_interfaces libcockatrice_models libcockatrice_network libcockatrice_protocol libcockatrice_rng libcockatrice_settings libcockatrice_utility
# This tag can be used to specify the character encoding of the source files
# that Doxygen parses. Internally Doxygen uses the UTF-8 encoding. Doxygen uses
@ -1145,7 +1145,7 @@ EXAMPLE_RECURSIVE = NO
# that contain images that are to be included in the documentation (see the
# \image command).
IMAGE_PATH =
IMAGE_PATH = doc/doxygen-images
# The INPUT_FILTER tag can be used to specify a program that Doxygen should
# invoke to filter for each input file. Doxygen will invoke the filter program

View file

@ -5,7 +5,7 @@
#include <QtMath>
CardCounterSettings::CardCounterSettings(const QString &settingsPath, QObject *parent)
: SettingsManager(settingsPath + "global.ini", parent)
: SettingsManager(settingsPath + "global.ini", "cards", "counters", parent)
{
}

View file

@ -41,8 +41,8 @@ void DeckViewCardDragItem::updatePosition(const QPointF &cursorScenePos)
void DeckViewCardDragItem::handleDrop(DeckViewCardContainer *target)
{
DeckViewCard *card = static_cast<DeckViewCard *>(item);
DeckViewCardContainer *start = static_cast<DeckViewCardContainer *>(item->parentItem());
auto *card = static_cast<DeckViewCard *>(item);
auto *start = static_cast<DeckViewCardContainer *>(item->parentItem());
start->removeCard(card);
target->addCard(card);
card->setParentItem(target);
@ -51,13 +51,13 @@ void DeckViewCardDragItem::handleDrop(DeckViewCardContainer *target)
void DeckViewCardDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
setCursor(Qt::OpenHandCursor);
DeckViewScene *sc = static_cast<DeckViewScene *>(scene());
auto *sc = static_cast<DeckViewScene *>(scene());
sc->removeItem(this);
if (currentZone) {
handleDrop(currentZone);
for (int i = 0; i < childDrags.size(); i++) {
DeckViewCardDragItem *c = static_cast<DeckViewCardDragItem *>(childDrags[i]);
auto *c = static_cast<DeckViewCardDragItem *>(childDrags[i]);
c->handleDrop(currentZone);
sc->removeItem(c);
}
@ -118,12 +118,12 @@ void DeckViewCard::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
QList<QGraphicsItem *> sel = scene()->selectedItems();
int j = 0;
for (int i = 0; i < sel.size(); i++) {
DeckViewCard *c = static_cast<DeckViewCard *>(sel.at(i));
auto *c = static_cast<DeckViewCard *>(sel.at(i));
if (c == this)
continue;
++j;
QPointF childPos = QPointF(j * CARD_WIDTH / 2, 0);
DeckViewCardDragItem *drag = new DeckViewCardDragItem(c, childPos, dragItem);
auto childPos = QPointF(j * CARD_WIDTH / 2, 0);
auto *drag = new DeckViewCardDragItem(c, childPos, dragItem);
drag->setPos(dragItem->pos() + childPos);
scene()->addItem(drag);
}
@ -140,8 +140,8 @@ void DeckView::mouseDoubleClickEvent(QMouseEvent *event)
QList<QGraphicsItem *> sel = scene()->selectedItems();
for (int i = 0; i < sel.size(); i++) {
DeckViewCard *c = static_cast<DeckViewCard *>(sel.at(i));
DeckViewCardContainer *zone = static_cast<DeckViewCardContainer *>(c->parentItem());
auto *c = static_cast<DeckViewCard *>(sel.at(i));
auto *zone = static_cast<DeckViewCardContainer *>(c->parentItem());
MoveCard_ToZone m;
m.set_card_name(c->getName().toStdString());
m.set_start_zone(zone->getName().toStdString());
@ -345,7 +345,7 @@ void DeckViewScene::rebuildTree()
InnerDecklistNode *listRoot = deck->getRoot();
for (int i = 0; i < listRoot->size(); i++) {
InnerDecklistNode *currentZone = dynamic_cast<InnerDecklistNode *>(listRoot->at(i));
auto *currentZone = dynamic_cast<InnerDecklistNode *>(listRoot->at(i));
DeckViewCardContainer *container = cardContainers.value(currentZone->getName(), 0);
if (!container) {
@ -355,12 +355,12 @@ void DeckViewScene::rebuildTree()
}
for (int j = 0; j < currentZone->size(); j++) {
DecklistCardNode *currentCard = dynamic_cast<DecklistCardNode *>(currentZone->at(j));
auto *currentCard = dynamic_cast<DecklistCardNode *>(currentZone->at(j));
if (!currentCard)
continue;
for (int k = 0; k < currentCard->getNumber(); ++k) {
DeckViewCard *newCard = new DeckViewCard(container, currentCard->toCardRef(), currentZone->getName());
auto *newCard = new DeckViewCard(container, currentCard->toCardRef(), currentZone->getName());
container->addCard(newCard);
emit newCardAdded(newCard);
}

View file

@ -67,7 +67,7 @@ DlgCreateToken::DlgCreateToken(const QStringList &_predefinedTokens, QWidget *pa
faceDownCheckBox = new QCheckBox(tr("Create face-down (Only hides name)"));
connect(faceDownCheckBox, &QCheckBox::toggled, this, &DlgCreateToken::faceDownCheckBoxToggled);
QGridLayout *grid = new QGridLayout;
auto *grid = new QGridLayout;
grid->addWidget(nameLabel, 0, 0);
grid->addWidget(nameEdit, 0, 1);
grid->addWidget(colorLabel, 1, 0);
@ -79,7 +79,7 @@ DlgCreateToken::DlgCreateToken(const QStringList &_predefinedTokens, QWidget *pa
grid->addWidget(destroyCheckBox, 4, 0, 1, 2);
grid->addWidget(faceDownCheckBox, 5, 0, 1, 2);
QGroupBox *tokenDataGroupBox = new QGroupBox(tr("Token data"));
auto *tokenDataGroupBox = new QGroupBox(tr("Token data"));
tokenDataGroupBox->setLayout(grid);
cardDatabaseModel = new CardDatabaseModel(CardDatabaseManager::getInstance(), false, this);
@ -125,25 +125,25 @@ DlgCreateToken::DlgCreateToken(const QStringList &_predefinedTokens, QWidget *pa
#endif
}
QVBoxLayout *tokenChooseLayout = new QVBoxLayout;
auto *tokenChooseLayout = new QVBoxLayout;
tokenChooseLayout->addWidget(chooseTokenFromAllRadioButton);
tokenChooseLayout->addWidget(chooseTokenFromDeckRadioButton);
tokenChooseLayout->addWidget(chooseTokenView);
QGroupBox *tokenChooseGroupBox = new QGroupBox(tr("Choose token from list"));
auto *tokenChooseGroupBox = new QGroupBox(tr("Choose token from list"));
tokenChooseGroupBox->setLayout(tokenChooseLayout);
QGridLayout *hbox = new QGridLayout;
auto *hbox = new QGridLayout;
hbox->addWidget(pic, 0, 0, 1, 1);
hbox->addWidget(tokenDataGroupBox, 1, 0, 1, 1);
hbox->addWidget(tokenChooseGroupBox, 0, 1, 2, 1);
hbox->setColumnStretch(1, 1);
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
auto *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgCreateToken::actOk);
connect(buttonBox, &QDialogButtonBox::rejected, this, &DlgCreateToken::actReject);
QVBoxLayout *mainLayout = new QVBoxLayout;
auto *mainLayout = new QVBoxLayout;
mainLayout->addLayout(hbox);
mainLayout->addWidget(buttonBox);
setLayout(mainLayout);

View file

@ -194,7 +194,7 @@ void Player::processPlayerInfo(const ServerInfo_Player &info)
} else {
for (int j = 0; j < cardListSize; ++j) {
const ServerInfo_Card &cardInfo = zoneInfo.card_list(j);
CardItem *card = new CardItem(this);
auto *card = new CardItem(this);
card->processCardInfo(cardInfo);
zone->addCard(card, false, cardInfo.x(), cardInfo.y());
}

View file

@ -50,8 +50,8 @@ void PlayerGraphicsItem::onPlayerActiveChanged(bool _active)
void PlayerGraphicsItem::initializeZones()
{
deckZoneGraphicsItem = new PileZone(player->getDeckZone(), this);
QPointF base = QPointF(counterAreaWidth + (CARD_HEIGHT - CARD_WIDTH + 15) / 2.0,
10 + playerTarget->boundingRect().height() + 5 - (CARD_HEIGHT - CARD_WIDTH) / 2.0);
auto base = QPointF(counterAreaWidth + (CARD_HEIGHT - CARD_WIDTH + 15) / 2.0,
10 + playerTarget->boundingRect().height() + 5 - (CARD_HEIGHT - CARD_WIDTH) / 2.0);
deckZoneGraphicsItem->setPos(base);
qreal h = deckZoneGraphicsItem->boundingRect().width() + 5;
@ -147,7 +147,7 @@ void PlayerGraphicsItem::rearrangeCounters()
void PlayerGraphicsItem::rearrangeZones()
{
QPointF base = QPointF(CARD_HEIGHT + counterAreaWidth + 15, 0);
auto base = QPointF(CARD_HEIGHT + counterAreaWidth + 15, 0);
if (SettingsCache::instance().getHorizontalHand()) {
if (mirrored) {
if (player->getHandZone()->contentsKnown()) {

View file

@ -27,7 +27,7 @@ bool PlayerListItemDelegate::editorEvent(QEvent *event,
const QModelIndex &index)
{
if ((event->type() == QEvent::MouseButtonPress) && index.isValid()) {
QMouseEvent *const mouseEvent = static_cast<QMouseEvent *>(event);
auto *const mouseEvent = static_cast<QMouseEvent *>(event);
if (mouseEvent->button() == Qt::RightButton) {
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
static_cast<PlayerListWidget *>(parent())->showContextMenu(mouseEvent->globalPosition().toPoint(), index);

View file

@ -1,9 +1,3 @@
/**
* @file card_picture_loader.h
* @ingroup PictureLoader
* @brief TODO: Document this.
*/
#ifndef CARD_PICTURE_LOADER_H
#define CARD_PICTURE_LOADER_H
@ -16,10 +10,37 @@
inline Q_LOGGING_CATEGORY(CardPictureLoaderLog, "card_picture_loader");
inline Q_LOGGING_CATEGORY(CardPictureLoaderCardBackCacheFailLog, "card_picture_loader.card_back_cache_fail");
/**
* @class CardPictureLoader
* @ingroup PictureLoader
* @brief Singleton class to manage card image loading and caching. Provides functionality to asynchronously load,
* cache, and manage card images for the client.
*
* This class is a singleton and handles:
* - Loading card images from disk or network.
* - Caching images in QPixmapCache for fast reuse.
* - Providing themed card backs, including fallback and in-progress/failed states.
* - Emitting updates when pixmaps are loaded.
*
* It interacts with CardPictureLoaderWorker for background loading and
* CardPictureLoaderStatusBar to display loading progress in the main window.
*
* Provides static accessors for:
* - Card images by ExactCard.
* - Card back images (normal, in-progress, failed).
* - Cache management (clearPixmapCache(), clearNetworkCache(), cacheCardPixmaps(const QList<ExactCard> &cards)).
*
* Uses a worker thread for asynchronous image loading and a status bar widget
* to track load progress.
*/
class CardPictureLoader : public QObject
{
Q_OBJECT
public:
/**
* @brief Access the singleton instance of CardPictureLoader.
* @return Reference to the singleton.
*/
static CardPictureLoader &getInstance()
{
static CardPictureLoader instance;
@ -29,30 +50,87 @@ public:
private:
explicit CardPictureLoader();
~CardPictureLoader() override;
// Singleton - Don't implement copy constructor and assign operator
CardPictureLoader(CardPictureLoader const &);
void operator=(CardPictureLoader const &);
CardPictureLoaderWorker *worker;
CardPictureLoaderStatusBar *statusBar;
// Disable copy and assignment for singleton
CardPictureLoader(CardPictureLoader const &) = delete;
void operator=(CardPictureLoader const &) = delete;
CardPictureLoaderWorker *worker; ///< Worker thread for async image loading
CardPictureLoaderStatusBar *statusBar; ///< Status bar widget showing load progress
public:
/**
* @brief Retrieve a card pixmap, either from cache or enqueued for loading.
* @param pixmap Reference to QPixmap where result will be stored.
* @param card ExactCard to load.
* @param size Desired size of pixmap.
*/
static void getPixmap(QPixmap &pixmap, const ExactCard &card, QSize size);
/**
* @brief Retrieve a generic card back pixmap.
* @param pixmap Reference to QPixmap where result will be stored.
* @param size Desired size of pixmap.
*/
static void getCardBackPixmap(QPixmap &pixmap, QSize size);
/**
* @brief Retrieve a card back pixmap for the loading-in-progress state.
* @param pixmap Reference to QPixmap where result will be stored.
* @param size Desired size of pixmap.
*/
static void getCardBackLoadingInProgressPixmap(QPixmap &pixmap, QSize size);
/**
* @brief Retrieve a card back pixmap for the loading-failed state.
* @param pixmap Reference to QPixmap where result will be stored.
* @param size Desired size of pixmap.
*/
static void getCardBackLoadingFailedPixmap(QPixmap &pixmap, QSize size);
static void clearPixmapCache();
/**
* @brief Preload a list of cards into the pixmap cache (limited to CACHED_CARD_PER_DECK_MAX).
* @param cards List of ExactCard objects to preload.
*/
static void cacheCardPixmaps(const QList<ExactCard> &cards);
/**
* @brief Check if the user has custom card art in the picsPath directory.
* @return True if any custom art exists.
*/
static bool hasCustomArt();
/**
* @brief Clears the in-memory QPixmap cache for all cards.
*/
static void clearPixmapCache();
public slots:
/**
* @brief Clears the network disk cache of the worker.
*/
static void clearNetworkCache();
private slots:
void picDownloadChanged();
void picsPathChanged();
public slots:
/**
* @brief Slot called by the worker when an image is loaded.
* Inserts the pixmap into the cache and emits pixmap updated signals.
* @param card ExactCard that was loaded.
* @param image Loaded QImage.
*/
void imageLoaded(const ExactCard &card, const QImage &image);
private slots:
/**
* @brief Triggered when the user changes the picture download settings.
* Clears the QPixmap cache to reload images.
*/
void picDownloadChanged();
/**
* @brief Triggered when the pictures path changes.
* Clears the QPixmap cache to reload images.
*/
void picsPathChanged();
};
#endif

View file

@ -47,12 +47,6 @@ void CardPictureLoaderLocal::refreshIndex()
<< customFolderIndex.size() << "entries.";
}
/**
* Tries to load the card image from the local images.
*
* @param toLoad The card to load
* @return The loaded image, or an empty QImage if loading failed.
*/
QImage CardPictureLoaderLocal::tryLoad(const ExactCard &toLoad) const
{
PrintingInfo setInstance = toLoad.getPrinting();

View file

@ -1,9 +1,3 @@
/**
* @file card_picture_loader_local.h
* @ingroup PictureLoader
* @brief TODO: Document this.
*/
#ifndef PICTURE_LOADER_LOCAL_H
#define PICTURE_LOADER_LOCAL_H
@ -15,32 +9,80 @@
inline Q_LOGGING_CATEGORY(CardPictureLoaderLocalLog, "card_picture_loader.local");
/**
* Handles searching for and loading card images from the local pics and custom image folders.
* This class maintains an index of the CUSTOM folder, to avoid repeatedly searching the entire directory.
* @class CardPictureLoaderLocal
* @ingroup PictureLoader
* @brief Handles searching for and loading card images from local and custom image folders.
*
* This class maintains an index of the CUSTOM folder to avoid repeatedly scanning
* directories, and supports periodic refreshes to update the index.
*
* Responsibilities:
* - Load images for ExactCard objects from local disk or custom folders.
* - Maintain an index for fast lookup of images in the CUSTOM folder.
* - Refresh the index periodically to account for changes in local image directories.
*/
class CardPictureLoaderLocal : public QObject
{
Q_OBJECT
public:
/**
* @brief Constructs a CardPictureLoaderLocal object.
* @param parent Optional parent QObject.
*
* Initializes paths from SettingsCache, connects to settings change signals,
* builds the initial folder index, and starts a periodic refresh timer.
*/
explicit CardPictureLoaderLocal(QObject *parent);
/**
* @brief Attempts to load a card image from local disk or custom folders.
* @param toLoad ExactCard object representing the card to load.
* @return Loaded QImage if found; otherwise, an empty QImage.
*
* Uses a set of name variants and folder paths to attempt to locate the correct image.
*/
QImage tryLoad(const ExactCard &toLoad) const;
private:
QString picsPath, customPicsPath;
QString picsPath; ///< Path to standard card image folder
QString customPicsPath; ///< Path to custom card image folder
QMultiHash<QString, QString> customFolderIndex; // multimap of cardName to picPaths
QTimer *refreshTimer;
QMultiHash<QString, QString> customFolderIndex; ///< Multimap from cardName to file paths in CUSTOM folder
QTimer *refreshTimer; ///< Timer for periodic folder index refresh
/**
* @brief Rebuilds the index of the custom image folder.
*
* Iterates through all subdirectories of the CUSTOM folder and populates
* `customFolderIndex` with all discovered image files keyed by base name
* and complete base name.
*/
void refreshIndex();
/**
* @brief Attempts to load a card image from disk given its set and name info.
* @param setName Corrected short name of the card's set.
* @param correctedCardName Corrected card name (e.g., normalized name).
* @param collectorNumber Collector number of the card.
* @param providerId Optional provider UUID of the card.
* @return Loaded QImage if found; otherwise, an empty QImage.
*
* Searches in both the custom folder index and standard pictures paths.
* Uses several filename patterns to match card images, in order from
* most-specific to least-specific.
*/
QImage tryLoadCardImageFromDisk(const QString &setName,
const QString &correctedCardName,
const QString &collectorNumber,
const QString &providerId) const;
private slots:
/**
* @brief Updates internal paths when the user changes picture settings.
*
* Triggered by `SettingsCache::picsPathChanged`.
*/
void picsPathChanged();
};

View file

@ -1,26 +1,43 @@
/**
* @file card_picture_loader_request_status_display_widget.h
* @ingroup PictureLoader
* @brief TODO: Document this.
*/
#ifndef PICTURE_LOADER_REQUEST_STATUS_DISPLAY_WIDGET_H
#define PICTURE_LOADER_REQUEST_STATUS_DISPLAY_WIDGET_H
#include "card_picture_loader_worker_work.h"
#include <QHBoxLayout>
#include <QLabel>
#include <QWidget>
/**
* @class CardPictureLoaderRequestStatusDisplayWidget
* @ingroup PictureLoader
* @brief A small widget to display the status of a single card image request.
*
* Displays:
* - Card name
* - Set short name
* - Provider ID
* - Start time of request
* - Elapsed time since start
* - Finished status
* - URL of the requested image
*/
class CardPictureLoaderRequestStatusDisplayWidget : public QWidget
{
Q_OBJECT
public:
/**
* @brief Constructs a new status widget for a specific card image request.
* @param parent Parent widget
* @param url URL of the image being requested
* @param card The ExactCard being downloaded
* @param setName Set name for the card
*/
CardPictureLoaderRequestStatusDisplayWidget(QWidget *parent,
const QUrl &url,
const ExactCard &card,
const QString &setName);
/** Marks the request as finished */
void setFinished()
{
finished->setText("True");
@ -28,19 +45,25 @@ public:
repaint();
}
/** Returns whether the request has finished */
[[nodiscard]] bool getFinished() const
{
return finished->text() == "True";
}
/** Updates the elapsed time display */
void setElapsedTime(const QString &_elapsedTime) const
{
elapsedTime->setText(_elapsedTime);
}
/**
* @brief Queries the elapsed time in seconds since the request started
* @return Elapsed time in seconds
*/
int queryElapsedSeconds()
{
if (!finished) {
if (!getFinished()) {
int elapsedSeconds = QDateTime::fromString(startTime->text()).secsTo(QDateTime::currentDateTime());
elapsedTime->setText(QString::number(elapsedSeconds));
update();
@ -50,25 +73,27 @@ public:
return elapsedTime->text().toInt();
}
/** Returns the start time as a string */
[[nodiscard]] QString getStartTime() const
{
return startTime->text();
}
/** Returns the URL of the request */
[[nodiscard]] QString getUrl() const
{
return url->text();
}
private:
QHBoxLayout *layout;
QLabel *name;
QLabel *setShortname;
QLabel *providerId;
QLabel *startTime;
QLabel *elapsedTime;
QLabel *finished;
QLabel *url;
QHBoxLayout *layout; ///< Horizontal layout for arranging labels
QLabel *name; ///< Card name
QLabel *setShortname; ///< Set short name
QLabel *providerId; ///< Provider ID for the card
QLabel *startTime; ///< Start time of the request
QLabel *elapsedTime; ///< Elapsed time since start
QLabel *finished; ///< Whether the request has finished
QLabel *url; ///< URL of the requested image
};
#endif // PICTURE_LOADER_REQUEST_STATUS_DISPLAY_WIDGET_H

View file

@ -1,9 +1,3 @@
/**
* @file card_picture_loader_status_bar.h
* @ingroup PictureLoader
* @brief TODO: Document this.
*/
#ifndef PICTURE_LOADER_STATUS_BAR_H
#define PICTURE_LOADER_STATUS_BAR_H
@ -12,24 +6,56 @@
#include <QHBoxLayout>
#include <QProgressBar>
#include <QTimer>
#include <QWidget>
/**
* @class CardPictureLoaderStatusBar
* @ingroup PictureLoader
* @brief Displays the status of card image downloads in a horizontal progress bar with a log popup.
*
* Responsibilities:
* - Shows overall progress of image downloads.
* - Maintains a log of individual requests via a popup (SettingsButtonWidget).
* - Cleans up finished request entries automatically after a delay.
*/
class CardPictureLoaderStatusBar : public QWidget
{
Q_OBJECT
public:
/**
* @brief Constructs a status bar with progress bar and log button.
* @param parent Parent widget
*/
explicit CardPictureLoaderStatusBar(QWidget *parent);
public slots:
/**
* @brief Adds a queued image download to the log and increments the progress bar maximum.
* @param url URL of the image
* @param card The card being loaded
* @param setName The set name of the card
*/
void addQueuedImageLoad(const QUrl &url, const ExactCard &card, const QString &setName);
/**
* @brief Marks an image as successfully loaded.
* Updates the progress bar and marks the corresponding log entry as finished.
* @param url URL of the successfully loaded image
*/
void addSuccessfulImageLoad(const QUrl &url);
/**
* @brief Cleans up old entries from the log that have finished more than 10 seconds ago.
* Adjusts the progress bar accordingly.
*/
void cleanOldEntries();
private:
QHBoxLayout *layout;
QProgressBar *progressBar;
SettingsButtonWidget *loadLog;
QTimer *cleaner;
QHBoxLayout *layout; ///< Horizontal layout containing progress bar and log button
QProgressBar *progressBar; ///< Progress bar showing overall download progress
SettingsButtonWidget *loadLog; ///< Popup log showing individual request statuses
QTimer *cleaner; ///< Timer for periodically cleaning old log entries
};
#endif // PICTURE_LOADER_STATUS_BAR_H

View file

@ -105,9 +105,6 @@ void CardPictureLoaderWorker::resetRequestQuota()
processQueuedRequests();
}
/**
* Keeps processing requests from the queue until it is empty or until the quota runs out.
*/
void CardPictureLoaderWorker::processQueuedRequests()
{
while (requestQuota > 0 && processSingleRequest()) {
@ -115,10 +112,6 @@ void CardPictureLoaderWorker::processQueuedRequests()
}
}
/**
* Immediately processes a single queued request. No-ops if the load queue is empty
* @return If a request was processed
*/
bool CardPictureLoaderWorker::processSingleRequest()
{
if (!requestLoadQueue.isEmpty()) {

View file

@ -1,9 +1,3 @@
/**
* @file card_picture_loader_worker.h
* @ingroup PictureLoader
* @brief TODO: Document this.
*/
#ifndef PICTURE_LOADER_WORKER_H
#define PICTURE_LOADER_WORKER_H
@ -27,57 +21,131 @@
#define REDIRECT_TIMESTAMP "timestamp"
#define REDIRECT_CACHE_FILENAME "cache.ini"
inline Q_LOGGING_CATEGORY(CardPictureLoaderWorkerLog, "card_picture_loader.worker");
inline Q_LOGGING_CATEGORY(CardPictureLoaderWorkerLog, "card_picture_loader.worker")
class CardPictureLoaderWorkerWork;
class CardPictureLoaderWorkerWork;
/**
* @class CardPictureLoaderWorker
* @ingroup PictureLoader
* @brief Handles asynchronous loading of card images, both locally and via network.
*
* Responsibilities:
* - Maintain a queue of network image requests with rate-limiting.
* - Load images from local cache first via CardPictureLoaderLocal.
* - Handle network redirects and persistent caching of redirects.
* - Deduplicate simultaneous requests for the same card.
* - Emit signals for status updates and loaded images.
*/
class CardPictureLoaderWorker : public QObject
{
Q_OBJECT
public:
/**
* @brief Constructs a CardPictureLoaderWorker.
*
* Initializes network manager, cache, redirect cache, local loader, and request quota timer.
*/
explicit CardPictureLoaderWorker();
~CardPictureLoaderWorker() override;
void enqueueImageLoad(const ExactCard &card); // Starts a thread for the image to be loaded
void queueRequest(const QUrl &url, CardPictureLoaderWorkerWork *worker); // Queues network requests for load threads
/**
* @brief Enqueues an ExactCard for loading.
* @param card ExactCard to load
*
* This will first try to load the image locally; if that fails, it will enqueue a network request.
*/
void enqueueImageLoad(const ExactCard &card);
/**
* @brief Queues a network request for a given URL and worker thread.
* @param url URL to load
* @param worker Worker handling this request
*/
void queueRequest(const QUrl &url, CardPictureLoaderWorkerWork *worker);
/** @brief Clears the network cache and redirect cache. */
void clearNetworkCache();
public slots:
/**
* @brief Makes a network request for the given URL using the specified worker.
* @param url URL to load
* @param workThread Worker handling the request
* @return The QNetworkReply object representing the request
*/
QNetworkReply *makeRequest(const QUrl &url, CardPictureLoaderWorkerWork *workThread);
/** @brief Processes all queued requests respecting the request quota. */
void processQueuedRequests();
/**
* @brief Processes a single queued request.
* @return true if a request was processed, false if queue is empty.
*/
bool processSingleRequest();
/**
* @brief Handles an image that has finished loading.
* @param card The ExactCard that was loaded
* @param image The loaded QImage; empty if loading failed
*/
void handleImageLoaded(const ExactCard &card, const QImage &image);
/** @brief Caches a redirect mapping between original and redirected URL. */
void cacheRedirect(const QUrl &originalUrl, const QUrl &redirectUrl);
/** @brief Removes a URL from the network cache. */
void removedCachedUrl(const QUrl &url);
private:
QThread *pictureLoaderThread;
QNetworkAccessManager *networkManager;
QNetworkDiskCache *cache;
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
bool picDownload;
QQueue<QPair<QUrl, CardPictureLoaderWorkerWork *>> requestLoadQueue;
QThread *pictureLoaderThread; ///< Thread for executing worker tasks
QNetworkAccessManager *networkManager; ///< Network manager for HTTP requests
QNetworkDiskCache *cache; ///< Disk cache for downloaded images
QHash<QUrl, QPair<QUrl, QDateTime>> redirectCache; ///< Maps original URLs to redirects with timestamp
QString cacheFilePath; ///< Path to persistent redirect cache file
static constexpr int CacheTTLInDays = 30; ///< Time-to-live for redirect cache entries (days)
bool picDownload; ///< Whether downloading images from network is enabled
QQueue<QPair<QUrl, CardPictureLoaderWorkerWork *>> requestLoadQueue; ///< Queue of pending network requests
int requestQuota;
QTimer requestTimer; // Timer for refreshing request quota
int requestQuota; ///< Remaining requests allowed per second
QTimer requestTimer; ///< Timer to reset the request quota
CardPictureLoaderLocal *localLoader;
QSet<QString> currentlyLoading; // for deduplication purposes. Contains pixmapCacheKey
CardPictureLoaderLocal *localLoader; ///< Loader for local images
QSet<QString> currentlyLoading; ///< Deduplication: contains pixmapCacheKey currently being loaded
/** @brief Returns cached redirect URL for the given original URL, if available. */
[[nodiscard]] QUrl getCachedRedirect(const QUrl &originalUrl) const;
/** @brief Loads redirect cache from disk. */
void loadRedirectCache();
/** @brief Saves redirect cache to disk. */
void saveRedirectCache() const;
/** @brief Removes stale redirect entries older than TTL. */
void cleanStaleEntries();
private slots:
/** @brief Resets the request quota for rate-limiting. */
void resetRequestQuota();
/** @brief Handles image load requests enqueued on this worker. */
void handleImageLoadEnqueued(const ExactCard &card);
signals:
/** @brief Emitted when an image load is enqueued. */
void imageLoadEnqueued(const ExactCard &card);
/** @brief Emitted when an image has finished loading. */
void imageLoaded(const ExactCard &card, const QImage &image);
/** @brief Emitted when a request is added to the network queue. */
void imageRequestQueued(const QUrl &url, const ExactCard &card, const QString &setName);
/** @brief Emitted when a network request successfully completes. */
void imageRequestSucceeded(const QUrl &url);
};

View file

@ -51,10 +51,6 @@ void CardPictureLoaderWorkerWork::startNextPicDownload()
}
}
/**
* Starts another pic download using the next possible url combination for the card.
* If all possibilities are exhausted, then concludes the image loading with an empty QImage.
*/
void CardPictureLoaderWorkerWork::picDownloadFailed()
{
/* Take advantage of short-circuiting here to call the nextUrl until one
@ -73,10 +69,6 @@ void CardPictureLoaderWorkerWork::picDownloadFailed()
}
}
/**
*
* @param reply The reply. Takes ownership of the object
*/
void CardPictureLoaderWorkerWork::handleNetworkReply(QNetworkReply *reply)
{
QVariant redirectTarget = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
@ -180,10 +172,6 @@ void CardPictureLoaderWorkerWork::handleSuccessfulReply(QNetworkReply *reply)
}
}
/**
* @param reply The reply to load the image from
* @return The loaded image, or an empty QImage if loading failed
*/
QImage CardPictureLoaderWorkerWork::tryLoadImageFromReply(QNetworkReply *reply)
{
static constexpr int riffHeaderSize = 12; // RIFF_HEADER_SIZE from webp/format_constants.h
@ -207,10 +195,6 @@ QImage CardPictureLoaderWorkerWork::tryLoadImageFromReply(QNetworkReply *reply)
return imgReader.read();
}
/**
* Call this method when the image has finished being loaded.
* @param image The image that was loaded. Empty QImage indicates failure.
*/
void CardPictureLoaderWorkerWork::concludeImageLoad(const QImage &image)
{
emit imageLoaded(cardToDownload.getCard(), image);

View file

@ -1,9 +1,3 @@
/**
* @file picture_loader_worker_work.h
* @ingroup PictureLoader
* @brief TODO: Document this.
*/
#ifndef PICTURE_LOADER_WORKER_WORK_H
#define PICTURE_LOADER_WORKER_WORK_H
@ -17,55 +11,96 @@
#include <QThread>
#include <libcockatrice/card/database/card_database.h>
#define REDIRECT_HEADER_NAME "redirects"
#define REDIRECT_ORIGINAL_URL "original"
#define REDIRECT_URL "redirect"
#define REDIRECT_TIMESTAMP "timestamp"
#define REDIRECT_CACHE_FILENAME "cache.ini"
inline Q_LOGGING_CATEGORY(CardPictureLoaderWorkerWorkLog, "card_picture_loader.worker");
class CardPictureLoaderWorker;
/**
* @class CardPictureLoaderWorkerWork
* @ingroup PictureLoader
* @brief Handles downloading a single card image from network or local sources.
*
* Responsibilities:
* - Try to load images from all possible URLs and sets for a given ExactCard.
* - Handle network redirects and cache invalidation.
* - Check for blacklisted images and discard them.
* - Emit signals when image is successfully loaded or all attempts fail.
* - Delete itself after loading completes.
*/
class CardPictureLoaderWorkerWork : public QObject
{
Q_OBJECT
public:
/**
* @brief Constructs a worker for downloading a specific card image.
* @param worker The orchestrating CardPictureLoaderWorker
* @param toLoad The ExactCard to download
*/
explicit CardPictureLoaderWorkerWork(const CardPictureLoaderWorker *worker, const ExactCard &toLoad);
CardPictureToLoad cardToDownload;
CardPictureToLoad cardToDownload; ///< The card and associated URLs to try downloading
public slots:
/**
* @brief Handles a finished network reply for the card image.
* @param reply The QNetworkReply object to process. Ownership is transferred.
*/
void handleNetworkReply(QNetworkReply *reply);
private:
bool picDownload;
bool picDownload; ///< Whether network downloading is enabled
/** @brief Starts downloading the next URL for this card. */
void startNextPicDownload();
/** @brief Called when all URLs have been exhausted or download failed. */
void picDownloadFailed();
/** @brief Processes a failed network reply. */
void handleFailedReply(const QNetworkReply *reply);
/** @brief Processes a successful network reply. */
void handleSuccessfulReply(QNetworkReply *reply);
/**
* @brief Attempts to read an image from a network reply.
* @param reply The network reply
* @return The loaded QImage or an empty image if reading failed
*/
QImage tryLoadImageFromReply(QNetworkReply *reply);
/**
* @brief Finalizes the image loading process.
* @param image The loaded image (empty if failed)
*
* Emits imageLoaded() and deletes this object.
*/
void concludeImageLoad(const QImage &image);
private slots:
/** @brief Updates the picDownload setting when it changes. */
void picDownloadChanged();
signals:
/**
* Emitted when this worker has successfully loaded the image or has exhausted all attempts at loading the image.
* Failures are represented by an empty QImage.
* Note that this object will delete itself as this signal is emitted.
* @brief Emitted when the image has been loaded or all attempts failed.
* @param card The card corresponding to the image
* @param image The loaded image (empty if failed)
*
* The worker deletes itself after emitting this signal.
*/
void imageLoaded(const ExactCard &card, const QImage &image);
/**
* Emitted when a request did not return a 400 or 500 response
*/
/** @brief Emitted when a network request completes successfully. */
void requestSucceeded(const QUrl &url);
/** @brief Request that a URL be downloaded. */
void requestImageDownload(const QUrl &url, CardPictureLoaderWorkerWork *instance);
/** @brief Emitted when a URL has been redirected. */
void urlRedirected(const QUrl &originalUrl, const QUrl &redirectUrl);
/** @brief Emitted when a cached URL is invalid and must be removed. */
void cachedUrlInvalidated(const QUrl &url);
};

View file

@ -20,12 +20,6 @@ CardPictureToLoad::CardPictureToLoad(const ExactCard &_card)
}
}
/**
* Extracts a list of all the sets from the card, sorted in priority order.
* If the card does not contain any sets, then a dummy set will be inserted into the list.
*
* @return A list of sets. Will not be empty.
*/
QList<CardSetPtr> CardPictureToLoad::extractSetsSorted(const ExactCard &card)
{
QList<CardSetPtr> sortedSets;
@ -109,11 +103,6 @@ void CardPictureToLoad::populateSetUrls()
(void)nextUrl();
}
/**
* Advances the currentSet to the next set in the list. Then repopulates the url list with the urls from that set.
* If we are already at the end of the list, then currentSet is set to empty.
* @return If we are already at the end of the list
*/
bool CardPictureToLoad::nextSet()
{
if (!sortedSets.isEmpty()) {
@ -125,11 +114,6 @@ bool CardPictureToLoad::nextSet()
return false;
}
/**
* Advances the currentUrl to the next url in the list.
* If we are already at the end of the list, then currentUrl is set to empty.
* @return If we are already at the end of the list
*/
bool CardPictureToLoad::nextUrl()
{
if (!currentSetUrls.isEmpty()) {

View file

@ -1,9 +1,3 @@
/**
* @file card_picture_to_load.h
* @ingroup PictureLoader
* @brief TODO: Document this.
*/
#ifndef PICTURE_TO_LOAD_H
#define PICTURE_TO_LOAD_H
@ -12,37 +6,97 @@
inline Q_LOGGING_CATEGORY(CardPictureToLoadLog, "card_picture_loader.picture_to_load");
/**
* @class CardPictureToLoad
* @ingroup PictureLoader
* @brief Manages all URLs and sets for downloading a specific card image.
*
* Responsibilities:
* - Maintains a sorted list of sets for a card.
* - Generates URLs to download images from, including custom URLs and URL templates.
* - Tracks which URL and set is currently being attempted.
* - Provides helper methods to advance to next URL or set.
*/
class CardPictureToLoad
{
private:
ExactCard card;
QList<CardSetPtr> sortedSets;
QList<QString> urlTemplates;
QList<QString> currentSetUrls;
QString currentUrl;
CardSetPtr currentSet;
ExactCard card; ///< The ExactCard being downloaded
QList<CardSetPtr> sortedSets; ///< All sets for this card, sorted by priority
QList<QString> urlTemplates; ///< URL templates from settings
QList<QString> currentSetUrls; ///< URLs for the current set being attempted
QString currentUrl; ///< Currently active URL to download
CardSetPtr currentSet; ///< Currently active set
public:
/**
* @brief Constructs a CardPictureToLoad for a given ExactCard.
* @param _card The card to download
*
* Initializes URL templates and pre-populates the first set URLs.
*/
explicit CardPictureToLoad(const ExactCard &_card);
/** @return The card being loaded. */
[[nodiscard]] const ExactCard &getCard() const
{
return card;
}
/** @return The current URL being attempted. */
[[nodiscard]] QString getCurrentUrl() const
{
return currentUrl;
}
/** @return The current set being attempted. */
[[nodiscard]] CardSetPtr getCurrentSet() const
{
return currentSet;
}
/** @return The short name of the current set, or empty string if no set. */
[[nodiscard]] QString getSetName() const;
[[nodiscard]] QString transformUrl(const QString &urlTemplate) const;
/**
* @brief Transforms a URL template into a concrete URL for this card/set.
* @param urlTemplate The URL template to transform
* @return The transformed URL or empty string if the template cannot be fulfilled
*/
QString transformUrl(const QString &urlTemplate) const;
/**
* @brief Advance to the next set in the list.
* @return True if a next set exists and was selected, false if at the end.
*
* Updates currentSet and repopulates currentSetUrls.
* If we are already at the end of the list, then currentSet is set to empty.
*/
bool nextSet();
/**
* @brief Advance to the next URL in the current set's list.
* @return True if a next URL exists, false if at the end.
*
* Updates currentUrl.
* If we are already at the end of the list, then currentUrl is set to empty.
*/
bool nextUrl();
/**
* @brief Populates the currentSetUrls list with URLs for the current set.
*
* Includes custom URLs first, followed by template-based URLs.
*/
void populateSetUrls();
/**
* @brief Extract all sets from the card and sort them by priority.
* @param card The card to extract sets from
* @return A non-empty list of CardSetPtr, sorted by priority
*
* If the card has no sets, a dummy set is inserted. Also ensures
* the printing corresponding to the ExactCard is first in the list.
*/
static QList<CardSetPtr> extractSetsSorted(const ExactCard &card);
};

View file

@ -78,20 +78,14 @@ void ManaBaseWidget::updateDisplay()
QHash<QString, int> ManaBaseWidget::analyzeManaBase()
{
manaBaseMap.clear();
InnerDecklistNode *listRoot = deckListModel->getDeckList()->getRoot();
for (int i = 0; i < listRoot->size(); i++) {
InnerDecklistNode *currentZone = dynamic_cast<InnerDecklistNode *>(listRoot->at(i));
for (int j = 0; j < currentZone->size(); j++) {
DecklistCardNode *currentCard = dynamic_cast<DecklistCardNode *>(currentZone->at(j));
if (!currentCard)
continue;
QList<DecklistCardNode *> cardsInDeck = deckListModel->getDeckList()->getCardNodes();
for (int k = 0; k < currentCard->getNumber(); ++k) {
CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(currentCard->getName());
if (info) {
auto devotion = determineManaProduction(info->getText());
mergeManaCounts(manaBaseMap, devotion);
}
for (auto currentCard : cardsInDeck) {
for (int k = 0; k < currentCard->getNumber(); ++k) {
CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(currentCard->getName());
if (info) {
auto devotion = determineManaProduction(info->getText());
mergeManaCounts(manaBaseMap, devotion);
}
}
}

View file

@ -44,20 +44,15 @@ void ManaCurveWidget::setDeckModel(DeckListModel *deckModel)
std::unordered_map<int, int> ManaCurveWidget::analyzeManaCurve()
{
manaCurveMap.clear();
InnerDecklistNode *listRoot = deckListModel->getDeckList()->getRoot();
for (int i = 0; i < listRoot->size(); i++) {
InnerDecklistNode *currentZone = dynamic_cast<InnerDecklistNode *>(listRoot->at(i));
for (int j = 0; j < currentZone->size(); j++) {
DecklistCardNode *currentCard = dynamic_cast<DecklistCardNode *>(currentZone->at(j));
if (!currentCard)
continue;
for (int k = 0; k < currentCard->getNumber(); ++k) {
CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(currentCard->getName());
if (info) {
int cmc = info->getCmc().toInt();
manaCurveMap[cmc]++;
}
QList<DecklistCardNode *> cardsInDeck = deckListModel->getDeckList()->getCardNodes();
for (auto currentCard : cardsInDeck) {
for (int k = 0; k < currentCard->getNumber(); ++k) {
CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(currentCard->getName());
if (info) {
int cmc = info->getCmc().toInt();
manaCurveMap[cmc]++;
}
}
}

View file

@ -46,20 +46,15 @@ void ManaDevotionWidget::setDeckModel(DeckListModel *deckModel)
std::unordered_map<char, int> ManaDevotionWidget::analyzeManaDevotion()
{
manaDevotionMap.clear();
InnerDecklistNode *listRoot = deckListModel->getDeckList()->getRoot();
for (int i = 0; i < listRoot->size(); i++) {
InnerDecklistNode *currentZone = dynamic_cast<InnerDecklistNode *>(listRoot->at(i));
for (int j = 0; j < currentZone->size(); j++) {
DecklistCardNode *currentCard = dynamic_cast<DecklistCardNode *>(currentZone->at(j));
if (!currentCard)
continue;
for (int k = 0; k < currentCard->getNumber(); ++k) {
CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(currentCard->getName());
if (info) {
auto devotion = countManaSymbols(info->getManaCost());
mergeManaCounts(manaDevotionMap, devotion);
}
QList<DecklistCardNode *> cardsInDeck = deckListModel->getDeckList()->getCardNodes();
for (auto currentCard : cardsInDeck) {
for (int k = 0; k < currentCard->getNumber(); ++k) {
CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(currentCard->getName());
if (info) {
auto devotion = countManaSymbols(info->getManaCost());
mergeManaCounts(manaDevotionMap, devotion);
}
}
}

View file

@ -34,7 +34,7 @@ void DeckEditorDeckDockWidget::createDeckDock()
deckLoader = new DeckLoader(this, deckModel->getDeckList());
DeckListStyleProxy *proxy = new DeckListStyleProxy(this);
proxy = new DeckListStyleProxy(this);
proxy->setSourceModel(deckModel);
deckView = new QTreeView();
@ -283,18 +283,12 @@ void DeckEditorDeckDockWidget::updateBannerCardComboBox()
// Prepare the new items with deduplication
QSet<QPair<QString, QString>> bannerCardSet;
InnerDecklistNode *listRoot = deckModel->getDeckList()->getRoot();
for (int i = 0; i < listRoot->size(); i++) {
InnerDecklistNode *currentZone = dynamic_cast<InnerDecklistNode *>(listRoot->at(i));
for (int j = 0; j < currentZone->size(); j++) {
DecklistCardNode *currentCard = dynamic_cast<DecklistCardNode *>(currentZone->at(j));
if (!currentCard)
continue;
QList<DecklistCardNode *> cardsInDeck = deckModel->getDeckList()->getCardNodes();
for (int k = 0; k < currentCard->getNumber(); ++k) {
if (CardDatabaseManager::query()->getCard(currentCard->toCardRef())) {
bannerCardSet.insert({currentCard->getName(), currentCard->getCardProviderId()});
}
for (auto currentCard : cardsInDeck) {
for (int k = 0; k < currentCard->getNumber(); ++k) {
if (CardDatabaseManager::query()->getCard(currentCard->toCardRef())) {
bannerCardSet.insert({currentCard->getName(), currentCard->getCardProviderId()});
}
}
}
@ -438,7 +432,9 @@ QModelIndexList DeckEditorDeckDockWidget::getSelectedCardNodes() const
{
auto selectedRows = deckView->selectionModel()->selectedRows();
const auto notLeafNode = [this](const auto &index) { return deckModel->hasChildren(index); };
const auto notLeafNode = [this](const QModelIndex &index) {
return deckModel->hasChildren(proxy->mapToSource(index));
};
selectedRows.erase(std::remove_if(selectedRows.begin(), selectedRows.end(), notLeafNode), selectedRows.end());
std::reverse(selectedRows.begin(), selectedRows.end());
@ -505,7 +501,7 @@ bool DeckEditorDeckDockWidget::swapCard(const QModelIndex &currentIndex)
QModelIndex newCardIndex = card ? deckModel->addCard(card, otherZoneName)
// Third argument (true) says create the card no matter what, even if not in DB
: deckModel->addPreferredPrintingCard(cardName, otherZoneName, true);
recursiveExpand(newCardIndex);
recursiveExpand(proxy->mapToSource(newCardIndex));
return true;
}
@ -526,7 +522,7 @@ void DeckEditorDeckDockWidget::actDecrementCard(const ExactCard &card, QString z
}
deckView->clearSelection();
deckView->setCurrentIndex(idx);
deckView->setCurrentIndex(proxy->mapToSource(idx));
offsetCountAtIndex(idx, -1);
}
@ -562,7 +558,8 @@ void DeckEditorDeckDockWidget::actRemoveCard()
if (!index.isValid() || deckModel->hasChildren(index)) {
continue;
}
deckModel->removeRow(index.row(), index.parent());
QModelIndex sourceIndex = proxy->mapToSource(index);
deckModel->removeRow(sourceIndex.row(), sourceIndex.parent());
isModified = true;
}
@ -579,13 +576,17 @@ void DeckEditorDeckDockWidget::offsetCountAtIndex(const QModelIndex &idx, int of
return;
}
const QModelIndex numberIndex = idx.sibling(idx.row(), 0);
QModelIndex sourceIndex = proxy->mapToSource(idx);
const QModelIndex numberIndex = sourceIndex.sibling(sourceIndex.row(), 0);
const int count = deckModel->data(numberIndex, Qt::EditRole).toInt();
const int new_count = count + offset;
if (new_count <= 0)
deckModel->removeRow(idx.row(), idx.parent());
else
if (new_count <= 0) {
deckModel->removeRow(sourceIndex.row(), sourceIndex.parent());
} else {
deckModel->setData(numberIndex, new_count, Qt::EditRole);
}
emit deckModified();
}

View file

@ -12,6 +12,7 @@
#include "../../key_signals.h"
#include "../utility/custom_line_edit.h"
#include "../visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.h"
#include "deck_list_style_proxy.h"
#include <QComboBox>
#include <QDockWidget>
@ -28,6 +29,7 @@ class DeckEditorDeckDockWidget : public QDockWidget
public:
explicit DeckEditorDeckDockWidget(AbstractTabDeckEditor *parent);
DeckLoader *deckLoader;
DeckListStyleProxy *proxy;
DeckListModel *deckModel;
QTreeView *deckView;
QComboBox *bannerCardComboBox;

View file

@ -35,7 +35,7 @@ void DlgCreateGame::sharedCtor()
maxPlayersEdit->setValue(2);
maxPlayersLabel->setBuddy(maxPlayersEdit);
QGridLayout *generalGrid = new QGridLayout;
auto *generalGrid = new QGridLayout;
generalGrid->addWidget(descriptionLabel, 0, 0);
generalGrid->addWidget(descriptionEdit, 0, 1);
generalGrid->addWidget(maxPlayersLabel, 1, 0);
@ -43,17 +43,17 @@ void DlgCreateGame::sharedCtor()
generalGroupBox = new QGroupBox(tr("General"));
generalGroupBox->setLayout(generalGrid);
QVBoxLayout *gameTypeLayout = new QVBoxLayout;
auto *gameTypeLayout = new QVBoxLayout;
QMapIterator<int, QString> gameTypeIterator(gameTypes);
while (gameTypeIterator.hasNext()) {
gameTypeIterator.next();
QRadioButton *gameTypeRadioButton = new QRadioButton(gameTypeIterator.value(), this);
auto *gameTypeRadioButton = new QRadioButton(gameTypeIterator.value(), this);
gameTypeLayout->addWidget(gameTypeRadioButton);
gameTypeCheckBoxes.insert(gameTypeIterator.key(), gameTypeRadioButton);
bool isChecked = SettingsCache::instance().getGameTypes().contains(gameTypeIterator.value() + ", ");
gameTypeCheckBoxes[gameTypeIterator.key()]->setChecked(isChecked);
}
QGroupBox *gameTypeGroupBox = new QGroupBox(tr("Game type"));
auto *gameTypeGroupBox = new QGroupBox(tr("Game type"));
gameTypeGroupBox->setLayout(gameTypeLayout);
passwordLabel = new QLabel(tr("&Password:"));
@ -70,13 +70,13 @@ void DlgCreateGame::sharedCtor()
onlyRegisteredCheckBox->setEnabled(false);
}
QGridLayout *joinRestrictionsLayout = new QGridLayout;
auto *joinRestrictionsLayout = new QGridLayout;
joinRestrictionsLayout->addWidget(passwordLabel, 0, 0);
joinRestrictionsLayout->addWidget(passwordEdit, 0, 1);
joinRestrictionsLayout->addWidget(onlyBuddiesCheckBox, 1, 0, 1, 2);
joinRestrictionsLayout->addWidget(onlyRegisteredCheckBox, 2, 0, 1, 2);
QGroupBox *joinRestrictionsGroupBox = new QGroupBox(tr("Joining restrictions"));
auto *joinRestrictionsGroupBox = new QGroupBox(tr("Joining restrictions"));
joinRestrictionsGroupBox->setLayout(joinRestrictionsLayout);
spectatorsAllowedCheckBox = new QCheckBox(tr("&Spectators can watch"));
@ -86,7 +86,7 @@ void DlgCreateGame::sharedCtor()
spectatorsCanTalkCheckBox = new QCheckBox(tr("Spectators can &chat"));
spectatorsSeeEverythingCheckBox = new QCheckBox(tr("Spectators can see &hands"));
createGameAsSpectatorCheckBox = new QCheckBox(tr("Create game as spectator"));
QVBoxLayout *spectatorsLayout = new QVBoxLayout;
auto *spectatorsLayout = new QVBoxLayout;
spectatorsLayout->addWidget(spectatorsAllowedCheckBox);
spectatorsLayout->addWidget(spectatorsNeedPasswordCheckBox);
spectatorsLayout->addWidget(spectatorsCanTalkCheckBox);
@ -106,7 +106,7 @@ void DlgCreateGame::sharedCtor()
shareDecklistsOnLoadCheckBox = new QCheckBox();
shareDecklistsOnLoadLabel->setBuddy(shareDecklistsOnLoadCheckBox);
QGridLayout *gameSetupOptionsLayout = new QGridLayout;
auto *gameSetupOptionsLayout = new QGridLayout;
gameSetupOptionsLayout->addWidget(startingLifeTotalLabel, 0, 0);
gameSetupOptionsLayout->addWidget(startingLifeTotalEdit, 0, 1);
gameSetupOptionsLayout->addWidget(shareDecklistsOnLoadLabel, 1, 0);
@ -136,7 +136,7 @@ void DlgCreateGame::sharedCtor()
buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok);
connect(buttonBox, &QDialogButtonBox::rejected, this, &DlgCreateGame::reject);
QVBoxLayout *mainLayout = new QVBoxLayout;
auto *mainLayout = new QVBoxLayout;
mainLayout->addLayout(grid);
mainLayout->addWidget(buttonBox);
@ -275,7 +275,7 @@ void DlgCreateGame::actOK()
cmd.set_starting_life_total(startingLifeTotalEdit->value());
cmd.set_share_decklists_on_load(shareDecklistsOnLoadCheckBox->isChecked());
QString _gameTypes = QString();
auto _gameTypes = QString();
QMapIterator<int, QRadioButton *> gameTypeCheckBoxIterator(gameTypeCheckBoxes);
while (gameTypeCheckBoxIterator.hasNext()) {
gameTypeCheckBoxIterator.next();

View file

@ -23,16 +23,16 @@ DlgEditAvatar::DlgEditAvatar(QWidget *parent) : QDialog(parent), image()
browseButton = new QPushButton(tr("Browse..."));
connect(browseButton, &QPushButton::clicked, this, &DlgEditAvatar::actBrowse);
QGridLayout *grid = new QGridLayout;
auto *grid = new QGridLayout;
grid->addWidget(imageLabel, 0, 0, 1, 2);
grid->addWidget(textLabel, 1, 0);
grid->addWidget(browseButton, 1, 1);
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
auto *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgEditAvatar::actOk);
connect(buttonBox, &QDialogButtonBox::rejected, this, &DlgEditAvatar::reject);
QVBoxLayout *mainLayout = new QVBoxLayout;
auto *mainLayout = new QVBoxLayout;
mainLayout->addLayout(grid);
mainLayout->addWidget(buttonBox);
setLayout(mainLayout);

View file

@ -66,7 +66,7 @@ DlgForgotPasswordChallenge::DlgForgotPasswordChallenge(QWidget *parent) : QDialo
playernameEdit->hide();
}
QGridLayout *grid = new QGridLayout;
auto *grid = new QGridLayout;
grid->addWidget(infoLabel, 0, 0, 1, 2);
grid->addWidget(hostLabel, 1, 0);
grid->addWidget(hostEdit, 1, 1);
@ -77,11 +77,11 @@ DlgForgotPasswordChallenge::DlgForgotPasswordChallenge(QWidget *parent) : QDialo
grid->addWidget(emailLabel, 4, 0);
grid->addWidget(emailEdit, 4, 1);
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
auto *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgForgotPasswordChallenge::actOk);
connect(buttonBox, &QDialogButtonBox::rejected, this, &DlgForgotPasswordChallenge::reject);
QVBoxLayout *mainLayout = new QVBoxLayout;
auto *mainLayout = new QVBoxLayout;
mainLayout->addLayout(grid);
mainLayout->addWidget(buttonBox);
setLayout(mainLayout);

View file

@ -45,7 +45,7 @@ DlgForgotPasswordRequest::DlgForgotPasswordRequest(QWidget *parent) : QDialog(pa
playernameEdit->setMaxLength(MAX_NAME_LENGTH);
playernameLabel->setBuddy(playernameEdit);
QGridLayout *grid = new QGridLayout;
auto *grid = new QGridLayout;
grid->addWidget(infoLabel, 0, 0, 1, 2);
grid->addWidget(hostLabel, 1, 0);
grid->addWidget(hostEdit, 1, 1);
@ -54,11 +54,11 @@ DlgForgotPasswordRequest::DlgForgotPasswordRequest(QWidget *parent) : QDialog(pa
grid->addWidget(playernameLabel, 3, 0);
grid->addWidget(playernameEdit, 3, 1);
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
auto *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgForgotPasswordRequest::actOk);
connect(buttonBox, &QDialogButtonBox::rejected, this, &DlgForgotPasswordRequest::reject);
QVBoxLayout *mainLayout = new QVBoxLayout;
auto *mainLayout = new QVBoxLayout;
mainLayout->addLayout(grid);
mainLayout->addWidget(buttonBox);
setLayout(mainLayout);

View file

@ -24,7 +24,7 @@ AbstractDlgDeckTextEdit::AbstractDlgDeckTextEdit(QWidget *parent) : QDialog(pare
refreshButton = new QPushButton(tr("&Refresh"));
connect(refreshButton, &QPushButton::clicked, this, &AbstractDlgDeckTextEdit::actRefresh);
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
auto *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
buttonBox->addButton(refreshButton, QDialogButtonBox::ActionRole);
connect(buttonBox, &QDialogButtonBox::accepted, this, &AbstractDlgDeckTextEdit::actOK);
connect(buttonBox, &QDialogButtonBox::rejected, this, &AbstractDlgDeckTextEdit::reject);

View file

@ -18,7 +18,7 @@ DlgLoadRemoteDeck::DlgLoadRemoteDeck(AbstractClient *_client, QWidget *parent) :
connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgLoadRemoteDeck::accept);
connect(buttonBox, &QDialogButtonBox::rejected, this, &DlgLoadRemoteDeck::reject);
QVBoxLayout *mainLayout = new QVBoxLayout;
auto *mainLayout = new QVBoxLayout;
mainLayout->addWidget(dirView);
mainLayout->addWidget(buttonBox);

View file

@ -321,7 +321,7 @@ DlgRegister::DlgRegister(QWidget *parent) : QDialog(parent)
realnameEdit->setMaxLength(MAX_NAME_LENGTH);
realnameLabel->setBuddy(realnameEdit);
QGridLayout *grid = new QGridLayout;
auto *grid = new QGridLayout;
grid->addWidget(infoLabel, 0, 0, 1, 2);
grid->addWidget(hostLabel, 1, 0);
grid->addWidget(hostEdit, 1, 1);
@ -342,11 +342,11 @@ DlgRegister::DlgRegister(QWidget *parent) : QDialog(parent)
grid->addWidget(realnameLabel, 10, 0);
grid->addWidget(realnameEdit, 10, 1);
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
auto *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgRegister::actOk);
connect(buttonBox, &QDialogButtonBox::rejected, this, &DlgRegister::reject);
QVBoxLayout *mainLayout = new QVBoxLayout;
auto *mainLayout = new QVBoxLayout;
mainLayout->addLayout(grid);
mainLayout->addWidget(buttonBox);
setLayout(mainLayout);

View file

@ -209,30 +209,19 @@ QMap<QString, int> DlgSelectSetForCards::getSetsForCards()
if (!decklist)
return setCounts;
InnerDecklistNode *listRoot = decklist->getRoot();
if (!listRoot)
return setCounts;
QList<DecklistCardNode *> cardsInDeck = decklist->getCardNodes();
for (auto *i : *listRoot) {
auto *countCurrentZone = dynamic_cast<InnerDecklistNode *>(i);
if (!countCurrentZone)
for (auto currentCard : cardsInDeck) {
CardInfoPtr infoPtr = CardDatabaseManager::query()->getCardInfo(currentCard->getName());
if (!infoPtr)
continue;
for (auto *cardNode : *countCurrentZone) {
auto *currentCard = dynamic_cast<DecklistCardNode *>(cardNode);
if (!currentCard)
continue;
CardInfoPtr infoPtr = CardDatabaseManager::query()->getCardInfo(currentCard->getName());
if (!infoPtr)
continue;
SetToPrintingsMap setMap = infoPtr->getSets();
for (auto &setName : setMap.keys()) {
setCounts[setName]++;
}
SetToPrintingsMap setMap = infoPtr->getSets();
for (auto &setName : setMap.keys()) {
setCounts[setName]++;
}
}
return setCounts;
}
@ -263,48 +252,36 @@ void DlgSelectSetForCards::updateCardLists()
if (!decklist)
return;
InnerDecklistNode *listRoot = decklist->getRoot();
if (!listRoot)
return;
QList<DecklistCardNode *> cardsInDeck = decklist->getCardNodes();
for (auto *i : *listRoot) {
auto *countCurrentZone = dynamic_cast<InnerDecklistNode *>(i);
if (!countCurrentZone)
continue;
for (auto currentCard : cardsInDeck) {
bool found = false;
QString foundSetName;
for (auto *cardNode : *countCurrentZone) {
auto *currentCard = dynamic_cast<DecklistCardNode *>(cardNode);
if (!currentCard)
continue;
bool found = false;
QString foundSetName;
// Check across all sets if the card is present
for (auto it = selectedCardsBySet.begin(); it != selectedCardsBySet.end(); ++it) {
if (it.value().contains(currentCard->getName())) {
found = true;
foundSetName = it.key(); // Store the set name where it was found
break; // Stop at the first match
}
// Check across all sets if the card is present
for (auto it = selectedCardsBySet.begin(); it != selectedCardsBySet.end(); ++it) {
if (it.value().contains(currentCard->getName())) {
found = true;
foundSetName = it.key(); // Store the set name where it was found
break; // Stop at the first match
}
}
if (!found) {
// The card was not in any selected set
ExactCard card = CardDatabaseManager::query()->getCard({currentCard->getName()});
CardInfoPictureWidget *picture_widget = new CardInfoPictureWidget(uneditedCardsFlowWidget);
picture_widget->setCard(card);
uneditedCardsFlowWidget->addWidget(picture_widget);
} else {
ExactCard card = CardDatabaseManager::query()->getCard(
{currentCard->getName(), CardDatabaseManager::getInstance()
->query()
->getSpecificPrinting(currentCard->getName(), foundSetName, "")
.getUuid()});
CardInfoPictureWidget *picture_widget = new CardInfoPictureWidget(modifiedCardsFlowWidget);
picture_widget->setCard(card);
modifiedCardsFlowWidget->addWidget(picture_widget);
}
if (!found) {
// The card was not in any selected set
ExactCard card = CardDatabaseManager::query()->getCard({currentCard->getName()});
CardInfoPictureWidget *picture_widget = new CardInfoPictureWidget(uneditedCardsFlowWidget);
picture_widget->setCard(card);
uneditedCardsFlowWidget->addWidget(picture_widget);
} else {
ExactCard card = CardDatabaseManager::query()->getCard(
{currentCard->getName(), CardDatabaseManager::getInstance()
->query()
->getSpecificPrinting(currentCard->getName(), foundSetName, "")
.getUuid()});
CardInfoPictureWidget *picture_widget = new CardInfoPictureWidget(modifiedCardsFlowWidget);
picture_widget->setCard(card);
modifiedCardsFlowWidget->addWidget(picture_widget);
}
}
}
@ -364,30 +341,19 @@ QMap<QString, QStringList> DlgSelectSetForCards::getCardsForSets()
if (!decklist)
return setCards;
InnerDecklistNode *listRoot = decklist->getRoot();
if (!listRoot)
return setCards;
QList<DecklistCardNode *> cardsInDeck = decklist->getCardNodes();
for (auto *i : *listRoot) {
auto *countCurrentZone = dynamic_cast<InnerDecklistNode *>(i);
if (!countCurrentZone)
for (auto currentCard : cardsInDeck) {
CardInfoPtr infoPtr = CardDatabaseManager::query()->getCardInfo(currentCard->getName());
if (!infoPtr)
continue;
for (auto *cardNode : *countCurrentZone) {
auto *currentCard = dynamic_cast<DecklistCardNode *>(cardNode);
if (!currentCard)
continue;
CardInfoPtr infoPtr = CardDatabaseManager::query()->getCardInfo(currentCard->getName());
if (!infoPtr)
continue;
SetToPrintingsMap setMap = infoPtr->getSets();
for (auto it = setMap.begin(); it != setMap.end(); ++it) {
setCards[it.key()].append(currentCard->getName());
}
SetToPrintingsMap setMap = infoPtr->getSets();
for (auto it = setMap.begin(); it != setMap.end(); ++it) {
setCards[it.key()].append(currentCard->getName());
}
}
return setCards;
}

View file

@ -141,38 +141,38 @@ GeneralSettingsPage::GeneralSettingsPage()
deckPathEdit = new QLineEdit(settings.getDeckPath());
deckPathEdit->setReadOnly(true);
QPushButton *deckPathButton = new QPushButton("...");
auto *deckPathButton = new QPushButton("...");
connect(deckPathButton, &QPushButton::clicked, this, &GeneralSettingsPage::deckPathButtonClicked);
filtersPathEdit = new QLineEdit(settings.getFiltersPath());
filtersPathEdit->setReadOnly(true);
QPushButton *filtersPathButton = new QPushButton("...");
auto *filtersPathButton = new QPushButton("...");
connect(filtersPathButton, &QPushButton::clicked, this, &GeneralSettingsPage::filtersPathButtonClicked);
replaysPathEdit = new QLineEdit(settings.getReplaysPath());
replaysPathEdit->setReadOnly(true);
QPushButton *replaysPathButton = new QPushButton("...");
auto *replaysPathButton = new QPushButton("...");
connect(replaysPathButton, &QPushButton::clicked, this, &GeneralSettingsPage::replaysPathButtonClicked);
picsPathEdit = new QLineEdit(settings.getPicsPath());
picsPathEdit->setReadOnly(true);
QPushButton *picsPathButton = new QPushButton("...");
auto *picsPathButton = new QPushButton("...");
connect(picsPathButton, &QPushButton::clicked, this, &GeneralSettingsPage::picsPathButtonClicked);
cardDatabasePathEdit = new QLineEdit(settings.getCardDatabasePath());
cardDatabasePathEdit->setReadOnly(true);
QPushButton *cardDatabasePathButton = new QPushButton("...");
auto *cardDatabasePathButton = new QPushButton("...");
connect(cardDatabasePathButton, &QPushButton::clicked, this, &GeneralSettingsPage::cardDatabasePathButtonClicked);
customCardDatabasePathEdit = new QLineEdit(settings.getCustomCardDatabasePath());
customCardDatabasePathEdit->setReadOnly(true);
QPushButton *customCardDatabasePathButton = new QPushButton("...");
auto *customCardDatabasePathButton = new QPushButton("...");
connect(customCardDatabasePathButton, &QPushButton::clicked, this,
&GeneralSettingsPage::customCardDatabaseButtonClicked);
tokenDatabasePathEdit = new QLineEdit(settings.getTokenDatabasePath());
tokenDatabasePathEdit->setReadOnly(true);
QPushButton *tokenDatabasePathButton = new QPushButton("...");
auto *tokenDatabasePathButton = new QPushButton("...");
connect(tokenDatabasePathButton, &QPushButton::clicked, this, &GeneralSettingsPage::tokenDatabasePathButtonClicked);
// Required init here to avoid crashing on Portable builds

View file

@ -266,35 +266,16 @@ int CardAmountWidget::countCardsInZone(const QString &deckZone)
return -1;
}
InnerDecklistNode *listRoot = decklist->getRoot();
if (!listRoot) {
return -1;
}
QList<DecklistCardNode *> cardsInDeck = decklist->getCardNodes({deckZone});
int count = 0;
for (auto *i : *listRoot) {
auto *countCurrentZone = dynamic_cast<InnerDecklistNode *>(i);
if (!countCurrentZone) {
continue;
}
if (countCurrentZone->getName() != deckZone) {
continue;
}
for (auto *cardNode : *countCurrentZone) {
auto *currentCard = dynamic_cast<DecklistCardNode *>(cardNode);
if (!currentCard) {
continue;
}
for (int k = 0; k < currentCard->getNumber(); ++k) {
if (currentCard->getCardProviderId() == rootCard.getPrinting().getProperty("uuid")) {
count++;
}
for (auto currentCard : cardsInDeck) {
for (int k = 0; k < currentCard->getNumber(); ++k) {
if (currentCard->getCardProviderId() == rootCard.getPrinting().getProperty("uuid")) {
count++;
}
}
}
return count;
}

View file

@ -218,7 +218,7 @@ void PrintingSelector::getAllSetsForCurrentCard()
connect(widgetLoadingBufferTimer, &QTimer::timeout, this, [=, this]() mutable {
for (int i = 0; i < BATCH_SIZE && currentIndex < printingsToUse.size(); ++i, ++currentIndex) {
ExactCard card = ExactCard(selectedCard, printingsToUse[currentIndex]);
auto card = ExactCard(selectedCard, printingsToUse[currentIndex]);
auto *cardDisplayWidget = new PrintingSelectorCardDisplayWidget(
this, deckEditor, deckModel, deckView, cardSizeWidget->getSlider(), card, currentZone);
flowWidget->addWidget(cardDisplayWidget);

View file

@ -47,7 +47,7 @@ void PrintingSelectorCardSelectionWidget::connectSignals()
void PrintingSelectorCardSelectionWidget::selectSetForCards()
{
DlgSelectSetForCards *setSelectionDialog = new DlgSelectSetForCards(nullptr, parent->getDeckModel());
auto *setSelectionDialog = new DlgSelectSetForCards(nullptr, parent->getDeckModel());
if (!setSelectionDialog->exec()) {
return;
}

View file

@ -28,7 +28,7 @@ enum GameListColumn
const int DEFAULT_MAX_PLAYERS_MIN = 1;
const int DEFAULT_MAX_PLAYERS_MAX = 99;
constexpr QTime DEFAULT_MAX_GAME_AGE = QTime();
constexpr auto DEFAULT_MAX_GAME_AGE = QTime();
const QString GamesModel::getGameCreatedString(const int secs)
{
@ -333,7 +333,7 @@ void GamesProxyModel::setGameFilters(bool _hideBuddiesOnlyGames,
int GamesProxyModel::getNumFilteredGames() const
{
GamesModel *model = qobject_cast<GamesModel *>(sourceModel());
auto *model = qobject_cast<GamesModel *>(sourceModel());
if (!model)
return 0;

View file

@ -48,7 +48,7 @@ int RemoteReplayList_TreeModel::rowCount(const QModelIndex &parent) const
if (!parent.isValid())
return replayMatches.size();
MatchNode *matchNode = dynamic_cast<MatchNode *>(static_cast<Node *>(parent.internalPointer()));
auto *matchNode = dynamic_cast<MatchNode *>(static_cast<Node *>(parent.internalPointer()));
if (matchNode)
return matchNode->size();
else
@ -62,7 +62,7 @@ QVariant RemoteReplayList_TreeModel::data(const QModelIndex &index, int role) co
if (index.column() >= numberOfColumns)
return QVariant();
ReplayNode *replayNode = dynamic_cast<ReplayNode *>(static_cast<Node *>(index.internalPointer()));
auto *replayNode = dynamic_cast<ReplayNode *>(static_cast<Node *>(index.internalPointer()));
if (replayNode) {
const ServerInfo_Replay &replayInfo = replayNode->getReplayInfo();
switch (role) {
@ -84,7 +84,7 @@ QVariant RemoteReplayList_TreeModel::data(const QModelIndex &index, int role) co
return index.column() == 0 ? fileIcon : QVariant();
}
} else {
MatchNode *matchNode = dynamic_cast<MatchNode *>(static_cast<Node *>(index.internalPointer()));
auto *matchNode = dynamic_cast<MatchNode *>(static_cast<Node *>(index.internalPointer()));
const ServerInfo_ReplayMatch &matchInfo = matchNode->getMatchInfo();
switch (role) {
case Qt::TextAlignmentRole:
@ -170,7 +170,7 @@ QModelIndex RemoteReplayList_TreeModel::index(int row, int column, const QModelI
if (!hasIndex(row, column, parent))
return QModelIndex();
MatchNode *matchNode = dynamic_cast<MatchNode *>(static_cast<Node *>(parent.internalPointer()));
auto *matchNode = dynamic_cast<MatchNode *>(static_cast<Node *>(parent.internalPointer()));
if (matchNode) {
if (row >= matchNode->size())
return QModelIndex();
@ -188,7 +188,7 @@ QModelIndex RemoteReplayList_TreeModel::parent(const QModelIndex &ind) const
if (matchNode)
return QModelIndex();
else {
ReplayNode *replayNode = dynamic_cast<ReplayNode *>(static_cast<Node *>(ind.internalPointer()));
auto *replayNode = dynamic_cast<ReplayNode *>(static_cast<Node *>(ind.internalPointer()));
return createIndex(replayNode->getParent()->indexOf(replayNode), 0, replayNode->getParent());
}
}
@ -206,7 +206,7 @@ ServerInfo_Replay const *RemoteReplayList_TreeModel::getReplay(const QModelIndex
if (!index.isValid())
return 0;
ReplayNode *node = dynamic_cast<ReplayNode *>(static_cast<Node *>(index.internalPointer()));
auto *node = dynamic_cast<ReplayNode *>(static_cast<Node *>(index.internalPointer()));
if (!node)
return 0;
return &node->getReplayInfo();

View file

@ -110,7 +110,7 @@ void UserContextMenu::gamesOfUserReceived(const Response &resp, const CommandCon
gameTypeMap.insert(roomInfo.room_id(), tempMap);
}
GameSelector *selector = new GameSelector(client, tabSupervisor, nullptr, roomMap, gameTypeMap, false, false);
auto *selector = new GameSelector(client, tabSupervisor, nullptr, roomMap, gameTypeMap, false, false);
selector->setParent(static_cast<QWidget *>(parent()), Qt::Window);
const int gameListSize = response.game_list_size();
for (int i = 0; i < gameListSize; ++i) {
@ -128,7 +128,7 @@ void UserContextMenu::banUser_processUserInfoResponse(const Response &r)
const Response_GetUserInfo &response = r.GetExtension(Response_GetUserInfo::ext);
// The dialog needs to be non-modal in order to not block the event queue of the client.
BanDialog *dlg = new BanDialog(response.user_info(), static_cast<QWidget *>(parent()));
auto *dlg = new BanDialog(response.user_info(), static_cast<QWidget *>(parent()));
connect(dlg, &QDialog::accepted, this, &UserContextMenu::banUser_dialogFinished);
dlg->show();
}
@ -141,7 +141,7 @@ void UserContextMenu::warnUser_processGetWarningsListResponse(const Response &r)
QString clientid = QString::fromStdString(response.user_clientid()).simplified();
// The dialog needs to be non-modal in order to not block the event queue of the client.
WarningDialog *dlg = new WarningDialog(user, clientid, static_cast<QWidget *>(parent()));
auto *dlg = new WarningDialog(user, clientid, static_cast<QWidget *>(parent()));
connect(dlg, &QDialog::accepted, this, &UserContextMenu::warnUser_dialogFinished);
if (response.warning_size() > 0) {
@ -171,7 +171,7 @@ void UserContextMenu::banUserHistory_processResponse(const Response &resp)
if (resp.response_code() == Response::RespOk) {
if (response.ban_list_size() > 0) {
QTableWidget *table = new QTableWidget();
auto *table = new QTableWidget();
table->setWindowTitle(tr("Ban History"));
table->setRowCount(response.ban_list_size());
table->setColumnCount(5);
@ -209,7 +209,7 @@ void UserContextMenu::warnUserHistory_processResponse(const Response &resp)
if (resp.response_code() == Response::RespOk) {
if (response.warn_list_size() > 0) {
QTableWidget *table = new QTableWidget();
auto *table = new QTableWidget();
table->setWindowTitle(tr("Warning History"));
table->setRowCount(response.warn_list_size());
table->setColumnCount(4);
@ -278,7 +278,7 @@ void UserContextMenu::adjustMod_processUserResponse(const Response &resp, const
void UserContextMenu::banUser_dialogFinished()
{
BanDialog *dlg = static_cast<BanDialog *>(sender());
auto *dlg = static_cast<BanDialog *>(sender());
Command_BanFromServer cmd;
cmd.set_user_name(dlg->getBanName().toStdString());
@ -297,7 +297,7 @@ void UserContextMenu::banUser_dialogFinished()
void UserContextMenu::warnUser_dialogFinished()
{
WarningDialog *dlg = static_cast<WarningDialog *>(sender());
auto *dlg = static_cast<WarningDialog *>(sender());
if (dlg->getName().isEmpty() || userListProxy->getOwnUsername().simplified().isEmpty())
return;
@ -433,7 +433,7 @@ void UserContextMenu::showContextMenu(const QPoint &pos,
QAction *actionClicked = menu->exec(pos);
if (actionClicked == nullptr) {
} else if (actionClicked == aDetails) {
UserInfoBox *infoWidget =
auto *infoWidget =
new UserInfoBox(client, false, static_cast<QWidget *>(parent()),
Qt::Dialog | Qt::WindowTitleHint | Qt::CustomizeWindowHint | Qt::WindowCloseButtonHint);
infoWidget->setAttribute(Qt::WA_DeleteOnClose);

View file

@ -64,14 +64,14 @@ TabEdhRecMain::TabEdhRecMain(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor
auto cardDatabaseModel = new CardDatabaseModel(CardDatabaseManager::getInstance(), false, this);
auto displayModel = new CardDatabaseDisplayModel(this);
displayModel->setSourceModel(cardDatabaseModel);
CardSearchModel *searchModel = new CardSearchModel(displayModel, this);
auto *searchModel = new CardSearchModel(displayModel, this);
CardCompleterProxyModel *proxyModel = new CardCompleterProxyModel(this);
auto *proxyModel = new CardCompleterProxyModel(this);
proxyModel->setSourceModel(searchModel);
proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
proxyModel->setFilterRole(Qt::DisplayRole);
QCompleter *completer = new QCompleter(proxyModel, this);
auto *completer = new QCompleter(proxyModel, this);
completer->setCompletionRole(Qt::DisplayRole);
completer->setCompletionMode(QCompleter::PopupCompletion);
completer->setCaseSensitivity(Qt::CaseInsensitive);

View file

@ -17,22 +17,22 @@
ShutdownDialog::ShutdownDialog(QWidget *parent) : QDialog(parent)
{
QLabel *reasonLabel = new QLabel(tr("&Reason for shutdown:"));
auto *reasonLabel = new QLabel(tr("&Reason for shutdown:"));
reasonEdit = new QLineEdit;
reasonEdit->setMaxLength(MAX_TEXT_LENGTH);
reasonLabel->setBuddy(reasonEdit);
QLabel *minutesLabel = new QLabel(tr("&Time until shutdown (minutes):"));
auto *minutesLabel = new QLabel(tr("&Time until shutdown (minutes):"));
minutesEdit = new QSpinBox;
minutesLabel->setBuddy(minutesEdit);
minutesEdit->setMinimum(0);
minutesEdit->setValue(5);
minutesEdit->setMaximum(999);
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
auto *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
connect(buttonBox, &QDialogButtonBox::accepted, this, &ShutdownDialog::accept);
connect(buttonBox, &QDialogButtonBox::rejected, this, &ShutdownDialog::reject);
QGridLayout *mainLayout = new QGridLayout;
auto *mainLayout = new QGridLayout;
mainLayout->addWidget(reasonLabel, 0, 0);
mainLayout->addWidget(reasonEdit, 0, 1);
mainLayout->addWidget(minutesLabel, 1, 0);
@ -109,7 +109,7 @@ TabAdmin::TabAdmin(TabSupervisor *_tabSupervisor, AbstractClient *_client, bool
lockButton->setEnabled(false);
connect(lockButton, &QPushButton::clicked, this, &TabAdmin::actLock);
QVBoxLayout *mainLayout = new QVBoxLayout;
auto *mainLayout = new QVBoxLayout;
mainLayout->addWidget(adminGroupBox);
mainLayout->addWidget(moderatorGroupBox);
mainLayout->addStretch();
@ -118,7 +118,7 @@ TabAdmin::TabAdmin(TabSupervisor *_tabSupervisor, AbstractClient *_client, bool
retranslateUi();
QWidget *mainWidget = new QWidget(this);
auto *mainWidget = new QWidget(this);
mainWidget->setLayout(mainLayout);
setCentralWidget(mainWidget);

View file

@ -982,7 +982,7 @@ void TabGame::createMenuItems()
phasesMenu = new TearOffMenu(this);
for (int i = 0; i < phasesToolbar->phaseCount(); ++i) {
QAction *temp = new QAction(QString(), this);
auto *temp = new QAction(QString(), this);
connect(temp, &QAction::triggered, this, &TabGame::actPhaseAction);
phasesMenu->addAction(temp);
phaseActions.append(temp);

View file

@ -34,7 +34,7 @@ TabMessage::TabMessage(TabSupervisor *_tabSupervisor,
sayEdit->setMaxLength(MAX_TEXT_LENGTH);
connect(sayEdit, &LineEditUnfocusable::returnPressed, this, &TabMessage::sendMessage);
QVBoxLayout *vbox = new QVBoxLayout;
auto *vbox = new QVBoxLayout;
vbox->addWidget(chatView);
vbox->addWidget(sayEdit);
@ -47,7 +47,7 @@ TabMessage::TabMessage(TabSupervisor *_tabSupervisor,
retranslateUi();
QWidget *mainWidget = new QWidget(this);
auto *mainWidget = new QWidget(this);
mainWidget->setLayout(vbox);
setCentralWidget(mainWidget);
}

View file

@ -67,7 +67,7 @@ TabRoom::TabRoom(TabSupervisor *_tabSupervisor,
sayLabel->setBuddy(sayEdit);
connect(sayEdit, &LineEditCompleter::returnPressed, this, &TabRoom::sendMessage);
QMenu *chatSettingsMenu = new QMenu(this);
auto *chatSettingsMenu = new QMenu(this);
aClearChat = chatSettingsMenu->addAction(QString());
connect(aClearChat, &QAction::triggered, this, &TabRoom::actClearChat);
@ -77,28 +77,28 @@ TabRoom::TabRoom(TabSupervisor *_tabSupervisor,
aOpenChatSettings = chatSettingsMenu->addAction(QString());
connect(aOpenChatSettings, &QAction::triggered, this, &TabRoom::actOpenChatSettings);
QToolButton *chatSettingsButton = new QToolButton;
auto *chatSettingsButton = new QToolButton;
chatSettingsButton->setIcon(QPixmap("theme:icons/settings"));
chatSettingsButton->setMenu(chatSettingsMenu);
chatSettingsButton->setPopupMode(QToolButton::InstantPopup);
QHBoxLayout *sayHbox = new QHBoxLayout;
auto *sayHbox = new QHBoxLayout;
sayHbox->addWidget(sayLabel);
sayHbox->addWidget(sayEdit);
sayHbox->addWidget(chatSettingsButton);
QVBoxLayout *chatVbox = new QVBoxLayout;
auto *chatVbox = new QVBoxLayout;
chatVbox->addWidget(chatView);
chatVbox->addLayout(sayHbox);
chatGroupBox = new QGroupBox;
chatGroupBox->setLayout(chatVbox);
QSplitter *splitter = new QSplitter(Qt::Vertical);
auto *splitter = new QSplitter(Qt::Vertical);
splitter->addWidget(gameSelector);
splitter->addWidget(chatGroupBox);
QHBoxLayout *hbox = new QHBoxLayout;
auto *hbox = new QHBoxLayout;
hbox->addWidget(splitter, 3);
hbox->addWidget(userList, 1);
@ -133,7 +133,7 @@ TabRoom::TabRoom(TabSupervisor *_tabSupervisor,
retranslateUi();
QWidget *mainWidget = new QWidget(this);
auto *mainWidget = new QWidget(this);
mainWidget->setLayout(hbox);
setCentralWidget(mainWidget);
}

View file

@ -931,7 +931,7 @@ void TabSupervisor::deckEditorClosed(AbstractTabDeckEditor *tab)
void TabSupervisor::tabUserEvent(bool globalEvent)
{
Tab *tab = static_cast<Tab *>(sender());
auto *tab = static_cast<Tab *>(sender());
if (tab != currentWidget()) {
tab->setContentsChanged(true);
setTabIcon(indexOf(tab), QPixmap("theme:icons/tab_changed"));
@ -1032,7 +1032,7 @@ void TabSupervisor::processUserJoined(const ServerInfo_User &userInfoJoined)
void TabSupervisor::updateCurrent(int index)
{
if (index != -1) {
Tab *tab = static_cast<Tab *>(widget(index));
auto *tab = static_cast<Tab *>(widget(index));
if (tab->getContentsChanged()) {
setTabIcon(index, QIcon());
tab->setContentsChanged(false);

View file

@ -67,21 +67,13 @@ void VisualDatabaseDisplayNameFilterWidget::actLoadFromDeck()
DeckList *decklist = deckListModel->getDeckList();
if (!decklist)
return;
InnerDecklistNode *listRoot = decklist->getRoot();
if (!listRoot)
return;
for (int i = 0; i < listRoot->size(); i++) {
InnerDecklistNode *currentZone = dynamic_cast<InnerDecklistNode *>(listRoot->at(i));
if (!currentZone)
continue;
for (int j = 0; j < currentZone->size(); j++) {
DecklistCardNode *currentCard = dynamic_cast<DecklistCardNode *>(currentZone->at(j));
if (!currentCard)
continue;
createNameFilter(currentCard->getName());
}
QList<DecklistCardNode *> cardsInDeck = decklist->getCardNodes();
for (auto currentCard : cardsInDeck) {
createNameFilter(currentCard->getName());
}
updateFilterModel();
}

View file

@ -83,28 +83,15 @@ QList<ExactCard> VisualDeckEditorSampleHandWidget::getRandomCards(int amountToGe
DeckList *decklist = deckListModel->getDeckList();
if (!decklist)
return randomCards;
InnerDecklistNode *listRoot = decklist->getRoot();
if (!listRoot)
return randomCards;
QList<DecklistCardNode *> cardsInDeck = decklist->getCardNodes({DECK_ZONE_MAIN});
// Collect all cards in the main deck, allowing duplicates based on their count
for (int i = 0; i < listRoot->size(); i++) {
InnerDecklistNode *currentZone = dynamic_cast<InnerDecklistNode *>(listRoot->at(i));
if (!currentZone)
continue;
if (currentZone->getName() != DECK_ZONE_MAIN)
continue; // Only process the main deck
for (int j = 0; j < currentZone->size(); j++) {
DecklistCardNode *currentCard = dynamic_cast<DecklistCardNode *>(currentZone->at(j));
if (!currentCard)
continue;
for (int k = 0; k < currentCard->getNumber(); ++k) {
ExactCard card = CardDatabaseManager::query()->getCard(currentCard->toCardRef());
if (card) {
mainDeckCards.append(card);
}
for (auto currentCard : cardsInDeck) {
for (int k = 0; k < currentCard->getNumber(); ++k) {
ExactCard card = CardDatabaseManager::query()->getCard(currentCard->toCardRef());
if (card) {
mainDeckCards.append(card);
}
}
}

View file

@ -232,17 +232,12 @@ void DeckPreviewWidget::updateBannerCardComboBox()
// Prepare the new items with deduplication
QSet<QPair<QString, QString>> bannerCardSet;
InnerDecklistNode *listRoot = deckLoader->getDeckList()->getRoot();
for (auto i : *listRoot) {
auto *currentZone = dynamic_cast<InnerDecklistNode *>(i);
for (auto j : *currentZone) {
auto *currentCard = dynamic_cast<DecklistCardNode *>(j);
if (!currentCard)
continue;
for (int k = 0; k < currentCard->getNumber(); ++k) {
bannerCardSet.insert(QPair<QString, QString>(currentCard->getName(), currentCard->getCardProviderId()));
}
QList<DecklistCardNode *> cardsInDeck = deckLoader->getDeckList()->getCardNodes();
for (auto currentCard : cardsInDeck) {
for (int k = 0; k < currentCard->getNumber(); ++k) {
bannerCardSet.insert(QPair<QString, QString>(currentCard->getName(), currentCard->getCardProviderId()));
}
}

View file

@ -2,7 +2,7 @@
#include "mocks.h"
CardDatabaseSettings::CardDatabaseSettings(const QString &settingPath, QObject *parent)
: SettingsManager(settingPath + "cardDatabase.ini", parent)
: SettingsManager(settingPath + "cardDatabase.ini", QString(), QString(), parent)
{
}
void CardDatabaseSettings::setSortKey(QString /* shortName */, unsigned int /* sortKey */)

View file

@ -0,0 +1 @@
@page card_database_schema_and_parsing Card Database Schema and Parsing

View file

@ -0,0 +1,83 @@
@page displaying_cards Displaying Cards
Cockatrice offers a number of widgets for displaying cards. To pick the right one for your purpose, it is first
important
to determine the context in which you will be displaying the card.
# In-client
In the client (like in your custom widgets), you can use the existing card display widgets.
## Simple Display
The most general purpose of these is @ref CardInfoPictureWidget, which simply displays a picture of a card.
The textual equivalent to this is @ref CardInfoTextWidget, which displays all the properties of a card in text form.
## Detailed Display
The convenience class @ref CardInfoDisplayWidget displays both of these widgets in a tabbed container, which allows
choosing between either visual, textual, or both representations at the same time. It is the class that is most
familiar to our users when a single card is highlighted or inspected, since it is used in the deck editor and in-game
for these purposes.
If you would like the user to be able to inspect a single card in your custom widget, you should
expose a @ref CardInfoDisplayWidget to them and incorporate it as a dock widget inside your custom widget.
@ref CardInfoDisplayWidget is great for ensuring that the user has all the relevant details of a card available.
It is crucial anywhere the user might encounter a card for the first time or needs to make an informed decision.
## Visual Display
However, there is a reason why it is possible to have a visual-only representation of the card in the
@ref CardInfoDisplayWidget.
Cards are, by design, meant to be represented as images and cards which we can reasonably
expect the user to know (for example, cards contained in a user made deck or explicitly chosen by the user) can be
represented with just their image as a sort of "visual shorthand".
The simplest way to do this is of course, the above-mentioned @ref CardInfoPictureWidget which simply displays the
picture without modifications.
However, there exist a couple of other convenience classes which subclass @ref CardInfoPictureWidget to accomplish some
things which you might find useful as well.
- @ref CardInfoPictureWithTextOverlayWidget displays text overlaid on top of the card picture and scales this text to
fit automatically.
- @ref DeckPreviewCardPictureWidget is a @ref CardInfoPictureWithTextOverlayWidget which also features a
raise-on-mouse-enter animation, controlled by the global setting (TODO: specify which).
- @ref CardInfoPictureArtCropWidget attempts to display an 'art crop' of the card by cropping the upper region of the
card and only displaying that.
## Groups of Cards
Sometimes it is useful to display images of cards in groups, for example to represent the deck in the @ref
VisualDeckEditorWidget or during confirmation in the pre-game lobby.
To this end, Cockatrice offers three convenience classes to display cards for an index in a @ref DeckListModel in a
group and one to display all cards in a particular zone of a @ref DeckListModel.
The generic way to do this is @ref CardGroupDisplayWidget, which displays cards in @ref QVBoxLayout. This class is very
generic and it is probably a better choice to either subclass it or use one of the two existing implementations.
@ref FlatCardGroupDisplayWidget displays cards using a horizontal @ref FlowWidget. This means it will fill available
screen space, left to right with no margins, with card pictures, skipping to a new line if it overflows available screen
space horizontally.
@ref OverlappedCardGroupDisplayWidget displays cards using an @ref OverlapWidget, which displays widgets on top of each
other in vertical columns until it exceeds available screen space in which case it will start a new column. The amount
of overlap as well as the overlap direction is configurable.
@ref DeckCardZoneDisplayWidget can be used to visualize all cards in a zone with either a @ref
FlatCardGroupDisplayWidget or a @ref OverlappedCardGroupDisplayWidget and headed by a @ref BannerWidget. It also allows
grouping and sorting.
# In-game
The in-game view is a @ref QGraphicsScene, which means any widget we want to display inside of it should be a @ref
QGraphicsObject.
- @ref CardItem

View file

@ -1,3 +1,9 @@
@page developer_reference Developer Reference
- [Contributing](@subpage contributing)
- @subpage primer_cards
- @subpage card_database_schema_and_parsing
- @subpage querying_the_card_database
- @subpage loading_card_pictures
- @subpage displaying_cards

View file

@ -0,0 +1,52 @@
@page loading_card_pictures Loading Card Pictures
Pictures associated with CardInfo%s are retrieved either from on-disk or the network through the CardPictureLoader.
In most cases, you don't need to concern yourself with the internals of CardPictureLoader.
Simply using one of the ways described in @ref displaying_cards is enough to automatically queue a request to the
CardPictureLoader when the chosen widget is shown, emitting signals to refresh the widget when the request is finished.
# How requests are triggered
CardPictureLoader::getPixmap() is called exactly two times in the code base, in CardInfoPictureWidget::loadPixmap(), the
base class
for all widget based card picture display, and AbstractCardItem::paintPicture(), the base class for all QGraphicsItem
based card picture
display. See @ref displaying_cards for more information on the difference between these two display methods.
Because both of these calls are made in the paintEvent() methods of their respective classes, this means that requests
are issued as soon as but not before the widget is shown on screen.
It is also possible to "warm up" the cache by issuing card picture load requests to the CardPictureLoader without using
a display widget and waiting for it to be shown by calling CardPictureLoader::cacheCardPixmaps() with a list of
ExactCard%s.
# The QPixmapCache and QNetworkDiskCache
Cockatrice uses the QPixmapCache from the Qt GUI module to store card pictures in-memory and the QNetworkDiskCache from
the Qt Network module to cache network requests for card pictures on-disk.
What this means is that the CardPictureLoader will first attempt to look up a card in the QPixmapCache according to the
ExactCard::getPixmapCacheKey() method of an ExactCard object. If it does not find it in the in-memory cache, it will
issue a load request, which will first look for local images on-disk and then consult the QNetworkDiskCache and if
found, use the stored binary data from the network cache to populate the in-memory pixmap cache under the card's cache
key. If it is not found, it will then proceed with issuing a network request.
The size of both of these caches can be configured by the user in the "Card Sources" settings page.
# PixmapCacheKeys and ProviderIDs
TODO
# The Redirect Cache
TODO
# Local Image Loading
TODO
# URL Generation and Resolution
TODO

View file

@ -0,0 +1,71 @@
@page primer_cards A Primer on Cards
# The Cockatrice Card Library
All non-gui code related to cards used by Cockatrice is contained within libcockatrice_card.
# A Basic Card Object: CardInfo
A CardInfo object is the translational unit used by the CardDatabase to represent an on-disk XML entry as an in-memory
Qt object.
For a complete overview of the fields that define a CardInfo object, see the class documentation and more specifically
@ref PrivateCardProperties.
These fields correspond to either entries or a combination of entries in the XML card entry to which the
CardDatabaseParser applies special logic to map, populate or determine their respective values in the CardInfo object.
Any property to which special parsing is not applied is stored in CardInfo::properties and may be retrieved or set with
CardInfo::getProperty() or CardInfo::setProperty() as well as CardInfo::getProperties() to get a collection of the
property keys a CardInfo contains.
For more information on how special fields contained within @ref PrivateCardProperties are parsed, see
CockatriceXml4Parser::loadCardsFromXml().
Apart from access to the basic and extended properties of a card, CardInfo also provides two important mechanisms to us.
The first is the CardInfo::pixmapUpdated() signal, which allows the CardPictureLoader to signal when it is done
manipulating (in most cases: loading) the QPixmap of an ExactCard::getPixmapCacheKey(), where the ExactCard::card
corresponds to this CardInfo object.
Put simply: Whenever the CardPictureLoader loads a new PrintingInfo which is not already in the cache, it emits this
signal so that any widget using a CardInfo object can update the display of it to possibly use this new QPixmap.
\attention It should be noted that it is not possible to use CardPictureLoader::getPixmap() with anything but an
ExactCard, which is a CardInfo object associated with a PrintingInfo, i.e. a definitive printing of a specific card
The other signal, CardInfo::cardInfoChanged() conceptually works much the same way, except it is concerned with the
properties of the CardInfo object.
# Getting specific: PrintingInfo and ExactCard
## Printing Info
A CardInfo object describes the basic properties of a card in much the same way that we say "Two cards with the same
name are and should be equal if their properties are equal". However, there are certain (mostly visual) properties which
might differ between printings of a card but still conceptually classify two instances of a card as "the same card".
The most obvious example to this are cards which fundamentally share all properties except the artwork depicted on the
card.
We refer to such differences as Printings and track them in the PrintingInfo class. A PrintingInfo is always related to
the CardSet that introduced the variation. Multiple variations can exist in the same CardSet and the respective
PrintingInfo::getProperty() can be used to determine the differences, such as the collector numbers on the printings, if
there are any.
## Exact Card
A CardInfo object is used to hold the functional properties of a card and a PrintingInfo object can be used to hold the
visual properties of a card. To represent a 'physical' card, as in, a concrete immutable instance of a card, we can use
ExactCard, which combines a CardInfo and a PrintingInfo object into one class. The class is used as a container around
CardInfo objects whenever the user (and thus the program) expects to be presented with a defined card printing.
# Using Cards
For more information on the XML database schema which the CardDatabaseParser parses to generate CardInfo objects, see
@ref card_database_schema_and_parsing.
For more information on querying the CardDatabase for CardInfo objects, see @ref querying_the_card_database.
For more information on displaying CardInfo and ExactCard objects using Qt Widgets, see @ref displaying_cards.
For more information on how card pictures are loaded from disk or the network, see @ref loading_card_pictures as well as
the CardPictureLoader documentation.

View file

@ -0,0 +1,39 @@
@page querying_the_card_database Querying the Card Database
# The CardDatabaseQuerier Class
The CardDatabaseQuerier is the only class used for querying the database. The CardDatabase is an in-memory map and thus
provides no structured query language. CardDatabaseQuerier offers methods to retrieve cards by name, by providerID or in
bulk, as CardSet%s.
## Obtaining a handle to the CardDatabaseQuerier for usage
To obtain the CardDatabaseQuerier related to the global CardDatabase singleton, use CardDatabaseManager::query().
## Querying for known cards
There are, essentially, two ways to ensure card equality, with the second being optional but necessitating the first.
These two ways are CardInfo name equality and PrintingInfo provider ID equality.
Because of this, most queries require, at the very least, a card name to match against and optionally a providerID to
narrow the results.
### Generic Card Infos
To check if a card with the exact provided name exists as a CardInfo in the CardDatabase use,
CardDatabaseQuerier::getCardInfo() or CardDatabaseQuerier::getCardInfos() for multiple cards.
### Guessing Cards
If the exact name might not be present in the CardDatabase, you can use CardDatabaseQuerier::getCardBySimpleName(),
which automatically simplifies the card name and matches it against simplified card names in the CardDatabase.
Alternatively, you can use CardDatabaseQuerier::lookupCardByName(), which first attempts an exact match search and then
uses CardDatabaseQuerier::getCardBySimpleName() as a fallback.
### ExactCard%s
To obtain an ExactCard from the CardDatabaseQuerier, you must use a CardRef as a parameter to
CardDatabaseQuerier::getCard(), CardDatabaseQuerier::getCards(), or CardDatabaseQuerier::guessCard().
CardRef is a simple struct consisting of a card name and a card provider ID as QString%s.

View file

@ -0,0 +1,17 @@
@page creating_decks Creating Decks
Creating a new deck is done using either the TabDeckEditor or TabDeckEditorVisual.
They can be accessed either by clicking the "Create Deck" button in the TabHome screen, which will open the default deck
editor configured in the "User Interface" -> "Deck editor/storage settings" -> "Default deck editor type" setting, or
by selecting "Deck Editor" or "Visual Deck Editor" under the "Tabs" application menu located at the top.
# Further References
See @ref editing_decks for information on how to modify the attributes and contents of a deck in the Deck Editor
widgets.
See @ref exporting_decks for information on how to store and persist your deck either in-client or to external
services.
See @ref importing_decks for information on how to import existing decks either in-client or from external services

View file

@ -0,0 +1,5 @@
@page editing_decks Editing Decks
@subpage editing_decks_classic
@subpage editing_decks_visual

View file

@ -0,0 +1,54 @@
@page editing_decks_classic Classic Deck Editor
\image html classic_deck_editor.png width=900px
# Editing Basic Deck Information
Editing basic deck information is done through the deck dock widget (DeckEditorDeckDockWidget).
\image html deckeditordeckdockwidget.png
This widget allows editing:
- The name
- The comments
- The banner card, which is used to represent the deck in the visual deck storage
- The tags, which are used for filtering in the visual deck storage
# Adding Cards
Adding cards is done using the list of cards in the database presented in the list view on the left.
\image html classic_database_display.png
Cards can be added by either double-clicking an entry, pressing return with an entry selected to add it to the mainboard
or pressing ctrl/cmd + return to add it to the sideboard.
There are also buttons for these two functions on the right of the search bar above the list view.
\image html classic_database_display_add_buttons.png
# Modifying the Deck List
To modify or remove cards in the deck list, the tree list view in the deck dock widget can be used.
\image html deck_dock_deck_list.png
Just above the list, at the top right, there are four buttons to manipulate the currently selected card(s):
- Increment Card
- Decrement Card
- Remove Card
- Switch Card between Mainboard and Sideboard
\image html deck_dock_deck_list_buttons.png
Additionally, there is a combo box above the list, which may be used to change how cards are grouped in the list
display. This is only for visual display and does not affect how the list is saved.
\image html deck_dock_deck_list_group_by.png
# Modifying printings
For more information on modifying the printings in a deck see @ref editing_decks_printings

View file

@ -0,0 +1 @@
@page editing_decks_printings Printing Selector

View file

@ -0,0 +1,71 @@
@page editing_decks_visual Visual Deck Editor
# Editing Basic Deck Information
Editing basic deck information is done through the deck dock widget (DeckEditorDeckDockWidget).
\image html deckeditordeckdockwidget.png
This widget allows editing:
- The name
- The comments
- The banner card, which is used to represent the deck in the visual deck storage
- The tags, which are used for filtering in the visual deck storage
# Adding Cards
Adding cards is done by either using the "Quick search and add card" search bar at the top of the "Visual Deck View" tab
or by clicking on a picture of a card in the "Visual Database Display" tab.
See @ref visual_database_display for more information on how to utilize the visual database display.
# Modifying the Deck List
To modify or remove cards in the deck list, the tree list view in the deck dock widget can be used.
Just above the list, at the top right, there are four buttons to manipulate the currently selected card(s):
- Increment Card
- Decrement Card
- Remove Card
- Switch Card between Mainboard and Sideboard
Additionally, there is a combo box above the list, which may be used to change how cards are grouped in the list
display. This is only for visual display and does not affect how the list is saved.
# Modifying the visual deck layout
The visual deck editor displays cards visually, as opposed to simply in list form in the Deck Dock Widget. Each entry in
the deck list is represented by a picture. These entries are grouped together under their respective sub-groups.
Sub-groups may be collapsed (i.e. the pictures contained within them are hidden) by clicking on the name of the group in
the banner.
Cards may either be displayed in a "Flat" layout, which displays each picture next to each other and ensures full
visibility for each card, or in an "Overlap" layout, which overlaps cards on top of each other (leaving the top 20% of
the card uncovered so names remain readable) and arranges them in stacks to save space and allow for an easy overview.
Additionally, it is possible to change how the cards in the deck list are grouped by selecting a different grouping
method from the combo box, either in the top left of the "Visual Deck View" tab or above the list view in the deck dock
widget.
Furthermore, it is possible to change how the cards are sorted within the sub-group. This is done by clicking on the
button with the cogwheel icon next to the combo box that adjusts grouping in the top left of the "Visual Deck View" tab.
This presents a list of available sort criteria, which may be rearranged to change their priorities.
# Modifying printings
For more information on modifying the printings in a deck see @ref editing_decks_printings
# Deck Analytics
The visual deck editor offers a "Deck Analytics" tab, which displays information about:
- The mana curve
- The mana devotion
- The mana base
# Sample Hand
The visual deck editor offers a "Sample Hand" tab, which allows simulating drawing a configurable amount of cards from
the deck, which reduces the need to launch a single player game for testing purposes.

View file

@ -0,0 +1,95 @@
@page exporting_decks Exporting Decks
# Where to export?
There are two screens in the client which can be used to import decks, depending on the context.
- The deck editor tab
- The deck storage tab (not to be confused with the visual deck storage tab)
# The Deck Editor Tab
The deck editor tabs (Classic and Visual) offer three ways of export a deck:
- To a file on your local storage
- To your clipboard
- To an online service
## Local File Storage
To save a deck to a file on your local storage, select the "Save Deck" action in the "Deck Editor" or "Visual Deck
Editor" menu in the application menu bar at the top of the screen. Alternatively, you can use the shortcut Ctrl/Cmd + S
to access this action.
Selecting this action will open a file picker dialog provided by your operating system. Simply enter a file name and
select a format (.cod is recommended) and confirm.
Just below the "Save Deck" action described above is the "Save Deck as..." option, which allows saving an existing file
under a different filename, which is useful for saving a different version or copy of a deck.
## From Clipboard
To save a deck to your clipboard, select the "Save deck to clipboard..." action in the "Deck Editor" or "Visual Deck
Editor" menu in the application menu bar at the top of the screen. Alternatively, you can use the shortcut Ctrl/Cmd +
Shift + C or Ctrl/Cmd + Shift + R to access this action.
Selecting this action will save the currently open deck list to your clipboard.
Saving the decklist without annotations will export the decklist, with each card being described in the following format
```
CARD_AMOUNT CARD_NAME (SET_SHORT_NAME) CARD_COLLECTOR_NUMBER
```
There is also the (no set info) option, which will simply export each card as
```
CARD_AMOUNT CARD_NAME
```
Mainboard and sideboard are delimited by a newline like so:
```
1 MainboardCard
1 OtherMainboardCard
1 SideboardCard
```
Saving the decklist as annotated will insert comments (marked with // in front of them).
It will first insert the name and any comments associated with the deck before separating each deck section into its own
newline delimited and annotated group.
Example: TODO: Adjust this to be non mtg based.
```
// Deck Name
// Deck Comment
// 10 Maindeck
// 6 Artifact
2 The Darkness Crystal (FIN) 335
2 The Fire Crystal (FIN) 337
2 Black Mage's Rod (FIN) 90
// 6 Sorcery
2 Nibelheim Aflame (FIN) 339
4 Cornered by Black Mages (FIN) 93
// 6 Sideboard
// 6 Creature
SB: 4 Blazing Bomb (FIN) 130
SB: 1 Garland, Knight of Cornelia (FIN) 221
SB: 1 Undercity Dire Rat (FIN) 123
```
## From an online service
To export a deck to an online service, select the "Send deck to online service..." action in the "Deck Editor" or "
Visual Deck Editor" menu in the application menu bar at the top of the screen.
Selecting this action will open your browser with the selected service open and the deck list information from the
client supplied to it.
Currently supported services are DeckList and TappedOut.

View file

@ -0,0 +1,62 @@
@page importing_decks Importing Decks
# Where to import?
There are three screens in the client which can be used to import decks, depending on the context.
- The deck editor tab
- The pre-game lobby tab
- The deck storage tab (not to be confused with the visual deck storage tab)
# The Deck Editor Tab
The deck editor tabs (Classic and Visual) offer three ways of importing a deck:
- From a file on your local storage
- From your clipboard
- From an online service
## Local File Storage
To load a deck from a file on your local storage, select the "Load Deck" action in the "Deck Editor" or "Visual Deck
Editor" menu in the application menu bar at the top of the screen. Alternatively, you can use the shortcut Ctrl/Cmd + O
to access this action.
Selecting this action will open a file picker dialog provided by your operating system. Simply select a supported file
here and it will be loaded.
Just below the "Load Deck" action described above is the "Load recent deck" option, which keeps a record of the last 10
loaded decks for quick access.
## From Clipboard
To load a deck from your clipboard, select the "Load deck from clipboard..." action in the "Deck Editor" or "Visual Deck
Editor" menu in the application menu bar at the top of the screen. Alternatively, you can use the shortcut Ctrl/Cmd +
Shift + V to access this action.
Selecting this action will open a new text editor dialog with the contents of your clipboard pasted inside it.
The import dialog expects each line to be a card with the following format:
TODO
Each card should be on a separate line and there should be no empty lines between cards. The first empty line between
two blocks of cards will be considered as the divider between mainboard and sideboard.
Selecting "Parse Set Name and Number (if available)" will automatically parse these options and attempt to resolve them
to valid provider IDs found in the card database. If this option is unselected, Cockatrice will import all cards as
versions without provider IDs, which means they will display to everyone according to their own user defined set
preferences, rather than being the same defined printing for everyone.
## From an online service
To load a deck from an online service, select the "Load deck from online service..." action in the "Deck Editor" or "
Visual Deck Editor" menu in the application menu bar at the top of the screen.
Selecting this action will open a dialog containing the contents of your clipboard pasted into it. If your clipboard
currently contains a supported URL, the dialog will accept it and close on its own, otherwise you may adjust the URL and
confirm.
The action will automatically import the deck from the online service without any other required user action.
Currently supported services are Archidekt, Deckstats, Moxfield, and TappedOut.

View file

@ -1,4 +1,13 @@
@page user_reference User Reference
@subpage search_syntax_help
@subpage deck_search_syntax_help
@subpage deck_search_syntax_help
@subpage creating_decks
@subpage importing_decks
@subpage editing_decks
@subpage exporting_decks

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 458 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 885 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View file

@ -1,9 +1,3 @@
/**
* @file card_info.h
* @ingroup Cards
* @brief TODO: Document this.
*/
#ifndef CARD_INFO_H
#define CARD_INFO_H
@ -54,6 +48,10 @@ class CardInfo : public QObject
Q_OBJECT
private:
/** @name Private Card Properties
* @anchor PrivateCardProperties
*/
///@{
CardInfoPtr smartThis; ///< Smart pointer to self for safe cross-references.
QString name; ///< Full name of the card.
QString simpleName; ///< Simplified name for fuzzy matching.
@ -69,6 +67,7 @@ private:
bool landscapeOrientation; ///< Orientation flag for rendering.
int tableRow; ///< Row index in a table or visual representation.
bool upsideDownArt; ///< Whether artwork is flipped for visual purposes.
///@}
public:
/**
@ -161,7 +160,7 @@ public:
*/
CardInfoPtr clone() const
{
CardInfoPtr newCardInfo = CardInfoPtr(new CardInfo(*this));
auto newCardInfo = CardInfoPtr(new CardInfo(*this));
newCardInfo->setSmartPointer(newCardInfo); // Set the smart pointer for the new instance
return newCardInfo;
}

View file

@ -1,10 +1,3 @@
/**
* @file card_database.h
* @ingroup CardDatabase
* @brief The CardDatabase is responsible for holding the card and set maps, managing card additions/removals,
* and providing access to sets and card querying via CardDatabaseQuerier.
*/
#ifndef CARDDATABASE_H
#define CARDDATABASE_H

View file

@ -1,12 +1,3 @@
/**
* @file card_database_loader.h
* @ingroup CardDatabase
* @brief The CardDatabaseLoader is responsible for discovering, loading, and saving card databases from files on disk.
*
* This class interacts with available parsers to populate a CardDatabase instance. It also handles
* loading main, token, spoiler, and custom card databases.
*/
#ifndef COCKATRICE_CARD_DATABASE_LOADER_H
#define COCKATRICE_CARD_DATABASE_LOADER_H

View file

@ -5,7 +5,7 @@
/**
* @class ExactCard
* @ingroup Cards
* @ingroup CardPrintings
*
* @brief Represents a specific card instance, defined by its CardInfo
* and a particular printing.

View file

@ -14,7 +14,7 @@ using SetToPrintingsMap = QMap<QString, QList<PrintingInfo>>;
/**
* @class PrintingInfo
* @ingroup Cards
* @ingroup CardPrintings
*
* @brief Represents metadata for a specific variation of a card within a set.
*

View file

@ -15,7 +15,7 @@ using CardSetPtr = QSharedPointer<CardSet>;
/**
* @class CardSet
* @ingroup Cards
* @ingroup CardSets
*
* @brief A collection of cards grouped under a common identifier.
*

View file

@ -1,6 +1,6 @@
/**
* @file card_set_comparator.h
* @ingroup Cards
* @ingroup CardSets
* @brief TODO: Document this.
*/

View file

@ -8,7 +8,7 @@
/**
* @class CardSetList
* @ingroup Cards
* @ingroup CardSets
*
* @brief A list-like container for CardSet objects with extended management methods.
*

View file

@ -563,6 +563,29 @@ QList<CardRef> DeckList::getCardRefList() const
return result;
}
QList<DecklistCardNode *> DeckList::getCardNodes(const QStringList &restrictToZones) const
{
QList<DecklistCardNode *> result;
for (auto *node : *root) {
auto *zoneNode = dynamic_cast<InnerDecklistNode *>(node);
if (zoneNode == nullptr) {
continue;
}
if (!restrictToZones.isEmpty() && !restrictToZones.contains(node->getName())) {
continue;
}
for (auto *cardNode : *zoneNode) {
auto *cardCardNode = dynamic_cast<DecklistCardNode *>(cardNode);
if (cardCardNode != nullptr) {
result.append(cardCardNode);
}
}
}
return result;
}
int DeckList::getSideboardSize() const
{
int size = 0;

View file

@ -289,6 +289,7 @@ public:
}
QStringList getCardList() const;
QList<CardRef> getCardRefList() const;
QList<DecklistCardNode *> getCardNodes(const QStringList &restrictToZones = QStringList()) const;
int getSideboardSize() const;
InnerDecklistNode *getRoot() const
{

View file

@ -145,10 +145,10 @@ bool InnerDecklistNode::readElement(QXmlStreamReader *xml)
const QString childName = xml->name().toString();
if (xml->isStartElement()) {
if (childName == "zone") {
InnerDecklistNode *newZone = new InnerDecklistNode(xml->attributes().value("name").toString(), this);
auto *newZone = new InnerDecklistNode(xml->attributes().value("name").toString(), this);
newZone->readElement(xml);
} else if (childName == "card") {
DecklistCardNode *newCard = new DecklistCardNode(
auto *newCard = new DecklistCardNode(
xml->attributes().value("name").toString(), xml->attributes().value("number").toString().toInt(),
this, -1, xml->attributes().value("setShortName").toString(),
xml->attributes().value("collectorNumber").toString(), xml->attributes().value("uuid").toString());

View file

@ -1095,12 +1095,11 @@ Server_AbstractPlayer::cmdCreateToken(const Command_CreateToken &cmd, ResponseCo
arrowInfo->set_start_player_id(player->getPlayerId());
arrowInfo->set_start_zone(startCard->getZone()->getName().toStdString());
arrowInfo->set_start_card_id(startCard->getId());
const Server_AbstractPlayer *arrowTargetPlayer =
qobject_cast<const Server_AbstractPlayer *>(targetItem);
const auto *arrowTargetPlayer = qobject_cast<const Server_AbstractPlayer *>(targetItem);
if (arrowTargetPlayer != nullptr) {
arrowInfo->set_target_player_id(arrowTargetPlayer->getPlayerId());
} else {
const Server_Card *arrowTargetCard = qobject_cast<const Server_Card *>(targetItem);
const auto *arrowTargetCard = qobject_cast<const Server_Card *>(targetItem);
arrowInfo->set_target_player_id(arrowTargetCard->getZone()->getPlayer()->getPlayerId());
arrowInfo->set_target_zone(arrowTargetCard->getZone()->getName().toStdString());
arrowInfo->set_target_card_id(arrowTargetCard->getId());

View file

@ -19,7 +19,7 @@ void Server_Arrow::getInfo(ServerInfo_Arrow *info)
info->set_start_card_id(startCard->getId());
info->mutable_arrow_color()->CopyFrom(arrowColor);
Server_Card *targetCard = qobject_cast<Server_Card *>(targetItem);
auto *targetCard = qobject_cast<Server_Card *>(targetItem);
if (targetCard) {
info->set_target_player_id(targetCard->getZone()->getPlayer()->getPlayerId());
info->set_target_zone(targetCard->getZone()->getName().toStdString());

View file

@ -589,7 +589,7 @@ void Server_Game::removeArrowsRelatedToPlayer(GameEventStorage &ges, Server_Abst
for (Server_AbstractPlayer *anyPlayer : getPlayers().values()) {
QList<Server_Arrow *> toDelete;
for (auto *arrow : anyPlayer->getArrows().values()) {
Server_Card *targetCard = qobject_cast<Server_Card *>(arrow->getTargetItem());
auto *targetCard = qobject_cast<Server_Card *>(arrow->getTargetItem());
if (targetCard) {
if (targetCard->getZone() != nullptr && targetCard->getZone()->getPlayer() == player)
toDelete.append(arrow);
@ -781,7 +781,7 @@ void Server_Game::sendGameEventContainer(GameEventContainer *cont,
GameEventContainer *
Server_Game::prepareGameEvent(const ::google::protobuf::Message &gameEvent, int playerId, GameEventContext *context)
{
GameEventContainer *cont = new GameEventContainer;
auto *cont = new GameEventContainer;
cont->set_game_id(gameId);
if (context)
cont->mutable_context()->CopyFrom(*context);

View file

@ -35,7 +35,7 @@ void Server_AbstractUserInterface::sendProtocolItemByType(ServerMessage::Message
SessionEvent *Server_AbstractUserInterface::prepareSessionEvent(const ::google::protobuf::Message &sessionEvent)
{
SessionEvent *event = new SessionEvent;
auto *event = new SessionEvent;
event->GetReflection()
->MutableMessage(event, sessionEvent.GetDescriptor()->FindExtensionByName("ext"))
->CopyFrom(sessionEvent);

View file

@ -473,7 +473,7 @@ Response::ResponseCode Server_ProtocolHandler::cmdLogin(const Command_Login &cmd
if (!missingClientFeatures.isEmpty()) {
if (features.isRequiredFeaturesMissing(missingClientFeatures, server->getServerRequiredFeatureList())) {
Response_Login *re = new Response_Login;
auto *re = new Response_Login;
re->set_denied_reason_str("Client upgrade required");
QMap<QString, bool>::iterator i;
for (i = missingClientFeatures.begin(); i != missingClientFeatures.end(); ++i) {
@ -491,7 +491,7 @@ Response::ResponseCode Server_ProtocolHandler::cmdLogin(const Command_Login &cmd
clientId, clientVersion, connectionType);
switch (res) {
case UserIsBanned: {
Response_Login *re = new Response_Login;
auto *re = new Response_Login;
re->set_denied_reason_str(reasonStr.toStdString());
if (banSecondsLeft != 0)
re->set_denied_end_time(QDateTime::currentDateTime().addSecs(banSecondsLeft).toSecsSinceEpoch());
@ -503,7 +503,7 @@ Response::ResponseCode Server_ProtocolHandler::cmdLogin(const Command_Login &cmd
case WouldOverwriteOldSession:
return Response::RespWouldOverwriteOldSession;
case UsernameInvalid: {
Response_Login *re = new Response_Login;
auto *re = new Response_Login;
re->set_denied_reason_str(reasonStr.toStdString());
rc.setResponseExtension(re);
return Response::RespUsernameInvalid;
@ -534,7 +534,7 @@ Response::ResponseCode Server_ProtocolHandler::cmdLogin(const Command_Login &cmd
event.set_message(server->getLoginMessage().toStdString());
rc.enqueuePostResponseItem(ServerMessage::SESSION_EVENT, prepareSessionEvent(event));
Response_Login *re = new Response_Login;
auto *re = new Response_Login;
re->mutable_user_info()->CopyFrom(copyUserInfo(true));
if (authState == PasswordRight) {
@ -616,7 +616,7 @@ Response::ResponseCode Server_ProtocolHandler::cmdGetGamesOfUser(const Command_G
// We don't need to check whether the user is logged in; persistent games should also work.
// The client needs to deal with an empty result list.
Response_GetGamesOfUser *re = new Response_GetGamesOfUser;
auto *re = new Response_GetGamesOfUser;
server->roomsLock.lockForRead();
QMapIterator<int, Server_Room *> roomIterator(server->getRooms());
while (roomIterator.hasNext()) {
@ -640,7 +640,7 @@ Response::ResponseCode Server_ProtocolHandler::cmdGetUserInfo(const Command_GetU
return Response::RespLoginNeeded;
QString userName = nameFromStdString(cmd.user_name());
Response_GetUserInfo *re = new Response_GetUserInfo;
auto *re = new Response_GetUserInfo;
if (userName.isEmpty())
re->mutable_user_info()->CopyFrom(*userInfo);
else {
@ -713,7 +713,7 @@ Response::ResponseCode Server_ProtocolHandler::cmdJoinRoom(const Command_JoinRoo
joinMessageEvent.set_message_type(Event_RoomSay::Welcome);
rc.enqueuePostResponseItem(ServerMessage::ROOM_EVENT, room->prepareRoomEvent(joinMessageEvent));
Response_JoinRoom *re = new Response_JoinRoom;
auto *re = new Response_JoinRoom;
room->getInfo(*re->mutable_room_info(), true);
rc.setResponseExtension(re);
@ -725,7 +725,7 @@ Response::ResponseCode Server_ProtocolHandler::cmdListUsers(const Command_ListUs
if (authState == NotLoggedIn)
return Response::RespLoginNeeded;
Response_ListUsers *re = new Response_ListUsers;
auto *re = new Response_ListUsers;
server->clientsLock.lockForRead();
QMapIterator<QString, Server_ProtocolHandler *> userIterator = server->getUsers();
while (userIterator.hasNext())
@ -838,10 +838,10 @@ Server_ProtocolHandler::cmdCreateGame(const Command_CreateGame &cmd, Server_Room
// When server doesn't permit registered users to exist, do not honor only-reg setting
bool onlyRegisteredUsers = cmd.only_registered() && (server->permitUnregisteredUsers());
Server_Game *game = new Server_Game(
copyUserInfo(false), gameId, description, QString::fromStdString(cmd.password()), cmd.max_players(), gameTypes,
cmd.only_buddies(), onlyRegisteredUsers, cmd.spectators_allowed(), cmd.spectators_need_password(),
cmd.spectators_can_talk(), cmd.spectators_see_everything(), startingLifeTotal, shareDecklistsOnLoad, room);
auto *game = new Server_Game(copyUserInfo(false), gameId, description, QString::fromStdString(cmd.password()),
cmd.max_players(), gameTypes, cmd.only_buddies(), onlyRegisteredUsers,
cmd.spectators_allowed(), cmd.spectators_need_password(), cmd.spectators_can_talk(),
cmd.spectators_see_everything(), startingLifeTotal, shareDecklistsOnLoad, room);
game->addPlayer(this, rc, asSpectator, asJudge, false);
room->addGame(game);

View file

@ -128,7 +128,7 @@ Server_Room::getInfo(ServerInfo_Room &result, bool complete, bool showGameTypes,
RoomEvent *Server_Room::prepareRoomEvent(const ::google::protobuf::Message &roomEvent)
{
RoomEvent *event = new RoomEvent;
auto *event = new RoomEvent;
event->set_room_id(id);
event->GetReflection()
->MutableMessage(event, roomEvent.GetDescriptor()->FindExtensionByName("ext"))

View file

@ -1,7 +1,7 @@
#include "card_database_settings.h"
CardDatabaseSettings::CardDatabaseSettings(const QString &settingPath, QObject *parent)
: SettingsManager(settingPath + "cardDatabase.ini", parent)
: SettingsManager(settingPath + "cardDatabase.ini", QString(), QString(), parent)
{
}

View file

@ -1,21 +1,21 @@
#include "card_override_settings.h"
CardOverrideSettings::CardOverrideSettings(const QString &settingPath, QObject *parent)
: SettingsManager(settingPath + "cardPreferenceOverrides.ini", parent)
: SettingsManager(settingPath + "cardPreferenceOverrides.ini", "cards", QString(), parent)
{
}
void CardOverrideSettings::setCardPreferenceOverride(const CardRef &cardRef)
{
setValue(cardRef.providerId, cardRef.name, "cards");
setValue(cardRef.providerId, cardRef.name);
}
void CardOverrideSettings::deleteCardPreferenceOverride(const QString &cardName)
{
deleteValue(cardName, "cards");
deleteValue(cardName);
}
QString CardOverrideSettings::getCardPreferenceOverride(const QString &cardName)
{
return getValue(cardName, "cards").toString();
return getValue(cardName).toString();
}

View file

@ -3,7 +3,7 @@
#include <QtCore/QFile>
DebugSettings::DebugSettings(const QString &settingPath, QObject *parent)
: SettingsManager(settingPath + "debug.ini", parent)
: SettingsManager(settingPath + "debug.ini", "debug", QString(), parent)
{
// Create the default debug.ini if it doesn't exist yet
if (!QFile(settingPath + "debug.ini").exists()) {
@ -13,7 +13,7 @@ DebugSettings::DebugSettings(const QString &settingPath, QObject *parent)
bool DebugSettings::getShowCardId()
{
return getValue("showCardId", "debug").toBool();
return getValue("showCardId").toBool();
}
bool DebugSettings::getLocalGameOnStartup()

View file

@ -9,21 +9,21 @@ const QStringList DownloadSettings::DEFAULT_DOWNLOAD_URLS = {
"https://gatherer.wizards.com/Handlers/Image.ashx?name=!name!&type=card"};
DownloadSettings::DownloadSettings(const QString &settingPath, QObject *parent = nullptr)
: SettingsManager(settingPath + "downloads.ini", parent)
: SettingsManager(settingPath + "downloads.ini", "downloads", QString(), parent)
{
}
void DownloadSettings::setDownloadUrls(const QStringList &downloadURLs)
{
setValue(QVariant::fromValue(downloadURLs), "urls", "downloads");
setValue(QVariant::fromValue(downloadURLs), "urls");
}
QStringList DownloadSettings::getAllURLs()
{
return getValue("urls", "downloads").toStringList();
return getValue("urls").toStringList();
}
void DownloadSettings::resetToDefaultURLs()
{
setValue(QVariant::fromValue(DEFAULT_DOWNLOAD_URLS), "urls", "downloads");
setValue(QVariant::fromValue(DEFAULT_DOWNLOAD_URLS), "urls");
}

View file

@ -4,7 +4,7 @@
#include <QTime>
GameFiltersSettings::GameFiltersSettings(const QString &settingPath, QObject *parent)
: SettingsManager(settingPath + "gamefilters.ini", parent)
: SettingsManager(settingPath + "gamefilters.ini", "filter_games", QString(), parent)
{
}
@ -19,190 +19,190 @@ QString GameFiltersSettings::hashGameType(const QString &gameType) const
void GameFiltersSettings::setHideBuddiesOnlyGames(bool hide)
{
setValue(hide, "hide_buddies_only_games", "filter_games");
setValue(hide, "hide_buddies_only_games");
}
bool GameFiltersSettings::isHideBuddiesOnlyGames()
{
QVariant previous = getValue("hide_buddies_only_games", "filter_games");
QVariant previous = getValue("hide_buddies_only_games");
return previous == QVariant() || previous.toBool();
}
void GameFiltersSettings::setHideFullGames(bool hide)
{
setValue(hide, "hide_full_games", "filter_games");
setValue(hide, "hide_full_games");
}
bool GameFiltersSettings::isHideFullGames()
{
QVariant previous = getValue("hide_full_games", "filter_games");
QVariant previous = getValue("hide_full_games");
return !(previous == QVariant()) && previous.toBool();
}
void GameFiltersSettings::setHideGamesThatStarted(bool hide)
{
setValue(hide, "hide_games_that_started", "filter_games");
setValue(hide, "hide_games_that_started");
}
bool GameFiltersSettings::isHideGamesThatStarted()
{
QVariant previous = getValue("hide_games_that_started", "filter_games");
QVariant previous = getValue("hide_games_that_started");
return !(previous == QVariant()) && previous.toBool();
}
void GameFiltersSettings::setHidePasswordProtectedGames(bool hide)
{
setValue(hide, "hide_password_protected_games", "filter_games");
setValue(hide, "hide_password_protected_games");
}
bool GameFiltersSettings::isHidePasswordProtectedGames()
{
QVariant previous = getValue("hide_password_protected_games", "filter_games");
QVariant previous = getValue("hide_password_protected_games");
return previous == QVariant() || previous.toBool();
}
void GameFiltersSettings::setHideIgnoredUserGames(bool hide)
{
setValue(hide, "hide_ignored_user_games", "filter_games");
setValue(hide, "hide_ignored_user_games");
}
bool GameFiltersSettings::isHideIgnoredUserGames()
{
QVariant previous = getValue("hide_ignored_user_games", "filter_games");
QVariant previous = getValue("hide_ignored_user_games");
return !(previous == QVariant()) && previous.toBool();
}
void GameFiltersSettings::setHideNotBuddyCreatedGames(bool hide)
{
setValue(hide, "hide_not_buddy_created_games", "filter_games");
setValue(hide, "hide_not_buddy_created_games");
}
bool GameFiltersSettings::isHideNotBuddyCreatedGames()
{
QVariant previous = getValue("hide_not_buddy_created_games", "filter_games");
QVariant previous = getValue("hide_not_buddy_created_games");
return !(previous == QVariant()) && previous.toBool();
}
void GameFiltersSettings::setHideOpenDecklistGames(bool hide)
{
setValue(hide, "hide_open_decklist_games", "filter_games");
setValue(hide, "hide_open_decklist_games");
}
bool GameFiltersSettings::isHideOpenDecklistGames()
{
QVariant previous = getValue("hide_open_decklist_games", "filter_games");
QVariant previous = getValue("hide_open_decklist_games");
return !(previous == QVariant()) && previous.toBool();
}
void GameFiltersSettings::setGameNameFilter(QString gameName)
{
setValue(gameName, "game_name_filter", "filter_games");
setValue(gameName, "game_name_filter");
}
QString GameFiltersSettings::getGameNameFilter()
{
return getValue("game_name_filter", "filter_games").toString();
return getValue("game_name_filter").toString();
}
void GameFiltersSettings::setCreatorNameFilter(QString creatorName)
{
setValue(creatorName, "creator_name_filter", "filter_games");
setValue(creatorName, "creator_name_filter");
}
QString GameFiltersSettings::getCreatorNameFilter()
{
return getValue("creator_name_filter", "filter_games").toString();
return getValue("creator_name_filter").toString();
}
void GameFiltersSettings::setMinPlayers(int min)
{
setValue(min, "min_players", "filter_games");
setValue(min, "min_players");
}
int GameFiltersSettings::getMinPlayers()
{
QVariant previous = getValue("min_players", "filter_games");
QVariant previous = getValue("min_players");
return previous == QVariant() ? 1 : previous.toInt();
}
void GameFiltersSettings::setMaxPlayers(int max)
{
setValue(max, "max_players", "filter_games");
setValue(max, "max_players");
}
int GameFiltersSettings::getMaxPlayers()
{
QVariant previous = getValue("max_players", "filter_games");
QVariant previous = getValue("max_players");
return previous == QVariant() ? 99 : previous.toInt();
}
void GameFiltersSettings::setMaxGameAge(const QTime &maxGameAge)
{
setValue(maxGameAge, "max_game_age_time", "filter_games");
setValue(maxGameAge, "max_game_age_time");
}
QTime GameFiltersSettings::getMaxGameAge()
{
QVariant previous = getValue("max_game_age_time", "filter_games");
QVariant previous = getValue("max_game_age_time");
return previous.toTime();
}
void GameFiltersSettings::setGameTypeEnabled(QString gametype, bool enabled)
{
setValue(enabled, "game_type/" + hashGameType(gametype), "filter_games");
setValue(enabled, "game_type/" + hashGameType(gametype));
}
void GameFiltersSettings::setGameHashedTypeEnabled(QString gametypeHASHED, bool enabled)
{
setValue(enabled, gametypeHASHED, "filter_games");
setValue(enabled, gametypeHASHED);
}
bool GameFiltersSettings::isGameTypeEnabled(QString gametype)
{
QVariant previous = getValue("game_type/" + hashGameType(gametype), "filter_games");
QVariant previous = getValue("game_type/" + hashGameType(gametype));
return previous == QVariant() ? false : previous.toBool();
}
void GameFiltersSettings::setShowOnlyIfSpectatorsCanWatch(bool show)
{
setValue(show, "show_only_if_spectators_can_watch", "filter_games");
setValue(show, "show_only_if_spectators_can_watch");
}
bool GameFiltersSettings::isShowOnlyIfSpectatorsCanWatch()
{
QVariant previous = getValue("show_only_if_spectators_can_watch", "filter_games");
QVariant previous = getValue("show_only_if_spectators_can_watch");
return previous == QVariant() ? false : previous.toBool();
}
void GameFiltersSettings::setShowSpectatorPasswordProtected(bool show)
{
setValue(show, "show_spectator_password_protected", "filter_games");
setValue(show, "show_spectator_password_protected");
}
bool GameFiltersSettings::isShowSpectatorPasswordProtected()
{
QVariant previous = getValue("show_spectator_password_protected", "filter_games");
QVariant previous = getValue("show_spectator_password_protected");
return previous == QVariant() ? true : previous.toBool();
}
void GameFiltersSettings::setShowOnlyIfSpectatorsCanChat(bool show)
{
setValue(show, "show_only_if_spectators_can_chat", "filter_games");
setValue(show, "show_only_if_spectators_can_chat");
}
bool GameFiltersSettings::isShowOnlyIfSpectatorsCanChat()
{
QVariant previous = getValue("show_only_if_spectators_can_chat", "filter_games");
QVariant previous = getValue("show_only_if_spectators_can_chat");
return previous == QVariant() ? true : previous.toBool();
}
void GameFiltersSettings::setShowOnlyIfSpectatorsCanSeeHands(bool show)
{
setValue(show, "show_only_if_spectators_can_see_hands", "filter_games");
setValue(show, "show_only_if_spectators_can_see_hands");
}
bool GameFiltersSettings::isShowOnlyIfSpectatorsCanSeeHands()
{
QVariant previous = getValue("show_only_if_spectators_can_see_hands", "filter_games");
QVariant previous = getValue("show_only_if_spectators_can_see_hands");
return previous == QVariant() ? true : previous.toBool();
}

View file

@ -1,7 +1,7 @@
#include "layouts_settings.h"
LayoutsSettings::LayoutsSettings(const QString &settingPath, QObject *parent)
: SettingsManager(settingPath + "layouts.ini", parent)
: SettingsManager(settingPath + "layouts.ini", "layouts", QString(), parent)
{
}

View file

@ -1,26 +1,26 @@
#include "message_settings.h"
MessageSettings::MessageSettings(const QString &settingPath, QObject *parent)
: SettingsManager(settingPath + "messages.ini", parent)
: SettingsManager(settingPath + "messages.ini", "messages", QString(), parent)
{
}
QString MessageSettings::getMessageAt(int index)
{
return getValue(QString("msg%1").arg(index), "messages").toString();
return getValue(QString("msg%1").arg(index)).toString();
}
int MessageSettings::getCount()
{
return getValue("count", "messages").toInt();
return getValue("count").toInt();
}
void MessageSettings::setCount(int count)
{
setValue(count, "count", "messages");
setValue(count, "count");
}
void MessageSettings::setMessageAt(int index, QString message)
{
setValue(message, QString("msg%1").arg(index), "messages");
setValue(message, QString("msg%1").arg(index));
}

View file

@ -3,22 +3,22 @@
#define MAX_RECENT_DECK_COUNT 10
RecentsSettings::RecentsSettings(const QString &settingPath, QObject *parent)
: SettingsManager(settingPath + "recents.ini", parent)
: SettingsManager(settingPath + "recents.ini", "deckbuilder", QString(), parent)
{
}
QStringList RecentsSettings::getRecentlyOpenedDeckPaths()
{
return getValue("deckpaths", "deckbuilder").toStringList();
return getValue("deckpaths").toStringList();
}
void RecentsSettings::clearRecentlyOpenedDeckPaths()
{
deleteValue("deckpaths", "deckbuilder");
deleteValue("deckpaths");
emit recentlyOpenedDeckPathsChanged();
}
void RecentsSettings::updateRecentlyOpenedDeckPaths(const QString &deckPath)
{
auto deckPaths = getValue("deckpaths", "deckbuilder").toStringList();
auto deckPaths = getValue("deckpaths").toStringList();
deckPaths.removeAll(deckPath);
deckPaths.prepend(deckPath);
@ -27,7 +27,7 @@ void RecentsSettings::updateRecentlyOpenedDeckPaths(const QString &deckPath)
deckPaths.removeLast();
}
setValue(deckPaths, "deckpaths", "deckbuilder");
setValue(deckPaths, "deckpaths");
emit recentlyOpenedDeckPathsChanged();
}

View file

@ -4,34 +4,34 @@
#include <utility>
ServersSettings::ServersSettings(const QString &settingPath, QObject *parent)
: SettingsManager(settingPath + "servers.ini", parent)
: SettingsManager(settingPath + "servers.ini", "server", QString(), parent)
{
}
void ServersSettings::setPreviousHostLogin(int previous)
{
setValue(previous, "previoushostlogin", "server");
setValue(previous, "previoushostlogin");
}
int ServersSettings::getPreviousHostLogin()
{
QVariant previous = getValue("previoushostlogin", "server");
QVariant previous = getValue("previoushostlogin");
return previous == QVariant() ? 1 : previous.toInt();
}
void ServersSettings::setPreviousHostList(QStringList list)
{
setValue(list, "previoushosts", "server");
setValue(list, "previoushosts");
}
QStringList ServersSettings::getPreviousHostList()
{
return getValue("previoushosts", "server").toStringList();
return getValue("previoushosts").toStringList();
}
void ServersSettings::setPrevioushostName(const QString &name)
{
setValue(name, "previoushostName", "server");
setValue(name, "previoushostName");
}
QString ServersSettings::getSaveName(QString defaultname)
@ -50,7 +50,7 @@ QString ServersSettings::getSite(QString defaultSite)
QString ServersSettings::getPrevioushostName()
{
QVariant value = getValue("previoushostName", "server");
QVariant value = getValue("previoushostName");
return value == QVariant() ? "Rooster Ranges" : value.toString();
}
@ -107,56 +107,56 @@ bool ServersSettings::getSavePassword()
void ServersSettings::setAutoConnect(int autoconnect)
{
setValue(autoconnect, "auto_connect", "server");
setValue(autoconnect, "auto_connect");
}
int ServersSettings::getAutoConnect()
{
QVariant autoconnect = getValue("auto_connect", "server");
QVariant autoconnect = getValue("auto_connect");
return autoconnect == QVariant() ? 0 : autoconnect.toInt();
}
void ServersSettings::setFPHostName(QString hostname)
{
setValue(hostname, "fphostname", "server");
setValue(hostname, "fphostname");
}
QString ServersSettings::getFPHostname(QString defaultHost)
{
QVariant hostname = getValue("fphostname", "server");
QVariant hostname = getValue("fphostname");
return hostname == QVariant() ? std::move(defaultHost) : hostname.toString();
}
void ServersSettings::setFPPort(QString port)
{
setValue(port, "fpport", "server");
setValue(port, "fpport");
}
QString ServersSettings::getFPPort(QString defaultPort)
{
QVariant port = getValue("fpport", "server");
QVariant port = getValue("fpport");
return port == QVariant() ? std::move(defaultPort) : port.toString();
}
void ServersSettings::setFPPlayerName(QString playerName)
{
setValue(playerName, "fpplayername", "server");
setValue(playerName, "fpplayername");
}
QString ServersSettings::getFPPlayerName(QString defaultName)
{
QVariant name = getValue("fpplayername", "server");
QVariant name = getValue("fpplayername");
return name == QVariant() ? std::move(defaultName) : name.toString();
}
void ServersSettings::setClearDebugLogStatus(bool abIsChecked)
{
setValue(abIsChecked, "save_debug_log", "server");
setValue(abIsChecked, "save_debug_log");
}
bool ServersSettings::getClearDebugLogStatus(bool abDefaultValue)
{
QVariant cbFlushLog = getValue("save_debug_log", "server");
QVariant cbFlushLog = getValue("save_debug_log");
return cbFlushLog == QVariant() ? abDefaultValue : cbFlushLog.toBool();
}

View file

@ -1,10 +1,35 @@
#include "settings_manager.h"
SettingsManager::SettingsManager(const QString &settingPath, QObject *parent)
: QObject(parent), settings(settingPath, QSettings::IniFormat)
SettingsManager::SettingsManager(const QString &settingPath,
const QString &_defaultGroup,
const QString &_defaultSubGroup,
QObject *parent)
: QObject(parent), settings(settingPath, QSettings::IniFormat), defaultGroup(_defaultGroup),
defaultSubGroup(_defaultSubGroup)
{
}
void SettingsManager::setValue(const QVariant &value, const QString &name)
{
if (!defaultGroup.isEmpty()) {
settings.beginGroup(defaultGroup);
}
if (!defaultSubGroup.isEmpty()) {
settings.beginGroup(defaultSubGroup);
}
settings.setValue(name, value);
if (!defaultSubGroup.isEmpty()) {
settings.endGroup();
}
if (!defaultGroup.isEmpty()) {
settings.endGroup();
}
}
void SettingsManager::setValue(const QVariant &value,
const QString &name,
const QString &group,
@ -29,6 +54,27 @@ void SettingsManager::setValue(const QVariant &value,
}
}
void SettingsManager::deleteValue(const QString &name)
{
if (!defaultGroup.isEmpty()) {
settings.beginGroup(defaultGroup);
}
if (!defaultSubGroup.isEmpty()) {
settings.beginGroup(defaultSubGroup);
}
settings.remove(name);
if (!defaultSubGroup.isEmpty()) {
settings.endGroup();
}
if (!defaultGroup.isEmpty()) {
settings.endGroup();
}
}
void SettingsManager::deleteValue(const QString &name, const QString &group, const QString &subGroup)
{
if (!group.isEmpty()) {
@ -50,6 +96,29 @@ void SettingsManager::deleteValue(const QString &name, const QString &group, con
}
}
QVariant SettingsManager::getValue(const QString &name)
{
if (!defaultGroup.isEmpty()) {
settings.beginGroup(defaultGroup);
}
if (!defaultSubGroup.isEmpty()) {
settings.beginGroup(defaultSubGroup);
}
QVariant value = settings.value(name);
if (!defaultSubGroup.isEmpty()) {
settings.endGroup();
}
if (!defaultGroup.isEmpty()) {
settings.endGroup();
}
return value;
}
QVariant SettingsManager::getValue(const QString &name, const QString &group, const QString &subGroup)
{
if (!group.isEmpty()) {

View file

@ -16,14 +16,23 @@ class SettingsManager : public QObject
{
Q_OBJECT
public:
explicit SettingsManager(const QString &settingPath, QObject *parent = nullptr);
QVariant getValue(const QString &name, const QString &group = "", const QString &subGroup = "");
explicit SettingsManager(const QString &settingPath,
const QString &defaultGroup = QString(),
const QString &defaultSubGroup = QString(),
QObject *parent = nullptr);
QVariant getValue(const QString &name);
QVariant getValue(const QString &name, const QString &group, const QString &subGroup = QString());
void sync();
protected:
QSettings settings;
void setValue(const QVariant &value, const QString &name, const QString &group = "", const QString &subGroup = "");
void deleteValue(const QString &name, const QString &group = "", const QString &subGroup = "");
QString defaultGroup;
QString defaultSubGroup;
void setValue(const QVariant &value, const QString &name);
void
setValue(const QVariant &value, const QString &name, const QString &group, const QString &subGroup = QString());
void deleteValue(const QString &name);
void deleteValue(const QString &name, const QString &group, const QString &subGroup = QString());
};
#endif // SETTINGSMANAGER_H

View file

@ -20,6 +20,7 @@ set(oracle_SOURCES
src/main.cpp
src/oraclewizard.cpp
src/oracleimporter.cpp
src/pages.cpp
src/pagetemplates.cpp
src/parsehelpers.cpp
src/qt-json/json.cpp

View file

@ -1,6 +1,8 @@
#include "oracleimporter.h"
#include "client/settings/cache_settings.h"
#include "libcockatrice/interfaces/noop_card_preference_provider.h"
#include "libcockatrice/interfaces/noop_card_set_priority_controller.h"
#include "parsehelpers.h"
#include "qt-json/json.h"
@ -8,7 +10,6 @@
#include <QRegularExpression>
#include <algorithm>
#include <climits>
#include <database/interface/settings_card_preference_provider.h>
#include <libcockatrice/card/database/parser/cockatrice_xml_4.h>
#include <libcockatrice/card/relation/card_relation.h>
@ -457,14 +458,17 @@ int OracleImporter::importCardsFromSet(const CardSetPtr &currentSet, const QList
int OracleImporter::startImport()
{
int setIndex = 0;
static ICardSetPriorityController *noOpController = new NoopCardSetPriorityController();
// add an empty set for tokens
CardSetPtr tokenSet = CardSet::newInstance(SettingsCache::instance().cardDatabase(), CardSet::TOKENS_SETNAME,
tr("Dummy set containing tokens"), "Tokens");
CardSetPtr tokenSet =
CardSet::newInstance(noOpController, CardSet::TOKENS_SETNAME, tr("Dummy set containing tokens"), "Tokens");
sets.insert(CardSet::TOKENS_SETNAME, tokenSet);
int setIndex = 0;
for (const SetToDownload &curSetToParse : allSets) {
CardSetPtr newSet = CardSet::newInstance(SettingsCache::instance().cardDatabase(), curSetToParse.getShortName(),
CardSetPtr newSet = CardSet::newInstance(noOpController, curSetToParse.getShortName(),
curSetToParse.getLongName(), curSetToParse.getSetType(),
curSetToParse.getReleaseDate(), curSetToParse.getPriority());
if (!sets.contains(newSet->getShortName()))
@ -485,7 +489,7 @@ int OracleImporter::startImport()
bool OracleImporter::saveToFile(const QString &fileName, const QString &sourceUrl, const QString &sourceVersion)
{
CockatriceXml4Parser parser(new SettingsCardPreferenceProvider());
CockatriceXml4Parser parser(new NoopCardPreferenceProvider());
return parser.saveToFile(sets, cards, fileName, sourceUrl, sourceVersion);
}

View file

@ -3,55 +3,19 @@
#include "client/settings/cache_settings.h"
#include "main.h"
#include "oracleimporter.h"
#include "version_string.h"
#include "pages.h"
#include "pagetemplates.h"
#include <QAbstractButton>
#include <QBuffer>
#include <QCheckBox>
#include <QComboBox>
#include <QDir>
#include <QFileDialog>
#include <QGridLayout>
#include <QLabel>
#include <QLineEdit>
#include <QMessageBox>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QProgressBar>
#include <QPushButton>
#include <QRadioButton>
#include <QScrollBar>
#include <QStandardPaths>
#include <QTextEdit>
#include <QtConcurrent>
#include <QtGui>
#ifdef HAS_LZMA
#include "lzma/decompress.h"
#endif
#ifdef HAS_ZLIB
#include "zip/unzip.h"
#endif
#define ZIP_SIGNATURE "PK"
// Xz stream header: 0xFD + "7zXZ"
#define XZ_SIGNATURE "\xFD\x37\x7A\x58\x5A"
#define MTGJSON_V4_URL_COMPONENT "mtgjson.com/files/"
#define ALLSETS_URL_FALLBACK "https://www.mtgjson.com/api/v5/AllPrintings.json"
#define MTGJSON_VERSION_URL "https://www.mtgjson.com/api/v5/Meta.json"
#ifdef HAS_LZMA
#define ALLSETS_URL "https://www.mtgjson.com/api/v5/AllPrintings.json.xz"
#elif defined(HAS_ZLIB)
#define ALLSETS_URL "https://www.mtgjson.com/api/v5/AllPrintings.json.zip"
#else
#define ALLSETS_URL "https://www.mtgjson.com/api/v5/AllPrintings.json"
#endif
#define TOKENS_URL "https://raw.githubusercontent.com/Cockatrice/Magic-Token/master/tokens.xml"
#define SPOILERS_URL "https://raw.githubusercontent.com/Cockatrice/Magic-Spoiler/files/spoiler.xml"
OracleWizard::OracleWizard(QWidget *parent) : QWizard(parent)
{
// define a dummy context that will be used where needed
@ -142,669 +106,3 @@ bool OracleWizard::saveTokensToFile(const QString &fileName)
file.close();
return true;
}
IntroPage::IntroPage(QWidget *parent) : OracleWizardPage(parent)
{
label = new QLabel(this);
label->setWordWrap(true);
languageLabel = new QLabel(this);
versionLabel = new QLabel(this);
languageBox = new QComboBox(this);
QStringList languageCodes = findQmFiles();
for (const QString &code : languageCodes) {
QString langName = languageName(code);
languageBox->addItem(langName, code);
}
QString setLanguage = QCoreApplication::translate("i18n", DEFAULT_LANG_NAME);
int index = languageBox->findText(setLanguage, Qt::MatchExactly);
if (index == -1) {
qWarning() << "could not find language" << setLanguage;
} else {
languageBox->setCurrentIndex(index);
}
connect(languageBox, qOverload<int>(&QComboBox::currentIndexChanged), this, &IntroPage::languageBoxChanged);
auto *layout = new QGridLayout(this);
layout->addWidget(label, 0, 0, 1, 2);
layout->addWidget(languageLabel, 1, 0);
layout->addWidget(languageBox, 1, 1);
layout->addWidget(versionLabel, 2, 0, 1, 2);
setLayout(layout);
}
void IntroPage::initializePage()
{
if (wizard()->backgroundMode) {
emit readyToContinue();
}
}
QStringList IntroPage::findQmFiles()
{
QDir dir(translationPath);
QStringList fileNames = dir.entryList(QStringList(translationPrefix + "_*.qm"), QDir::Files, QDir::Name);
fileNames.replaceInStrings(QRegularExpression(translationPrefix + "_(.*)\\.qm"), "\\1");
return fileNames;
}
QString IntroPage::languageName(const QString &lang)
{
QTranslator qTranslator;
QString appNameHint = translationPrefix + "_" + lang;
bool appTranslationLoaded = qTranslator.load(appNameHint, translationPath);
if (!appTranslationLoaded) {
qDebug() << "Unable to load" << translationPrefix << "translation" << appNameHint << "at" << translationPath;
}
return qTranslator.translate("i18n", DEFAULT_LANG_NAME);
}
void IntroPage::languageBoxChanged(int index)
{
SettingsCache::instance().setLang(languageBox->itemData(index).toString());
}
void IntroPage::retranslateUi()
{
setTitle(tr("Introduction"));
label->setText(tr("This wizard will import the list of sets, cards, and tokens "
"that will be used by Cockatrice."));
languageLabel->setText(tr("Interface language:"));
versionLabel->setText(tr("Version:") + QString(" %1").arg(VERSION_STRING));
}
void OutroPage::retranslateUi()
{
setTitle(tr("Finished"));
setSubTitle(tr("The wizard has finished.") + "<br>" +
tr("You can now start using Cockatrice with the newly updated cards.") + "<br><br>" +
tr("If the card databases don't reload automatically, restart the Cockatrice client."));
}
void OutroPage::initializePage()
{
if (wizard()->backgroundMode) {
wizard()->accept();
exit(0);
}
}
LoadSetsPage::LoadSetsPage(QWidget *parent) : OracleWizardPage(parent)
{
urlRadioButton = new QRadioButton(this);
fileRadioButton = new QRadioButton(this);
urlLineEdit = new QLineEdit(this);
fileLineEdit = new QLineEdit(this);
progressLabel = new QLabel(this);
progressBar = new QProgressBar(this);
urlRadioButton->setChecked(true);
urlButton = new QPushButton(this);
connect(urlButton, &QPushButton::clicked, this, &LoadSetsPage::actRestoreDefaultUrl);
fileButton = new QPushButton(this);
connect(fileButton, &QPushButton::clicked, this, &LoadSetsPage::actLoadSetsFile);
auto *layout = new QGridLayout(this);
layout->addWidget(urlRadioButton, 0, 0);
layout->addWidget(urlLineEdit, 0, 1);
layout->addWidget(urlButton, 1, 1, Qt::AlignRight);
layout->addWidget(fileRadioButton, 2, 0);
layout->addWidget(fileLineEdit, 2, 1);
layout->addWidget(fileButton, 3, 1, Qt::AlignRight);
layout->addWidget(progressLabel, 4, 0);
layout->addWidget(progressBar, 4, 1);
connect(&watcher, &QFutureWatcher<bool>::finished, this, &LoadSetsPage::importFinished);
setLayout(layout);
}
void LoadSetsPage::initializePage()
{
urlLineEdit->setText(wizard()->settings->value("allsetsurl", ALLSETS_URL).toString());
progressLabel->hide();
progressBar->hide();
if (wizard()->backgroundMode) {
if (isEnabled()) {
validatePage();
}
}
}
void LoadSetsPage::retranslateUi()
{
setTitle(tr("Source selection"));
setSubTitle(tr("Please specify a compatible source for the list of sets and cards. "
"You can specify a URL address that will be downloaded or "
"use an existing file from your computer."));
urlRadioButton->setText(tr("Download URL:"));
fileRadioButton->setText(tr("Local file:"));
urlButton->setText(tr("Restore default URL"));
fileButton->setText(tr("Choose file..."));
}
void LoadSetsPage::actRestoreDefaultUrl()
{
urlLineEdit->setText(ALLSETS_URL);
}
void LoadSetsPage::actLoadSetsFile()
{
QFileDialog dialog(this, tr("Load sets file"));
dialog.setFileMode(QFileDialog::ExistingFile);
QString extensions = "*.json *.xml";
#ifdef HAS_ZLIB
extensions += " *.zip";
#endif
#ifdef HAS_LZMA
extensions += " *.xz";
#endif
dialog.setNameFilter(tr("Sets file (%1)").arg(extensions));
if (!fileLineEdit->text().isEmpty() && QFile::exists(fileLineEdit->text())) {
dialog.selectFile(fileLineEdit->text());
}
if (!dialog.exec()) {
return;
}
fileLineEdit->setText(dialog.selectedFiles().at(0));
}
bool LoadSetsPage::validatePage()
{
// once the import is finished, we call next(); skip validation
if (wizard()->downloadedPlainXml || wizard()->importer->getSets().count() > 0) {
return true;
}
// else, try to import sets
if (urlRadioButton->isChecked()) {
// If a user attempts to download from V4, redirect them to V5
if (urlLineEdit->text().contains(MTGJSON_V4_URL_COMPONENT)) {
actRestoreDefaultUrl();
}
const auto url = QUrl::fromUserInput(urlLineEdit->text());
if (!url.isValid()) {
QMessageBox::critical(this, tr("Error"), tr("The provided URL is not valid."));
return false;
}
progressLabel->setText(tr("Downloading (0MB)"));
// show an infinite progressbar
progressBar->setMaximum(0);
progressBar->setMinimum(0);
progressBar->setValue(0);
progressLabel->show();
progressBar->show();
wizard()->disableButtons();
setEnabled(false);
downloadSetsFile(url);
} else if (fileRadioButton->isChecked()) {
QFile setsFile(fileLineEdit->text());
if (!setsFile.exists()) {
QMessageBox::critical(this, tr("Error"), tr("Please choose a file."));
return false;
}
if (!setsFile.open(QIODevice::ReadOnly)) {
QMessageBox::critical(nullptr, tr("Error"), tr("Cannot open file '%1'.").arg(fileLineEdit->text()));
return false;
}
wizard()->disableButtons();
setEnabled(false);
wizard()->setCardSourceUrl(setsFile.fileName());
wizard()->setCardSourceVersion("unknown");
readSetsFromByteArray(setsFile.readAll());
}
return false;
}
void LoadSetsPage::downloadSetsFile(const QUrl &url)
{
wizard()->setCardSourceVersion("unknown");
const auto urlString = url.toString();
if (urlString == ALLSETS_URL || urlString == ALLSETS_URL_FALLBACK) {
const auto versionUrl = QUrl::fromUserInput(MTGJSON_VERSION_URL);
auto *versionReply = wizard()->nam->get(QNetworkRequest(versionUrl));
connect(versionReply, &QNetworkReply::finished, [this, versionReply]() {
if (versionReply->error() == QNetworkReply::NoError) {
auto jsonData = versionReply->readAll();
QJsonParseError jsonError{};
auto jsonResponse = QJsonDocument::fromJson(jsonData, &jsonError);
if (jsonError.error == QJsonParseError::NoError) {
const auto jsonMap = jsonResponse.toVariant().toMap();
auto versionString = jsonMap.value("meta").toMap().value("version").toString();
if (versionString.isEmpty()) {
versionString = "unknown";
}
wizard()->setCardSourceVersion(versionString);
}
}
versionReply->deleteLater();
});
}
wizard()->setCardSourceUrl(url.toString());
auto *reply = wizard()->nam->get(QNetworkRequest(url));
connect(reply, &QNetworkReply::finished, this, &LoadSetsPage::actDownloadFinishedSetsFile);
connect(reply, &QNetworkReply::downloadProgress, this, &LoadSetsPage::actDownloadProgressSetsFile);
}
void LoadSetsPage::actDownloadProgressSetsFile(qint64 received, qint64 total)
{
if (total > 0) {
progressBar->setMaximum(static_cast<int>(total));
progressBar->setValue(static_cast<int>(received));
}
progressLabel->setText(tr("Downloading (%1MB)").arg((int)received / (1024 * 1024)));
}
void LoadSetsPage::actDownloadFinishedSetsFile()
{
// check for a reply
auto *reply = dynamic_cast<QNetworkReply *>(sender());
auto errorCode = reply->error();
if (errorCode != QNetworkReply::NoError) {
QMessageBox::critical(this, tr("Error"), tr("Network error: %1.").arg(reply->errorString()));
wizard()->enableButtons();
setEnabled(true);
reply->deleteLater();
return;
}
auto statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (statusCode == 301 || statusCode == 302) {
const auto redirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
qDebug() << "following redirect url:" << redirectUrl.toString();
downloadSetsFile(redirectUrl);
reply->deleteLater();
return;
}
progressLabel->hide();
progressBar->hide();
// save AllPrintings.json url, but only if the user customized it and download was successful
if (urlLineEdit->text() != QString(ALLSETS_URL)) {
wizard()->settings->setValue("allsetsurl", urlLineEdit->text());
} else {
wizard()->settings->remove("allsetsurl");
}
readSetsFromByteArray(reply->readAll());
reply->deleteLater();
}
void LoadSetsPage::readSetsFromByteArray(QByteArray _data)
{
// show an infinite progressbar
progressBar->setMaximum(0);
progressBar->setMinimum(0);
progressBar->setValue(0);
progressLabel->setText(tr("Parsing file"));
progressLabel->show();
progressBar->show();
wizard()->downloadedPlainXml = false;
wizard()->xmlData.clear();
readSetsFromByteArrayRef(_data);
}
void LoadSetsPage::readSetsFromByteArrayRef(QByteArray &_data)
{
// unzip the file if needed
if (_data.startsWith(XZ_SIGNATURE)) {
#ifdef HAS_LZMA
// zipped file
auto *inBuffer = new QBuffer(&_data);
auto newData = QByteArray();
auto *outBuffer = new QBuffer(&newData);
inBuffer->open(QBuffer::ReadOnly);
outBuffer->open(QBuffer::WriteOnly);
XzDecompressor xz;
if (!xz.decompress(inBuffer, outBuffer)) {
zipDownloadFailed(tr("Xz extraction failed."));
return;
}
_data.clear();
readSetsFromByteArrayRef(newData);
return;
#else
zipDownloadFailed(tr("Sorry, this version of Oracle does not support xz compressed files."));
wizard()->enableButtons();
setEnabled(true);
progressLabel->hide();
progressBar->hide();
return;
#endif
} else if (_data.startsWith(ZIP_SIGNATURE)) {
#ifdef HAS_ZLIB
// zipped file
auto *inBuffer = new QBuffer(&_data);
auto newData = QByteArray();
auto *outBuffer = new QBuffer(&newData);
QString fileName;
UnZip::ErrorCode ec;
UnZip uz;
ec = uz.openArchive(inBuffer);
if (ec != UnZip::Ok) {
zipDownloadFailed(tr("Failed to open Zip archive: %1.").arg(uz.formatError(ec)));
return;
}
if (uz.fileList().size() != 1) {
zipDownloadFailed(tr("Zip extraction failed: the Zip archive doesn't contain exactly one file."));
return;
}
fileName = uz.fileList().at(0);
outBuffer->open(QBuffer::ReadWrite);
ec = uz.extractFile(fileName, outBuffer);
if (ec != UnZip::Ok) {
zipDownloadFailed(tr("Zip extraction failed: %1.").arg(uz.formatError(ec)));
uz.closeArchive();
return;
}
_data.clear();
readSetsFromByteArrayRef(newData);
return;
#else
zipDownloadFailed(tr("Sorry, this version of Oracle does not support zipped files."));
wizard()->enableButtons();
setEnabled(true);
progressLabel->hide();
progressBar->hide();
return;
#endif
} else if (_data.startsWith("{")) {
// Start the computation.
jsonData = std::move(_data);
future = QtConcurrent::run([this] { return wizard()->importer->readSetsFromByteArray(std::move(jsonData)); });
watcher.setFuture(future);
} else if (_data.startsWith("<")) {
// save xml file and don't do any processing
wizard()->downloadedPlainXml = true;
wizard()->xmlData = std::move(_data);
importFinished();
} else {
wizard()->enableButtons();
setEnabled(true);
progressLabel->hide();
progressBar->hide();
QMessageBox::critical(this, tr("Error"), tr("Failed to interpret downloaded data."));
}
}
void LoadSetsPage::zipDownloadFailed(const QString &message)
{
wizard()->enableButtons();
setEnabled(true);
progressLabel->hide();
progressBar->hide();
QMessageBox::StandardButton reply;
reply = static_cast<QMessageBox::StandardButton>(QMessageBox::question(
this, tr("Error"), message + "<br>" + tr("Do you want to download the uncompressed file instead?"),
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes));
if (reply == QMessageBox::Yes) {
urlRadioButton->setChecked(true);
urlLineEdit->setText(ALLSETS_URL_FALLBACK);
wizard()->next();
}
}
void LoadSetsPage::importFinished()
{
wizard()->enableButtons();
setEnabled(true);
progressLabel->hide();
progressBar->hide();
if (wizard()->downloadedPlainXml || watcher.future().result()) {
wizard()->next();
} else {
QMessageBox::critical(this, tr("Error"),
tr("The file was retrieved successfully, but it does not contain any sets data."));
}
}
SaveSetsPage::SaveSetsPage(QWidget *parent) : OracleWizardPage(parent)
{
pathLabel = new QLabel(this);
saveLabel = new QLabel(this);
defaultPathCheckBox = new QCheckBox(this);
messageLog = new QTextEdit(this);
messageLog->setReadOnly(true);
auto *layout = new QGridLayout(this);
layout->addWidget(messageLog, 0, 0);
layout->addWidget(saveLabel, 1, 0);
layout->addWidget(pathLabel, 2, 0);
layout->addWidget(defaultPathCheckBox, 3, 0);
setLayout(layout);
}
void SaveSetsPage::cleanupPage()
{
wizard()->importer->clear();
disconnect(wizard()->importer, &OracleImporter::setIndexChanged, nullptr, nullptr);
}
void SaveSetsPage::initializePage()
{
messageLog->clear();
retranslateUi();
if (wizard()->downloadedPlainXml) {
messageLog->hide();
} else {
messageLog->show();
connect(wizard()->importer, &OracleImporter::setIndexChanged, this, &SaveSetsPage::updateTotalProgress);
int setsImported = wizard()->importer->startImport();
if (setsImported == 0) {
QMessageBox::critical(this, tr("Error"), tr("No set has been imported."));
}
}
if (wizard()->backgroundMode) {
emit readyToContinue();
}
}
void SaveSetsPage::retranslateUi()
{
setTitle(tr("Sets imported"));
if (wizard()->downloadedPlainXml) {
setSubTitle(tr("A cockatrice database file of %1 MB has been downloaded.")
.arg(qRound(wizard()->xmlData.size() / 1000000.0)));
} else {
setSubTitle(tr("The following sets have been found:"));
}
saveLabel->setText(tr("Press \"Save\" to store the imported cards in the Cockatrice database."));
pathLabel->setText(tr("The card database will be saved at the following location:") + "<br>" +
SettingsCache::instance().getCardDatabasePath());
defaultPathCheckBox->setText(tr("Save to a custom path (not recommended)"));
setButtonText(QWizard::NextButton, tr("&Save"));
}
void SaveSetsPage::updateTotalProgress(int cardsImported, int /* setIndex */, const QString &setName)
{
if (setName.isEmpty()) {
messageLog->append("<b>" + tr("Import finished: %1 cards.").arg(wizard()->importer->getCardList().size()) +
"</b>");
} else {
messageLog->append(tr("%1: %2 cards imported").arg(setName).arg(cardsImported));
}
messageLog->verticalScrollBar()->setValue(messageLog->verticalScrollBar()->maximum());
}
bool SaveSetsPage::validatePage()
{
QString defaultPath = SettingsCache::instance().getCardDatabasePath();
QString windowName = tr("Save card database");
QString fileType = tr("XML; card database (*.xml)");
QString fileName;
if (defaultPathCheckBox->isChecked()) {
fileName = QFileDialog::getSaveFileName(this, windowName, defaultPath, fileType);
} else {
fileName = defaultPath;
}
if (fileName.isEmpty()) {
return false;
}
QFileInfo fi(fileName);
QDir fileDir(fi.path());
if (!fileDir.exists() && !fileDir.mkpath(fileDir.absolutePath())) {
return false;
}
if (wizard()->downloadedPlainXml) {
QFile file(fileName);
if (!file.open(QIODevice::WriteOnly)) {
qDebug() << "File write (w) failed for" << fileName;
return false;
}
if (file.write(wizard()->xmlData) < 1) {
qDebug() << "File write (w) failed for" << fileName;
return false;
}
wizard()->xmlData.clear();
} else if (!wizard()->importer->saveToFile(fileName, wizard()->getCardSourceUrl(),
wizard()->getCardSourceVersion())) {
QMessageBox::critical(this, tr("Error"), tr("The file could not be saved to %1").arg(fileName));
return false;
}
return true;
}
void LoadTokensPage::initializePage()
{
SimpleDownloadFilePage::initializePage();
if (wizard()->backgroundMode) {
emit readyToContinue();
}
}
QString LoadTokensPage::getDefaultUrl()
{
return TOKENS_URL;
}
QString LoadTokensPage::getCustomUrlSettingsKey()
{
return "tokensurl";
}
QString LoadTokensPage::getDefaultSavePath()
{
return SettingsCache::instance().getTokenDatabasePath();
}
QString LoadTokensPage::getWindowTitle()
{
return tr("Save token database");
}
QString LoadTokensPage::getFileType()
{
return tr("XML; token database (*.xml)");
}
void LoadTokensPage::retranslateUi()
{
setTitle(tr("Tokens import"));
setSubTitle(tr("Please specify a compatible source for token data."));
urlLabel->setText(tr("Download URL:"));
urlButton->setText(tr("Restore default URL"));
pathLabel->setText(tr("The token database will be saved at the following location:") + "<br>" +
SettingsCache::instance().getTokenDatabasePath());
defaultPathCheckBox->setText(tr("Save to a custom path (not recommended)"));
}
QString LoadSpoilersPage::getDefaultUrl()
{
return SPOILERS_URL;
}
QString LoadSpoilersPage::getCustomUrlSettingsKey()
{
return "spoilersurl";
}
QString LoadSpoilersPage::getDefaultSavePath()
{
return SettingsCache::instance().getTokenDatabasePath();
}
QString LoadSpoilersPage::getWindowTitle()
{
return tr("Save spoiler database");
}
QString LoadSpoilersPage::getFileType()
{
return tr("XML; spoiler database (*.xml)");
}
void LoadSpoilersPage::retranslateUi()
{
setTitle(tr("Spoilers import"));
setSubTitle(tr("Please specify a compatible source for spoiler data."));
urlLabel->setText(tr("Download URL:"));
urlButton->setText(tr("Restore default URL"));
pathLabel->setText(tr("The spoiler database will be saved at the following location:") + "<br>" +
SettingsCache::instance().getSpoilerCardDatabasePath());
defaultPathCheckBox->setText(tr("Save to a custom path (not recommended)"));
}

Some files were not shown because too many files have changed in this diff Show more