1 #include "tab_deck_editor.h"
2 
3 #include "abstractclient.h"
4 #include "carddatabasemodel.h"
5 #include "cardframe.h"
6 #include "decklistmodel.h"
7 #include "deckstats_interface.h"
8 #include "dlg_load_deck_from_clipboard.h"
9 #include "filterbuilder.h"
10 #include "filtertreemodel.h"
11 #include "main.h"
12 #include "pb/command_deck_upload.pb.h"
13 #include "pb/response.pb.h"
14 #include "pending_command.h"
15 #include "pictureloader.h"
16 #include "pixmapgenerator.h"
17 #include "settingscache.h"
18 #include "tab_supervisor.h"
19 #include "tappedout_interface.h"
20 
21 #include <QAction>
22 #include <QApplication>
23 #include <QClipboard>
24 #include <QCloseEvent>
25 #include <QDesktopServices>
26 #include <QDir>
27 #include <QDockWidget>
28 #include <QFileDialog>
29 #include <QHeaderView>
30 #include <QLabel>
31 #include <QLineEdit>
32 #include <QMenu>
33 #include <QMenuBar>
34 #include <QMessageBox>
35 #include <QPrintPreviewDialog>
36 #include <QProcessEnvironment>
37 #include <QPushButton>
38 #include <QRegularExpression>
39 #include <QSplitter>
40 #include <QTextBrowser>
41 #include <QTextEdit>
42 #include <QTextStream>
43 #include <QTimer>
44 #include <QToolBar>
45 #include <QToolButton>
46 #include <QTreeView>
47 #include <QUrl>
48 #include <QVBoxLayout>
49 
keyPressEvent(QKeyEvent * event)50 void SearchLineEdit::keyPressEvent(QKeyEvent *event)
51 {
52     if (treeView && ((event->key() == Qt::Key_Up) || (event->key() == Qt::Key_Down)))
53         QCoreApplication::sendEvent(treeView, event);
54     LineEditUnfocusable::keyPressEvent(event);
55 }
56 
createDeckDock()57 void TabDeckEditor::createDeckDock()
58 {
59     deckModel = new DeckListModel(this);
60     deckModel->setObjectName("deckModel");
61     connect(deckModel, SIGNAL(deckHashChanged()), this, SLOT(updateHash()));
62     deckView = new QTreeView();
63     deckView->setObjectName("deckView");
64     deckView->setModel(deckModel);
65     deckView->setUniformRowHeights(true);
66     deckView->setSortingEnabled(true);
67     deckView->sortByColumn(1, Qt::AscendingOrder);
68     deckView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
69     deckView->installEventFilter(&deckViewKeySignals);
70     connect(deckView->selectionModel(), SIGNAL(currentRowChanged(const QModelIndex &, const QModelIndex &)), this,
71             SLOT(updateCardInfoRight(const QModelIndex &, const QModelIndex &)));
72     connect(deckView, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(actSwapCard()));
73     connect(&deckViewKeySignals, SIGNAL(onShiftS()), this, SLOT(actSwapCard()));
74     connect(&deckViewKeySignals, SIGNAL(onEnter()), this, SLOT(actIncrement()));
75     connect(&deckViewKeySignals, SIGNAL(onCtrlAltEqual()), this, SLOT(actIncrement()));
76     connect(&deckViewKeySignals, SIGNAL(onCtrlAltMinus()), this, SLOT(actDecrement()));
77     connect(&deckViewKeySignals, SIGNAL(onShiftRight()), this, SLOT(actIncrement()));
78     connect(&deckViewKeySignals, SIGNAL(onShiftLeft()), this, SLOT(actDecrement()));
79     connect(&deckViewKeySignals, SIGNAL(onDelete()), this, SLOT(actRemoveCard()));
80 
81     nameLabel = new QLabel();
82     nameLabel->setObjectName("nameLabel");
83     nameEdit = new LineEditUnfocusable;
84     nameEdit->setObjectName("nameEdit");
85     nameLabel->setBuddy(nameEdit);
86     connect(nameEdit, SIGNAL(textChanged(const QString &)), this, SLOT(updateName(const QString &)));
87     commentsLabel = new QLabel();
88     commentsLabel->setObjectName("commentsLabel");
89     commentsEdit = new QTextEdit;
90     commentsEdit->setMinimumHeight(nameEdit->minimumSizeHint().height());
91     commentsEdit->setObjectName("commentsEdit");
92     commentsLabel->setBuddy(commentsEdit);
93     connect(commentsEdit, SIGNAL(textChanged()), this, SLOT(updateComments()));
94 
95     aIncrement = new QAction(QString(), this);
96     aIncrement->setIcon(QPixmap("theme:icons/increment"));
97     connect(aIncrement, SIGNAL(triggered()), this, SLOT(actIncrement()));
98     auto *tbIncrement = new QToolButton(this);
99     tbIncrement->setDefaultAction(aIncrement);
100 
101     aDecrement = new QAction(QString(), this);
102     aDecrement->setIcon(QPixmap("theme:icons/decrement"));
103     connect(aDecrement, SIGNAL(triggered()), this, SLOT(actDecrement()));
104     auto *tbDecrement = new QToolButton(this);
105     tbDecrement->setDefaultAction(aDecrement);
106 
107     aRemoveCard = new QAction(QString(), this);
108     aRemoveCard->setIcon(QPixmap("theme:icons/remove_row"));
109     connect(aRemoveCard, SIGNAL(triggered()), this, SLOT(actRemoveCard()));
110     auto *tbRemoveCard = new QToolButton(this);
111     tbRemoveCard->setDefaultAction(aRemoveCard);
112 
113     auto *upperLayout = new QGridLayout;
114     upperLayout->setObjectName("upperLayout");
115     upperLayout->addWidget(nameLabel, 0, 0);
116     upperLayout->addWidget(nameEdit, 0, 1);
117 
118     upperLayout->addWidget(commentsLabel, 1, 0);
119     upperLayout->addWidget(commentsEdit, 1, 1);
120 
121     hashLabel1 = new QLabel();
122     hashLabel1->setObjectName("hashLabel1");
123     auto *hashSizePolicy = new QSizePolicy();
124     hashSizePolicy->setHorizontalPolicy(QSizePolicy::Fixed);
125     hashLabel1->setSizePolicy(*hashSizePolicy);
126     hashLabel = new LineEditUnfocusable;
127     hashLabel->setObjectName("hashLabel");
128     hashLabel->setReadOnly(true);
129     hashLabel->setFrame(false);
130 
131     auto *lowerLayout = new QGridLayout;
132     lowerLayout->setObjectName("lowerLayout");
133     lowerLayout->addWidget(hashLabel1, 0, 0);
134     lowerLayout->addWidget(hashLabel, 0, 1);
135     lowerLayout->addWidget(tbIncrement, 0, 2);
136     lowerLayout->addWidget(tbDecrement, 0, 3);
137     lowerLayout->addWidget(tbRemoveCard, 0, 4);
138     lowerLayout->addWidget(deckView, 1, 0, 1, 5);
139 
140     // Create widgets for both layouts to make splitter work correctly
141     auto *topWidget = new QWidget;
142     topWidget->setLayout(upperLayout);
143     auto *bottomWidget = new QWidget;
144     bottomWidget->setLayout(lowerLayout);
145 
146     auto *split = new QSplitter;
147     split->setObjectName("deckSplitter");
148     split->setOrientation(Qt::Vertical);
149     split->setChildrenCollapsible(true);
150     split->addWidget(topWidget);
151     split->addWidget(bottomWidget);
152     split->setStretchFactor(0, 1);
153     split->setStretchFactor(1, 4);
154 
155     auto *rightFrame = new QVBoxLayout;
156     rightFrame->setObjectName("rightFrame");
157     rightFrame->addWidget(split);
158 
159     deckDock = new QDockWidget(this);
160     deckDock->setObjectName("deckDock");
161 
162     deckDock->setMinimumSize(QSize(200, 41));
163     deckDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
164     deckDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable |
165                           QDockWidget::DockWidgetMovable);
166     auto *deckDockContents = new QWidget();
167     deckDockContents->setObjectName("deckDockContents");
168     deckDockContents->setLayout(rightFrame);
169     deckDock->setWidget(deckDockContents);
170 
171     deckDock->installEventFilter(this);
172     connect(deckDock, SIGNAL(topLevelChanged(bool)), this, SLOT(dockTopLevelChanged(bool)));
173 }
174 
createCardInfoDock()175 void TabDeckEditor::createCardInfoDock()
176 {
177     cardInfo = new CardFrame();
178     cardInfo->setObjectName("cardInfo");
179     auto *cardInfoFrame = new QVBoxLayout;
180     cardInfoFrame->setObjectName("cardInfoFrame");
181     cardInfoFrame->addWidget(cardInfo);
182 
183     cardInfoDock = new QDockWidget(this);
184     cardInfoDock->setObjectName("cardInfoDock");
185 
186     cardInfoDock->setMinimumSize(QSize(200, 41));
187     cardInfoDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
188     cardInfoDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable |
189                               QDockWidget::DockWidgetMovable);
190     auto *cardInfoDockContents = new QWidget();
191     cardInfoDockContents->setObjectName("cardInfoDockContents");
192     cardInfoDockContents->setLayout(cardInfoFrame);
193     cardInfoDock->setWidget(cardInfoDockContents);
194 
195     cardInfoDock->installEventFilter(this);
196     connect(cardInfoDock, SIGNAL(topLevelChanged(bool)), this, SLOT(dockTopLevelChanged(bool)));
197 }
198 
createFiltersDock()199 void TabDeckEditor::createFiltersDock()
200 {
201     filterModel = new FilterTreeModel();
202     filterModel->setObjectName("filterModel");
203     databaseDisplayModel->setFilterTree(filterModel->filterTree());
204     databaseDisplayModel->setObjectName("databaseDisplayModel");
205     filterView = new QTreeView;
206     filterView->setObjectName("filterView");
207     filterView->setModel(filterModel);
208     filterView->setUniformRowHeights(true);
209     filterView->setHeaderHidden(true);
210     filterView->setContextMenuPolicy(Qt::CustomContextMenu);
211     filterView->installEventFilter(&filterViewKeySignals);
212     connect(filterModel, SIGNAL(layoutChanged()), filterView, SLOT(expandAll()));
213     connect(filterView, SIGNAL(customContextMenuRequested(const QPoint &)), this,
214             SLOT(filterViewCustomContextMenu(const QPoint &)));
215     connect(&filterViewKeySignals, SIGNAL(onDelete()), this, SLOT(actClearFilterOne()));
216 
217     auto *filterBuilder = new FilterBuilder;
218     filterBuilder->setObjectName("filterBuilder");
219     connect(filterBuilder, SIGNAL(add(const CardFilter *)), filterModel, SLOT(addFilter(const CardFilter *)));
220 
221     auto *filterDelOne = new QToolButton();
222     filterDelOne->setObjectName("filterDelOne");
223     filterDelOne->setDefaultAction(aClearFilterOne);
224     filterDelOne->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
225 
226     auto *filterDelAll = new QToolButton();
227     filterDelAll->setObjectName("filterDelAll");
228     filterDelAll->setDefaultAction(aClearFilterAll);
229     filterDelAll->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
230 
231     auto *filterLayout = new QGridLayout;
232     filterLayout->setObjectName("filterLayout");
233     filterLayout->setContentsMargins(0, 0, 0, 0);
234     filterLayout->addWidget(filterBuilder, 0, 0, 1, 3);
235     filterLayout->addWidget(filterView, 1, 0, 1, 3);
236     filterLayout->addWidget(filterDelOne, 2, 0, 1, 1);
237     filterLayout->addWidget(filterDelAll, 2, 2, 1, 1);
238 
239     filterBox = new QWidget();
240     filterBox->setObjectName("filterBox");
241     filterBox->setLayout(filterLayout);
242 
243     auto *filterFrame = new QVBoxLayout;
244     filterFrame->setObjectName("filterFrame");
245     filterFrame->addWidget(filterBox);
246 
247     filterDock = new QDockWidget(this);
248     filterDock->setObjectName("filterDock");
249 
250     filterDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable |
251                             QDockWidget::DockWidgetMovable);
252     auto *filterDockContents = new QWidget(this);
253     filterDockContents->setObjectName("filterDockContents");
254     filterDockContents->setLayout(filterFrame);
255     filterDock->setWidget(filterDockContents);
256 
257     filterDock->installEventFilter(this);
258     connect(filterDock, SIGNAL(topLevelChanged(bool)), this, SLOT(dockTopLevelChanged(bool)));
259 }
260 
createMenus()261 void TabDeckEditor::createMenus()
262 {
263     aNewDeck = new QAction(QString(), this);
264     connect(aNewDeck, SIGNAL(triggered()), this, SLOT(actNewDeck()));
265 
266     aLoadDeck = new QAction(QString(), this);
267     connect(aLoadDeck, SIGNAL(triggered()), this, SLOT(actLoadDeck()));
268 
269     aSaveDeck = new QAction(QString(), this);
270     connect(aSaveDeck, SIGNAL(triggered()), this, SLOT(actSaveDeck()));
271 
272     aSaveDeckAs = new QAction(QString(), this);
273     connect(aSaveDeckAs, SIGNAL(triggered()), this, SLOT(actSaveDeckAs()));
274 
275     aLoadDeckFromClipboard = new QAction(QString(), this);
276     connect(aLoadDeckFromClipboard, SIGNAL(triggered()), this, SLOT(actLoadDeckFromClipboard()));
277 
278     aSaveDeckToClipboard = new QAction(QString(), this);
279     connect(aSaveDeckToClipboard, SIGNAL(triggered()), this, SLOT(actSaveDeckToClipboard()));
280 
281     aSaveDeckToClipboardRaw = new QAction(QString(), this);
282     connect(aSaveDeckToClipboardRaw, SIGNAL(triggered()), this, SLOT(actSaveDeckToClipboardRaw()));
283 
284     aPrintDeck = new QAction(QString(), this);
285     connect(aPrintDeck, SIGNAL(triggered()), this, SLOT(actPrintDeck()));
286 
287     aExportDeckDecklist = new QAction(QString(), this);
288     connect(aExportDeckDecklist, SIGNAL(triggered()), this, SLOT(actExportDeckDecklist()));
289 
290     aAnalyzeDeckDeckstats = new QAction(QString(), this);
291     connect(aAnalyzeDeckDeckstats, SIGNAL(triggered()), this, SLOT(actAnalyzeDeckDeckstats()));
292 
293     aAnalyzeDeckTappedout = new QAction(QString(), this);
294     connect(aAnalyzeDeckTappedout, SIGNAL(triggered()), this, SLOT(actAnalyzeDeckTappedout()));
295 
296     analyzeDeckMenu = new QMenu(this);
297     analyzeDeckMenu->addAction(aExportDeckDecklist);
298     analyzeDeckMenu->addAction(aAnalyzeDeckDeckstats);
299     analyzeDeckMenu->addAction(aAnalyzeDeckTappedout);
300 
301     aClose = new QAction(QString(), this);
302     connect(aClose, SIGNAL(triggered()), this, SLOT(closeRequest()));
303 
304     aClearFilterAll = new QAction(QString(), this);
305     aClearFilterAll->setIcon(QPixmap("theme:icons/clearsearch"));
306     connect(aClearFilterAll, SIGNAL(triggered()), this, SLOT(actClearFilterAll()));
307 
308     aClearFilterOne = new QAction(QString(), this);
309     aClearFilterOne->setIcon(QPixmap("theme:icons/decrement"));
310     connect(aClearFilterOne, SIGNAL(triggered()), this, SLOT(actClearFilterOne()));
311 
312     saveDeckToClipboardMenu = new QMenu(this);
313     saveDeckToClipboardMenu->addAction(aSaveDeckToClipboard);
314     saveDeckToClipboardMenu->addAction(aSaveDeckToClipboardRaw);
315 
316     deckMenu = new QMenu(this);
317     deckMenu->addAction(aNewDeck);
318     deckMenu->addAction(aLoadDeck);
319     deckMenu->addAction(aSaveDeck);
320     deckMenu->addAction(aSaveDeckAs);
321     deckMenu->addSeparator();
322     deckMenu->addAction(aLoadDeckFromClipboard);
323     deckMenu->addMenu(saveDeckToClipboardMenu);
324     deckMenu->addSeparator();
325     deckMenu->addAction(aPrintDeck);
326     deckMenu->addMenu(analyzeDeckMenu);
327     deckMenu->addSeparator();
328     deckMenu->addAction(aClearFilterOne);
329     deckMenu->addAction(aClearFilterAll);
330     deckMenu->addSeparator();
331     deckMenu->addAction(aClose);
332     addTabMenu(deckMenu);
333 
334     viewMenu = new QMenu(this);
335 
336     cardInfoDockMenu = viewMenu->addMenu(QString());
337     deckDockMenu = viewMenu->addMenu(QString());
338     filterDockMenu = viewMenu->addMenu(QString());
339 
340     aCardInfoDockVisible = cardInfoDockMenu->addAction(QString());
341     aCardInfoDockVisible->setCheckable(true);
342     connect(aCardInfoDockVisible, SIGNAL(triggered()), this, SLOT(dockVisibleTriggered()));
343     aCardInfoDockFloating = cardInfoDockMenu->addAction(QString());
344     aCardInfoDockFloating->setCheckable(true);
345     connect(aCardInfoDockFloating, SIGNAL(triggered()), this, SLOT(dockFloatingTriggered()));
346 
347     aDeckDockVisible = deckDockMenu->addAction(QString());
348     aDeckDockVisible->setCheckable(true);
349     connect(aDeckDockVisible, SIGNAL(triggered()), this, SLOT(dockVisibleTriggered()));
350     aDeckDockFloating = deckDockMenu->addAction(QString());
351     aDeckDockFloating->setCheckable(true);
352     connect(aDeckDockFloating, SIGNAL(triggered()), this, SLOT(dockFloatingTriggered()));
353 
354     aFilterDockVisible = filterDockMenu->addAction(QString());
355     aFilterDockVisible->setCheckable(true);
356     connect(aFilterDockVisible, SIGNAL(triggered()), this, SLOT(dockVisibleTriggered()));
357     aFilterDockFloating = filterDockMenu->addAction(QString());
358     aFilterDockFloating->setCheckable(true);
359     connect(aFilterDockFloating, SIGNAL(triggered()), this, SLOT(dockFloatingTriggered()));
360 
361     viewMenu->addSeparator();
362 
363     aResetLayout = viewMenu->addAction(QString());
364     connect(aResetLayout, SIGNAL(triggered()), this, SLOT(restartLayout()));
365     viewMenu->addAction(aResetLayout);
366 
367     setSaveStatus(false);
368 
369     addTabMenu(viewMenu);
370 }
371 
createCentralFrame()372 void TabDeckEditor::createCentralFrame()
373 {
374     searchEdit = new SearchLineEdit;
375     searchEdit->setObjectName("searchEdit");
376     searchEdit->setPlaceholderText(tr("Search by card name"));
377     searchEdit->setClearButtonEnabled(true);
378     searchEdit->addAction(loadColorAdjustedPixmap("theme:icons/search"), QLineEdit::LeadingPosition);
379     auto help = searchEdit->addAction(QPixmap("theme:icons/info"), QLineEdit::TrailingPosition);
380     searchEdit->installEventFilter(&searchKeySignals);
381 
382     setFocusProxy(searchEdit);
383     setFocusPolicy(Qt::ClickFocus);
384 
385     searchKeySignals.setObjectName("searchKeySignals");
386     connect(searchEdit, SIGNAL(textChanged(const QString &)), this, SLOT(updateSearch(const QString &)));
387     connect(&searchKeySignals, SIGNAL(onEnter()), this, SLOT(actAddCard()));
388     connect(&searchKeySignals, SIGNAL(onCtrlAltEqual()), this, SLOT(actAddCard()));
389     connect(&searchKeySignals, SIGNAL(onCtrlAltRBracket()), this, SLOT(actAddCardToSideboard()));
390     connect(&searchKeySignals, SIGNAL(onCtrlAltMinus()), this, SLOT(actDecrementCard()));
391     connect(&searchKeySignals, SIGNAL(onCtrlAltLBracket()), this, SLOT(actDecrementCardFromSideboard()));
392     connect(&searchKeySignals, SIGNAL(onCtrlAltEnter()), this, SLOT(actAddCardToSideboard()));
393     connect(&searchKeySignals, SIGNAL(onCtrlEnter()), this, SLOT(actAddCardToSideboard()));
394     connect(help, &QAction::triggered, this, &TabDeckEditor::showSearchSyntaxHelp);
395 
396     databaseModel = new CardDatabaseModel(db, true, this);
397     databaseModel->setObjectName("databaseModel");
398     databaseDisplayModel = new CardDatabaseDisplayModel(this);
399     databaseDisplayModel->setSourceModel(databaseModel);
400     databaseDisplayModel->setFilterKeyColumn(0);
401 
402     databaseView = new QTreeView();
403     databaseView->setObjectName("databaseView");
404     databaseView->setFocusProxy(searchEdit);
405     databaseView->setUniformRowHeights(true);
406     databaseView->setRootIsDecorated(false);
407     databaseView->setAlternatingRowColors(true);
408     databaseView->setSortingEnabled(true);
409     databaseView->sortByColumn(0, Qt::AscendingOrder);
410     databaseView->setModel(databaseDisplayModel);
411     databaseView->setContextMenuPolicy(Qt::CustomContextMenu);
412     connect(databaseView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(databaseCustomMenu(QPoint)));
413     connect(databaseView->selectionModel(), SIGNAL(currentRowChanged(const QModelIndex &, const QModelIndex &)), this,
414             SLOT(updateCardInfoLeft(const QModelIndex &, const QModelIndex &)));
415     connect(databaseView, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(actAddCard()));
416 
417     QByteArray dbHeaderState = SettingsCache::instance().layouts().getDeckEditorDbHeaderState();
418     if (dbHeaderState.isNull()) {
419         // first run
420         databaseView->setColumnWidth(0, 200);
421     } else {
422         databaseView->header()->restoreState(dbHeaderState);
423     }
424     connect(databaseView->header(), SIGNAL(geometriesChanged()), this, SLOT(saveDbHeaderState()));
425 
426     searchEdit->setTreeView(databaseView);
427 
428     aAddCard = new QAction(QString(), this);
429     aAddCard->setIcon(QPixmap("theme:icons/arrow_right_green"));
430     connect(aAddCard, SIGNAL(triggered()), this, SLOT(actAddCard()));
431     auto *tbAddCard = new QToolButton(this);
432     tbAddCard->setDefaultAction(aAddCard);
433 
434     aAddCardToSideboard = new QAction(QString(), this);
435     aAddCardToSideboard->setIcon(QPixmap("theme:icons/arrow_right_blue"));
436     connect(aAddCardToSideboard, SIGNAL(triggered()), this, SLOT(actAddCardToSideboard()));
437     auto *tbAddCardToSideboard = new QToolButton(this);
438     tbAddCardToSideboard->setDefaultAction(aAddCardToSideboard);
439 
440     searchLayout = new QHBoxLayout;
441     searchLayout->setObjectName("searchLayout");
442     searchLayout->addWidget(searchEdit);
443     searchLayout->addWidget(tbAddCard);
444     searchLayout->addWidget(tbAddCardToSideboard);
445 
446     centralFrame = new QVBoxLayout;
447     centralFrame->setObjectName("centralFrame");
448     centralFrame->addLayout(searchLayout);
449     centralFrame->addWidget(databaseView);
450 
451     centralWidget = new QWidget(this);
452     centralWidget->setObjectName("centralWidget");
453     centralWidget->setLayout(centralFrame);
454     setCentralWidget(centralWidget);
455     setDockOptions(QMainWindow::AnimatedDocks | QMainWindow::AllowNestedDocks | QMainWindow::AllowTabbedDocks);
456 }
457 
databaseCustomMenu(QPoint point)458 void TabDeckEditor::databaseCustomMenu(QPoint point)
459 {
460     QMenu menu;
461     const CardInfoPtr info = currentCardInfo();
462 
463     // add to deck and sideboard options
464     QAction *addToDeck, *addToSideboard;
465     addToDeck = menu.addAction(tr("Add to Deck"));
466     addToSideboard = menu.addAction(tr("Add to Sideboard"));
467     connect(addToDeck, SIGNAL(triggered()), this, SLOT(actAddCard()));
468     connect(addToSideboard, SIGNAL(triggered()), this, SLOT(actAddCardToSideboard()));
469 
470     // filling out the related cards submenu
471     auto *relatedMenu = new QMenu(tr("Show Related cards"));
472     menu.addMenu(relatedMenu);
473     auto relatedCards = info->getAllRelatedCards();
474     if (relatedCards.isEmpty()) {
475         relatedMenu->setDisabled(true);
476     } else {
477         for (const CardRelation *rel : relatedCards) {
478             const QString &relatedCardName = rel->getName();
479             QAction *relatedCard = relatedMenu->addAction(relatedCardName);
480             connect(relatedCard, &QAction::triggered, cardInfo,
481                     [this, relatedCardName] { cardInfo->setCard(relatedCardName); });
482         }
483     }
484     menu.exec(databaseView->mapToGlobal(point));
485 }
486 
restartLayout()487 void TabDeckEditor::restartLayout()
488 {
489     deckDock->setVisible(true);
490     cardInfoDock->setVisible(true);
491     filterDock->setVisible(true);
492 
493     deckDock->setFloating(false);
494     cardInfoDock->setFloating(false);
495     filterDock->setFloating(false);
496 
497     aCardInfoDockVisible->setChecked(true);
498     aDeckDockVisible->setChecked(true);
499     aFilterDockVisible->setChecked(true);
500 
501     aCardInfoDockFloating->setChecked(false);
502     aDeckDockFloating->setChecked(false);
503     aFilterDockFloating->setChecked(false);
504 
505     addDockWidget(static_cast<Qt::DockWidgetArea>(2), deckDock);
506     addDockWidget(static_cast<Qt::DockWidgetArea>(2), cardInfoDock);
507     addDockWidget(static_cast<Qt::DockWidgetArea>(2), filterDock);
508 
509     splitDockWidget(cardInfoDock, deckDock, Qt::Horizontal);
510     splitDockWidget(cardInfoDock, filterDock, Qt::Vertical);
511 
512     deckDock->setMinimumWidth(360);
513     deckDock->setMaximumWidth(360);
514 
515     cardInfoDock->setMinimumSize(250, 360);
516     cardInfoDock->setMaximumSize(250, 360);
517     QTimer::singleShot(100, this, SLOT(freeDocksSize()));
518 }
519 
freeDocksSize()520 void TabDeckEditor::freeDocksSize()
521 {
522     deckDock->setMinimumSize(100, 100);
523     deckDock->setMaximumSize(5000, 5000);
524 
525     cardInfoDock->setMinimumSize(100, 100);
526     cardInfoDock->setMaximumSize(5000, 5000);
527 
528     filterDock->setMinimumSize(100, 100);
529     filterDock->setMaximumSize(5000, 5000);
530 }
531 
refreshShortcuts()532 void TabDeckEditor::refreshShortcuts()
533 {
534     ShortcutsSettings &shortcuts = SettingsCache::instance().shortcuts();
535     aNewDeck->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aNewDeck"));
536     aLoadDeck->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aLoadDeck"));
537     aSaveDeck->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aSaveDeck"));
538     aExportDeckDecklist->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aExportDeckDecklist"));
539     aSaveDeckAs->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aSaveDeckAs"));
540     aLoadDeckFromClipboard->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aLoadDeckFromClipboard"));
541     aPrintDeck->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aPrintDeck"));
542     aAnalyzeDeckDeckstats->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aAnalyzeDeck"));
543     aClose->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aClose"));
544     aResetLayout->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aResetLayout"));
545     aClearFilterAll->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aClearFilterAll"));
546     aClearFilterOne->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aClearFilterOne"));
547 
548     aSaveDeckToClipboard->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aSaveDeckToClipboard"));
549     aSaveDeckToClipboardRaw->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aSaveDeckToClipboardRaw"));
550 
551     aClearFilterOne->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aClearFilterOne"));
552     aClose->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aClose"));
553     aRemoveCard->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aRemoveCard"));
554     aIncrement->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aIncrement"));
555     aDecrement->setShortcuts(shortcuts.getShortcut("TabDeckEditor/aDecrement"));
556 }
557 
loadLayout()558 void TabDeckEditor::loadLayout()
559 {
560     LayoutsSettings &layouts = SettingsCache::instance().layouts();
561     restoreState(layouts.getDeckEditorLayoutState());
562     restoreGeometry(layouts.getDeckEditorGeometry());
563 
564     aCardInfoDockVisible->setChecked(cardInfoDock->isVisible());
565     aFilterDockVisible->setChecked(filterDock->isVisible());
566     aDeckDockVisible->setChecked(deckDock->isVisible());
567 
568     aCardInfoDockFloating->setEnabled(aCardInfoDockVisible->isChecked());
569     aDeckDockFloating->setEnabled(aDeckDockVisible->isChecked());
570     aFilterDockFloating->setEnabled(aFilterDockVisible->isChecked());
571 
572     aCardInfoDockFloating->setChecked(cardInfoDock->isFloating());
573     aFilterDockFloating->setChecked(filterDock->isFloating());
574     aDeckDockFloating->setChecked(deckDock->isFloating());
575 
576     cardInfoDock->setMinimumSize(layouts.getDeckEditorCardSize());
577     cardInfoDock->setMaximumSize(layouts.getDeckEditorCardSize());
578 
579     filterDock->setMinimumSize(layouts.getDeckEditorFilterSize());
580     filterDock->setMaximumSize(layouts.getDeckEditorFilterSize());
581 
582     deckDock->setMinimumSize(layouts.getDeckEditorDeckSize());
583     deckDock->setMaximumSize(layouts.getDeckEditorDeckSize());
584 
585     QTimer::singleShot(100, this, SLOT(freeDocksSize()));
586 }
587 
TabDeckEditor(TabSupervisor * _tabSupervisor,QWidget * parent)588 TabDeckEditor::TabDeckEditor(TabSupervisor *_tabSupervisor, QWidget *parent)
589     : Tab(_tabSupervisor, parent), modified(false)
590 {
591     setObjectName("TabDeckEditor");
592 
593     createMenus();
594 
595     createCentralFrame();
596 
597     createDeckDock();
598     createCardInfoDock();
599     createFiltersDock();
600 
601     restartLayout();
602 
603     this->installEventFilter(this);
604 
605     retranslateUi();
606     connect(&SettingsCache::instance().shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts()));
607     refreshShortcuts();
608 
609     QTimer::singleShot(0, this, SLOT(loadLayout()));
610 }
611 
~TabDeckEditor()612 TabDeckEditor::~TabDeckEditor()
613 {
614     emit deckEditorClosing(this);
615 }
616 
retranslateUi()617 void TabDeckEditor::retranslateUi()
618 {
619     cardInfo->retranslateUi();
620 
621     aClearFilterAll->setText(tr("&Clear all filters"));
622     aClearFilterOne->setText(tr("Delete selected"));
623 
624     nameLabel->setText(tr("Deck &name:"));
625     commentsLabel->setText(tr("&Comments:"));
626     hashLabel1->setText(tr("Hash:"));
627 
628     aNewDeck->setText(tr("&New deck"));
629     aLoadDeck->setText(tr("&Load deck..."));
630     aSaveDeck->setText(tr("&Save deck"));
631     aSaveDeckAs->setText(tr("Save deck &as..."));
632     aLoadDeckFromClipboard->setText(tr("Load deck from cl&ipboard..."));
633 
634     saveDeckToClipboardMenu->setTitle(tr("Save deck to clipboard"));
635     aSaveDeckToClipboard->setText(tr("Annotated"));
636     aSaveDeckToClipboardRaw->setText(tr("Not Annotated"));
637 
638     aPrintDeck->setText(tr("&Print deck..."));
639 
640     analyzeDeckMenu->setTitle(tr("&Send deck to online service"));
641     aExportDeckDecklist->setText(tr("Create decklist (decklist.org)"));
642     aAnalyzeDeckDeckstats->setText(tr("Analyze deck (deckstats.net)"));
643     aAnalyzeDeckTappedout->setText(tr("Analyze deck (tappedout.net)"));
644 
645     aClose->setText(tr("&Close"));
646 
647     aAddCard->setText(tr("Add card to &maindeck"));
648     aAddCardToSideboard->setText(tr("Add card to &sideboard"));
649 
650     aRemoveCard->setText(tr("&Remove row"));
651 
652     aIncrement->setText(tr("&Increment number"));
653 
654     aDecrement->setText(tr("&Decrement number"));
655 
656     deckMenu->setTitle(tr("&Deck Editor"));
657 
658     cardInfoDock->setWindowTitle(tr("Card Info"));
659     deckDock->setWindowTitle(tr("Deck"));
660     filterDock->setWindowTitle(tr("Filters"));
661 
662     viewMenu->setTitle(tr("&View"));
663     cardInfoDockMenu->setTitle(tr("Card Info"));
664     deckDockMenu->setTitle(tr("Deck"));
665     filterDockMenu->setTitle(tr("Filters"));
666 
667     aCardInfoDockVisible->setText(tr("Visible"));
668     aCardInfoDockFloating->setText(tr("Floating"));
669 
670     aDeckDockVisible->setText(tr("Visible"));
671     aDeckDockFloating->setText(tr("Floating"));
672 
673     aFilterDockVisible->setText(tr("Visible"));
674     aFilterDockFloating->setText(tr("Floating"));
675 
676     aResetLayout->setText(tr("Reset layout"));
677 }
678 
getTabText() const679 QString TabDeckEditor::getTabText() const
680 {
681     QString result = tr("Deck: %1").arg(nameEdit->text().simplified());
682     if (modified)
683         result.prepend("* ");
684     return result;
685 }
686 
updateName(const QString & name)687 void TabDeckEditor::updateName(const QString &name)
688 {
689     deckModel->getDeckList()->setName(name);
690     setModified(true);
691     setSaveStatus(true);
692 }
693 
updateComments()694 void TabDeckEditor::updateComments()
695 {
696     deckModel->getDeckList()->setComments(commentsEdit->toPlainText());
697     setModified(true);
698     setSaveStatus(true);
699 }
700 
updateCardInfoLeft(const QModelIndex & current,const QModelIndex &)701 void TabDeckEditor::updateCardInfoLeft(const QModelIndex &current, const QModelIndex & /*previous*/)
702 {
703     cardInfo->setCard(current.sibling(current.row(), 0).data().toString());
704 }
705 
updateCardInfoRight(const QModelIndex & current,const QModelIndex &)706 void TabDeckEditor::updateCardInfoRight(const QModelIndex &current, const QModelIndex & /*previous*/)
707 {
708     if (!current.isValid())
709         return;
710     if (!current.model()->hasChildren(current.sibling(current.row(), 0)))
711         cardInfo->setCard(current.sibling(current.row(), 1).data().toString());
712 }
713 
updateSearch(const QString & search)714 void TabDeckEditor::updateSearch(const QString &search)
715 {
716     databaseDisplayModel->setStringFilter(search);
717     QModelIndexList sel = databaseView->selectionModel()->selectedRows();
718     if (sel.isEmpty() && databaseDisplayModel->rowCount())
719         databaseView->selectionModel()->setCurrentIndex(databaseDisplayModel->index(0, 0),
720                                                         QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
721 }
722 
updateHash()723 void TabDeckEditor::updateHash()
724 {
725     hashLabel->setText(deckModel->getDeckList()->getDeckHash());
726 }
727 
confirmClose()728 bool TabDeckEditor::confirmClose()
729 {
730     if (modified) {
731         tabSupervisor->setCurrentWidget(this);
732         QMessageBox::StandardButton ret = QMessageBox::warning(
733             this, tr("Are you sure?"), tr("The decklist has been modified.\nDo you want to save the changes?"),
734             QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
735         if (ret == QMessageBox::Save)
736             return actSaveDeck();
737         else if (ret == QMessageBox::Cancel)
738             return false;
739     }
740     return true;
741 }
742 
closeRequest()743 void TabDeckEditor::closeRequest()
744 {
745     if (confirmClose())
746         deleteLater();
747 }
748 
actNewDeck()749 void TabDeckEditor::actNewDeck()
750 {
751     if (!confirmClose())
752         return;
753 
754     deckModel->cleanList();
755     nameEdit->setText(QString());
756     commentsEdit->setText(QString());
757     hashLabel->setText(QString());
758     setModified(false);
759     setSaveStatus(false);
760 }
761 
actLoadDeck()762 void TabDeckEditor::actLoadDeck()
763 {
764     if (!confirmClose())
765         return;
766 
767     QFileDialog dialog(this, tr("Load deck"));
768     dialog.setDirectory(SettingsCache::instance().getDeckPath());
769     dialog.setNameFilters(DeckLoader::fileNameFilters);
770     if (!dialog.exec())
771         return;
772 
773     QString fileName = dialog.selectedFiles().at(0);
774     DeckLoader::FileFormat fmt = DeckLoader::getFormatFromName(fileName);
775 
776     auto *l = new DeckLoader;
777     if (l->loadFromFile(fileName, fmt)) {
778         setSaveStatus(false);
779         setDeck(l);
780     } else
781         delete l;
782     setSaveStatus(true);
783 }
784 
saveDeckRemoteFinished(const Response & response)785 void TabDeckEditor::saveDeckRemoteFinished(const Response &response)
786 {
787     if (response.response_code() != Response::RespOk)
788         QMessageBox::critical(this, tr("Error"), tr("The deck could not be saved."));
789     else
790         setModified(false);
791 }
792 
actSaveDeck()793 bool TabDeckEditor::actSaveDeck()
794 {
795     DeckLoader *const deck = deckModel->getDeckList();
796     if (deck->getLastRemoteDeckId() != -1) {
797         Command_DeckUpload cmd;
798         cmd.set_deck_id(static_cast<google::protobuf::uint32>(deck->getLastRemoteDeckId()));
799         cmd.set_deck_list(deck->writeToString_Native().toStdString());
800 
801         PendingCommand *pend = AbstractClient::prepareSessionCommand(cmd);
802         connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
803                 SLOT(saveDeckRemoteFinished(Response)));
804         tabSupervisor->getClient()->sendCommand(pend);
805 
806         return true;
807     } else if (deck->getLastFileName().isEmpty())
808         return actSaveDeckAs();
809     else if (deck->saveToFile(deck->getLastFileName(), deck->getLastFileFormat())) {
810         setModified(false);
811         return true;
812     }
813     QMessageBox::critical(
814         this, tr("Error"),
815         tr("The deck could not be saved.\nPlease check that the directory is writable and try again."));
816     return false;
817 }
818 
actSaveDeckAs()819 bool TabDeckEditor::actSaveDeckAs()
820 {
821     QFileDialog dialog(this, tr("Save deck"));
822     dialog.setDirectory(SettingsCache::instance().getDeckPath());
823     dialog.setAcceptMode(QFileDialog::AcceptSave);
824     dialog.setDefaultSuffix("cod");
825     dialog.setNameFilters(DeckLoader::fileNameFilters);
826     dialog.selectFile(deckModel->getDeckList()->getName().trimmed() + ".cod");
827     if (!dialog.exec())
828         return false;
829 
830     QString fileName = dialog.selectedFiles().at(0);
831     DeckLoader::FileFormat fmt = DeckLoader::getFormatFromName(fileName);
832 
833     if (!deckModel->getDeckList()->saveToFile(fileName, fmt)) {
834         QMessageBox::critical(
835             this, tr("Error"),
836             tr("The deck could not be saved.\nPlease check that the directory is writable and try again."));
837         return false;
838     }
839     setModified(false);
840     return true;
841 }
842 
actLoadDeckFromClipboard()843 void TabDeckEditor::actLoadDeckFromClipboard()
844 {
845     if (!confirmClose())
846         return;
847 
848     DlgLoadDeckFromClipboard dlg(this);
849     if (!dlg.exec())
850         return;
851 
852     setDeck(dlg.getDeckList());
853     setModified(true);
854     setSaveStatus(true);
855 }
856 
actSaveDeckToClipboard()857 void TabDeckEditor::actSaveDeckToClipboard()
858 {
859     QString buffer;
860     QTextStream stream(&buffer);
861     deckModel->getDeckList()->saveToStream_Plain(stream);
862     QApplication::clipboard()->setText(buffer, QClipboard::Clipboard);
863     QApplication::clipboard()->setText(buffer, QClipboard::Selection);
864 }
865 
actSaveDeckToClipboardRaw()866 void TabDeckEditor::actSaveDeckToClipboardRaw()
867 {
868     QString buffer;
869     QTextStream stream(&buffer);
870     deckModel->getDeckList()->saveToStream_Plain(stream, false);
871     QApplication::clipboard()->setText(buffer, QClipboard::Clipboard);
872     QApplication::clipboard()->setText(buffer, QClipboard::Selection);
873 }
874 
actPrintDeck()875 void TabDeckEditor::actPrintDeck()
876 {
877     auto *dlg = new QPrintPreviewDialog(this);
878     connect(dlg, SIGNAL(paintRequested(QPrinter *)), deckModel, SLOT(printDeckList(QPrinter *)));
879     dlg->exec();
880 }
881 
882 // Action called when export deck to decklist menu item is pressed.
actExportDeckDecklist()883 void TabDeckEditor::actExportDeckDecklist()
884 {
885     // Get the decklist class for the deck.
886     DeckLoader *const deck = deckModel->getDeckList();
887     // create a string to load the decklist url into.
888     QString decklistUrlString;
889     // check if deck is not null
890     if (deck) {
891         // Get the decklist url string from the deck loader class.
892         decklistUrlString = deck->exportDeckToDecklist();
893         // Check to make sure the string isn't empty.
894         if (QString::compare(decklistUrlString, "", Qt::CaseInsensitive) == 0) {
895             // Show an error if the deck is empty, and return.
896             QMessageBox::critical(this, tr("Error"), tr("There are no cards in your deck to be exported"));
897             return;
898         }
899         // Encode the string recieved from the model to make sure all characters are encoded.
900         // first we put it into a qurl object
901         QUrl decklistUrl = QUrl(decklistUrlString);
902         // we get the correctly encoded url.
903         decklistUrlString = decklistUrl.toEncoded();
904         // We open the url in the user's default browser
905         QDesktopServices::openUrl(decklistUrlString);
906     } else {
907         // if there's no deck loader object, return an error
908         QMessageBox::critical(this, tr("Error"), tr("No deck was selected to be saved."));
909     }
910 }
911 
actAnalyzeDeckDeckstats()912 void TabDeckEditor::actAnalyzeDeckDeckstats()
913 {
914     auto *interface = new DeckStatsInterface(*databaseModel->getDatabase(),
915                                              this); // it deletes itself when done
916     interface->analyzeDeck(deckModel->getDeckList());
917 }
918 
actAnalyzeDeckTappedout()919 void TabDeckEditor::actAnalyzeDeckTappedout()
920 {
921     auto *interface = new TappedOutInterface(*databaseModel->getDatabase(),
922                                              this); // it deletes itself when done
923     interface->analyzeDeck(deckModel->getDeckList());
924 }
925 
actClearFilterAll()926 void TabDeckEditor::actClearFilterAll()
927 {
928     databaseDisplayModel->clearFilterAll();
929     searchEdit->setText("");
930 }
931 
actClearFilterOne()932 void TabDeckEditor::actClearFilterOne()
933 {
934     QModelIndexList selIndexes = filterView->selectionModel()->selectedIndexes();
935     for (QModelIndex idx : selIndexes) {
936         filterModel->removeRow(idx.row(), idx.parent());
937     }
938 }
939 
recursiveExpand(const QModelIndex & index)940 void TabDeckEditor::recursiveExpand(const QModelIndex &index)
941 {
942     if (index.parent().isValid())
943         recursiveExpand(index.parent());
944     deckView->expand(index);
945 }
946 
currentCardInfo() const947 CardInfoPtr TabDeckEditor::currentCardInfo() const
948 {
949     const QModelIndex currentIndex = databaseView->selectionModel()->currentIndex();
950     if (!currentIndex.isValid()) {
951         return {};
952     }
953 
954     const QString cardName = currentIndex.sibling(currentIndex.row(), 0).data().toString();
955 
956     return db->getCard(cardName);
957 }
958 
addCardHelper(QString zoneName)959 void TabDeckEditor::addCardHelper(QString zoneName)
960 {
961     const CardInfoPtr info = currentCardInfo();
962     if (!info)
963         return;
964     if (info->getIsToken())
965         zoneName = DECK_ZONE_TOKENS;
966 
967     QModelIndex newCardIndex = deckModel->addCard(info->getName(), zoneName);
968     recursiveExpand(newCardIndex);
969     deckView->setCurrentIndex(newCardIndex);
970     setModified(true);
971     searchEdit->setSelection(0, searchEdit->text().length());
972 }
973 
actSwapCard()974 void TabDeckEditor::actSwapCard()
975 {
976     const QModelIndex currentIndex = deckView->selectionModel()->currentIndex();
977     if (!currentIndex.isValid())
978         return;
979     const QString cardName = currentIndex.sibling(currentIndex.row(), 1).data().toString();
980     const QModelIndex gparent = currentIndex.parent().parent();
981 
982     if (!gparent.isValid())
983         return;
984 
985     const QString zoneName = gparent.sibling(gparent.row(), 1).data(Qt::EditRole).toString();
986     actDecrement();
987     const QString otherZoneName = zoneName == DECK_ZONE_MAIN ? DECK_ZONE_SIDE : DECK_ZONE_MAIN;
988 
989     // Third argument (true) says create the card no mater what, even if not in DB
990     QModelIndex newCardIndex = deckModel->addCard(cardName, otherZoneName, true);
991     recursiveExpand(newCardIndex);
992 
993     setModified(true);
994     setSaveStatus(true);
995 }
996 
actAddCard()997 void TabDeckEditor::actAddCard()
998 {
999     if (QApplication::keyboardModifiers() & Qt::ControlModifier)
1000         actAddCardToSideboard();
1001     else
1002         addCardHelper(DECK_ZONE_MAIN);
1003     setSaveStatus(true);
1004 }
1005 
actAddCardToSideboard()1006 void TabDeckEditor::actAddCardToSideboard()
1007 {
1008     addCardHelper(DECK_ZONE_SIDE);
1009     setSaveStatus(true);
1010 }
1011 
actRemoveCard()1012 void TabDeckEditor::actRemoveCard()
1013 {
1014     const QModelIndex &currentIndex = deckView->selectionModel()->currentIndex();
1015     if (!currentIndex.isValid() || deckModel->hasChildren(currentIndex))
1016         return;
1017     deckModel->removeRow(currentIndex.row(), currentIndex.parent());
1018 
1019     DeckLoader *const deck = deckModel->getDeckList();
1020     setSaveStatus(!deck->isEmpty());
1021     setModified(true);
1022 }
1023 
offsetCountAtIndex(const QModelIndex & idx,int offset)1024 void TabDeckEditor::offsetCountAtIndex(const QModelIndex &idx, int offset)
1025 {
1026     if (!idx.isValid() || offset == 0)
1027         return;
1028 
1029     const QModelIndex numberIndex = idx.sibling(idx.row(), 0);
1030     const int count = deckModel->data(numberIndex, Qt::EditRole).toInt();
1031     const int new_count = count + offset;
1032     deckView->setCurrentIndex(numberIndex);
1033     if (new_count <= 0)
1034         deckModel->removeRow(idx.row(), idx.parent());
1035     else
1036         deckModel->setData(numberIndex, new_count, Qt::EditRole);
1037     setModified(true);
1038 }
1039 
decrementCardHelper(QString zoneName)1040 void TabDeckEditor::decrementCardHelper(QString zoneName)
1041 {
1042     const CardInfoPtr info = currentCardInfo();
1043     QModelIndex idx;
1044 
1045     if (!info)
1046         return;
1047     if (info->getIsToken())
1048         zoneName = DECK_ZONE_TOKENS;
1049 
1050     idx = deckModel->findCard(info->getName(), zoneName);
1051     offsetCountAtIndex(idx, -1);
1052 }
1053 
actDecrementCard()1054 void TabDeckEditor::actDecrementCard()
1055 {
1056     decrementCardHelper(DECK_ZONE_MAIN);
1057 }
1058 
actDecrementCardFromSideboard()1059 void TabDeckEditor::actDecrementCardFromSideboard()
1060 {
1061     decrementCardHelper(DECK_ZONE_SIDE);
1062 }
1063 
actIncrement()1064 void TabDeckEditor::actIncrement()
1065 {
1066     const QModelIndex &currentIndex = deckView->selectionModel()->currentIndex();
1067     offsetCountAtIndex(currentIndex, 1);
1068 }
1069 
actDecrement()1070 void TabDeckEditor::actDecrement()
1071 {
1072     const QModelIndex &currentIndex = deckView->selectionModel()->currentIndex();
1073     offsetCountAtIndex(currentIndex, -1);
1074 }
1075 
setDeck(DeckLoader * _deck)1076 void TabDeckEditor::setDeck(DeckLoader *_deck)
1077 {
1078     deckModel->setDeckList(_deck);
1079 
1080     nameEdit->setText(deckModel->getDeckList()->getName());
1081     commentsEdit->setText(deckModel->getDeckList()->getComments());
1082     updateHash();
1083     deckModel->sort(deckView->header()->sortIndicatorSection(), deckView->header()->sortIndicatorOrder());
1084     deckView->expandAll();
1085     setModified(false);
1086 
1087     PictureLoader::cacheCardPixmaps(db->getCards(deckModel->getDeckList()->getCardList()));
1088     deckView->expandAll();
1089     setModified(false);
1090 
1091     // If they load a deck, make the deck list appear
1092     aDeckDockVisible->setChecked(true);
1093     deckDock->setVisible(aDeckDockVisible->isChecked());
1094 }
1095 
setModified(bool _modified)1096 void TabDeckEditor::setModified(bool _modified)
1097 {
1098     modified = _modified;
1099     emit tabTextChanged(this, getTabText());
1100 }
1101 
filterViewCustomContextMenu(const QPoint & point)1102 void TabDeckEditor::filterViewCustomContextMenu(const QPoint &point)
1103 {
1104     QMenu menu;
1105     QAction *action;
1106     QModelIndex idx;
1107 
1108     idx = filterView->indexAt(point);
1109     if (!idx.isValid())
1110         return;
1111 
1112     action = menu.addAction(QString("delete"));
1113     action->setData(point);
1114     connect(&menu, SIGNAL(triggered(QAction *)), this, SLOT(filterRemove(QAction *)));
1115     menu.exec(filterView->mapToGlobal(point));
1116 }
1117 
filterRemove(QAction * action)1118 void TabDeckEditor::filterRemove(QAction *action)
1119 {
1120     QPoint point;
1121     QModelIndex idx;
1122 
1123     point = action->data().toPoint();
1124     idx = filterView->indexAt(point);
1125     if (!idx.isValid())
1126         return;
1127 
1128     filterModel->removeRow(idx.row(), idx.parent());
1129 }
1130 
1131 // Method uses to sync docks state with menu items state
eventFilter(QObject * o,QEvent * e)1132 bool TabDeckEditor::eventFilter(QObject *o, QEvent *e)
1133 {
1134     if (e->type() == QEvent::Close) {
1135         if (o == cardInfoDock) {
1136             aCardInfoDockVisible->setChecked(false);
1137             aCardInfoDockFloating->setEnabled(false);
1138         } else if (o == deckDock) {
1139             aDeckDockVisible->setChecked(false);
1140             aDeckDockFloating->setEnabled(false);
1141         } else if (o == filterDock) {
1142             aFilterDockVisible->setChecked(false);
1143             aFilterDockFloating->setEnabled(false);
1144         }
1145     }
1146     if (o == this && e->type() == QEvent::Hide) {
1147         LayoutsSettings &layouts = SettingsCache::instance().layouts();
1148         layouts.setDeckEditorLayoutState(saveState());
1149         layouts.setDeckEditorGeometry(saveGeometry());
1150         layouts.setDeckEditorCardSize(cardInfoDock->size());
1151         layouts.setDeckEditorFilterSize(filterDock->size());
1152         layouts.setDeckEditorDeckSize(deckDock->size());
1153     }
1154     return false;
1155 }
1156 
dockVisibleTriggered()1157 void TabDeckEditor::dockVisibleTriggered()
1158 {
1159     QObject *o = sender();
1160     if (o == aCardInfoDockVisible) {
1161         cardInfoDock->setVisible(aCardInfoDockVisible->isChecked());
1162         aCardInfoDockFloating->setEnabled(aCardInfoDockVisible->isChecked());
1163         return;
1164     }
1165 
1166     if (o == aDeckDockVisible) {
1167         deckDock->setVisible(aDeckDockVisible->isChecked());
1168         aDeckDockFloating->setEnabled(aDeckDockVisible->isChecked());
1169         return;
1170     }
1171 
1172     if (o == aFilterDockVisible) {
1173         filterDock->setVisible(aFilterDockVisible->isChecked());
1174         aFilterDockFloating->setEnabled(aFilterDockVisible->isChecked());
1175         return;
1176     }
1177 }
1178 
dockFloatingTriggered()1179 void TabDeckEditor::dockFloatingTriggered()
1180 {
1181     QObject *o = sender();
1182     if (o == aCardInfoDockFloating) {
1183         cardInfoDock->setFloating(aCardInfoDockFloating->isChecked());
1184         return;
1185     }
1186 
1187     if (o == aDeckDockFloating) {
1188         deckDock->setFloating(aDeckDockFloating->isChecked());
1189         return;
1190     }
1191 
1192     if (o == aFilterDockFloating) {
1193         filterDock->setFloating(aFilterDockFloating->isChecked());
1194         return;
1195     }
1196 }
1197 
dockTopLevelChanged(bool topLevel)1198 void TabDeckEditor::dockTopLevelChanged(bool topLevel)
1199 {
1200     QObject *o = sender();
1201     if (o == cardInfoDock) {
1202         aCardInfoDockFloating->setChecked(topLevel);
1203         return;
1204     }
1205 
1206     if (o == deckDock) {
1207         aDeckDockFloating->setChecked(topLevel);
1208         return;
1209     }
1210 
1211     if (o == filterDock) {
1212         aFilterDockFloating->setChecked(topLevel);
1213         return;
1214     }
1215 }
1216 
saveDbHeaderState()1217 void TabDeckEditor::saveDbHeaderState()
1218 {
1219     SettingsCache::instance().layouts().setDeckEditorDbHeaderState(databaseView->header()->saveState());
1220 }
1221 
setSaveStatus(bool newStatus)1222 void TabDeckEditor::setSaveStatus(bool newStatus)
1223 {
1224     aSaveDeck->setEnabled(newStatus);
1225     aSaveDeckAs->setEnabled(newStatus);
1226     aSaveDeckToClipboard->setEnabled(newStatus);
1227     aSaveDeckToClipboardRaw->setEnabled(newStatus);
1228     saveDeckToClipboardMenu->setEnabled(newStatus);
1229     aPrintDeck->setEnabled(newStatus);
1230     analyzeDeckMenu->setEnabled(newStatus);
1231 }
1232 
showSearchSyntaxHelp()1233 void TabDeckEditor::showSearchSyntaxHelp()
1234 {
1235 
1236     QFile file("theme:help/search.md");
1237 
1238     if (!file.open(QFile::ReadOnly | QFile::Text)) {
1239         return;
1240     }
1241 
1242     QTextStream in(&file);
1243     QString text = in.readAll();
1244     file.close();
1245 
1246     // Poor Markdown Converter
1247     auto opts = QRegularExpression::MultilineOption;
1248     text = text.replace(QRegularExpression("^(###)(.*)", opts), "<h3>\\2</h3>")
1249                .replace(QRegularExpression("^(##)(.*)", opts), "<h2>\\2</h2>")
1250                .replace(QRegularExpression("^(#)(.*)", opts), "<h1>\\2</h1>")
1251                .replace(QRegularExpression("^------*", opts), "<hr />")
1252                .replace(QRegularExpression(R"(\[([^[]+)\]\(([^\)]+)\))", opts), R"(<a href='\2'>\1</a>)");
1253 
1254     auto browser = new QTextBrowser;
1255     browser->setParent(this, Qt::Window | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowMinMaxButtonsHint |
1256                                  Qt::WindowCloseButtonHint | Qt::WindowFullscreenButtonHint);
1257     browser->setWindowTitle("Search Help");
1258     browser->setReadOnly(true);
1259     browser->setMinimumSize({500, 600});
1260 
1261     QString sheet = QString("a { text-decoration: underline; color: rgb(71,158,252) };");
1262     browser->document()->setDefaultStyleSheet(sheet);
1263 
1264     browser->setHtml(text);
1265     connect(browser, &QTextBrowser::anchorClicked, [=](const QUrl &link) { searchEdit->setText(link.fragment()); });
1266     browser->show();
1267 }
1268