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 ¤t, 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 ¤t, 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 ¤tIndex = 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 ¤tIndex = deckView->selectionModel()->currentIndex();
1067 offsetCountAtIndex(currentIndex, 1);
1068 }
1069
actDecrement()1070 void TabDeckEditor::actDecrement()
1071 {
1072 const QModelIndex ¤tIndex = 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