#include "tab_deck_editor.h" #include "abstractclient.h" #include "carddatabasemodel.h" #include "cardframe.h" #include "decklistmodel.h" #include "deckstats_interface.h" #include "dlg_load_deck_from_clipboard.h" #include "filterbuilder.h" #include "filtertreemodel.h" #include "main.h" #include "pb/command_deck_upload.pb.h" #include "pb/response.pb.h" #include "pending_command.h" #include "pictureloader.h" #include "pixmapgenerator.h" #include "settingscache.h" #include "stringsizes.h" #include "tab_supervisor.h" #include "tappedout_interface.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include void SearchLineEdit::keyPressEvent(QKeyEvent *event) { if (treeView && ((event->key() == Qt::Key_Up) || (event->key() == Qt::Key_Down))) QCoreApplication::sendEvent(treeView, event); LineEditUnfocusable::keyPressEvent(event); } void TabDeckEditor::createDeckDock() { deckModel = new DeckListModel(this); deckModel->setObjectName("deckModel"); connect(deckModel, SIGNAL(deckHashChanged()), this, SLOT(updateHash())); deckView = new QTreeView(); deckView->setObjectName("deckView"); deckView->setModel(deckModel); deckView->setUniformRowHeights(true); deckView->setSortingEnabled(true); deckView->sortByColumn(1, Qt::AscendingOrder); deckView->header()->setSectionResizeMode(QHeaderView::ResizeToContents); deckView->installEventFilter(&deckViewKeySignals); connect(deckView->selectionModel(), SIGNAL(currentRowChanged(const QModelIndex &, const QModelIndex &)), this, SLOT(updateCardInfoRight(const QModelIndex &, const QModelIndex &))); connect(deckView, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(actSwapCard())); connect(&deckViewKeySignals, SIGNAL(onShiftS()), this, SLOT(actSwapCard())); connect(&deckViewKeySignals, SIGNAL(onEnter()), this, SLOT(actIncrement())); connect(&deckViewKeySignals, SIGNAL(onCtrlAltEqual()), this, SLOT(actIncrement())); connect(&deckViewKeySignals, SIGNAL(onCtrlAltMinus()), this, SLOT(actDecrement())); connect(&deckViewKeySignals, SIGNAL(onShiftRight()), this, SLOT(actIncrement())); connect(&deckViewKeySignals, SIGNAL(onShiftLeft()), this, SLOT(actDecrement())); connect(&deckViewKeySignals, SIGNAL(onDelete()), this, SLOT(actRemoveCard())); nameLabel = new QLabel(); nameLabel->setObjectName("nameLabel"); nameEdit = new LineEditUnfocusable; nameEdit->setMaxLength(MAX_NAME_LENGTH); nameEdit->setObjectName("nameEdit"); nameLabel->setBuddy(nameEdit); connect(nameEdit, SIGNAL(textChanged(const QString &)), this, SLOT(updateName(const QString &))); commentsLabel = new QLabel(); commentsLabel->setObjectName("commentsLabel"); commentsEdit = new QTextEdit; commentsEdit->setAcceptRichText(false); commentsEdit->setMinimumHeight(nameEdit->minimumSizeHint().height()); commentsEdit->setObjectName("commentsEdit"); commentsLabel->setBuddy(commentsEdit); connect(commentsEdit, SIGNAL(textChanged()), this, SLOT(updateComments())); aIncrement = new QAction(QString(), this); aIncrement->setIcon(QPixmap("theme:icons/increment")); connect(aIncrement, SIGNAL(triggered()), this, SLOT(actIncrement())); auto *tbIncrement = new QToolButton(this); tbIncrement->setDefaultAction(aIncrement); aDecrement = new QAction(QString(), this); aDecrement->setIcon(QPixmap("theme:icons/decrement")); connect(aDecrement, SIGNAL(triggered()), this, SLOT(actDecrement())); auto *tbDecrement = new QToolButton(this); tbDecrement->setDefaultAction(aDecrement); aRemoveCard = new QAction(QString(), this); aRemoveCard->setIcon(QPixmap("theme:icons/remove_row")); connect(aRemoveCard, SIGNAL(triggered()), this, SLOT(actRemoveCard())); auto *tbRemoveCard = new QToolButton(this); tbRemoveCard->setDefaultAction(aRemoveCard); auto *upperLayout = new QGridLayout; upperLayout->setObjectName("upperLayout"); upperLayout->addWidget(nameLabel, 0, 0); upperLayout->addWidget(nameEdit, 0, 1); upperLayout->addWidget(commentsLabel, 1, 0); upperLayout->addWidget(commentsEdit, 1, 1); hashLabel1 = new QLabel(); hashLabel1->setObjectName("hashLabel1"); auto *hashSizePolicy = new QSizePolicy(); hashSizePolicy->setHorizontalPolicy(QSizePolicy::Fixed); hashLabel1->setSizePolicy(*hashSizePolicy); hashLabel = new LineEditUnfocusable; hashLabel->setObjectName("hashLabel"); hashLabel->setReadOnly(true); hashLabel->setFrame(false); auto *lowerLayout = new QGridLayout; lowerLayout->setObjectName("lowerLayout"); lowerLayout->addWidget(hashLabel1, 0, 0); lowerLayout->addWidget(hashLabel, 0, 1); lowerLayout->addWidget(tbIncrement, 0, 2); lowerLayout->addWidget(tbDecrement, 0, 3); lowerLayout->addWidget(tbRemoveCard, 0, 4); lowerLayout->addWidget(deckView, 1, 0, 1, 5); // Create widgets for both layouts to make splitter work correctly auto *topWidget = new QWidget; topWidget->setLayout(upperLayout); auto *bottomWidget = new QWidget; bottomWidget->setLayout(lowerLayout); auto *split = new QSplitter; split->setObjectName("deckSplitter"); split->setOrientation(Qt::Vertical); split->setChildrenCollapsible(true); split->addWidget(topWidget); split->addWidget(bottomWidget); split->setStretchFactor(0, 1); split->setStretchFactor(1, 4); auto *rightFrame = new QVBoxLayout; rightFrame->setObjectName("rightFrame"); rightFrame->addWidget(split); deckDock = new QDockWidget(this); deckDock->setObjectName("deckDock"); deckDock->setMinimumSize(QSize(200, 41)); deckDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); deckDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable); auto *deckDockContents = new QWidget(); deckDockContents->setObjectName("deckDockContents"); deckDockContents->setLayout(rightFrame); deckDock->setWidget(deckDockContents); deckDock->installEventFilter(this); connect(deckDock, SIGNAL(topLevelChanged(bool)), this, SLOT(dockTopLevelChanged(bool))); } void TabDeckEditor::createCardInfoDock() { cardInfo = new CardFrame(); cardInfo->setObjectName("cardInfo"); auto *cardInfoFrame = new QVBoxLayout; cardInfoFrame->setObjectName("cardInfoFrame"); cardInfoFrame->addWidget(cardInfo); cardInfoDock = new QDockWidget(this); cardInfoDock->setObjectName("cardInfoDock"); cardInfoDock->setMinimumSize(QSize(200, 41)); cardInfoDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); cardInfoDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable); auto *cardInfoDockContents = new QWidget(); cardInfoDockContents->setObjectName("cardInfoDockContents"); cardInfoDockContents->setLayout(cardInfoFrame); cardInfoDock->setWidget(cardInfoDockContents); cardInfoDock->installEventFilter(this); connect(cardInfoDock, SIGNAL(topLevelChanged(bool)), this, SLOT(dockTopLevelChanged(bool))); } void TabDeckEditor::createFiltersDock() { filterModel = new FilterTreeModel(); filterModel->setObjectName("filterModel"); databaseDisplayModel->setFilterTree(filterModel->filterTree()); databaseDisplayModel->setObjectName("databaseDisplayModel"); filterView = new QTreeView; filterView->setObjectName("filterView"); filterView->setModel(filterModel); filterView->setUniformRowHeights(true); filterView->setHeaderHidden(true); filterView->setContextMenuPolicy(Qt::CustomContextMenu); filterView->installEventFilter(&filterViewKeySignals); connect(filterModel, SIGNAL(layoutChanged()), filterView, SLOT(expandAll())); connect(filterView, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(filterViewCustomContextMenu(const QPoint &))); connect(&filterViewKeySignals, SIGNAL(onDelete()), this, SLOT(actClearFilterOne())); auto *filterBuilder = new FilterBuilder; filterBuilder->setObjectName("filterBuilder"); connect(filterBuilder, SIGNAL(add(const CardFilter *)), filterModel, SLOT(addFilter(const CardFilter *))); auto *filterDelOne = new QToolButton(); filterDelOne->setObjectName("filterDelOne"); filterDelOne->setDefaultAction(aClearFilterOne); filterDelOne->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); auto *filterDelAll = new QToolButton(); filterDelAll->setObjectName("filterDelAll"); filterDelAll->setDefaultAction(aClearFilterAll); filterDelAll->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); auto *filterLayout = new QGridLayout; filterLayout->setObjectName("filterLayout"); filterLayout->setContentsMargins(0, 0, 0, 0); filterLayout->addWidget(filterBuilder, 0, 0, 1, 3); filterLayout->addWidget(filterView, 1, 0, 1, 3); filterLayout->addWidget(filterDelOne, 2, 0, 1, 1); filterLayout->addWidget(filterDelAll, 2, 2, 1, 1); filterBox = new QWidget(); filterBox->setObjectName("filterBox"); filterBox->setLayout(filterLayout); auto *filterFrame = new QVBoxLayout; filterFrame->setObjectName("filterFrame"); filterFrame->addWidget(filterBox); filterDock = new QDockWidget(this); filterDock->setObjectName("filterDock"); filterDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable); auto *filterDockContents = new QWidget(this); filterDockContents->setObjectName("filterDockContents"); filterDockContents->setLayout(filterFrame); filterDock->setWidget(filterDockContents); filterDock->installEventFilter(this); connect(filterDock, SIGNAL(topLevelChanged(bool)), this, SLOT(dockTopLevelChanged(bool))); } void TabDeckEditor::createMenus() { aNewDeck = new QAction(QString(), this); connect(aNewDeck, SIGNAL(triggered()), this, SLOT(actNewDeck())); aLoadDeck = new QAction(QString(), this); connect(aLoadDeck, SIGNAL(triggered()), this, SLOT(actLoadDeck())); aSaveDeck = new QAction(QString(), this); connect(aSaveDeck, SIGNAL(triggered()), this, SLOT(actSaveDeck())); aSaveDeckAs = new QAction(QString(), this); connect(aSaveDeckAs, SIGNAL(triggered()), this, SLOT(actSaveDeckAs())); aLoadDeckFromClipboard = new QAction(QString(), this); connect(aLoadDeckFromClipboard, SIGNAL(triggered()), this, SLOT(actLoadDeckFromClipboard())); aSaveDeckToClipboard = new QAction(QString(), this); connect(aSaveDeckToClipboard, SIGNAL(triggered()), this, SLOT(actSaveDeckToClipboard())); aSaveDeckToClipboardRaw = new QAction(QString(), this); connect(aSaveDeckToClipboardRaw, SIGNAL(triggered()), this, SLOT(actSaveDeckToClipboardRaw())); aPrintDeck = new QAction(QString(), this); connect(aPrintDeck, SIGNAL(triggered()), this, SLOT(actPrintDeck())); aExportDeckDecklist = new QAction(QString(), this); connect(aExportDeckDecklist, SIGNAL(triggered()), this, SLOT(actExportDeckDecklist())); aAnalyzeDeckDeckstats = new QAction(QString(), this); connect(aAnalyzeDeckDeckstats, SIGNAL(triggered()), this, SLOT(actAnalyzeDeckDeckstats())); aAnalyzeDeckTappedout = new QAction(QString(), this); connect(aAnalyzeDeckTappedout, SIGNAL(triggered()), this, SLOT(actAnalyzeDeckTappedout())); analyzeDeckMenu = new QMenu(this); analyzeDeckMenu->addAction(aExportDeckDecklist); analyzeDeckMenu->addAction(aAnalyzeDeckDeckstats); analyzeDeckMenu->addAction(aAnalyzeDeckTappedout); aClose = new QAction(QString(), this); connect(aClose, SIGNAL(triggered()), this, SLOT(closeRequest())); aClearFilterAll = new QAction(QString(), this); aClearFilterAll->setIcon(QPixmap("theme:icons/clearsearch")); connect(aClearFilterAll, SIGNAL(triggered()), this, SLOT(actClearFilterAll())); aClearFilterOne = new QAction(QString(), this); aClearFilterOne->setIcon(QPixmap("theme:icons/decrement")); connect(aClearFilterOne, SIGNAL(triggered()), this, SLOT(actClearFilterOne())); saveDeckToClipboardMenu = new QMenu(this); saveDeckToClipboardMenu->addAction(aSaveDeckToClipboard); saveDeckToClipboardMenu->addAction(aSaveDeckToClipboardRaw); deckMenu = new QMenu(this); deckMenu->addAction(aNewDeck); deckMenu->addAction(aLoadDeck); deckMenu->addAction(aSaveDeck); deckMenu->addAction(aSaveDeckAs); deckMenu->addSeparator(); deckMenu->addAction(aLoadDeckFromClipboard); deckMenu->addMenu(saveDeckToClipboardMenu); deckMenu->addSeparator(); deckMenu->addAction(aPrintDeck); deckMenu->addMenu(analyzeDeckMenu); deckMenu->addSeparator(); deckMenu->addAction(aClearFilterOne); deckMenu->addAction(aClearFilterAll); deckMenu->addSeparator(); deckMenu->addAction(aClose); addTabMenu(deckMenu); viewMenu = new QMenu(this); cardInfoDockMenu = viewMenu->addMenu(QString()); deckDockMenu = viewMenu->addMenu(QString()); filterDockMenu = viewMenu->addMenu(QString()); aCardInfoDockVisible = cardInfoDockMenu->addAction(QString()); aCardInfoDockVisible->setCheckable(true); connect(aCardInfoDockVisible, SIGNAL(triggered()), this, SLOT(dockVisibleTriggered())); aCardInfoDockFloating = cardInfoDockMenu->addAction(QString()); aCardInfoDockFloating->setCheckable(true); connect(aCardInfoDockFloating, SIGNAL(triggered()), this, SLOT(dockFloatingTriggered())); aDeckDockVisible = deckDockMenu->addAction(QString()); aDeckDockVisible->setCheckable(true); connect(aDeckDockVisible, SIGNAL(triggered()), this, SLOT(dockVisibleTriggered())); aDeckDockFloating = deckDockMenu->addAction(QString()); aDeckDockFloating->setCheckable(true); connect(aDeckDockFloating, SIGNAL(triggered()), this, SLOT(dockFloatingTriggered())); aFilterDockVisible = filterDockMenu->addAction(QString()); aFilterDockVisible->setCheckable(true); connect(aFilterDockVisible, SIGNAL(triggered()), this, SLOT(dockVisibleTriggered())); aFilterDockFloating = filterDockMenu->addAction(QString()); aFilterDockFloating->setCheckable(true); connect(aFilterDockFloating, SIGNAL(triggered()), this, SLOT(dockFloatingTriggered())); viewMenu->addSeparator(); aResetLayout = viewMenu->addAction(QString()); connect(aResetLayout, SIGNAL(triggered()), this, SLOT(restartLayout())); viewMenu->addAction(aResetLayout); setSaveStatus(false); addTabMenu(viewMenu); } void TabDeckEditor::createCentralFrame() { searchEdit = new SearchLineEdit; searchEdit->setObjectName("searchEdit"); searchEdit->setPlaceholderText(tr("Search by card name")); searchEdit->setClearButtonEnabled(true); searchEdit->addAction(loadColorAdjustedPixmap("theme:icons/search"), QLineEdit::LeadingPosition); auto help = searchEdit->addAction(QPixmap("theme:icons/info"), QLineEdit::TrailingPosition); searchEdit->installEventFilter(&searchKeySignals); setFocusProxy(searchEdit); setFocusPolicy(Qt::ClickFocus); searchKeySignals.setObjectName("searchKeySignals"); connect(searchEdit, SIGNAL(textChanged(const QString &)), this, SLOT(updateSearch(const QString &))); connect(&searchKeySignals, SIGNAL(onEnter()), this, SLOT(actAddCard())); connect(&searchKeySignals, SIGNAL(onCtrlAltEqual()), this, SLOT(actAddCard())); connect(&searchKeySignals, SIGNAL(onCtrlAltRBracket()), this, SLOT(actAddCardToSideboard())); connect(&searchKeySignals, SIGNAL(onCtrlAltMinus()), this, SLOT(actDecrementCard())); connect(&searchKeySignals, SIGNAL(onCtrlAltLBracket()), this, SLOT(actDecrementCardFromSideboard())); connect(&searchKeySignals, SIGNAL(onCtrlAltEnter()), this, SLOT(actAddCardToSideboard())); connect(&searchKeySignals, SIGNAL(onCtrlEnter()), this, SLOT(actAddCardToSideboard())); connect(help, &QAction::triggered, this, &TabDeckEditor::showSearchSyntaxHelp); databaseModel = new CardDatabaseModel(db, true, this); databaseModel->setObjectName("databaseModel"); databaseDisplayModel = new CardDatabaseDisplayModel(this); databaseDisplayModel->setSourceModel(databaseModel); databaseDisplayModel->setFilterKeyColumn(0); databaseView = new QTreeView(); databaseView->setObjectName("databaseView"); databaseView->setFocusProxy(searchEdit); databaseView->setUniformRowHeights(true); databaseView->setRootIsDecorated(false); databaseView->setAlternatingRowColors(true); databaseView->setSortingEnabled(true); databaseView->sortByColumn(0, Qt::AscendingOrder); databaseView->setModel(databaseDisplayModel); databaseView->setContextMenuPolicy(Qt::CustomContextMenu); connect(databaseView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(databaseCustomMenu(QPoint))); connect(databaseView->selectionModel(), SIGNAL(currentRowChanged(const QModelIndex &, const QModelIndex &)), this, SLOT(updateCardInfoLeft(const QModelIndex &, const QModelIndex &))); connect(databaseView, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(actAddCard())); QByteArray dbHeaderState = SettingsCache::instance().layouts().getDeckEditorDbHeaderState(); if (dbHeaderState.isNull()) { // first run databaseView->setColumnWidth(0, 200); } else { databaseView->header()->restoreState(dbHeaderState); } connect(databaseView->header(), SIGNAL(geometriesChanged()), this, SLOT(saveDbHeaderState())); searchEdit->setTreeView(databaseView); aAddCard = new QAction(QString(), this); aAddCard->setIcon(QPixmap("theme:icons/arrow_right_green")); connect(aAddCard, SIGNAL(triggered()), this, SLOT(actAddCard())); auto *tbAddCard = new QToolButton(this); tbAddCard->setDefaultAction(aAddCard); aAddCardToSideboard = new QAction(QString(), this); aAddCardToSideboard->setIcon(QPixmap("theme:icons/arrow_right_blue")); connect(aAddCardToSideboard, SIGNAL(triggered()), this, SLOT(actAddCardToSideboard())); auto *tbAddCardToSideboard = new QToolButton(this); tbAddCardToSideboard->setDefaultAction(aAddCardToSideboard); searchLayout = new QHBoxLayout; searchLayout->setObjectName("searchLayout"); searchLayout->addWidget(searchEdit); searchLayout->addWidget(tbAddCard); searchLayout->addWidget(tbAddCardToSideboard); centralFrame = new QVBoxLayout; centralFrame->setObjectName("centralFrame"); centralFrame->addLayout(searchLayout); centralFrame->addWidget(databaseView); centralWidget = new QWidget(this); centralWidget->setObjectName("centralWidget"); centralWidget->setLayout(centralFrame); setCentralWidget(centralWidget); setDockOptions(QMainWindow::AnimatedDocks | QMainWindow::AllowNestedDocks | QMainWindow::AllowTabbedDocks); } void TabDeckEditor::databaseCustomMenu(QPoint point) { QMenu menu; const CardInfoPtr info = currentCardInfo(); // add to deck and sideboard options QAction *addToDeck, *addToSideboard; addToDeck = menu.addAction(tr("Add to Deck")); addToSideboard = menu.addAction(tr("Add to Sideboard")); connect(addToDeck, SIGNAL(triggered()), this, SLOT(actAddCard())); connect(addToSideboard, SIGNAL(triggered()), this, SLOT(actAddCardToSideboard())); // filling out the related cards submenu auto *relatedMenu = new QMenu(tr("Show Related cards")); menu.addMenu(relatedMenu); auto relatedCards = info->getAllRelatedCards(); if (relatedCards.isEmpty()) { relatedMenu->setDisabled(true); } else { for (const CardRelation *rel : relatedCards) { const QString &relatedCardName = rel->getName(); QAction *relatedCard = relatedMenu->addAction(relatedCardName); connect(relatedCard, &QAction::triggered, cardInfo, [this, relatedCardName] { cardInfo->setCard(relatedCardName); }); } } menu.exec(databaseView->mapToGlobal(point)); } void TabDeckEditor::restartLayout() { deckDock->setVisible(true); cardInfoDock->setVisible(true); filterDock->setVisible(true); deckDock->setFloating(false); cardInfoDock->setFloating(false); filterDock->setFloating(false); aCardInfoDockVisible->setChecked(true); aDeckDockVisible->setChecked(true); aFilterDockVisible->setChecked(true); aCardInfoDockFloating->setChecked(false); aDeckDockFloating->setChecked(false); aFilterDockFloating->setChecked(false); addDockWidget(static_cast(2), deckDock); addDockWidget(static_cast(2), cardInfoDock); addDockWidget(static_cast(2), filterDock); splitDockWidget(cardInfoDock, deckDock, Qt::Horizontal); splitDockWidget(cardInfoDock, filterDock, Qt::Vertical); deckDock->setMinimumWidth(360); deckDock->setMaximumWidth(360); cardInfoDock->setMinimumSize(250, 360); cardInfoDock->setMaximumSize(250, 360); QTimer::singleShot(100, this, SLOT(freeDocksSize())); } void TabDeckEditor::freeDocksSize() { deckDock->setMinimumSize(100, 100); deckDock->setMaximumSize(5000, 5000); cardInfoDock->setMinimumSize(100, 100); cardInfoDock->setMaximumSize(5000, 5000); filterDock->setMinimumSize(100, 100); filterDock->setMaximumSize(5000, 5000); } void TabDeckEditor::refreshShortcuts() { ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts(); aNewDeck->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aNewDeck")); aLoadDeck->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aLoadDeck")); aSaveDeck->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aSaveDeck")); aExportDeckDecklist->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aExportDeckDecklist")); aSaveDeckAs->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aSaveDeckAs")); aLoadDeckFromClipboard->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aLoadDeckFromClipboard")); aPrintDeck->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aPrintDeck")); aAnalyzeDeckDeckstats->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aAnalyzeDeck")); aClose->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aClose")); aResetLayout->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aResetLayout")); aClearFilterAll->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aClearFilterAll")); aClearFilterOne->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aClearFilterOne")); aSaveDeckToClipboard->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aSaveDeckToClipboard")); aSaveDeckToClipboardRaw->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aSaveDeckToClipboardRaw")); aClearFilterOne->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aClearFilterOne")); aClose->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aClose")); aRemoveCard->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aRemoveCard")); aIncrement->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aIncrement")); aDecrement->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aDecrement")); } void TabDeckEditor::loadLayout() { LayoutsSettings &layouts = SettingsCache::instance().layouts(); auto &layoutState = layouts.getDeckEditorLayoutState(); if (layoutState.isNull()) { restartLayout(); } else { restoreState(layoutState); restoreGeometry(layouts.getDeckEditorGeometry()); } aCardInfoDockVisible->setChecked(cardInfoDock->isVisible()); aFilterDockVisible->setChecked(filterDock->isVisible()); aDeckDockVisible->setChecked(deckDock->isVisible()); aCardInfoDockFloating->setEnabled(aCardInfoDockVisible->isChecked()); aDeckDockFloating->setEnabled(aDeckDockVisible->isChecked()); aFilterDockFloating->setEnabled(aFilterDockVisible->isChecked()); aCardInfoDockFloating->setChecked(cardInfoDock->isFloating()); aFilterDockFloating->setChecked(filterDock->isFloating()); aDeckDockFloating->setChecked(deckDock->isFloating()); cardInfoDock->setMinimumSize(layouts.getDeckEditorCardSize()); cardInfoDock->setMaximumSize(layouts.getDeckEditorCardSize()); filterDock->setMinimumSize(layouts.getDeckEditorFilterSize()); filterDock->setMaximumSize(layouts.getDeckEditorFilterSize()); deckDock->setMinimumSize(layouts.getDeckEditorDeckSize()); deckDock->setMaximumSize(layouts.getDeckEditorDeckSize()); QTimer::singleShot(100, this, SLOT(freeDocksSize())); } TabDeckEditor::TabDeckEditor(TabSupervisor *_tabSupervisor, QWidget *parent) : Tab(_tabSupervisor, parent), modified(false) { setObjectName("TabDeckEditor"); createMenus(); createCentralFrame(); createDeckDock(); createCardInfoDock(); createFiltersDock(); this->installEventFilter(this); retranslateUi(); connect(&SettingsCache::instance().shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts())); refreshShortcuts(); loadLayout(); } TabDeckEditor::~TabDeckEditor() { emit deckEditorClosing(this); } void TabDeckEditor::retranslateUi() { cardInfo->retranslateUi(); aClearFilterAll->setText(tr("&Clear all filters")); aClearFilterOne->setText(tr("Delete selected")); nameLabel->setText(tr("Deck &name:")); commentsLabel->setText(tr("&Comments:")); hashLabel1->setText(tr("Hash:")); aNewDeck->setText(tr("&New deck")); aLoadDeck->setText(tr("&Load deck...")); aSaveDeck->setText(tr("&Save deck")); aSaveDeckAs->setText(tr("Save deck &as...")); aLoadDeckFromClipboard->setText(tr("Load deck from cl&ipboard...")); saveDeckToClipboardMenu->setTitle(tr("Save deck to clipboard")); aSaveDeckToClipboard->setText(tr("Annotated")); aSaveDeckToClipboardRaw->setText(tr("Not Annotated")); aPrintDeck->setText(tr("&Print deck...")); analyzeDeckMenu->setTitle(tr("&Send deck to online service")); aExportDeckDecklist->setText(tr("Create decklist (decklist.org)")); aAnalyzeDeckDeckstats->setText(tr("Analyze deck (deckstats.net)")); aAnalyzeDeckTappedout->setText(tr("Analyze deck (tappedout.net)")); aClose->setText(tr("&Close")); aAddCard->setText(tr("Add card to &maindeck")); aAddCardToSideboard->setText(tr("Add card to &sideboard")); aRemoveCard->setText(tr("&Remove row")); aIncrement->setText(tr("&Increment number")); aDecrement->setText(tr("&Decrement number")); deckMenu->setTitle(tr("&Deck Editor")); cardInfoDock->setWindowTitle(tr("Card Info")); deckDock->setWindowTitle(tr("Deck")); filterDock->setWindowTitle(tr("Filters")); viewMenu->setTitle(tr("&View")); cardInfoDockMenu->setTitle(tr("Card Info")); deckDockMenu->setTitle(tr("Deck")); filterDockMenu->setTitle(tr("Filters")); aCardInfoDockVisible->setText(tr("Visible")); aCardInfoDockFloating->setText(tr("Floating")); aDeckDockVisible->setText(tr("Visible")); aDeckDockFloating->setText(tr("Floating")); aFilterDockVisible->setText(tr("Visible")); aFilterDockFloating->setText(tr("Floating")); aResetLayout->setText(tr("Reset layout")); } QString TabDeckEditor::getTabText() const { QString result = tr("Deck: %1").arg(nameEdit->text().simplified()); if (modified) result.prepend("* "); return result; } void TabDeckEditor::updateName(const QString &name) { deckModel->getDeckList()->setName(name); setModified(true); setSaveStatus(true); } void TabDeckEditor::updateComments() { deckModel->getDeckList()->setComments(commentsEdit->toPlainText()); setModified(true); setSaveStatus(true); } void TabDeckEditor::updateCardInfoLeft(const QModelIndex ¤t, const QModelIndex & /*previous*/) { cardInfo->setCard(current.sibling(current.row(), 0).data().toString()); } void TabDeckEditor::updateCardInfoRight(const QModelIndex ¤t, const QModelIndex & /*previous*/) { if (!current.isValid()) return; if (!current.model()->hasChildren(current.sibling(current.row(), 0))) cardInfo->setCard(current.sibling(current.row(), 1).data().toString()); } void TabDeckEditor::updateSearch(const QString &search) { databaseDisplayModel->setStringFilter(search); QModelIndexList sel = databaseView->selectionModel()->selectedRows(); if (sel.isEmpty() && databaseDisplayModel->rowCount()) databaseView->selectionModel()->setCurrentIndex(databaseDisplayModel->index(0, 0), QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); } void TabDeckEditor::updateHash() { hashLabel->setText(deckModel->getDeckList()->getDeckHash()); } bool TabDeckEditor::confirmClose() { if (modified) { tabSupervisor->setCurrentWidget(this); QMessageBox::StandardButton ret = QMessageBox::warning( this, tr("Are you sure?"), tr("The decklist has been modified.\nDo you want to save the changes?"), QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel); if (ret == QMessageBox::Save) return actSaveDeck(); else if (ret == QMessageBox::Cancel) return false; } return true; } void TabDeckEditor::closeRequest() { if (confirmClose()) deleteLater(); } void TabDeckEditor::actNewDeck() { if (!confirmClose()) return; deckModel->cleanList(); nameEdit->setText(QString()); commentsEdit->setText(QString()); hashLabel->setText(QString()); setModified(false); setSaveStatus(false); } void TabDeckEditor::actLoadDeck() { if (!confirmClose()) return; QFileDialog dialog(this, tr("Load deck")); dialog.setDirectory(SettingsCache::instance().getDeckPath()); dialog.setNameFilters(DeckLoader::fileNameFilters); if (!dialog.exec()) return; QString fileName = dialog.selectedFiles().at(0); DeckLoader::FileFormat fmt = DeckLoader::getFormatFromName(fileName); auto *l = new DeckLoader; if (l->loadFromFile(fileName, fmt)) { setSaveStatus(false); setDeck(l); } else delete l; setSaveStatus(true); } void TabDeckEditor::saveDeckRemoteFinished(const Response &response) { if (response.response_code() != Response::RespOk) QMessageBox::critical(this, tr("Error"), tr("The deck could not be saved.")); else setModified(false); } bool TabDeckEditor::actSaveDeck() { DeckLoader *const deck = deckModel->getDeckList(); if (deck->getLastRemoteDeckId() != -1) { QString deckString = deck->writeToString_Native(); if (deckString.length() > MAX_FILE_LENGTH) { QMessageBox::critical(this, tr("Error"), tr("Could not save remote deck")); return false; } Command_DeckUpload cmd; cmd.set_deck_id(static_cast(deck->getLastRemoteDeckId())); cmd.set_deck_list(deckString.toStdString()); PendingCommand *pend = AbstractClient::prepareSessionCommand(cmd); connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(saveDeckRemoteFinished(Response))); tabSupervisor->getClient()->sendCommand(pend); return true; } else if (deck->getLastFileName().isEmpty()) return actSaveDeckAs(); else if (deck->saveToFile(deck->getLastFileName(), deck->getLastFileFormat())) { setModified(false); return true; } QMessageBox::critical( this, tr("Error"), tr("The deck could not be saved.\nPlease check that the directory is writable and try again.")); return false; } bool TabDeckEditor::actSaveDeckAs() { QFileDialog dialog(this, tr("Save deck")); dialog.setDirectory(SettingsCache::instance().getDeckPath()); dialog.setAcceptMode(QFileDialog::AcceptSave); dialog.setDefaultSuffix("cod"); dialog.setNameFilters(DeckLoader::fileNameFilters); dialog.selectFile(deckModel->getDeckList()->getName().trimmed() + ".cod"); if (!dialog.exec()) return false; QString fileName = dialog.selectedFiles().at(0); DeckLoader::FileFormat fmt = DeckLoader::getFormatFromName(fileName); if (!deckModel->getDeckList()->saveToFile(fileName, fmt)) { QMessageBox::critical( this, tr("Error"), tr("The deck could not be saved.\nPlease check that the directory is writable and try again.")); return false; } setModified(false); return true; } void TabDeckEditor::actLoadDeckFromClipboard() { if (!confirmClose()) return; DlgLoadDeckFromClipboard dlg(this); if (!dlg.exec()) return; setDeck(dlg.getDeckList()); setModified(true); setSaveStatus(true); } void TabDeckEditor::actSaveDeckToClipboard() { QString buffer; QTextStream stream(&buffer); deckModel->getDeckList()->saveToStream_Plain(stream); QApplication::clipboard()->setText(buffer, QClipboard::Clipboard); QApplication::clipboard()->setText(buffer, QClipboard::Selection); } void TabDeckEditor::actSaveDeckToClipboardRaw() { QString buffer; QTextStream stream(&buffer); deckModel->getDeckList()->saveToStream_Plain(stream, false); QApplication::clipboard()->setText(buffer, QClipboard::Clipboard); QApplication::clipboard()->setText(buffer, QClipboard::Selection); } void TabDeckEditor::actPrintDeck() { auto *dlg = new QPrintPreviewDialog(this); connect(dlg, SIGNAL(paintRequested(QPrinter *)), deckModel, SLOT(printDeckList(QPrinter *))); dlg->exec(); } // Action called when export deck to decklist menu item is pressed. void TabDeckEditor::actExportDeckDecklist() { // Get the decklist class for the deck. DeckLoader *const deck = deckModel->getDeckList(); // create a string to load the decklist url into. QString decklistUrlString; // check if deck is not null if (deck) { // Get the decklist url string from the deck loader class. decklistUrlString = deck->exportDeckToDecklist(); // Check to make sure the string isn't empty. if (QString::compare(decklistUrlString, "", Qt::CaseInsensitive) == 0) { // Show an error if the deck is empty, and return. QMessageBox::critical(this, tr("Error"), tr("There are no cards in your deck to be exported")); return; } // Encode the string recieved from the model to make sure all characters are encoded. // first we put it into a qurl object QUrl decklistUrl = QUrl(decklistUrlString); // we get the correctly encoded url. decklistUrlString = decklistUrl.toEncoded(); // We open the url in the user's default browser QDesktopServices::openUrl(decklistUrlString); } else { // if there's no deck loader object, return an error QMessageBox::critical(this, tr("Error"), tr("No deck was selected to be saved.")); } } void TabDeckEditor::actAnalyzeDeckDeckstats() { auto *interface = new DeckStatsInterface(*databaseModel->getDatabase(), this); // it deletes itself when done interface->analyzeDeck(deckModel->getDeckList()); } void TabDeckEditor::actAnalyzeDeckTappedout() { auto *interface = new TappedOutInterface(*databaseModel->getDatabase(), this); // it deletes itself when done interface->analyzeDeck(deckModel->getDeckList()); } void TabDeckEditor::actClearFilterAll() { databaseDisplayModel->clearFilterAll(); searchEdit->setText(""); } void TabDeckEditor::actClearFilterOne() { QModelIndexList selIndexes = filterView->selectionModel()->selectedIndexes(); for (QModelIndex idx : selIndexes) { filterModel->removeRow(idx.row(), idx.parent()); } } void TabDeckEditor::recursiveExpand(const QModelIndex &index) { if (index.parent().isValid()) recursiveExpand(index.parent()); deckView->expand(index); } CardInfoPtr TabDeckEditor::currentCardInfo() const { const QModelIndex currentIndex = databaseView->selectionModel()->currentIndex(); if (!currentIndex.isValid()) { return {}; } const QString cardName = currentIndex.sibling(currentIndex.row(), 0).data().toString(); return db->getCard(cardName); } void TabDeckEditor::addCardHelper(QString zoneName) { const CardInfoPtr info = currentCardInfo(); if (!info) return; if (info->getIsToken()) zoneName = DECK_ZONE_TOKENS; QModelIndex newCardIndex = deckModel->addCard(info->getName(), zoneName); recursiveExpand(newCardIndex); deckView->setCurrentIndex(newCardIndex); setModified(true); searchEdit->setSelection(0, searchEdit->text().length()); } void TabDeckEditor::actSwapCard() { const QModelIndex currentIndex = deckView->selectionModel()->currentIndex(); if (!currentIndex.isValid()) return; const QString cardName = currentIndex.sibling(currentIndex.row(), 1).data().toString(); const QModelIndex gparent = currentIndex.parent().parent(); if (!gparent.isValid()) return; const QString zoneName = gparent.sibling(gparent.row(), 1).data(Qt::EditRole).toString(); actDecrement(); const QString otherZoneName = zoneName == DECK_ZONE_MAIN ? DECK_ZONE_SIDE : DECK_ZONE_MAIN; // Third argument (true) says create the card no mater what, even if not in DB QModelIndex newCardIndex = deckModel->addCard(cardName, otherZoneName, true); recursiveExpand(newCardIndex); setModified(true); setSaveStatus(true); } void TabDeckEditor::actAddCard() { if (QApplication::keyboardModifiers() & Qt::ControlModifier) actAddCardToSideboard(); else addCardHelper(DECK_ZONE_MAIN); setSaveStatus(true); } void TabDeckEditor::actAddCardToSideboard() { addCardHelper(DECK_ZONE_SIDE); setSaveStatus(true); } void TabDeckEditor::actRemoveCard() { const QModelIndex ¤tIndex = deckView->selectionModel()->currentIndex(); if (!currentIndex.isValid() || deckModel->hasChildren(currentIndex)) return; deckModel->removeRow(currentIndex.row(), currentIndex.parent()); DeckLoader *const deck = deckModel->getDeckList(); setSaveStatus(!deck->isEmpty()); setModified(true); } void TabDeckEditor::offsetCountAtIndex(const QModelIndex &idx, int offset) { if (!idx.isValid() || offset == 0) return; const QModelIndex numberIndex = idx.sibling(idx.row(), 0); const int count = deckModel->data(numberIndex, Qt::EditRole).toInt(); const int new_count = count + offset; deckView->setCurrentIndex(numberIndex); if (new_count <= 0) deckModel->removeRow(idx.row(), idx.parent()); else deckModel->setData(numberIndex, new_count, Qt::EditRole); setModified(true); } void TabDeckEditor::decrementCardHelper(QString zoneName) { const CardInfoPtr info = currentCardInfo(); QModelIndex idx; if (!info) return; if (info->getIsToken()) zoneName = DECK_ZONE_TOKENS; idx = deckModel->findCard(info->getName(), zoneName); offsetCountAtIndex(idx, -1); } void TabDeckEditor::actDecrementCard() { decrementCardHelper(DECK_ZONE_MAIN); } void TabDeckEditor::actDecrementCardFromSideboard() { decrementCardHelper(DECK_ZONE_SIDE); } void TabDeckEditor::actIncrement() { const QModelIndex ¤tIndex = deckView->selectionModel()->currentIndex(); offsetCountAtIndex(currentIndex, 1); } void TabDeckEditor::actDecrement() { const QModelIndex ¤tIndex = deckView->selectionModel()->currentIndex(); offsetCountAtIndex(currentIndex, -1); } void TabDeckEditor::setDeck(DeckLoader *_deck) { deckModel->setDeckList(_deck); nameEdit->setText(deckModel->getDeckList()->getName()); commentsEdit->setText(deckModel->getDeckList()->getComments()); updateHash(); deckModel->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder()); deckView->expandAll(); setModified(false); PictureLoader::cacheCardPixmaps(db->getCards(deckModel->getDeckList()->getCardList())); deckView->expandAll(); setModified(false); // If they load a deck, make the deck list appear aDeckDockVisible->setChecked(true); deckDock->setVisible(aDeckDockVisible->isChecked()); } void TabDeckEditor::setModified(bool _modified) { modified = _modified; emit tabTextChanged(this, getTabText()); } void TabDeckEditor::filterViewCustomContextMenu(const QPoint &point) { QMenu menu; QAction *action; QModelIndex idx; idx = filterView->indexAt(point); if (!idx.isValid()) return; action = menu.addAction(QString("delete")); action->setData(point); connect(&menu, SIGNAL(triggered(QAction *)), this, SLOT(filterRemove(QAction *))); menu.exec(filterView->mapToGlobal(point)); } void TabDeckEditor::filterRemove(QAction *action) { QPoint point; QModelIndex idx; point = action->data().toPoint(); idx = filterView->indexAt(point); if (!idx.isValid()) return; filterModel->removeRow(idx.row(), idx.parent()); } // Method uses to sync docks state with menu items state bool TabDeckEditor::eventFilter(QObject *o, QEvent *e) { if (e->type() == QEvent::Close) { if (o == cardInfoDock) { aCardInfoDockVisible->setChecked(false); aCardInfoDockFloating->setEnabled(false); } else if (o == deckDock) { aDeckDockVisible->setChecked(false); aDeckDockFloating->setEnabled(false); } else if (o == filterDock) { aFilterDockVisible->setChecked(false); aFilterDockFloating->setEnabled(false); } } if (o == this && e->type() == QEvent::Hide) { LayoutsSettings &layouts = SettingsCache::instance().layouts(); layouts.setDeckEditorLayoutState(saveState()); layouts.setDeckEditorGeometry(saveGeometry()); layouts.setDeckEditorCardSize(cardInfoDock->size()); layouts.setDeckEditorFilterSize(filterDock->size()); layouts.setDeckEditorDeckSize(deckDock->size()); } return false; } void TabDeckEditor::dockVisibleTriggered() { QObject *o = sender(); if (o == aCardInfoDockVisible) { cardInfoDock->setVisible(aCardInfoDockVisible->isChecked()); aCardInfoDockFloating->setEnabled(aCardInfoDockVisible->isChecked()); return; } if (o == aDeckDockVisible) { deckDock->setVisible(aDeckDockVisible->isChecked()); aDeckDockFloating->setEnabled(aDeckDockVisible->isChecked()); return; } if (o == aFilterDockVisible) { filterDock->setVisible(aFilterDockVisible->isChecked()); aFilterDockFloating->setEnabled(aFilterDockVisible->isChecked()); return; } } void TabDeckEditor::dockFloatingTriggered() { QObject *o = sender(); if (o == aCardInfoDockFloating) { cardInfoDock->setFloating(aCardInfoDockFloating->isChecked()); return; } if (o == aDeckDockFloating) { deckDock->setFloating(aDeckDockFloating->isChecked()); return; } if (o == aFilterDockFloating) { filterDock->setFloating(aFilterDockFloating->isChecked()); return; } } void TabDeckEditor::dockTopLevelChanged(bool topLevel) { QObject *o = sender(); if (o == cardInfoDock) { aCardInfoDockFloating->setChecked(topLevel); return; } if (o == deckDock) { aDeckDockFloating->setChecked(topLevel); return; } if (o == filterDock) { aFilterDockFloating->setChecked(topLevel); return; } } void TabDeckEditor::saveDbHeaderState() { SettingsCache::instance().layouts().setDeckEditorDbHeaderState(databaseView->header()->saveState()); } void TabDeckEditor::setSaveStatus(bool newStatus) { aSaveDeck->setEnabled(newStatus); aSaveDeckAs->setEnabled(newStatus); aSaveDeckToClipboard->setEnabled(newStatus); aSaveDeckToClipboardRaw->setEnabled(newStatus); saveDeckToClipboardMenu->setEnabled(newStatus); aPrintDeck->setEnabled(newStatus); analyzeDeckMenu->setEnabled(newStatus); } void TabDeckEditor::showSearchSyntaxHelp() { QFile file("theme:help/search.md"); if (!file.open(QFile::ReadOnly | QFile::Text)) { return; } QTextStream in(&file); QString text = in.readAll(); file.close(); // Poor Markdown Converter auto opts = QRegularExpression::MultilineOption; text = text.replace(QRegularExpression("^(###)(.*)", opts), "

\\2

") .replace(QRegularExpression("^(##)(.*)", opts), "

\\2

") .replace(QRegularExpression("^(#)(.*)", opts), "

\\2

") .replace(QRegularExpression("^------*", opts), "
") .replace(QRegularExpression(R"(\[([^[]+)\]\(([^\)]+)\))", opts), R"(\1)"); auto browser = new QTextBrowser; browser->setParent(this, Qt::Window | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowMinMaxButtonsHint | Qt::WindowCloseButtonHint | Qt::WindowFullscreenButtonHint); browser->setWindowTitle("Search Help"); browser->setReadOnly(true); browser->setMinimumSize({500, 600}); QString sheet = QString("a { text-decoration: underline; color: rgb(71,158,252) };"); browser->document()->setDefaultStyleSheet(sheet); browser->setHtml(text); connect(browser, &QTextBrowser::anchorClicked, [=](const QUrl &link) { searchEdit->setText(link.fragment()); }); browser->show(); }