* implement max lengths for input dialogs that are sent to the server * missed a double setMaxLength * implement max string lengths server side * add custom getText dialog with max length * fix deck storage tab and miscellaneous server side * add max size for deck uploads * final pass on client side limits
1279 lines
45 KiB
C++
1279 lines
45 KiB
C++
#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 <QAction>
|
|
#include <QApplication>
|
|
#include <QClipboard>
|
|
#include <QCloseEvent>
|
|
#include <QDesktopServices>
|
|
#include <QDir>
|
|
#include <QDockWidget>
|
|
#include <QFileDialog>
|
|
#include <QHeaderView>
|
|
#include <QLabel>
|
|
#include <QLineEdit>
|
|
#include <QMenu>
|
|
#include <QMenuBar>
|
|
#include <QMessageBox>
|
|
#include <QPrintPreviewDialog>
|
|
#include <QProcessEnvironment>
|
|
#include <QPushButton>
|
|
#include <QRegularExpression>
|
|
#include <QSplitter>
|
|
#include <QTextBrowser>
|
|
#include <QTextEdit>
|
|
#include <QTextStream>
|
|
#include <QTimer>
|
|
#include <QToolBar>
|
|
#include <QToolButton>
|
|
#include <QTreeView>
|
|
#include <QUrl>
|
|
#include <QVBoxLayout>
|
|
|
|
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<Qt::DockWidgetArea>(2), deckDock);
|
|
addDockWidget(static_cast<Qt::DockWidgetArea>(2), cardInfoDock);
|
|
addDockWidget(static_cast<Qt::DockWidgetArea>(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<google::protobuf::uint32>(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), "<h3>\\2</h3>")
|
|
.replace(QRegularExpression("^(##)(.*)", opts), "<h2>\\2</h2>")
|
|
.replace(QRegularExpression("^(#)(.*)", opts), "<h1>\\2</h1>")
|
|
.replace(QRegularExpression("^------*", opts), "<hr />")
|
|
.replace(QRegularExpression(R"(\[([^[]+)\]\(([^\)]+)\))", opts), R"(<a href='\2'>\1</a>)");
|
|
|
|
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();
|
|
}
|