diff --git a/Doxyfile b/Doxyfile index 5b125ef13..a1861f64f 100644 --- a/Doxyfile +++ b/Doxyfile @@ -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 diff --git a/cockatrice/src/client/settings/card_counter_settings.cpp b/cockatrice/src/client/settings/card_counter_settings.cpp index d22eb239f..71ce4cfc6 100644 --- a/cockatrice/src/client/settings/card_counter_settings.cpp +++ b/cockatrice/src/client/settings/card_counter_settings.cpp @@ -5,7 +5,7 @@ #include CardCounterSettings::CardCounterSettings(const QString &settingsPath, QObject *parent) - : SettingsManager(settingsPath + "global.ini", parent) + : SettingsManager(settingsPath + "global.ini", "cards", "counters", parent) { } diff --git a/cockatrice/src/game/deckview/deck_view.cpp b/cockatrice/src/game/deckview/deck_view.cpp index 6c5e51fc4..48c906100 100644 --- a/cockatrice/src/game/deckview/deck_view.cpp +++ b/cockatrice/src/game/deckview/deck_view.cpp @@ -41,8 +41,8 @@ void DeckViewCardDragItem::updatePosition(const QPointF &cursorScenePos) void DeckViewCardDragItem::handleDrop(DeckViewCardContainer *target) { - DeckViewCard *card = static_cast(item); - DeckViewCardContainer *start = static_cast(item->parentItem()); + auto *card = static_cast(item); + auto *start = static_cast(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(scene()); + auto *sc = static_cast(scene()); sc->removeItem(this); if (currentZone) { handleDrop(currentZone); for (int i = 0; i < childDrags.size(); i++) { - DeckViewCardDragItem *c = static_cast(childDrags[i]); + auto *c = static_cast(childDrags[i]); c->handleDrop(currentZone); sc->removeItem(c); } @@ -118,12 +118,12 @@ void DeckViewCard::mouseMoveEvent(QGraphicsSceneMouseEvent *event) QList sel = scene()->selectedItems(); int j = 0; for (int i = 0; i < sel.size(); i++) { - DeckViewCard *c = static_cast(sel.at(i)); + auto *c = static_cast(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 sel = scene()->selectedItems(); for (int i = 0; i < sel.size(); i++) { - DeckViewCard *c = static_cast(sel.at(i)); - DeckViewCardContainer *zone = static_cast(c->parentItem()); + auto *c = static_cast(sel.at(i)); + auto *zone = static_cast(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(listRoot->at(i)); + auto *currentZone = dynamic_cast(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(currentZone->at(j)); + auto *currentCard = dynamic_cast(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); } diff --git a/cockatrice/src/game/dialogs/dlg_create_token.cpp b/cockatrice/src/game/dialogs/dlg_create_token.cpp index 7cafc3219..6594e339d 100644 --- a/cockatrice/src/game/dialogs/dlg_create_token.cpp +++ b/cockatrice/src/game/dialogs/dlg_create_token.cpp @@ -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); diff --git a/cockatrice/src/game/player/player.cpp b/cockatrice/src/game/player/player.cpp index a18e464d6..300300de3 100644 --- a/cockatrice/src/game/player/player.cpp +++ b/cockatrice/src/game/player/player.cpp @@ -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()); } diff --git a/cockatrice/src/game/player/player_graphics_item.cpp b/cockatrice/src/game/player/player_graphics_item.cpp index e609591b3..27fbc82c8 100644 --- a/cockatrice/src/game/player/player_graphics_item.cpp +++ b/cockatrice/src/game/player/player_graphics_item.cpp @@ -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()) { diff --git a/cockatrice/src/game/player/player_list_widget.cpp b/cockatrice/src/game/player/player_list_widget.cpp index 3281d8541..7cf7c34a6 100644 --- a/cockatrice/src/game/player/player_list_widget.cpp +++ b/cockatrice/src/game/player/player_list_widget.cpp @@ -27,7 +27,7 @@ bool PlayerListItemDelegate::editorEvent(QEvent *event, const QModelIndex &index) { if ((event->type() == QEvent::MouseButtonPress) && index.isValid()) { - QMouseEvent *const mouseEvent = static_cast(event); + auto *const mouseEvent = static_cast(event); if (mouseEvent->button() == Qt::RightButton) { #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) static_cast(parent())->showContextMenu(mouseEvent->globalPosition().toPoint(), index); diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader.h b/cockatrice/src/interface/card_picture_loader/card_picture_loader.h index 213231cd9..29bdf26c8 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_loader.h +++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader.h @@ -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 &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 &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 diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader_local.cpp b/cockatrice/src/interface/card_picture_loader/card_picture_loader_local.cpp index 33a78ddaf..dd00e6e1d 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_loader_local.cpp +++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader_local.cpp @@ -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(); diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader_local.h b/cockatrice/src/interface/card_picture_loader/card_picture_loader_local.h index 1d4325412..84f16cc38 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_loader_local.h +++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader_local.h @@ -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 customFolderIndex; // multimap of cardName to picPaths - QTimer *refreshTimer; + QMultiHash 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(); }; diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader_request_status_display_widget.h b/cockatrice/src/interface/card_picture_loader/card_picture_loader_request_status_display_widget.h index 2fdf7001b..2e3233c2d 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_loader_request_status_display_widget.h +++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader_request_status_display_widget.h @@ -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 #include #include +/** + * @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 diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader_status_bar.h b/cockatrice/src/interface/card_picture_loader/card_picture_loader_status_bar.h index b44d2cc20..cb469269c 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_loader_status_bar.h +++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader_status_bar.h @@ -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 #include +#include #include +/** + * @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 diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker.cpp b/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker.cpp index 4a831c24a..a2da3c953 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker.cpp +++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker.cpp @@ -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()) { diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker.h b/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker.h index 368a0e4c6..f927abde6 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker.h +++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker.h @@ -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> redirectCache; // Stores redirect and timestamp - QString cacheFilePath; // Path to persistent storage - static constexpr int CacheTTLInDays = 30; // TODO: Make user configurable - bool picDownload; - QQueue> requestLoadQueue; + QThread *pictureLoaderThread; ///< Thread for executing worker tasks + QNetworkAccessManager *networkManager; ///< Network manager for HTTP requests + QNetworkDiskCache *cache; ///< Disk cache for downloaded images + QHash> 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> 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 currentlyLoading; // for deduplication purposes. Contains pixmapCacheKey + CardPictureLoaderLocal *localLoader; ///< Loader for local images + QSet 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); }; diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker_work.cpp b/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker_work.cpp index 07478ee70..e74103419 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker_work.cpp +++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker_work.cpp @@ -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); diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker_work.h b/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker_work.h index c7ccf7566..cdffc1dff 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker_work.h +++ b/cockatrice/src/interface/card_picture_loader/card_picture_loader_worker_work.h @@ -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 #include -#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); }; diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_to_load.cpp b/cockatrice/src/interface/card_picture_loader/card_picture_to_load.cpp index 3727988ad..d72226aab 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_to_load.cpp +++ b/cockatrice/src/interface/card_picture_loader/card_picture_to_load.cpp @@ -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 CardPictureToLoad::extractSetsSorted(const ExactCard &card) { QList 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()) { diff --git a/cockatrice/src/interface/card_picture_loader/card_picture_to_load.h b/cockatrice/src/interface/card_picture_loader/card_picture_to_load.h index 0c0aff72f..b0a555c86 100644 --- a/cockatrice/src/interface/card_picture_loader/card_picture_to_load.h +++ b/cockatrice/src/interface/card_picture_loader/card_picture_to_load.h @@ -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 sortedSets; - QList urlTemplates; - QList currentSetUrls; - QString currentUrl; - CardSetPtr currentSet; + ExactCard card; ///< The ExactCard being downloaded + QList sortedSets; ///< All sets for this card, sorted by priority + QList urlTemplates; ///< URL templates from settings + QList 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 extractSetsSorted(const ExactCard &card); }; diff --git a/cockatrice/src/interface/widgets/deck_analytics/mana_base_widget.cpp b/cockatrice/src/interface/widgets/deck_analytics/mana_base_widget.cpp index 27079dd52..568583af5 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/mana_base_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/mana_base_widget.cpp @@ -78,20 +78,14 @@ void ManaBaseWidget::updateDisplay() QHash ManaBaseWidget::analyzeManaBase() { manaBaseMap.clear(); - InnerDecklistNode *listRoot = deckListModel->getDeckList()->getRoot(); - for (int i = 0; i < listRoot->size(); i++) { - InnerDecklistNode *currentZone = dynamic_cast(listRoot->at(i)); - for (int j = 0; j < currentZone->size(); j++) { - DecklistCardNode *currentCard = dynamic_cast(currentZone->at(j)); - if (!currentCard) - continue; + QList 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); } } } diff --git a/cockatrice/src/interface/widgets/deck_analytics/mana_curve_widget.cpp b/cockatrice/src/interface/widgets/deck_analytics/mana_curve_widget.cpp index 668b3afd5..4515b723b 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/mana_curve_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/mana_curve_widget.cpp @@ -44,20 +44,15 @@ void ManaCurveWidget::setDeckModel(DeckListModel *deckModel) std::unordered_map ManaCurveWidget::analyzeManaCurve() { manaCurveMap.clear(); - InnerDecklistNode *listRoot = deckListModel->getDeckList()->getRoot(); - for (int i = 0; i < listRoot->size(); i++) { - InnerDecklistNode *currentZone = dynamic_cast(listRoot->at(i)); - for (int j = 0; j < currentZone->size(); j++) { - DecklistCardNode *currentCard = dynamic_cast(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 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]++; } } } diff --git a/cockatrice/src/interface/widgets/deck_analytics/mana_devotion_widget.cpp b/cockatrice/src/interface/widgets/deck_analytics/mana_devotion_widget.cpp index 4454caef1..3cafdaeab 100644 --- a/cockatrice/src/interface/widgets/deck_analytics/mana_devotion_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_analytics/mana_devotion_widget.cpp @@ -46,20 +46,15 @@ void ManaDevotionWidget::setDeckModel(DeckListModel *deckModel) std::unordered_map ManaDevotionWidget::analyzeManaDevotion() { manaDevotionMap.clear(); - InnerDecklistNode *listRoot = deckListModel->getDeckList()->getRoot(); - for (int i = 0; i < listRoot->size(); i++) { - InnerDecklistNode *currentZone = dynamic_cast(listRoot->at(i)); - for (int j = 0; j < currentZone->size(); j++) { - DecklistCardNode *currentCard = dynamic_cast(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 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); } } } diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp index f05cbd31c..7a4820dd5 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.cpp @@ -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> bannerCardSet; - InnerDecklistNode *listRoot = deckModel->getDeckList()->getRoot(); - for (int i = 0; i < listRoot->size(); i++) { - InnerDecklistNode *currentZone = dynamic_cast(listRoot->at(i)); - for (int j = 0; j < currentZone->size(); j++) { - DecklistCardNode *currentCard = dynamic_cast(currentZone->at(j)); - if (!currentCard) - continue; + QList 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 ¤tIndex) 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(); } diff --git a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h index 90b6f9797..d4703a5dc 100644 --- a/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h +++ b/cockatrice/src/interface/widgets/deck_editor/deck_editor_deck_dock_widget.h @@ -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 #include @@ -28,6 +29,7 @@ class DeckEditorDeckDockWidget : public QDockWidget public: explicit DeckEditorDeckDockWidget(AbstractTabDeckEditor *parent); DeckLoader *deckLoader; + DeckListStyleProxy *proxy; DeckListModel *deckModel; QTreeView *deckView; QComboBox *bannerCardComboBox; diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_create_game.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_create_game.cpp index d69c7a08a..2b1b63065 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_create_game.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_create_game.cpp @@ -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 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 gameTypeCheckBoxIterator(gameTypeCheckBoxes); while (gameTypeCheckBoxIterator.hasNext()) { gameTypeCheckBoxIterator.next(); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_edit_avatar.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_edit_avatar.cpp index 28be359a3..7330c749b 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_edit_avatar.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_edit_avatar.cpp @@ -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); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_challenge.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_challenge.cpp index c8c86d1f1..7ad957e6c 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_challenge.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_challenge.cpp @@ -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); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_request.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_request.cpp index 4921630f3..8f79b543f 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_request.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_forgot_password_request.cpp @@ -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); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.cpp index 59170c0fc..b1f4d066f 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_load_deck_from_clipboard.cpp @@ -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); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_load_remote_deck.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_load_remote_deck.cpp index 9abe1949a..214ccc588 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_load_remote_deck.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_load_remote_deck.cpp @@ -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); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_register.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_register.cpp index dff99e4d8..3493c1047 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_register.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_register.cpp @@ -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); diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_select_set_for_cards.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_select_set_for_cards.cpp index 31e2f8d14..3bb81844c 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_select_set_for_cards.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_select_set_for_cards.cpp @@ -209,30 +209,19 @@ QMap DlgSelectSetForCards::getSetsForCards() if (!decklist) return setCounts; - InnerDecklistNode *listRoot = decklist->getRoot(); - if (!listRoot) - return setCounts; + QList cardsInDeck = decklist->getCardNodes(); - for (auto *i : *listRoot) { - auto *countCurrentZone = dynamic_cast(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(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 cardsInDeck = decklist->getCardNodes(); - for (auto *i : *listRoot) { - auto *countCurrentZone = dynamic_cast(i); - if (!countCurrentZone) - continue; + for (auto currentCard : cardsInDeck) { + bool found = false; + QString foundSetName; - for (auto *cardNode : *countCurrentZone) { - auto *currentCard = dynamic_cast(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 DlgSelectSetForCards::getCardsForSets() if (!decklist) return setCards; - InnerDecklistNode *listRoot = decklist->getRoot(); - if (!listRoot) - return setCards; + QList cardsInDeck = decklist->getCardNodes(); - for (auto *i : *listRoot) { - auto *countCurrentZone = dynamic_cast(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(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; } diff --git a/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp b/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp index d825a2125..73508d843 100644 --- a/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp +++ b/cockatrice/src/interface/widgets/dialogs/dlg_settings.cpp @@ -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 diff --git a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp index 168a21ee7..416de18dc 100644 --- a/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/card_amount_widget.cpp @@ -266,35 +266,16 @@ int CardAmountWidget::countCardsInZone(const QString &deckZone) return -1; } - InnerDecklistNode *listRoot = decklist->getRoot(); - if (!listRoot) { - return -1; - } + QList cardsInDeck = decklist->getCardNodes({deckZone}); int count = 0; - - for (auto *i : *listRoot) { - auto *countCurrentZone = dynamic_cast(i); - if (!countCurrentZone) { - continue; - } - - if (countCurrentZone->getName() != deckZone) { - continue; - } - - for (auto *cardNode : *countCurrentZone) { - auto *currentCard = dynamic_cast(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; } \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp index 1a47c56a6..a551ac290 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector.cpp @@ -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); diff --git a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_selection_widget.cpp b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_selection_widget.cpp index 9c046a51b..57d5fd895 100644 --- a/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_selection_widget.cpp +++ b/cockatrice/src/interface/widgets/printing_selector/printing_selector_card_selection_widget.cpp @@ -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; } diff --git a/cockatrice/src/interface/widgets/server/games_model.cpp b/cockatrice/src/interface/widgets/server/games_model.cpp index 1da92cdad..4c68390c9 100644 --- a/cockatrice/src/interface/widgets/server/games_model.cpp +++ b/cockatrice/src/interface/widgets/server/games_model.cpp @@ -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(sourceModel()); + auto *model = qobject_cast(sourceModel()); if (!model) return 0; diff --git a/cockatrice/src/interface/widgets/server/remote/remote_replay_list_tree_widget.cpp b/cockatrice/src/interface/widgets/server/remote/remote_replay_list_tree_widget.cpp index a5e8e46df..5152880d9 100644 --- a/cockatrice/src/interface/widgets/server/remote/remote_replay_list_tree_widget.cpp +++ b/cockatrice/src/interface/widgets/server/remote/remote_replay_list_tree_widget.cpp @@ -48,7 +48,7 @@ int RemoteReplayList_TreeModel::rowCount(const QModelIndex &parent) const if (!parent.isValid()) return replayMatches.size(); - MatchNode *matchNode = dynamic_cast(static_cast(parent.internalPointer())); + auto *matchNode = dynamic_cast(static_cast(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(static_cast(index.internalPointer())); + auto *replayNode = dynamic_cast(static_cast(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(static_cast(index.internalPointer())); + auto *matchNode = dynamic_cast(static_cast(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(static_cast(parent.internalPointer())); + auto *matchNode = dynamic_cast(static_cast(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(static_cast(ind.internalPointer())); + auto *replayNode = dynamic_cast(static_cast(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(static_cast(index.internalPointer())); + auto *node = dynamic_cast(static_cast(index.internalPointer())); if (!node) return 0; return &node->getReplayInfo(); diff --git a/cockatrice/src/interface/widgets/server/user/user_context_menu.cpp b/cockatrice/src/interface/widgets/server/user/user_context_menu.cpp index a255a38d5..cb7d2eca9 100644 --- a/cockatrice/src/interface/widgets/server/user/user_context_menu.cpp +++ b/cockatrice/src/interface/widgets/server/user/user_context_menu.cpp @@ -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(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(parent())); + auto *dlg = new BanDialog(response.user_info(), static_cast(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(parent())); + auto *dlg = new WarningDialog(user, clientid, static_cast(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(sender()); + auto *dlg = static_cast(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(sender()); + auto *dlg = static_cast(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(parent()), Qt::Dialog | Qt::WindowTitleHint | Qt::CustomizeWindowHint | Qt::WindowCloseButtonHint); infoWidget->setAttribute(Qt::WA_DeleteOnClose); diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.cpp index 95754e8ad..208a5f717 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.cpp @@ -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); diff --git a/cockatrice/src/interface/widgets/tabs/tab_admin.cpp b/cockatrice/src/interface/widgets/tabs/tab_admin.cpp index 3ddb53579..533e1cc83 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_admin.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_admin.cpp @@ -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); diff --git a/cockatrice/src/interface/widgets/tabs/tab_game.cpp b/cockatrice/src/interface/widgets/tabs/tab_game.cpp index 02d176cef..8f187575c 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_game.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_game.cpp @@ -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); diff --git a/cockatrice/src/interface/widgets/tabs/tab_message.cpp b/cockatrice/src/interface/widgets/tabs/tab_message.cpp index f1a942f16..bd0457990 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_message.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_message.cpp @@ -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); } diff --git a/cockatrice/src/interface/widgets/tabs/tab_room.cpp b/cockatrice/src/interface/widgets/tabs/tab_room.cpp index e1fcca216..92e38662b 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_room.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_room.cpp @@ -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); } diff --git a/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp b/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp index 6d6f2d4ec..6d550be01 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp @@ -931,7 +931,7 @@ void TabSupervisor::deckEditorClosed(AbstractTabDeckEditor *tab) void TabSupervisor::tabUserEvent(bool globalEvent) { - Tab *tab = static_cast(sender()); + auto *tab = static_cast(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(widget(index)); + auto *tab = static_cast(widget(index)); if (tab->getContentsChanged()) { setTabIcon(index, QIcon()); tab->setContentsChanged(false); diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp index cdb9a8d0c..64eed8b16 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp @@ -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(listRoot->at(i)); - if (!currentZone) - continue; - for (int j = 0; j < currentZone->size(); j++) { - DecklistCardNode *currentCard = dynamic_cast(currentZone->at(j)); - if (!currentCard) - continue; - createNameFilter(currentCard->getName()); - } + QList cardsInDeck = decklist->getCardNodes(); + + for (auto currentCard : cardsInDeck) { + createNameFilter(currentCard->getName()); } + updateFilterModel(); } diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp index ae9cb10ef..b1db1cf37 100644 --- a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp @@ -83,28 +83,15 @@ QList VisualDeckEditorSampleHandWidget::getRandomCards(int amountToGe DeckList *decklist = deckListModel->getDeckList(); if (!decklist) return randomCards; - InnerDecklistNode *listRoot = decklist->getRoot(); - if (!listRoot) - return randomCards; + + QList 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(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(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); } } } diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp index ca380bc22..df401e247 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp @@ -232,17 +232,12 @@ void DeckPreviewWidget::updateBannerCardComboBox() // Prepare the new items with deduplication QSet> bannerCardSet; - InnerDecklistNode *listRoot = deckLoader->getDeckList()->getRoot(); - for (auto i : *listRoot) { - auto *currentZone = dynamic_cast(i); - for (auto j : *currentZone) { - auto *currentCard = dynamic_cast(j); - if (!currentCard) - continue; - for (int k = 0; k < currentCard->getNumber(); ++k) { - bannerCardSet.insert(QPair(currentCard->getName(), currentCard->getCardProviderId())); - } + QList cardsInDeck = deckLoader->getDeckList()->getCardNodes(); + + for (auto currentCard : cardsInDeck) { + for (int k = 0; k < currentCard->getNumber(); ++k) { + bannerCardSet.insert(QPair(currentCard->getName(), currentCard->getCardProviderId())); } } diff --git a/dbconverter/src/mocks.cpp b/dbconverter/src/mocks.cpp index c54d2b9c2..75195fc96 100644 --- a/dbconverter/src/mocks.cpp +++ b/dbconverter/src/mocks.cpp @@ -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 */) diff --git a/doc/doxygen-extra-pages/developer_documentation/card_database_schema_and_parsing.md b/doc/doxygen-extra-pages/developer_documentation/card_database_schema_and_parsing.md new file mode 100644 index 000000000..6c5e3512b --- /dev/null +++ b/doc/doxygen-extra-pages/developer_documentation/card_database_schema_and_parsing.md @@ -0,0 +1 @@ +@page card_database_schema_and_parsing Card Database Schema and Parsing \ No newline at end of file diff --git a/doc/doxygen-extra-pages/developer_documentation/displaying_cards.md b/doc/doxygen-extra-pages/developer_documentation/displaying_cards.md new file mode 100644 index 000000000..6a43097c5 --- /dev/null +++ b/doc/doxygen-extra-pages/developer_documentation/displaying_cards.md @@ -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 + + + diff --git a/doc/doxygen-extra-pages/developer_documentation/index.md b/doc/doxygen-extra-pages/developer_documentation/index.md index 121ee7c6f..b26faf836 100644 --- a/doc/doxygen-extra-pages/developer_documentation/index.md +++ b/doc/doxygen-extra-pages/developer_documentation/index.md @@ -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 \ No newline at end of file diff --git a/doc/doxygen-extra-pages/developer_documentation/loading_card_pictures.md b/doc/doxygen-extra-pages/developer_documentation/loading_card_pictures.md new file mode 100644 index 000000000..b606f9e4b --- /dev/null +++ b/doc/doxygen-extra-pages/developer_documentation/loading_card_pictures.md @@ -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 \ No newline at end of file diff --git a/doc/doxygen-extra-pages/developer_documentation/primer_cards.md b/doc/doxygen-extra-pages/developer_documentation/primer_cards.md new file mode 100644 index 000000000..8f79adbc9 --- /dev/null +++ b/doc/doxygen-extra-pages/developer_documentation/primer_cards.md @@ -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. \ No newline at end of file diff --git a/doc/doxygen-extra-pages/developer_documentation/querying_the_card_database.md b/doc/doxygen-extra-pages/developer_documentation/querying_the_card_database.md new file mode 100644 index 000000000..b2812b0ad --- /dev/null +++ b/doc/doxygen-extra-pages/developer_documentation/querying_the_card_database.md @@ -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. \ No newline at end of file diff --git a/doc/doxygen-extra-pages/user_documentation/deck_management/creating_decks.md b/doc/doxygen-extra-pages/user_documentation/deck_management/creating_decks.md new file mode 100644 index 000000000..0de587b64 --- /dev/null +++ b/doc/doxygen-extra-pages/user_documentation/deck_management/creating_decks.md @@ -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 \ No newline at end of file diff --git a/doc/doxygen-extra-pages/user_documentation/deck_management/editing_decks.md b/doc/doxygen-extra-pages/user_documentation/deck_management/editing_decks.md new file mode 100644 index 000000000..d8b96dac3 --- /dev/null +++ b/doc/doxygen-extra-pages/user_documentation/deck_management/editing_decks.md @@ -0,0 +1,5 @@ +@page editing_decks Editing Decks + +@subpage editing_decks_classic + +@subpage editing_decks_visual \ No newline at end of file diff --git a/doc/doxygen-extra-pages/user_documentation/deck_management/editing_decks_classic.md b/doc/doxygen-extra-pages/user_documentation/deck_management/editing_decks_classic.md new file mode 100644 index 000000000..f9f2239bc --- /dev/null +++ b/doc/doxygen-extra-pages/user_documentation/deck_management/editing_decks_classic.md @@ -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 + diff --git a/doc/doxygen-extra-pages/user_documentation/deck_management/editing_decks_printings.md b/doc/doxygen-extra-pages/user_documentation/deck_management/editing_decks_printings.md new file mode 100644 index 000000000..27ade3f63 --- /dev/null +++ b/doc/doxygen-extra-pages/user_documentation/deck_management/editing_decks_printings.md @@ -0,0 +1 @@ +@page editing_decks_printings Printing Selector \ No newline at end of file diff --git a/doc/doxygen-extra-pages/user_documentation/deck_management/editing_decks_visual.md b/doc/doxygen-extra-pages/user_documentation/deck_management/editing_decks_visual.md new file mode 100644 index 000000000..adef7335f --- /dev/null +++ b/doc/doxygen-extra-pages/user_documentation/deck_management/editing_decks_visual.md @@ -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. \ No newline at end of file diff --git a/doc/doxygen-extra-pages/user_documentation/deck_management/exporting_decks.md b/doc/doxygen-extra-pages/user_documentation/deck_management/exporting_decks.md new file mode 100644 index 000000000..644336681 --- /dev/null +++ b/doc/doxygen-extra-pages/user_documentation/deck_management/exporting_decks.md @@ -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. \ No newline at end of file diff --git a/doc/doxygen-extra-pages/user_documentation/deck_management/importing_decks.md b/doc/doxygen-extra-pages/user_documentation/deck_management/importing_decks.md new file mode 100644 index 000000000..e9626d57f --- /dev/null +++ b/doc/doxygen-extra-pages/user_documentation/deck_management/importing_decks.md @@ -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. \ No newline at end of file diff --git a/doc/doxygen-extra-pages/user_documentation/index.md b/doc/doxygen-extra-pages/user_documentation/index.md index 97c6f375c..8a5c88c37 100644 --- a/doc/doxygen-extra-pages/user_documentation/index.md +++ b/doc/doxygen-extra-pages/user_documentation/index.md @@ -1,4 +1,13 @@ @page user_reference User Reference @subpage search_syntax_help -@subpage deck_search_syntax_help \ No newline at end of file + +@subpage deck_search_syntax_help + +@subpage creating_decks + +@subpage importing_decks + +@subpage editing_decks + +@subpage exporting_decks \ No newline at end of file diff --git a/doc/doxygen-images/classic_database_display.png b/doc/doxygen-images/classic_database_display.png new file mode 100644 index 000000000..1f4773514 Binary files /dev/null and b/doc/doxygen-images/classic_database_display.png differ diff --git a/doc/doxygen-images/classic_database_display_add_buttons.png b/doc/doxygen-images/classic_database_display_add_buttons.png new file mode 100644 index 000000000..b9cb7cd32 Binary files /dev/null and b/doc/doxygen-images/classic_database_display_add_buttons.png differ diff --git a/doc/doxygen-images/classic_deck_editor.png b/doc/doxygen-images/classic_deck_editor.png new file mode 100644 index 000000000..2e41b6e1d Binary files /dev/null and b/doc/doxygen-images/classic_deck_editor.png differ diff --git a/doc/doxygen-images/deck_dock_deck_list.png b/doc/doxygen-images/deck_dock_deck_list.png new file mode 100644 index 000000000..d01a5334e Binary files /dev/null and b/doc/doxygen-images/deck_dock_deck_list.png differ diff --git a/doc/doxygen-images/deck_dock_deck_list_buttons.png b/doc/doxygen-images/deck_dock_deck_list_buttons.png new file mode 100644 index 000000000..cb66b41da Binary files /dev/null and b/doc/doxygen-images/deck_dock_deck_list_buttons.png differ diff --git a/doc/doxygen-images/deck_dock_deck_list_group_by.png b/doc/doxygen-images/deck_dock_deck_list_group_by.png new file mode 100644 index 000000000..ba23b1fb5 Binary files /dev/null and b/doc/doxygen-images/deck_dock_deck_list_group_by.png differ diff --git a/doc/doxygen-images/deckeditordeckdockwidget.png b/doc/doxygen-images/deckeditordeckdockwidget.png new file mode 100644 index 000000000..d71720db7 Binary files /dev/null and b/doc/doxygen-images/deckeditordeckdockwidget.png differ diff --git a/libcockatrice_card/libcockatrice/card/card_info.h b/libcockatrice_card/libcockatrice/card/card_info.h index c2a8453d9..b72d1fbbe 100644 --- a/libcockatrice_card/libcockatrice/card/card_info.h +++ b/libcockatrice_card/libcockatrice/card/card_info.h @@ -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; } diff --git a/libcockatrice_card/libcockatrice/card/database/card_database.h b/libcockatrice_card/libcockatrice/card/database/card_database.h index 37e53d16e..cb46d6d00 100644 --- a/libcockatrice_card/libcockatrice/card/database/card_database.h +++ b/libcockatrice_card/libcockatrice/card/database/card_database.h @@ -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 diff --git a/libcockatrice_card/libcockatrice/card/database/card_database_loader.h b/libcockatrice_card/libcockatrice/card/database/card_database_loader.h index 7bf02cc9a..db1376ac7 100644 --- a/libcockatrice_card/libcockatrice/card/database/card_database_loader.h +++ b/libcockatrice_card/libcockatrice/card/database/card_database_loader.h @@ -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 diff --git a/libcockatrice_card/libcockatrice/card/printing/exact_card.h b/libcockatrice_card/libcockatrice/card/printing/exact_card.h index b36233bd5..01c61a3a1 100644 --- a/libcockatrice_card/libcockatrice/card/printing/exact_card.h +++ b/libcockatrice_card/libcockatrice/card/printing/exact_card.h @@ -5,7 +5,7 @@ /** * @class ExactCard - * @ingroup Cards + * @ingroup CardPrintings * * @brief Represents a specific card instance, defined by its CardInfo * and a particular printing. diff --git a/libcockatrice_card/libcockatrice/card/printing/printing_info.h b/libcockatrice_card/libcockatrice/card/printing/printing_info.h index cacb98068..37d7a15e2 100644 --- a/libcockatrice_card/libcockatrice/card/printing/printing_info.h +++ b/libcockatrice_card/libcockatrice/card/printing/printing_info.h @@ -14,7 +14,7 @@ using SetToPrintingsMap = QMap>; /** * @class PrintingInfo - * @ingroup Cards + * @ingroup CardPrintings * * @brief Represents metadata for a specific variation of a card within a set. * diff --git a/libcockatrice_card/libcockatrice/card/set/card_set.h b/libcockatrice_card/libcockatrice/card/set/card_set.h index b3e35f2db..5fc769bb6 100644 --- a/libcockatrice_card/libcockatrice/card/set/card_set.h +++ b/libcockatrice_card/libcockatrice/card/set/card_set.h @@ -15,7 +15,7 @@ using CardSetPtr = QSharedPointer; /** * @class CardSet - * @ingroup Cards + * @ingroup CardSets * * @brief A collection of cards grouped under a common identifier. * diff --git a/libcockatrice_card/libcockatrice/card/set/card_set_comparator.h b/libcockatrice_card/libcockatrice/card/set/card_set_comparator.h index 53e2cb97d..96f1052a9 100644 --- a/libcockatrice_card/libcockatrice/card/set/card_set_comparator.h +++ b/libcockatrice_card/libcockatrice/card/set/card_set_comparator.h @@ -1,6 +1,6 @@ /** * @file card_set_comparator.h - * @ingroup Cards + * @ingroup CardSets * @brief TODO: Document this. */ diff --git a/libcockatrice_card/libcockatrice/card/set/card_set_list.h b/libcockatrice_card/libcockatrice/card/set/card_set_list.h index 2e644a277..21b4a55d4 100644 --- a/libcockatrice_card/libcockatrice/card/set/card_set_list.h +++ b/libcockatrice_card/libcockatrice/card/set/card_set_list.h @@ -8,7 +8,7 @@ /** * @class CardSetList - * @ingroup Cards + * @ingroup CardSets * * @brief A list-like container for CardSet objects with extended management methods. * diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp index b2fbdb028..43a474ca8 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.cpp @@ -563,6 +563,29 @@ QList DeckList::getCardRefList() const return result; } +QList DeckList::getCardNodes(const QStringList &restrictToZones) const +{ + QList result; + + for (auto *node : *root) { + auto *zoneNode = dynamic_cast(node); + if (zoneNode == nullptr) { + continue; + } + if (!restrictToZones.isEmpty() && !restrictToZones.contains(node->getName())) { + continue; + } + for (auto *cardNode : *zoneNode) { + auto *cardCardNode = dynamic_cast(cardNode); + if (cardCardNode != nullptr) { + result.append(cardCardNode); + } + } + } + + return result; +} + int DeckList::getSideboardSize() const { int size = 0; diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h index 1d2ab6e01..2a03c4374 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h +++ b/libcockatrice_deck_list/libcockatrice/deck_list/deck_list.h @@ -289,6 +289,7 @@ public: } QStringList getCardList() const; QList getCardRefList() const; + QList getCardNodes(const QStringList &restrictToZones = QStringList()) const; int getSideboardSize() const; InnerDecklistNode *getRoot() const { diff --git a/libcockatrice_deck_list/libcockatrice/deck_list/inner_deck_list_node.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/inner_deck_list_node.cpp index c91b3ea14..eca58963a 100644 --- a/libcockatrice_deck_list/libcockatrice/deck_list/inner_deck_list_node.cpp +++ b/libcockatrice_deck_list/libcockatrice/deck_list/inner_deck_list_node.cpp @@ -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()); diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.cpp index 5ac0a26ad..1c81b6823 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.cpp @@ -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(targetItem); + const auto *arrowTargetPlayer = qobject_cast(targetItem); if (arrowTargetPlayer != nullptr) { arrowInfo->set_target_player_id(arrowTargetPlayer->getPlayerId()); } else { - const Server_Card *arrowTargetCard = qobject_cast(targetItem); + const auto *arrowTargetCard = qobject_cast(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()); diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_arrow.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_arrow.cpp index e077416bc..9433d8d3f 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_arrow.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_arrow.cpp @@ -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(targetItem); + auto *targetCard = qobject_cast(targetItem); if (targetCard) { info->set_target_player_id(targetCard->getZone()->getPlayer()->getPlayerId()); info->set_target_zone(targetCard->getZone()->getName().toStdString()); diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp index 647d1d078..82b24ccde 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp @@ -589,7 +589,7 @@ void Server_Game::removeArrowsRelatedToPlayer(GameEventStorage &ges, Server_Abst for (Server_AbstractPlayer *anyPlayer : getPlayers().values()) { QList toDelete; for (auto *arrow : anyPlayer->getArrows().values()) { - Server_Card *targetCard = qobject_cast(arrow->getTargetItem()); + auto *targetCard = qobject_cast(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); diff --git a/libcockatrice_network/libcockatrice/network/server/remote/server_abstractuserinterface.cpp b/libcockatrice_network/libcockatrice/network/server/remote/server_abstractuserinterface.cpp index 96626a4bf..e228f0128 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/server_abstractuserinterface.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/server_abstractuserinterface.cpp @@ -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); diff --git a/libcockatrice_network/libcockatrice/network/server/remote/server_protocolhandler.cpp b/libcockatrice_network/libcockatrice/network/server/remote/server_protocolhandler.cpp index 92dcf4230..bfd8d113c 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/server_protocolhandler.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/server_protocolhandler.cpp @@ -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::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 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 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); diff --git a/libcockatrice_network/libcockatrice/network/server/remote/server_room.cpp b/libcockatrice_network/libcockatrice/network/server/remote/server_room.cpp index e94b255bd..bfa8912b1 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/server_room.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/server_room.cpp @@ -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")) diff --git a/libcockatrice_settings/libcockatrice/settings/card_database_settings.cpp b/libcockatrice_settings/libcockatrice/settings/card_database_settings.cpp index f0b8d58b1..79738f1cd 100644 --- a/libcockatrice_settings/libcockatrice/settings/card_database_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/card_database_settings.cpp @@ -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) { } diff --git a/libcockatrice_settings/libcockatrice/settings/card_override_settings.cpp b/libcockatrice_settings/libcockatrice/settings/card_override_settings.cpp index 2e2c7607d..894358be6 100644 --- a/libcockatrice_settings/libcockatrice/settings/card_override_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/card_override_settings.cpp @@ -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(); } \ No newline at end of file diff --git a/libcockatrice_settings/libcockatrice/settings/debug_settings.cpp b/libcockatrice_settings/libcockatrice/settings/debug_settings.cpp index f6f12f60e..084696dc1 100644 --- a/libcockatrice_settings/libcockatrice/settings/debug_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/debug_settings.cpp @@ -3,7 +3,7 @@ #include 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() diff --git a/libcockatrice_settings/libcockatrice/settings/download_settings.cpp b/libcockatrice_settings/libcockatrice/settings/download_settings.cpp index adbe26363..ad4b81ec5 100644 --- a/libcockatrice_settings/libcockatrice/settings/download_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/download_settings.cpp @@ -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"); } diff --git a/libcockatrice_settings/libcockatrice/settings/game_filters_settings.cpp b/libcockatrice_settings/libcockatrice/settings/game_filters_settings.cpp index e82b18a47..2da9b9bc3 100644 --- a/libcockatrice_settings/libcockatrice/settings/game_filters_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/game_filters_settings.cpp @@ -4,7 +4,7 @@ #include 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(); } \ No newline at end of file diff --git a/libcockatrice_settings/libcockatrice/settings/layouts_settings.cpp b/libcockatrice_settings/libcockatrice/settings/layouts_settings.cpp index 5791cbfe9..4b1400e0c 100644 --- a/libcockatrice_settings/libcockatrice/settings/layouts_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/layouts_settings.cpp @@ -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) { } diff --git a/libcockatrice_settings/libcockatrice/settings/message_settings.cpp b/libcockatrice_settings/libcockatrice/settings/message_settings.cpp index 786ea9cd1..761c94484 100644 --- a/libcockatrice_settings/libcockatrice/settings/message_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/message_settings.cpp @@ -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)); } diff --git a/libcockatrice_settings/libcockatrice/settings/recents_settings.cpp b/libcockatrice_settings/libcockatrice/settings/recents_settings.cpp index 5bbec3c8c..e64dd7494 100644 --- a/libcockatrice_settings/libcockatrice/settings/recents_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/recents_settings.cpp @@ -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(); } diff --git a/libcockatrice_settings/libcockatrice/settings/servers_settings.cpp b/libcockatrice_settings/libcockatrice/settings/servers_settings.cpp index 01a6c795b..5f69f47c9 100644 --- a/libcockatrice_settings/libcockatrice/settings/servers_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/servers_settings.cpp @@ -4,34 +4,34 @@ #include 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(); } diff --git a/libcockatrice_settings/libcockatrice/settings/settings_manager.cpp b/libcockatrice_settings/libcockatrice/settings/settings_manager.cpp index dc2da741e..ede5a2027 100644 --- a/libcockatrice_settings/libcockatrice/settings/settings_manager.cpp +++ b/libcockatrice_settings/libcockatrice/settings/settings_manager.cpp @@ -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()) { diff --git a/libcockatrice_settings/libcockatrice/settings/settings_manager.h b/libcockatrice_settings/libcockatrice/settings/settings_manager.h index 1a24ecb28..a72094b4e 100644 --- a/libcockatrice_settings/libcockatrice/settings/settings_manager.h +++ b/libcockatrice_settings/libcockatrice/settings/settings_manager.h @@ -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 diff --git a/oracle/CMakeLists.txt b/oracle/CMakeLists.txt index 36001a566..3bb4de5df 100644 --- a/oracle/CMakeLists.txt +++ b/oracle/CMakeLists.txt @@ -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 diff --git a/oracle/src/oracleimporter.cpp b/oracle/src/oracleimporter.cpp index 98149e0b0..049dbaede 100644 --- a/oracle/src/oracleimporter.cpp +++ b/oracle/src/oracleimporter.cpp @@ -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 #include #include -#include #include #include @@ -457,14 +458,17 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, 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); } diff --git a/oracle/src/oraclewizard.cpp b/oracle/src/oraclewizard.cpp index 6b463d5de..46e4722c6 100644 --- a/oracle/src/oraclewizard.cpp +++ b/oracle/src/oraclewizard.cpp @@ -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 -#include #include -#include -#include #include #include -#include #include -#include -#include #include -#include #include -#include #include -#include -#include #include #include -#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(&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.") + "
" + - tr("You can now start using Cockatrice with the newly updated cards.") + "

" + - 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::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(total)); - progressBar->setValue(static_cast(received)); - } - progressLabel->setText(tr("Downloading (%1MB)").arg((int)received / (1024 * 1024))); -} - -void LoadSetsPage::actDownloadFinishedSetsFile() -{ - // check for a reply - auto *reply = dynamic_cast(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::question( - this, tr("Error"), message + "
" + 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:") + "
" + - 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("" + tr("Import finished: %1 cards.").arg(wizard()->importer->getCardList().size()) + - ""); - } 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:") + "
" + - 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:") + "
" + - SettingsCache::instance().getSpoilerCardDatabasePath()); - defaultPathCheckBox->setText(tr("Save to a custom path (not recommended)")); -} diff --git a/oracle/src/oraclewizard.h b/oracle/src/oraclewizard.h index fe0bf1a21..c913094e3 100644 --- a/oracle/src/oraclewizard.h +++ b/oracle/src/oraclewizard.h @@ -1,9 +1,6 @@ #ifndef ORACLEWIZARD_H #define ORACLEWIZARD_H -#include -#include -#include #include #include @@ -20,8 +17,6 @@ class QVBoxLayout; class OracleImporter; class QSettings; -#include "pagetemplates.h" - class OracleWizard : public QWizard { Q_OBJECT @@ -84,133 +79,4 @@ protected: void changeEvent(QEvent *event) override; }; -class IntroPage : public OracleWizardPage -{ - Q_OBJECT -public: - explicit IntroPage(QWidget *parent = nullptr); - void retranslateUi() override; - -private: - QStringList findQmFiles(); - QString languageName(const QString &lang); - -private: - QLabel *label, *languageLabel, *versionLabel; - QComboBox *languageBox; - -private slots: - void languageBoxChanged(int index); - -protected slots: - void initializePage() override; -}; - -class OutroPage : public OracleWizardPage -{ - Q_OBJECT -public: - explicit OutroPage(QWidget * = nullptr) - { - } - void retranslateUi() override; - -protected: - void initializePage() override; -}; - -class LoadSetsPage : public OracleWizardPage -{ - Q_OBJECT -public: - explicit LoadSetsPage(QWidget *parent = nullptr); - void retranslateUi() override; - -protected: - void initializePage() override; - bool validatePage() override; - void readSetsFromByteArray(QByteArray _data); - void readSetsFromByteArrayRef(QByteArray &_data); - void downloadSetsFile(const QUrl &url); - -private: - QRadioButton *urlRadioButton; - QRadioButton *fileRadioButton; - QLineEdit *urlLineEdit; - QLineEdit *fileLineEdit; - QPushButton *urlButton; - QPushButton *fileButton; - QLabel *progressLabel; - QProgressBar *progressBar; - - QFutureWatcher watcher; - QFuture future; - QByteArray jsonData; - -private slots: - void actLoadSetsFile(); - void actRestoreDefaultUrl(); - void actDownloadProgressSetsFile(qint64 received, qint64 total); - void actDownloadFinishedSetsFile(); - void importFinished(); - void zipDownloadFailed(const QString &message); -}; - -class SaveSetsPage : public OracleWizardPage -{ - Q_OBJECT -public: - explicit SaveSetsPage(QWidget *parent = nullptr); - void retranslateUi() override; - -private: - QTextEdit *messageLog; - QCheckBox *defaultPathCheckBox; - QLabel *pathLabel; - QLabel *saveLabel; - -protected: - void initializePage() override; - void cleanupPage() override; - bool validatePage() override; - -private slots: - void updateTotalProgress(int cardsImported, int setIndex, const QString &setName); -}; - -class LoadSpoilersPage : public SimpleDownloadFilePage -{ - Q_OBJECT -public: - explicit LoadSpoilersPage(QWidget * = nullptr) - { - } - void retranslateUi() override; - -protected: - QString getDefaultUrl() override; - QString getCustomUrlSettingsKey() override; - QString getDefaultSavePath() override; - QString getWindowTitle() override; - QString getFileType() override; -}; - -class LoadTokensPage : public SimpleDownloadFilePage -{ - Q_OBJECT -public: - explicit LoadTokensPage(QWidget * = nullptr) - { - } - void retranslateUi() override; - -protected: - QString getDefaultUrl() override; - QString getCustomUrlSettingsKey() override; - QString getDefaultSavePath() override; - QString getWindowTitle() override; - QString getFileType() override; - void initializePage() override; -}; - #endif diff --git a/oracle/src/pages.cpp b/oracle/src/pages.cpp new file mode 100644 index 000000000..9b2f5ce7c --- /dev/null +++ b/oracle/src/pages.cpp @@ -0,0 +1,722 @@ +#include "pages.h" + +#include "client/settings/cache_settings.h" +#include "main.h" +#include "oracleimporter.h" +#include "oraclewizard.h" +#include "pages.h" +#include "pagetemplates.h" +#include "version_string.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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" + +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(&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.") + "
" + + tr("You can now start using Cockatrice with the newly updated cards.") + "

" + + 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::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 data = versionReply->readAll(); + QJsonParseError jsonError{}; + auto jsonResponse = QJsonDocument::fromJson(data, &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(total)); + progressBar->setValue(static_cast(received)); + } + progressLabel->setText(tr("Downloading (%1MB)").arg((int)received / (1024 * 1024))); +} + +void LoadSetsPage::actDownloadFinishedSetsFile() +{ + // check for a reply + auto *reply = dynamic_cast(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::question( + this, tr("Error"), message + "
" + 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:") + "
" + + 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("" + tr("Import finished: %1 cards.").arg(wizard()->importer->getCardList().size()) + + ""); + } 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:") + "
" + + 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:") + "
" + + SettingsCache::instance().getSpoilerCardDatabasePath()); + defaultPathCheckBox->setText(tr("Save to a custom path (not recommended)")); +} \ No newline at end of file diff --git a/oracle/src/pages.h b/oracle/src/pages.h new file mode 100644 index 000000000..6a88b6099 --- /dev/null +++ b/oracle/src/pages.h @@ -0,0 +1,154 @@ +#ifndef COCKATRICE_PAGES_H +#define COCKATRICE_PAGES_H + +#include "pagetemplates.h" + +#include +#include +#include +#include +#include + +class QCheckBox; +class QGroupBox; +class QComboBox; +class QLabel; +class QLineEdit; +class QRadioButton; +class QProgressBar; +class QNetworkAccessManager; +class QTextEdit; +class QVBoxLayout; +class OracleImporter; +class QSettings; + +class IntroPage : public OracleWizardPage +{ + Q_OBJECT +public: + explicit IntroPage(QWidget *parent = nullptr); + void retranslateUi() override; + +private: + QStringList findQmFiles(); + QString languageName(const QString &lang); + +private: + QLabel *label, *languageLabel, *versionLabel; + QComboBox *languageBox; + +private slots: + void languageBoxChanged(int index); + +protected slots: + void initializePage() override; +}; + +class OutroPage : public OracleWizardPage +{ + Q_OBJECT +public: + explicit OutroPage(QWidget * = nullptr) + { + } + void retranslateUi() override; + +protected: + void initializePage() override; +}; + +class LoadSetsPage : public OracleWizardPage +{ + Q_OBJECT +public: + explicit LoadSetsPage(QWidget *parent = nullptr); + void retranslateUi() override; + +protected: + void initializePage() override; + bool validatePage() override; + void readSetsFromByteArray(QByteArray _data); + void readSetsFromByteArrayRef(QByteArray &_data); + void downloadSetsFile(const QUrl &url); + +private: + QRadioButton *urlRadioButton; + QRadioButton *fileRadioButton; + QLineEdit *urlLineEdit; + QLineEdit *fileLineEdit; + QPushButton *urlButton; + QPushButton *fileButton; + QLabel *progressLabel; + QProgressBar *progressBar; + + QFutureWatcher watcher; + QFuture future; + QByteArray jsonData; + +private slots: + void actLoadSetsFile(); + void actRestoreDefaultUrl(); + void actDownloadProgressSetsFile(qint64 received, qint64 total); + void actDownloadFinishedSetsFile(); + void importFinished(); + void zipDownloadFailed(const QString &message); +}; + +class SaveSetsPage : public OracleWizardPage +{ + Q_OBJECT +public: + explicit SaveSetsPage(QWidget *parent = nullptr); + void retranslateUi() override; + +private: + QTextEdit *messageLog; + QCheckBox *defaultPathCheckBox; + QLabel *pathLabel; + QLabel *saveLabel; + +protected: + void initializePage() override; + void cleanupPage() override; + bool validatePage() override; + +private slots: + void updateTotalProgress(int cardsImported, int setIndex, const QString &setName); +}; + +class LoadSpoilersPage : public SimpleDownloadFilePage +{ + Q_OBJECT +public: + explicit LoadSpoilersPage(QWidget * = nullptr) + { + } + void retranslateUi() override; + +protected: + QString getDefaultUrl() override; + QString getCustomUrlSettingsKey() override; + QString getDefaultSavePath() override; + QString getWindowTitle() override; + QString getFileType() override; +}; + +class LoadTokensPage : public SimpleDownloadFilePage +{ + Q_OBJECT +public: + explicit LoadTokensPage(QWidget * = nullptr) + { + } + void retranslateUi() override; + +protected: + QString getDefaultUrl() override; + QString getCustomUrlSettingsKey() override; + QString getDefaultSavePath() override; + QString getWindowTitle() override; + QString getFileType() override; + void initializePage() override; +}; + +#endif // COCKATRICE_PAGES_H