diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 2cf9ffd98..04a7c52c4 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -35,6 +35,7 @@ set(cockatrice_SOURCES src/deck/deck_list_model.cpp src/deck/deck_stats_interface.cpp src/dialogs/dlg_connect.cpp + src/dialogs/dlg_convert_deck_to_cod_format.cpp src/dialogs/dlg_create_token.cpp src/dialogs/dlg_create_game.cpp src/dialogs/dlg_edit_avatar.cpp diff --git a/cockatrice/src/client/tabs/tab_deck_storage.cpp b/cockatrice/src/client/tabs/tab_deck_storage.cpp index 4435e5a22..9d35819fc 100644 --- a/cockatrice/src/client/tabs/tab_deck_storage.cpp +++ b/cockatrice/src/client/tabs/tab_deck_storage.cpp @@ -4,7 +4,6 @@ #include "../../server/pending_command.h" #include "../../server/remote/remote_decklist_tree_widget.h" #include "../../settings/cache_settings.h" -#include "../game_logic/abstract_client.h" #include "../get_text_with_max.h" #include "decklist.h" #include "pb/command_deck_del.pb.h" @@ -31,7 +30,9 @@ #include #include -TabDeckStorage::TabDeckStorage(TabSupervisor *_tabSupervisor, AbstractClient *_client) +TabDeckStorage::TabDeckStorage(TabSupervisor *_tabSupervisor, + AbstractClient *_client, + const ServerInfo_User *currentUserInfo) : Tab(_tabSupervisor), client(_client) { localDirModel = new QFileSystemModel(this); @@ -151,6 +152,10 @@ TabDeckStorage::TabDeckStorage(TabSupervisor *_tabSupervisor, AbstractClient *_c QWidget *mainWidget = new QWidget(this); mainWidget->setLayout(hbox); setCentralWidget(mainWidget); + + connect(client, &AbstractClient::userInfoChanged, this, &TabDeckStorage::handleConnected); + connect(client, &AbstractClient::statusChanged, this, &TabDeckStorage::handleConnectionChanged); + setRemoteEnabled(currentUserInfo && currentUserInfo->user_level() & ServerInfo_User::IsRegistered); } void TabDeckStorage::retranslateUi() @@ -187,6 +192,36 @@ QString TabDeckStorage::getTargetPath() const } } +void TabDeckStorage::handleConnected(const ServerInfo_User &userInfo) +{ + setRemoteEnabled(userInfo.user_level() & ServerInfo_User::IsRegistered); +} + +/** + * This is only responsible for handling the disconnect. The connect is already handled elsewhere + */ +void TabDeckStorage::handleConnectionChanged(ClientStatus status) +{ + if (status == StatusDisconnected) { + setRemoteEnabled(false); + } +} + +void TabDeckStorage::setRemoteEnabled(bool enabled) +{ + aUpload->setEnabled(enabled); + aOpenRemoteDeck->setEnabled(enabled); + aDownload->setEnabled(enabled); + aNewFolder->setEnabled(enabled); + aDeleteRemoteDeck->setEnabled(enabled); + + if (enabled) { + serverDirView->refreshTree(); + } else { + serverDirView->clearTree(); + } +} + void TabDeckStorage::actLocalDoubleClick(const QModelIndex &curLeft) { if (!localDirModel->isDir(curLeft)) { diff --git a/cockatrice/src/client/tabs/tab_deck_storage.h b/cockatrice/src/client/tabs/tab_deck_storage.h index 774b9f609..748852ec2 100644 --- a/cockatrice/src/client/tabs/tab_deck_storage.h +++ b/cockatrice/src/client/tabs/tab_deck_storage.h @@ -2,8 +2,10 @@ #define TAB_DECK_STORAGE_H #include "../../server/remote/remote_decklist_tree_widget.h" +#include "../game_logic/abstract_client.h" #include "tab.h" +class ServerInfo_User; class AbstractClient; class QTreeView; class QFileSystemModel; @@ -31,12 +33,17 @@ private: QAction *aOpenRemoteDeck, *aDownload, *aNewFolder, *aDeleteRemoteDeck; QString getTargetPath() const; + void setRemoteEnabled(bool enabled); + void uploadDeck(const QString &filePath, const QString &targetPath); void deleteRemoteDeck(const RemoteDeckList_TreeModel::Node *node); void downloadNodeAtIndex(const QModelIndex &curLeft, const QModelIndex &curRight); private slots: + void handleConnected(const ServerInfo_User &userInfo); + void handleConnectionChanged(ClientStatus status); + void actLocalDoubleClick(const QModelIndex &curLeft); void actOpenLocalDeck(); @@ -63,7 +70,7 @@ private slots: void deleteDeckFinished(const Response &response, const CommandContainer &commandContainer); public: - TabDeckStorage(TabSupervisor *_tabSupervisor, AbstractClient *_client); + TabDeckStorage(TabSupervisor *_tabSupervisor, AbstractClient *_client, const ServerInfo_User *currentUserInfo); void retranslateUi() override; QString getTabText() const override { diff --git a/cockatrice/src/client/tabs/tab_supervisor.cpp b/cockatrice/src/client/tabs/tab_supervisor.cpp index 3f4612347..2f5c873a2 100644 --- a/cockatrice/src/client/tabs/tab_supervisor.cpp +++ b/cockatrice/src/client/tabs/tab_supervisor.cpp @@ -279,6 +279,7 @@ void TabSupervisor::initStartupTabs() addDeckEditorTab(nullptr); checkAndTrigger(aTabVisualDeckStorage, SettingsCache::instance().getTabVisualDeckStorageOpen()); + checkAndTrigger(aTabDeckStorage, SettingsCache::instance().getTabDeckStorageOpen()); } /** @@ -334,6 +335,7 @@ void TabSupervisor::resetTabsMenu() tabsMenu->addAction(aTabDeckEditor); tabsMenu->addSeparator(); tabsMenu->addAction(aTabVisualDeckStorage); + tabsMenu->addAction(aTabDeckStorage); } void TabSupervisor::start(const ServerInfo_User &_userInfo) @@ -355,10 +357,8 @@ void TabSupervisor::start(const ServerInfo_User &_userInfo) updatePingTime(0, -1); if (userInfo->user_level() & ServerInfo_User::IsRegistered) { - tabsMenu->addAction(aTabDeckStorage); tabsMenu->addAction(aTabReplays); - checkAndTrigger(aTabDeckStorage, SettingsCache::instance().getTabDeckStorageOpen()); checkAndTrigger(aTabReplays, SettingsCache::instance().getTabReplaysOpen()); } @@ -379,7 +379,6 @@ void TabSupervisor::startLocal(const QList &_clients) resetTabsMenu(); tabAccount = nullptr; - tabDeckStorage = nullptr; tabReplays = nullptr; tabAdmin = nullptr; tabLog = nullptr; @@ -416,9 +415,6 @@ void TabSupervisor::stop() if (tabServer) { tabServer->closeRequest(true); } - if (tabDeckStorage) { - tabDeckStorage->closeRequest(true); - } if (tabReplays) { tabReplays->closeRequest(true); } @@ -508,7 +504,7 @@ void TabSupervisor::actTabDeckStorage(bool checked) { SettingsCache::instance().setTabDeckStorageOpen(checked); if (checked && !tabDeckStorage) { - tabDeckStorage = new TabDeckStorage(this, client); + tabDeckStorage = new TabDeckStorage(this, client, userInfo); connect(tabDeckStorage, &TabDeckStorage::openDeckEditor, this, &TabSupervisor::addDeckEditorTab); myAddTab(tabDeckStorage, aTabDeckStorage); connect(tabDeckStorage, &Tab::closed, this, [this] { diff --git a/cockatrice/src/client/ui/widgets/general/layout_containers/flow_widget.cpp b/cockatrice/src/client/ui/widgets/general/layout_containers/flow_widget.cpp index d95fde106..13e1f15ea 100644 --- a/cockatrice/src/client/ui/widgets/general/layout_containers/flow_widget.cpp +++ b/cockatrice/src/client/ui/widgets/general/layout_containers/flow_widget.cpp @@ -85,15 +85,6 @@ FlowWidget::FlowWidget(QWidget *parent, */ void FlowWidget::addWidget(QWidget *widget_to_add) const { - // Adjust size policy if scrollbars are disabled - // if (scrollArea->horizontalScrollBarPolicy() == Qt::ScrollBarAlwaysOff) { - // widget_to_add->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); - //} - // if (scrollArea->verticalScrollBarPolicy() == Qt::ScrollBarAlwaysOff) { - // widget_to_add->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); - //} - - // Add the widget to the flow layout flowLayout->addWidget(widget_to_add); } @@ -145,7 +136,6 @@ void FlowWidget::resizeEvent(QResizeEvent *event) qCDebug(FlowWidgetSizeLog) << "Got a scrollarea: " << scrollArea->widget()->size(); scrollArea->widget()->adjustSize(); } else { - qCDebug(FlowWidgetSizeLog) << "Rawdogging the container, daddy: " << container->size(); container->adjustSize(); } } diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp index 3572daaee..a188f23b6 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_tag_addition_widget.cpp @@ -1,5 +1,7 @@ #include "deck_preview_tag_addition_widget.h" +#include "../../../../../dialogs/dlg_convert_deck_to_cod_format.h" +#include "../../../../../settings/cache_settings.h" #include "deck_preview_tag_dialog.h" #include @@ -40,11 +42,50 @@ void DeckPreviewTagAdditionWidget::mousePressEvent(QMouseEvent *event) QStringList knownTags = parent->parent->parent->gatherAllTagsFromFlowWidget(); QStringList activeTags = parent->deckLoader->getTags(); - DeckPreviewTagDialog dialog(knownTags, activeTags); - if (dialog.exec() == QDialog::Accepted) { - QStringList updatedTags = dialog.getActiveTags(); - parent->deckLoader->setTags(updatedTags); - parent->deckLoader->saveToFile(parent->parent->filePath, DeckLoader::CockatriceFormat); + bool canAddTags = true; + + if (parent->deckLoader->getLastFileFormat() != DeckLoader::CockatriceFormat) { + canAddTags = false; + // Retrieve saved preference if the prompt is disabled + if (!SettingsCache::instance().getVisualDeckStoragePromptForConversion()) { + if (SettingsCache::instance().getVisualDeckStorageAlwaysConvert()) { + parent->deckLoader->convertToCockatriceFormat(parent->parent->filePath); + parent->parent->filePath = parent->deckLoader->getLastFileName(); + parent->parent->refreshBannerCardText(); + canAddTags = true; + } + } else { + // Show the dialog to the user + DialogConvertDeckToCodFormat conversionDialog(parent); + if (conversionDialog.exec() == QDialog::Accepted) { + parent->deckLoader->convertToCockatriceFormat(parent->parent->filePath); + parent->parent->filePath = parent->deckLoader->getLastFileName(); + parent->parent->refreshBannerCardText(); + canAddTags = true; + + if (conversionDialog.dontAskAgain()) { + SettingsCache::instance().setVisualDeckStoragePromptForConversion(Qt::CheckState::Unchecked); + SettingsCache::instance().setVisualDeckStorageAlwaysConvert(Qt::CheckState::Checked); + } + } else { + SettingsCache::instance().setVisualDeckStorageAlwaysConvert(Qt::CheckState::Unchecked); + + if (conversionDialog.dontAskAgain()) { + SettingsCache::instance().setVisualDeckStoragePromptForConversion(Qt::CheckState::Unchecked); + } else { + SettingsCache::instance().setVisualDeckStoragePromptForConversion(Qt::CheckState::Checked); + } + } + } + } + + if (canAddTags) { + DeckPreviewTagDialog dialog(knownTags, activeTags); + if (dialog.exec() == QDialog::Accepted) { + QStringList updatedTags = dialog.getActiveTags(); + parent->deckLoader->setTags(updatedTags); + parent->deckLoader->saveToFile(parent->parent->filePath, DeckLoader::CockatriceFormat); + } } } diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp index 18bc771d1..59e02ccbc 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp @@ -17,7 +17,7 @@ DeckPreviewWidget::DeckPreviewWidget(VisualDeckStorageWidget *_parent, const QSt deckLoader = new DeckLoader(); connect(deckLoader, &DeckLoader::loadFinished, this, &DeckPreviewWidget::initializeUi); - deckLoader->loadFromFileAsync(filePath, DeckLoader::CockatriceFormat, false); + deckLoader->loadFromFileAsync(filePath, DeckLoader::getFormatFromName(filePath), false); bannerCardDisplayWidget = new DeckPreviewCardPictureWidget(this); @@ -88,6 +88,12 @@ void DeckPreviewWidget::setFilePath(const QString &_filePath) filePath = _filePath; } +void DeckPreviewWidget::refreshBannerCardText() +{ + bannerCardDisplayWidget->setOverlayText( + deckLoader->getName().isEmpty() ? QFileInfo(deckLoader->getLastFileName()).fileName() : deckLoader->getName()); +} + void DeckPreviewWidget::imageClickedEvent(QMouseEvent *event, DeckPreviewCardPictureWidget *instance) { Q_UNUSED(instance); diff --git a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h index 689c94b18..91a2060a3 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h +++ b/cockatrice/src/client/ui/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h @@ -33,6 +33,7 @@ signals: public slots: void setFilePath(const QString &filePath); + void refreshBannerCardText(); void imageClickedEvent(QMouseEvent *event, DeckPreviewCardPictureWidget *instance); void imageDoubleClickedEvent(QMouseEvent *event, DeckPreviewCardPictureWidget *instance); void initializeUi(bool deckLoadSuccess); diff --git a/cockatrice/src/deck/deck_loader.cpp b/cockatrice/src/deck/deck_loader.cpp index bfb8711ea..ed1d8fa71 100644 --- a/cockatrice/src/deck/deck_loader.cpp +++ b/cockatrice/src/deck/deck_loader.cpp @@ -6,6 +6,7 @@ #include "decklist.h" #include +#include #include #include #include @@ -481,6 +482,53 @@ void DeckLoader::saveToStream_DeckZoneCards(QTextStream &out, } } +bool DeckLoader::convertToCockatriceFormat(QString fileName) +{ + // Change the file extension to .cod + QFileInfo fileInfo(fileName); + QString newFileName = QDir::toNativeSeparators(fileInfo.path() + "/" + fileInfo.completeBaseName() + ".cod"); + + // Open the new file for writing + QFile file(newFileName); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + qCWarning(DeckLoaderLog) << "Failed to open file for writing:" << newFileName; + return false; + } + + bool result = false; + + // Perform file modifications based on the detected format + switch (getFormatFromName(fileName)) { + case PlainTextFormat: + // Save in Cockatrice's native format + result = saveToFile_Native(&file); + break; + case CockatriceFormat: + qCInfo(DeckLoaderLog) << "File is already in Cockatrice format. No conversion needed."; + result = true; + break; + default: + qCWarning(DeckLoaderLog) << "Unsupported file format for conversion:" << fileName; + result = false; + break; + } + + file.close(); + + // Delete the old file if conversion was successful + if (result) { + if (!QFile::remove(fileName)) { + qCWarning(DeckLoaderLog) << "Failed to delete original file:" << fileName; + } else { + qCInfo(DeckLoaderLog) << "Original file deleted successfully:" << fileName; + } + lastFileName = newFileName; + lastFileFormat = CockatriceFormat; + } + + return result; +} + QString DeckLoader::getCardZoneFromName(QString cardName, QString currentZoneName) { CardInfoPtr card = CardDatabaseManager::getInstance()->getCard(cardName); diff --git a/cockatrice/src/deck/deck_loader.h b/cockatrice/src/deck/deck_loader.h index 0e9dfa374..d9863ac3e 100644 --- a/cockatrice/src/deck/deck_loader.h +++ b/cockatrice/src/deck/deck_loader.h @@ -59,6 +59,7 @@ public: // overload bool saveToStream_Plain(QTextStream &out, bool addComments = true, bool addSetNameAndNumber = true); + bool convertToCockatriceFormat(QString fileName); protected: void saveToStream_DeckHeader(QTextStream &out); diff --git a/cockatrice/src/dialogs/dlg_convert_deck_to_cod_format.cpp b/cockatrice/src/dialogs/dlg_convert_deck_to_cod_format.cpp new file mode 100644 index 000000000..198fa259b --- /dev/null +++ b/cockatrice/src/dialogs/dlg_convert_deck_to_cod_format.cpp @@ -0,0 +1,40 @@ +#include "dlg_convert_deck_to_cod_format.h" + +#include +#include +#include +#include + +DialogConvertDeckToCodFormat::DialogConvertDeckToCodFormat(QWidget *parent) : QDialog(parent) +{ + layout = new QVBoxLayout(this); + label = new QLabel(); + layout->addWidget(label); + + dontAskAgainCheckbox = new QCheckBox(this); + layout->addWidget(dontAskAgainCheckbox); + + buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); + layout->addWidget(buttonBox); + + connect(buttonBox, &QDialogButtonBox::accepted, this, [this]() { accept(); }); + + connect(buttonBox, &QDialogButtonBox::rejected, this, [this]() { reject(); }); + + setLayout(layout); + retranslateUi(); +} + +void DialogConvertDeckToCodFormat::retranslateUi() +{ + setWindowTitle(tr("Deck Format Conversion")); + label->setText( + tr("You tried to add a tag to a .txt format deck.\n Tags can only be added to .cod format decks.\n Do " + "you want to convert the deck to the .cod format?")); + dontAskAgainCheckbox->setText(tr("Remember and automatically apply choice in the future")); +} + +bool DialogConvertDeckToCodFormat::dontAskAgain() const +{ + return dontAskAgainCheckbox->isChecked(); +} diff --git a/cockatrice/src/dialogs/dlg_convert_deck_to_cod_format.h b/cockatrice/src/dialogs/dlg_convert_deck_to_cod_format.h new file mode 100644 index 000000000..88a19591e --- /dev/null +++ b/cockatrice/src/dialogs/dlg_convert_deck_to_cod_format.h @@ -0,0 +1,29 @@ +#ifndef DIALOG_CONVERT_DECK_TO_COD_FORMAT_H +#define DIALOG_CONVERT_DECK_TO_COD_FORMAT_H + +#include +#include +#include +#include +#include + +class DialogConvertDeckToCodFormat : public QDialog +{ + Q_OBJECT + +public: + explicit DialogConvertDeckToCodFormat(QWidget *parent); + void retranslateUi(); + + bool dontAskAgain() const; + +private: + QVBoxLayout *layout; + QLabel *label; + QCheckBox *dontAskAgainCheckbox; + QDialogButtonBox *buttonBox; + + Q_DISABLE_COPY(DialogConvertDeckToCodFormat) +}; + +#endif // DIALOG_CONVERT_DECK_TO_COD_FORMAT_H diff --git a/cockatrice/src/dialogs/dlg_settings.cpp b/cockatrice/src/dialogs/dlg_settings.cpp index 496aa7ee6..8010eaf39 100644 --- a/cockatrice/src/dialogs/dlg_settings.cpp +++ b/cockatrice/src/dialogs/dlg_settings.cpp @@ -573,6 +573,15 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage() connect(&useTearOffMenusCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), [](const QT_STATE_CHANGED_T state) { SettingsCache::instance().setUseTearOffMenus(state == Qt::Checked); }); + visualDeckStoragePromptForConversionCheckBox.setChecked( + SettingsCache::instance().getVisualDeckStoragePromptForConversion()); + connect(&visualDeckStoragePromptForConversionCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setVisualDeckStoragePromptForConversion); + + visualDeckStorageAlwaysConvertCheckBox.setChecked(SettingsCache::instance().getVisualDeckStorageAlwaysConvert()); + connect(&visualDeckStorageAlwaysConvertCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance(), + &SettingsCache::setVisualDeckStorageAlwaysConvert); + auto *generalGrid = new QGridLayout; generalGrid->addWidget(&doubleClickToPlayCheckBox, 0, 0); generalGrid->addWidget(&clickPlaysAllSelectedCheckBox, 1, 0); @@ -580,6 +589,8 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage() generalGrid->addWidget(&closeEmptyCardViewCheckBox, 3, 0); generalGrid->addWidget(&annotateTokensCheckBox, 4, 0); generalGrid->addWidget(&useTearOffMenusCheckBox, 5, 0); + generalGrid->addWidget(&visualDeckStoragePromptForConversionCheckBox, 6, 0); + generalGrid->addWidget(&visualDeckStorageAlwaysConvertCheckBox, 7, 0); generalGroupBox = new QGroupBox; generalGroupBox->setLayout(generalGrid); @@ -658,6 +669,8 @@ void UserInterfaceSettingsPage::retranslateUi() closeEmptyCardViewCheckBox.setText(tr("Close card view window when last card is removed")); annotateTokensCheckBox.setText(tr("Annotate card text on tokens")); useTearOffMenusCheckBox.setText(tr("Use tear-off menus, allowing right click menus to persist on screen")); + visualDeckStoragePromptForConversionCheckBox.setText(tr("Prompt before converting .txt decks to .cod format")); + visualDeckStorageAlwaysConvertCheckBox.setText(tr("Always convert if not prompted")); notificationsGroupBox->setTitle(tr("Notifications settings")); notificationsEnabledCheckBox.setText(tr("Enable notifications in taskbar")); specNotificationsEnabledCheckBox.setText(tr("Notify in the taskbar for game events while you are spectating")); diff --git a/cockatrice/src/dialogs/dlg_settings.h b/cockatrice/src/dialogs/dlg_settings.h index 489d798e9..ad9429cd2 100644 --- a/cockatrice/src/dialogs/dlg_settings.h +++ b/cockatrice/src/dialogs/dlg_settings.h @@ -142,6 +142,8 @@ private: QCheckBox closeEmptyCardViewCheckBox; QCheckBox annotateTokensCheckBox; QCheckBox useTearOffMenusCheckBox; + QCheckBox visualDeckStoragePromptForConversionCheckBox; + QCheckBox visualDeckStorageAlwaysConvertCheckBox; QCheckBox tapAnimationCheckBox; QCheckBox openDeckInNewTabCheckBox; QLabel rewindBufferingMsLabel; diff --git a/cockatrice/src/game/zones/card_zone.cpp b/cockatrice/src/game/zones/card_zone.cpp index f444cb464..f3ebbd214 100644 --- a/cockatrice/src/game/zones/card_zone.cpp +++ b/cockatrice/src/game/zones/card_zone.cpp @@ -141,7 +141,7 @@ void CardZone::addCard(CardItem *card, const bool reorganize, const int x, const } for (auto *view : views) { - if (view->getIsReversed() || (x <= view->getCards().size()) || (view->getNumberCards() == -1)) { + if (view->prepareAddCard(x)) { view->addCard(new CardItem(player, nullptr, card->getName(), card->getProviderId(), card->getId()), reorganize, x, y); } @@ -173,7 +173,7 @@ CardItem *CardZone::getCard(int cardId, const QString &cardName) return c; } -CardItem *CardZone::takeCard(int position, int cardId, bool /*canResize*/) +CardItem *CardZone::takeCard(int position, int cardId, bool toNewZone) { if (position == -1) { // position == -1 means either that the zone is indexed by card id @@ -190,7 +190,7 @@ CardItem *CardZone::takeCard(int position, int cardId, bool /*canResize*/) return nullptr; for (auto *view : views) { - view->removeCard(position); + view->removeCard(position, toNewZone); } CardItem *c = cards.takeAt(position); diff --git a/cockatrice/src/game/zones/table_zone.cpp b/cockatrice/src/game/zones/table_zone.cpp index ee1d0219d..686cb3588 100644 --- a/cockatrice/src/game/zones/table_zone.cpp +++ b/cockatrice/src/game/zones/table_zone.cpp @@ -244,10 +244,10 @@ void TableZone::toggleTapped() player->sendGameCommand(player->prepareGameCommand(cmdList)); } -CardItem *TableZone::takeCard(int position, int cardId, bool canResize) +CardItem *TableZone::takeCard(int position, int cardId, bool toNewZone) { CardItem *result = CardZone::takeCard(position, cardId); - if (canResize) + if (toNewZone) resizeToContents(); return result; } diff --git a/cockatrice/src/game/zones/table_zone.h b/cockatrice/src/game/zones/table_zone.h index 4653b564a..271276967 100644 --- a/cockatrice/src/game/zones/table_zone.h +++ b/cockatrice/src/game/zones/table_zone.h @@ -147,10 +147,10 @@ public: @param position card position @param cardId id of card to take - @param canResize defaults to true + @param toNewZone Whether the destination of the card is not the same as the starting zone. Defaults to true @return CardItem that has been removed */ - CardItem *takeCard(int position, int cardId, bool canResize = true) override; + CardItem *takeCard(int position, int cardId, bool toNewZone = true) override; /** Resizes the TableZone in case CardItems are within or diff --git a/cockatrice/src/game/zones/view_zone.cpp b/cockatrice/src/game/zones/view_zone.cpp index bd7ddd966..d86e71f90 100644 --- a/cockatrice/src/game/zones/view_zone.cpp +++ b/cockatrice/src/game/zones/view_zone.cpp @@ -282,6 +282,51 @@ void ZoneViewZone::setPileView(int _pileView) reorganizeCards(); } +/** + * Checks if inserting a card at the given position requires an actual new card to be created and added to the view. + * Also does any cardId updates that would be required if a card is inserted in that position. + * + * Note that this method can end up modifying the cardIds despite returning false. + * (for example, if the card is inserted into a hidden portion of the deck while the view is reversed) + * + * Make sure to call this method once before calling addCard(), so that you skip creating a new CardItem and calling + * addCard() if it's not required. + * + * @param x The position to insert the card at. + * @return Whether to proceed with calling addCard. + */ +bool ZoneViewZone::prepareAddCard(int x) +{ + bool doInsert = false; + if (!isReversed) { + if (x <= cards.size() || cards.size() == -1) { + doInsert = true; + } + } else { + // map x (which is in origZone indexes) to this viewZone's cardList index + int firstId = cards.isEmpty() ? origZone->getCards().size() : cards.front()->getId(); + int insertionIndex = x - firstId; + if (insertionIndex >= 0) { + // card was put into a portion of the deck that's in the view + doInsert = true; + } else { + // card was put into a portion of the deck that's not in the view; update ids but don't insert card + updateCardIds(ADD_CARD); + } + } + + // autoclose check is done both here and in removeCard + if (cards.isEmpty() && !doInsert && SettingsCache::instance().getCloseEmptyCardView()) { + close(); + } + + return doInsert; +} + +/** + * Make sure prepareAddCard() was called before calling addCard(). + * This method assumes we already checked that the card is being inserted into the visible portion + */ void ZoneViewZone::addCardImpl(CardItem *card, int x, int /*y*/) { if (!isReversed) { @@ -295,15 +340,8 @@ void ZoneViewZone::addCardImpl(CardItem *card, int x, int /*y*/) // map x (which is in origZone indexes) to this viewZone's cardList index int firstId = cards.isEmpty() ? origZone->getCards().size() : cards.front()->getId(); int insertionIndex = x - firstId; - if (insertionIndex >= 0) { - // card was put into a portion of the deck that's in the view - // qMin to prevent out-of-bounds error when bottoming a card that is already in the view - cards.insert(qMin(insertionIndex, cards.size()), card); - } else { - // card was put into a portion of the deck that's not in the view - updateCardIds(ADD_CARD); - return; - } + // qMin to prevent out-of-bounds error when bottoming a card that is already in the view + cards.insert(qMin(insertionIndex, cards.size()), card); } card->setParentItem(this); @@ -332,7 +370,7 @@ void ZoneViewZone::handleDropEvent(const QList &dragItems, player->sendGameCommand(cmd); } -void ZoneViewZone::removeCard(int position) +void ZoneViewZone::removeCard(int position, bool toNewZone) { if (isReversed) { position -= cards.first()->getId(); @@ -349,7 +387,11 @@ void ZoneViewZone::removeCard(int position) CardItem *card = cards.takeAt(position); card->deleteLater(); - if (cards.isEmpty() && SettingsCache::instance().getCloseEmptyCardView()) { + // The toNewZone check is to prevent the view from auto-closing if the view contains only a single card and that + // card gets dragged within the view. + // Another autoclose check is done in prepareAddCard so that the view autocloses if the last card was moved to an + // unrevealed portion of the same zone. + if (cards.isEmpty() && SettingsCache::instance().getCloseEmptyCardView() && toNewZone) { close(); return; } diff --git a/cockatrice/src/game/zones/view_zone.h b/cockatrice/src/game/zones/view_zone.h index 0f6de060b..3c69317aa 100644 --- a/cockatrice/src/game/zones/view_zone.h +++ b/cockatrice/src/game/zones/view_zone.h @@ -70,7 +70,8 @@ public: void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; void reorganizeCards() override; void initializeCards(const QList &cardList = QList()); - void removeCard(int position); + bool prepareAddCard(int x); + void removeCard(int position, bool toNewZone); int getNumberCards() const { return numberCards; diff --git a/cockatrice/src/server/remote/remote_decklist_tree_widget.cpp b/cockatrice/src/server/remote/remote_decklist_tree_widget.cpp index 314d7687b..b694a2fdb 100644 --- a/cockatrice/src/server/remote/remote_decklist_tree_widget.cpp +++ b/cockatrice/src/server/remote/remote_decklist_tree_widget.cpp @@ -270,15 +270,18 @@ void RemoteDeckList_TreeModel::refreshTree() client->sendCommand(pend); } +void RemoteDeckList_TreeModel::clearTree() +{ + beginResetModel(); + root->clearTree(); + endResetModel(); +} + void RemoteDeckList_TreeModel::deckListFinished(const Response &r) { const Response_DeckList &resp = r.GetExtension(Response_DeckList::ext); - beginResetModel(); - - root->clearTree(); - - endResetModel(); + clearTree(); ServerInfo_DeckStorage_TreeItem tempRoot; tempRoot.set_id(0); @@ -361,3 +364,8 @@ void RemoteDeckList_TreeWidget::refreshTree() { treeModel->refreshTree(); } + +void RemoteDeckList_TreeWidget::clearTree() +{ + treeModel->clearTree(); +} diff --git a/cockatrice/src/server/remote/remote_decklist_tree_widget.h b/cockatrice/src/server/remote/remote_decklist_tree_widget.h index 59a3e4950..5fdb100de 100644 --- a/cockatrice/src/server/remote/remote_decklist_tree_widget.h +++ b/cockatrice/src/server/remote/remote_decklist_tree_widget.h @@ -106,6 +106,7 @@ public: DirectoryNode *addNamedFolderToTree(const QString &name, DirectoryNode *parent); void removeNode(Node *node); void refreshTree(); + void clearTree(); }; class RemoteDeckList_TreeWidget : public QTreeView @@ -127,6 +128,7 @@ public: void addFolderToTree(const QString &name, RemoteDeckList_TreeModel::DirectoryNode *parent); void removeNode(RemoteDeckList_TreeModel::Node *node); void refreshTree(); + void clearTree(); }; #endif diff --git a/cockatrice/src/settings/cache_settings.cpp b/cockatrice/src/settings/cache_settings.cpp index 329c08a20..5bf114706 100644 --- a/cockatrice/src/settings/cache_settings.cpp +++ b/cockatrice/src/settings/cache_settings.cpp @@ -274,6 +274,9 @@ SettingsCache::SettingsCache() settings->value("interface/visualdeckstoragedrawunusedcoloridentities", true).toBool(); visualDeckStorageUnusedColorIdentitiesOpacity = settings->value("interface/visualdeckstorageunusedcoloridentitiesopacity", 15).toInt(); + visualDeckStoragePromptForConversion = + settings->value("interface/visualdeckstoragepromptforconversion", true).toBool(); + visualDeckStorageAlwaysConvert = settings->value("interface/visualdeckstoragealwaysconvert", false).toBool(); horizontalHand = settings->value("hand/horizontal", true).toBool(); invertVerticalCoordinate = settings->value("table/invert_vertical", false).toBool(); minPlayersForMultiColumnLayout = settings->value("interface/min_players_multicolumn", 4).toInt(); @@ -699,6 +702,18 @@ void SettingsCache::setVisualDeckStorageUnusedColorIdentitiesOpacity(int _visual visualDeckStorageUnusedColorIdentitiesOpacity); } +void SettingsCache::setVisualDeckStoragePromptForConversion(QT_STATE_CHANGED_T _visualDeckStoragePromptForConversion) +{ + visualDeckStoragePromptForConversion = _visualDeckStoragePromptForConversion; + settings->setValue("interface/visualdeckstoragepromptforconversion", visualDeckStoragePromptForConversion); +} + +void SettingsCache::setVisualDeckStorageAlwaysConvert(QT_STATE_CHANGED_T _visualDeckStorageAlwaysConvert) +{ + visualDeckStorageAlwaysConvert = _visualDeckStorageAlwaysConvert; + settings->setValue("interface/visualdeckstoragealwaysconvert", visualDeckStorageAlwaysConvert); +} + void SettingsCache::setHorizontalHand(QT_STATE_CHANGED_T _horizontalHand) { horizontalHand = static_cast(_horizontalHand); diff --git a/cockatrice/src/settings/cache_settings.h b/cockatrice/src/settings/cache_settings.h index 7b5649904..50c970a6b 100644 --- a/cockatrice/src/settings/cache_settings.h +++ b/cockatrice/src/settings/cache_settings.h @@ -133,6 +133,8 @@ private: int visualDeckStorageCardSize; bool visualDeckStorageDrawUnusedColorIdentities; int visualDeckStorageUnusedColorIdentitiesOpacity; + bool visualDeckStoragePromptForConversion; + bool visualDeckStorageAlwaysConvert; bool horizontalHand; bool invertVerticalCoordinate; int minPlayersForMultiColumnLayout; @@ -424,6 +426,14 @@ public: { return visualDeckStorageUnusedColorIdentitiesOpacity; } + bool getVisualDeckStoragePromptForConversion() const + { + return visualDeckStoragePromptForConversion; + } + bool getVisualDeckStorageAlwaysConvert() const + { + return visualDeckStorageAlwaysConvert; + } bool getHorizontalHand() const { return horizontalHand; @@ -748,6 +758,8 @@ public slots: void setVisualDeckStorageCardSize(int _visualDeckStorageCardSize); void setVisualDeckStorageDrawUnusedColorIdentities(QT_STATE_CHANGED_T _visualDeckStorageDrawUnusedColorIdentities); void setVisualDeckStorageUnusedColorIdentitiesOpacity(int _visualDeckStorageUnusedColorIdentitiesOpacity); + void setVisualDeckStoragePromptForConversion(QT_STATE_CHANGED_T _visualDeckStoragePromptForConversion); + void setVisualDeckStorageAlwaysConvert(QT_STATE_CHANGED_T _visualDeckStorageAlwaysConvert); void setHorizontalHand(QT_STATE_CHANGED_T _horizontalHand); void setInvertVerticalCoordinate(QT_STATE_CHANGED_T _invertVerticalCoordinate); void setMinPlayersForMultiColumnLayout(int _minPlayersForMultiColumnLayout); diff --git a/dbconverter/src/mocks.cpp b/dbconverter/src/mocks.cpp index 00336305a..a34dcc191 100644 --- a/dbconverter/src/mocks.cpp +++ b/dbconverter/src/mocks.cpp @@ -222,6 +222,13 @@ void SettingsCache::setVisualDeckStorageUnusedColorIdentitiesOpacity( int /* _visualDeckStorageUnusedColorIdentitiesOpacity */) { } +void SettingsCache::setVisualDeckStoragePromptForConversion( + QT_STATE_CHANGED_T /* _visualDeckStoragePromptForConversion */) +{ +} +void SettingsCache::setVisualDeckStorageAlwaysConvert(QT_STATE_CHANGED_T /* _visualDeckStorageAlwaysConvert */) +{ +} void SettingsCache::setHorizontalHand(QT_STATE_CHANGED_T /* _horizontalHand */) { } diff --git a/tests/carddatabase/mocks.cpp b/tests/carddatabase/mocks.cpp index 358b8361d..40e8fc5a9 100644 --- a/tests/carddatabase/mocks.cpp +++ b/tests/carddatabase/mocks.cpp @@ -226,6 +226,13 @@ void SettingsCache::setVisualDeckStorageUnusedColorIdentitiesOpacity( int /* _visualDeckStorageUnusedColorIdentitiesOpacity */) { } +void SettingsCache::setVisualDeckStoragePromptForConversion( + QT_STATE_CHANGED_T /* _visualDeckStoragePromptForConversion */) +{ +} +void SettingsCache::setVisualDeckStorageAlwaysConvert(QT_STATE_CHANGED_T /* _visualDeckStorageAlwaysConvert */) +{ +} void SettingsCache::setHorizontalHand(QT_STATE_CHANGED_T /* _horizontalHand */) { }