1 #include "dlg_settings.h"
2
3 #include "carddatabase.h"
4 #include "main.h"
5 #include "releasechannel.h"
6 #include "sequenceEdit/sequenceedit.h"
7 #include "settingscache.h"
8 #include "soundengine.h"
9 #include "spoilerbackgroundupdater.h"
10 #include "thememanager.h"
11
12 #include <QAction>
13 #include <QApplication>
14 #include <QCheckBox>
15 #include <QCloseEvent>
16 #include <QComboBox>
17 #include <QDebug>
18 #include <QDesktopWidget>
19 #include <QDialogButtonBox>
20 #include <QFileDialog>
21 #include <QGridLayout>
22 #include <QGroupBox>
23 #include <QHeaderView>
24 #include <QInputDialog>
25 #include <QLabel>
26 #include <QLineEdit>
27 #include <QListWidget>
28 #include <QMessageBox>
29 #include <QPushButton>
30 #include <QRadioButton>
31 #include <QScreen>
32 #include <QSlider>
33 #include <QSpinBox>
34 #include <QStackedWidget>
35 #include <QToolBar>
36 #include <QTranslator>
37 #include <QTreeWidget>
38 #include <QTreeWidgetItem>
39
40 #define WIKI_CUSTOM_PIC_URL "https://github.com/Cockatrice/Cockatrice/wiki/Custom-Picture-Download-URLs"
41 #define WIKI_CUSTOM_SHORTCUTS "https://github.com/Cockatrice/Cockatrice/wiki/Custom-Keyboard-Shortcuts"
42
GeneralSettingsPage()43 GeneralSettingsPage::GeneralSettingsPage()
44 {
45 QString setLanguage = SettingsCache::instance().getLang();
46 QStringList qmFiles = findQmFiles();
47 for (int i = 0; i < qmFiles.size(); i++) {
48 QString langName = languageName(qmFiles[i]);
49 languageBox.addItem(langName, qmFiles[i]);
50 if ((qmFiles[i] == setLanguage) ||
51 (setLanguage.isEmpty() && langName == QCoreApplication::translate("i18n", DEFAULT_LANG_NAME)))
52 languageBox.setCurrentIndex(i);
53 }
54
55 // updates
56 QList<ReleaseChannel *> channels = SettingsCache::instance().getUpdateReleaseChannels();
57 foreach (ReleaseChannel *chan, channels) {
58 updateReleaseChannelBox.insertItem(chan->getIndex(), tr(chan->getName().toUtf8()));
59 }
60 updateReleaseChannelBox.setCurrentIndex(SettingsCache::instance().getUpdateReleaseChannel()->getIndex());
61
62 updateNotificationCheckBox.setChecked(SettingsCache::instance().getNotifyAboutUpdates());
63 newVersionOracleCheckBox.setChecked(SettingsCache::instance().getNotifyAboutNewVersion());
64
65 // pixmap cache
66 pixmapCacheEdit.setMinimum(PIXMAPCACHE_SIZE_MIN);
67 // 2047 is the max value to avoid overflowing of QPixmapCache::setCacheLimit(int size)
68 pixmapCacheEdit.setMaximum(PIXMAPCACHE_SIZE_MAX);
69 pixmapCacheEdit.setSingleStep(64);
70 pixmapCacheEdit.setValue(SettingsCache::instance().getPixmapCacheSize());
71 pixmapCacheEdit.setSuffix(" MB");
72
73 showTipsOnStartup.setChecked(SettingsCache::instance().getShowTipsOnStartup());
74
75 connect(&languageBox, SIGNAL(currentIndexChanged(int)), this, SLOT(languageBoxChanged(int)));
76 connect(&pixmapCacheEdit, SIGNAL(valueChanged(int)), &SettingsCache::instance(), SLOT(setPixmapCacheSize(int)));
77 connect(&updateReleaseChannelBox, SIGNAL(currentIndexChanged(int)), &SettingsCache::instance(),
78 SLOT(setUpdateReleaseChannel(int)));
79 connect(&updateNotificationCheckBox, SIGNAL(stateChanged(int)), &SettingsCache::instance(),
80 SLOT(setNotifyAboutUpdate(int)));
81 connect(&newVersionOracleCheckBox, SIGNAL(stateChanged(int)), &SettingsCache::instance(),
82 SLOT(setNotifyAboutNewVersion(int)));
83 connect(&showTipsOnStartup, SIGNAL(clicked(bool)), &SettingsCache::instance(), SLOT(setShowTipsOnStartup(bool)));
84
85 auto *personalGrid = new QGridLayout;
86 personalGrid->addWidget(&languageLabel, 0, 0);
87 personalGrid->addWidget(&languageBox, 0, 1);
88 personalGrid->addWidget(&updateReleaseChannelLabel, 1, 0);
89 personalGrid->addWidget(&updateReleaseChannelBox, 1, 1);
90 personalGrid->addWidget(&pixmapCacheLabel, 2, 0);
91 personalGrid->addWidget(&pixmapCacheEdit, 2, 1);
92 personalGrid->addWidget(&updateNotificationCheckBox, 3, 0, 1, 2);
93 personalGrid->addWidget(&newVersionOracleCheckBox, 4, 0, 1, 2);
94 personalGrid->addWidget(&showTipsOnStartup, 5, 0, 1, 2);
95
96 personalGroupBox = new QGroupBox;
97 personalGroupBox->setLayout(personalGrid);
98
99 deckPathEdit = new QLineEdit(SettingsCache::instance().getDeckPath());
100 deckPathEdit->setReadOnly(true);
101 QPushButton *deckPathButton = new QPushButton("...");
102 connect(deckPathButton, SIGNAL(clicked()), this, SLOT(deckPathButtonClicked()));
103
104 replaysPathEdit = new QLineEdit(SettingsCache::instance().getReplaysPath());
105 replaysPathEdit->setReadOnly(true);
106 QPushButton *replaysPathButton = new QPushButton("...");
107 connect(replaysPathButton, SIGNAL(clicked()), this, SLOT(replaysPathButtonClicked()));
108
109 picsPathEdit = new QLineEdit(SettingsCache::instance().getPicsPath());
110 picsPathEdit->setReadOnly(true);
111 QPushButton *picsPathButton = new QPushButton("...");
112 connect(picsPathButton, SIGNAL(clicked()), this, SLOT(picsPathButtonClicked()));
113
114 cardDatabasePathEdit = new QLineEdit(SettingsCache::instance().getCardDatabasePath());
115 cardDatabasePathEdit->setReadOnly(true);
116 QPushButton *cardDatabasePathButton = new QPushButton("...");
117 connect(cardDatabasePathButton, SIGNAL(clicked()), this, SLOT(cardDatabasePathButtonClicked()));
118
119 customCardDatabasePathEdit = new QLineEdit(SettingsCache::instance().getCustomCardDatabasePath());
120 customCardDatabasePathEdit->setReadOnly(true);
121 QPushButton *customCardDatabasePathButton = new QPushButton("...");
122 connect(customCardDatabasePathButton, SIGNAL(clicked()), this, SLOT(customCardDatabaseButtonClicked()));
123
124 tokenDatabasePathEdit = new QLineEdit(SettingsCache::instance().getTokenDatabasePath());
125 tokenDatabasePathEdit->setReadOnly(true);
126 QPushButton *tokenDatabasePathButton = new QPushButton("...");
127 connect(tokenDatabasePathButton, SIGNAL(clicked()), this, SLOT(tokenDatabasePathButtonClicked()));
128
129 if (SettingsCache::instance().getIsPortableBuild()) {
130 deckPathEdit->setEnabled(false);
131 replaysPathEdit->setEnabled(false);
132 picsPathEdit->setEnabled(false);
133 cardDatabasePathEdit->setEnabled(false);
134 customCardDatabasePathEdit->setEnabled(false);
135 tokenDatabasePathEdit->setEnabled(false);
136
137 deckPathButton->setVisible(false);
138 replaysPathButton->setVisible(false);
139 picsPathButton->setVisible(false);
140 cardDatabasePathButton->setVisible(false);
141 customCardDatabasePathEdit->setVisible(false);
142 tokenDatabasePathButton->setVisible(false);
143 }
144
145 auto *pathsGrid = new QGridLayout;
146 pathsGrid->addWidget(&deckPathLabel, 0, 0);
147 pathsGrid->addWidget(deckPathEdit, 0, 1);
148 pathsGrid->addWidget(deckPathButton, 0, 2);
149 pathsGrid->addWidget(&replaysPathLabel, 1, 0);
150 pathsGrid->addWidget(replaysPathEdit, 1, 1);
151 pathsGrid->addWidget(replaysPathButton, 1, 2);
152 pathsGrid->addWidget(&picsPathLabel, 2, 0);
153 pathsGrid->addWidget(picsPathEdit, 2, 1);
154 pathsGrid->addWidget(picsPathButton, 2, 2);
155 pathsGrid->addWidget(&cardDatabasePathLabel, 3, 0);
156 pathsGrid->addWidget(cardDatabasePathEdit, 3, 1);
157 pathsGrid->addWidget(cardDatabasePathButton, 3, 2);
158 pathsGrid->addWidget(&customCardDatabasePathLabel, 4, 0);
159 pathsGrid->addWidget(customCardDatabasePathEdit, 4, 1);
160 pathsGrid->addWidget(customCardDatabasePathButton, 4, 2);
161 pathsGrid->addWidget(&tokenDatabasePathLabel, 5, 0);
162 pathsGrid->addWidget(tokenDatabasePathEdit, 5, 1);
163 pathsGrid->addWidget(tokenDatabasePathButton, 5, 2);
164 pathsGroupBox = new QGroupBox;
165 pathsGroupBox->setLayout(pathsGrid);
166
167 auto *mainLayout = new QVBoxLayout;
168 mainLayout->addWidget(personalGroupBox);
169 mainLayout->addWidget(pathsGroupBox);
170
171 setLayout(mainLayout);
172 }
173
findQmFiles()174 QStringList GeneralSettingsPage::findQmFiles()
175 {
176 QDir dir(translationPath);
177 QStringList fileNames = dir.entryList(QStringList(translationPrefix + "_*.qm"), QDir::Files, QDir::Name);
178 fileNames.replaceInStrings(QRegExp(translationPrefix + "_(.*)\\.qm"), "\\1");
179 return fileNames;
180 }
181
languageName(const QString & qmFile)182 QString GeneralSettingsPage::languageName(const QString &qmFile)
183 {
184 if (qmFile == DEFAULT_LANG_CODE)
185 return DEFAULT_LANG_NAME;
186
187 QTranslator translator;
188 translator.load(translationPrefix + "_" + qmFile + ".qm", translationPath);
189
190 return translator.translate("i18n", DEFAULT_LANG_NAME);
191 }
192
deckPathButtonClicked()193 void GeneralSettingsPage::deckPathButtonClicked()
194 {
195 QString path = QFileDialog::getExistingDirectory(this, tr("Choose path"), deckPathEdit->text());
196 if (path.isEmpty())
197 return;
198
199 deckPathEdit->setText(path);
200 SettingsCache::instance().setDeckPath(path);
201 }
202
replaysPathButtonClicked()203 void GeneralSettingsPage::replaysPathButtonClicked()
204 {
205 QString path = QFileDialog::getExistingDirectory(this, tr("Choose path"), replaysPathEdit->text());
206 if (path.isEmpty())
207 return;
208
209 replaysPathEdit->setText(path);
210 SettingsCache::instance().setReplaysPath(path);
211 }
212
picsPathButtonClicked()213 void GeneralSettingsPage::picsPathButtonClicked()
214 {
215 QString path = QFileDialog::getExistingDirectory(this, tr("Choose path"), picsPathEdit->text());
216 if (path.isEmpty())
217 return;
218
219 picsPathEdit->setText(path);
220 SettingsCache::instance().setPicsPath(path);
221 }
222
cardDatabasePathButtonClicked()223 void GeneralSettingsPage::cardDatabasePathButtonClicked()
224 {
225 QString path = QFileDialog::getOpenFileName(this, tr("Choose path"), cardDatabasePathEdit->text());
226 if (path.isEmpty())
227 return;
228
229 cardDatabasePathEdit->setText(path);
230 SettingsCache::instance().setCardDatabasePath(path);
231 }
232
customCardDatabaseButtonClicked()233 void GeneralSettingsPage::customCardDatabaseButtonClicked()
234 {
235 QString path = QFileDialog::getExistingDirectory(this, tr("Choose path"), customCardDatabasePathEdit->text());
236 if (path.isEmpty())
237 return;
238
239 customCardDatabasePathEdit->setText(path);
240 SettingsCache::instance().setCustomCardDatabasePath(path);
241 }
242
tokenDatabasePathButtonClicked()243 void GeneralSettingsPage::tokenDatabasePathButtonClicked()
244 {
245 QString path = QFileDialog::getOpenFileName(this, tr("Choose path"), tokenDatabasePathEdit->text());
246 if (path.isEmpty())
247 return;
248
249 tokenDatabasePathEdit->setText(path);
250 SettingsCache::instance().setTokenDatabasePath(path);
251 }
252
languageBoxChanged(int index)253 void GeneralSettingsPage::languageBoxChanged(int index)
254 {
255 SettingsCache::instance().setLang(languageBox.itemData(index).toString());
256 }
257
retranslateUi()258 void GeneralSettingsPage::retranslateUi()
259 {
260 personalGroupBox->setTitle(tr("Personal settings"));
261 languageLabel.setText(tr("Language:"));
262
263 if (SettingsCache::instance().getIsPortableBuild()) {
264 pathsGroupBox->setTitle(tr("Paths (editing disabled in portable mode)"));
265 } else {
266 pathsGroupBox->setTitle(tr("Paths"));
267 }
268
269 deckPathLabel.setText(tr("Decks directory:"));
270 replaysPathLabel.setText(tr("Replays directory:"));
271 picsPathLabel.setText(tr("Pictures directory:"));
272 cardDatabasePathLabel.setText(tr("Card database:"));
273 customCardDatabasePathLabel.setText(tr("Custom database directory:"));
274 tokenDatabasePathLabel.setText(tr("Token database:"));
275 pixmapCacheLabel.setText(tr("Picture cache size:"));
276 updateReleaseChannelLabel.setText(tr("Update channel"));
277 updateNotificationCheckBox.setText(tr("Notify if a feature supported by the server is missing in my client"));
278 newVersionOracleCheckBox.setText(tr("Automatically run Oracle when running a new version of Cockatrice"));
279 showTipsOnStartup.setText(tr("Show tips on startup"));
280 }
281
AppearanceSettingsPage()282 AppearanceSettingsPage::AppearanceSettingsPage()
283 {
284 QString themeName = SettingsCache::instance().getThemeName();
285
286 QStringList themeDirs = themeManager->getAvailableThemes().keys();
287 for (int i = 0; i < themeDirs.size(); i++) {
288 themeBox.addItem(themeDirs[i]);
289 if (themeDirs[i] == themeName)
290 themeBox.setCurrentIndex(i);
291 }
292
293 connect(&themeBox, SIGNAL(currentIndexChanged(int)), this, SLOT(themeBoxChanged(int)));
294
295 auto *themeGrid = new QGridLayout;
296 themeGrid->addWidget(&themeLabel, 0, 0);
297 themeGrid->addWidget(&themeBox, 0, 1);
298
299 themeGroupBox = new QGroupBox;
300 themeGroupBox->setLayout(themeGrid);
301
302 displayCardNamesCheckBox.setChecked(SettingsCache::instance().getDisplayCardNames());
303 connect(&displayCardNamesCheckBox, SIGNAL(stateChanged(int)), &SettingsCache::instance(),
304 SLOT(setDisplayCardNames(int)));
305
306 cardScalingCheckBox.setChecked(SettingsCache::instance().getScaleCards());
307 connect(&cardScalingCheckBox, SIGNAL(stateChanged(int)), &SettingsCache::instance(), SLOT(setCardScaling(int)));
308
309 auto *cardsGrid = new QGridLayout;
310 cardsGrid->addWidget(&displayCardNamesCheckBox, 0, 0, 1, 2);
311 cardsGrid->addWidget(&cardScalingCheckBox, 1, 0, 1, 2);
312
313 cardsGroupBox = new QGroupBox;
314 cardsGroupBox->setLayout(cardsGrid);
315
316 horizontalHandCheckBox.setChecked(SettingsCache::instance().getHorizontalHand());
317 connect(&horizontalHandCheckBox, SIGNAL(stateChanged(int)), &SettingsCache::instance(),
318 SLOT(setHorizontalHand(int)));
319
320 leftJustifiedHandCheckBox.setChecked(SettingsCache::instance().getLeftJustified());
321 connect(&leftJustifiedHandCheckBox, SIGNAL(stateChanged(int)), &SettingsCache::instance(),
322 SLOT(setLeftJustified(int)));
323
324 auto *handGrid = new QGridLayout;
325 handGrid->addWidget(&horizontalHandCheckBox, 0, 0, 1, 2);
326 handGrid->addWidget(&leftJustifiedHandCheckBox, 1, 0, 1, 2);
327
328 handGroupBox = new QGroupBox;
329 handGroupBox->setLayout(handGrid);
330
331 invertVerticalCoordinateCheckBox.setChecked(SettingsCache::instance().getInvertVerticalCoordinate());
332 connect(&invertVerticalCoordinateCheckBox, SIGNAL(stateChanged(int)), &SettingsCache::instance(),
333 SLOT(setInvertVerticalCoordinate(int)));
334
335 minPlayersForMultiColumnLayoutEdit.setMinimum(2);
336 minPlayersForMultiColumnLayoutEdit.setValue(SettingsCache::instance().getMinPlayersForMultiColumnLayout());
337 connect(&minPlayersForMultiColumnLayoutEdit, SIGNAL(valueChanged(int)), &SettingsCache::instance(),
338 SLOT(setMinPlayersForMultiColumnLayout(int)));
339 minPlayersForMultiColumnLayoutLabel.setBuddy(&minPlayersForMultiColumnLayoutEdit);
340
341 connect(&maxFontSizeForCardsEdit, SIGNAL(valueChanged(int)), &SettingsCache::instance(), SLOT(setMaxFontSize(int)));
342 maxFontSizeForCardsEdit.setValue(SettingsCache::instance().getMaxFontSize());
343 maxFontSizeForCardsLabel.setBuddy(&maxFontSizeForCardsEdit);
344 maxFontSizeForCardsEdit.setMinimum(9);
345 maxFontSizeForCardsEdit.setMaximum(100);
346
347 auto *tableGrid = new QGridLayout;
348 tableGrid->addWidget(&invertVerticalCoordinateCheckBox, 0, 0, 1, 2);
349 tableGrid->addWidget(&minPlayersForMultiColumnLayoutLabel, 1, 0, 1, 1);
350 tableGrid->addWidget(&minPlayersForMultiColumnLayoutEdit, 1, 1, 1, 1);
351 tableGrid->addWidget(&maxFontSizeForCardsLabel, 2, 0, 1, 1);
352 tableGrid->addWidget(&maxFontSizeForCardsEdit, 2, 1, 1, 1);
353
354 tableGroupBox = new QGroupBox;
355 tableGroupBox->setLayout(tableGrid);
356
357 auto *mainLayout = new QVBoxLayout;
358 mainLayout->addWidget(themeGroupBox);
359 mainLayout->addWidget(cardsGroupBox);
360 mainLayout->addWidget(handGroupBox);
361 mainLayout->addWidget(tableGroupBox);
362
363 setLayout(mainLayout);
364 }
365
themeBoxChanged(int index)366 void AppearanceSettingsPage::themeBoxChanged(int index)
367 {
368 QStringList themeDirs = themeManager->getAvailableThemes().keys();
369 if (index >= 0 && index < themeDirs.count())
370 SettingsCache::instance().setThemeName(themeDirs.at(index));
371 }
372
retranslateUi()373 void AppearanceSettingsPage::retranslateUi()
374 {
375 themeGroupBox->setTitle(tr("Theme settings"));
376 themeLabel.setText(tr("Current theme:"));
377
378 cardsGroupBox->setTitle(tr("Card rendering"));
379 displayCardNamesCheckBox.setText(tr("Display card names on cards having a picture"));
380 cardScalingCheckBox.setText(tr("Scale cards on mouse over"));
381
382 handGroupBox->setTitle(tr("Hand layout"));
383 horizontalHandCheckBox.setText(tr("Display hand horizontally (wastes space)"));
384 leftJustifiedHandCheckBox.setText(tr("Enable left justification"));
385
386 tableGroupBox->setTitle(tr("Table grid layout"));
387 invertVerticalCoordinateCheckBox.setText(tr("Invert vertical coordinate"));
388 minPlayersForMultiColumnLayoutLabel.setText(tr("Minimum player count for multi-column layout:"));
389 maxFontSizeForCardsLabel.setText(tr("Maximum font size for information displayed on cards:"));
390 }
391
UserInterfaceSettingsPage()392 UserInterfaceSettingsPage::UserInterfaceSettingsPage()
393 {
394 notificationsEnabledCheckBox.setChecked(SettingsCache::instance().getNotificationsEnabled());
395 connect(¬ificationsEnabledCheckBox, SIGNAL(stateChanged(int)), &SettingsCache::instance(),
396 SLOT(setNotificationsEnabled(int)));
397 connect(¬ificationsEnabledCheckBox, SIGNAL(stateChanged(int)), this, SLOT(setSpecNotificationEnabled(int)));
398
399 specNotificationsEnabledCheckBox.setChecked(SettingsCache::instance().getSpectatorNotificationsEnabled());
400 specNotificationsEnabledCheckBox.setEnabled(SettingsCache::instance().getNotificationsEnabled());
401 connect(&specNotificationsEnabledCheckBox, SIGNAL(stateChanged(int)), &SettingsCache::instance(),
402 SLOT(setSpectatorNotificationsEnabled(int)));
403
404 buddyConnectNotificationsEnabledCheckBox.setChecked(
405 SettingsCache::instance().getBuddyConnectNotificationsEnabled());
406 buddyConnectNotificationsEnabledCheckBox.setEnabled(SettingsCache::instance().getNotificationsEnabled());
407 connect(&buddyConnectNotificationsEnabledCheckBox, SIGNAL(stateChanged(int)), &SettingsCache::instance(),
408 SLOT(setBuddyConnectNotificationsEnabled(int)));
409
410 doubleClickToPlayCheckBox.setChecked(SettingsCache::instance().getDoubleClickToPlay());
411 connect(&doubleClickToPlayCheckBox, SIGNAL(stateChanged(int)), &SettingsCache::instance(),
412 SLOT(setDoubleClickToPlay(int)));
413
414 playToStackCheckBox.setChecked(SettingsCache::instance().getPlayToStack());
415 connect(&playToStackCheckBox, SIGNAL(stateChanged(int)), &SettingsCache::instance(), SLOT(setPlayToStack(int)));
416
417 annotateTokensCheckBox.setChecked(SettingsCache::instance().getAnnotateTokens());
418 connect(&annotateTokensCheckBox, SIGNAL(stateChanged(int)), &SettingsCache::instance(),
419 SLOT(setAnnotateTokens(int)));
420
421 useTearOffMenusCheckBox.setChecked(SettingsCache::instance().getUseTearOffMenus());
422 connect(&useTearOffMenusCheckBox, &QCheckBox::stateChanged, &SettingsCache::instance(),
423 [](int state) { SettingsCache::instance().setUseTearOffMenus(state == Qt::Checked); });
424
425 auto *generalGrid = new QGridLayout;
426 generalGrid->addWidget(&doubleClickToPlayCheckBox, 0, 0);
427 generalGrid->addWidget(&playToStackCheckBox, 1, 0);
428 generalGrid->addWidget(&annotateTokensCheckBox, 2, 0);
429 generalGrid->addWidget(&useTearOffMenusCheckBox, 3, 0);
430
431 generalGroupBox = new QGroupBox;
432 generalGroupBox->setLayout(generalGrid);
433
434 auto *notificationsGrid = new QGridLayout;
435 notificationsGrid->addWidget(¬ificationsEnabledCheckBox, 0, 0);
436 notificationsGrid->addWidget(&specNotificationsEnabledCheckBox, 1, 0);
437 notificationsGrid->addWidget(&buddyConnectNotificationsEnabledCheckBox, 2, 0);
438
439 notificationsGroupBox = new QGroupBox;
440 notificationsGroupBox->setLayout(notificationsGrid);
441
442 tapAnimationCheckBox.setChecked(SettingsCache::instance().getTapAnimation());
443 connect(&tapAnimationCheckBox, SIGNAL(stateChanged(int)), &SettingsCache::instance(), SLOT(setTapAnimation(int)));
444
445 auto *animationGrid = new QGridLayout;
446 animationGrid->addWidget(&tapAnimationCheckBox, 0, 0);
447
448 animationGroupBox = new QGroupBox;
449 animationGroupBox->setLayout(animationGrid);
450
451 auto *mainLayout = new QVBoxLayout;
452 mainLayout->addWidget(generalGroupBox);
453 mainLayout->addWidget(notificationsGroupBox);
454 mainLayout->addWidget(animationGroupBox);
455
456 setLayout(mainLayout);
457 }
458
setSpecNotificationEnabled(int i)459 void UserInterfaceSettingsPage::setSpecNotificationEnabled(int i)
460 {
461 specNotificationsEnabledCheckBox.setEnabled(i != 0);
462 }
463
retranslateUi()464 void UserInterfaceSettingsPage::retranslateUi()
465 {
466 generalGroupBox->setTitle(tr("General interface settings"));
467 doubleClickToPlayCheckBox.setText(tr("&Double-click cards to play them (instead of single-click)"));
468 playToStackCheckBox.setText(tr("&Play all nonlands onto the stack (not the battlefield) by default"));
469 annotateTokensCheckBox.setText(tr("Annotate card text on tokens"));
470 useTearOffMenusCheckBox.setText(tr("Use tear-off menus, allowing right click menus to persist on screen"));
471 notificationsGroupBox->setTitle(tr("Notifications settings"));
472 notificationsEnabledCheckBox.setText(tr("Enable notifications in taskbar"));
473 specNotificationsEnabledCheckBox.setText(tr("Notify in the taskbar for game events while you are spectating"));
474 buddyConnectNotificationsEnabledCheckBox.setText(tr("Notify in the taskbar when users in your buddy list connect"));
475 animationGroupBox->setTitle(tr("Animation settings"));
476 tapAnimationCheckBox.setText(tr("&Tap/untap animation"));
477 }
478
DeckEditorSettingsPage()479 DeckEditorSettingsPage::DeckEditorSettingsPage()
480 {
481 picDownloadCheckBox.setChecked(SettingsCache::instance().getPicDownload());
482 connect(&picDownloadCheckBox, SIGNAL(stateChanged(int)), &SettingsCache::instance(), SLOT(setPicDownload(int)));
483
484 urlLinkLabel.setTextInteractionFlags(Qt::LinksAccessibleByMouse);
485 urlLinkLabel.setOpenExternalLinks(true);
486
487 connect(&clearDownloadedPicsButton, SIGNAL(clicked()), this, SLOT(clearDownloadedPicsButtonClicked()));
488 connect(&resetDownloadURLs, SIGNAL(clicked()), this, SLOT(resetDownloadedURLsButtonClicked()));
489
490 auto *lpGeneralGrid = new QGridLayout;
491 auto *lpSpoilerGrid = new QGridLayout;
492
493 mcDownloadSpoilersCheckBox.setChecked(SettingsCache::instance().getDownloadSpoilersStatus());
494
495 mpSpoilerSavePathLineEdit = new QLineEdit(SettingsCache::instance().getSpoilerCardDatabasePath());
496 mpSpoilerSavePathLineEdit->setReadOnly(true);
497 mpSpoilerPathButton = new QPushButton("...");
498 connect(mpSpoilerPathButton, SIGNAL(clicked()), this, SLOT(spoilerPathButtonClicked()));
499
500 updateNowButton = new QPushButton(tr("Update Spoilers"));
501 updateNowButton->setFixedWidth(150);
502 connect(updateNowButton, SIGNAL(clicked()), this, SLOT(updateSpoilers()));
503
504 // Update the GUI depending on if the box is ticked or not
505 setSpoilersEnabled(mcDownloadSpoilersCheckBox.isChecked());
506
507 urlList = new QListWidget;
508 urlList->setSelectionMode(QAbstractItemView::SingleSelection);
509 urlList->setAlternatingRowColors(true);
510 urlList->setDragEnabled(true);
511 urlList->setDragDropMode(QAbstractItemView::InternalMove);
512 connect(urlList->model(), SIGNAL(rowsMoved(const QModelIndex, int, int, const QModelIndex, int)), this,
513 SLOT(urlListChanged(const QModelIndex, int, int, const QModelIndex, int)));
514
515 for (int i = 0; i < SettingsCache::instance().downloads().getCount(); i++)
516 urlList->addItem(SettingsCache::instance().downloads().getDownloadUrlAt(i));
517
518 auto aAdd = new QAction(this);
519 aAdd->setIcon(QPixmap("theme:icons/increment"));
520 connect(aAdd, SIGNAL(triggered()), this, SLOT(actAddURL()));
521 auto aEdit = new QAction(this);
522 aEdit->setIcon(QPixmap("theme:icons/pencil"));
523 connect(aEdit, SIGNAL(triggered()), this, SLOT(actEditURL()));
524 auto aRemove = new QAction(this);
525 aRemove->setIcon(QPixmap("theme:icons/decrement"));
526 connect(aRemove, SIGNAL(triggered()), this, SLOT(actRemoveURL()));
527
528 auto *messageToolBar = new QToolBar;
529 messageToolBar->setOrientation(Qt::Vertical);
530 messageToolBar->addAction(aAdd);
531 messageToolBar->addAction(aRemove);
532 messageToolBar->addAction(aEdit);
533 messageToolBar->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
534
535 auto *messageListLayout = new QHBoxLayout;
536 messageListLayout->addWidget(messageToolBar);
537 messageListLayout->addWidget(urlList);
538
539 // Top Layout
540 lpGeneralGrid->addWidget(&picDownloadCheckBox, 0, 0);
541 lpGeneralGrid->addWidget(&resetDownloadURLs, 0, 1);
542 lpGeneralGrid->addLayout(messageListLayout, 1, 0, 1, 2);
543 lpGeneralGrid->addWidget(&urlLinkLabel, 2, 0);
544 lpGeneralGrid->addWidget(&clearDownloadedPicsButton, 2, 1);
545
546 // Spoiler Layout
547 lpSpoilerGrid->addWidget(&mcDownloadSpoilersCheckBox, 0, 0);
548 lpSpoilerGrid->addWidget(&mcSpoilerSaveLabel, 1, 0);
549 lpSpoilerGrid->addWidget(mpSpoilerSavePathLineEdit, 1, 1);
550 lpSpoilerGrid->addWidget(mpSpoilerPathButton, 1, 2);
551 lpSpoilerGrid->addWidget(&lastUpdatedLabel, 2, 0);
552 lpSpoilerGrid->addWidget(updateNowButton, 2, 1);
553 lpSpoilerGrid->addWidget(&infoOnSpoilersLabel, 3, 0, 1, 3, Qt::AlignTop);
554
555 // On a change to the check box, hide/unhide the other fields
556 connect(&mcDownloadSpoilersCheckBox, SIGNAL(toggled(bool)), &SettingsCache::instance(),
557 SLOT(setDownloadSpoilerStatus(bool)));
558 connect(&mcDownloadSpoilersCheckBox, SIGNAL(toggled(bool)), this, SLOT(setSpoilersEnabled(bool)));
559
560 mpGeneralGroupBox = new QGroupBox;
561 mpGeneralGroupBox->setLayout(lpGeneralGrid);
562
563 mpSpoilerGroupBox = new QGroupBox;
564 mpSpoilerGroupBox->setLayout(lpSpoilerGrid);
565
566 auto *lpMainLayout = new QVBoxLayout;
567 lpMainLayout->addWidget(mpGeneralGroupBox);
568 lpMainLayout->addWidget(mpSpoilerGroupBox);
569
570 setLayout(lpMainLayout);
571 }
572
resetDownloadedURLsButtonClicked()573 void DeckEditorSettingsPage::resetDownloadedURLsButtonClicked()
574 {
575 SettingsCache::instance().downloads().clear();
576 urlList->clear();
577 urlList->addItems(SettingsCache::instance().downloads().getAllURLs());
578 QMessageBox::information(this, tr("Success"), tr("Download URLs have been reset."));
579 }
580
clearDownloadedPicsButtonClicked()581 void DeckEditorSettingsPage::clearDownloadedPicsButtonClicked()
582 {
583 QString picsPath = SettingsCache::instance().getPicsPath() + "/downloadedPics/";
584 QStringList dirs = QDir(picsPath).entryList(QDir::AllDirs | QDir::NoDotAndDotDot);
585 bool outerSuccessRemove = true;
586 for (const auto &dir : dirs) {
587 QString currentPath = picsPath + dir + "/";
588 QStringList files = QDir(currentPath).entryList(QDir::Files);
589 bool innerSuccessRemove = true;
590 for (int j = 0; j < files.length(); j++) {
591 if (!QDir(currentPath).remove(files.at(j))) {
592 qInfo() << "Failed to remove " + currentPath.toUtf8() + files.at(j).toUtf8();
593 outerSuccessRemove = false;
594 innerSuccessRemove = false;
595 }
596 qInfo() << "Removed " << currentPath << files.at(j);
597 }
598
599 if (innerSuccessRemove) {
600 bool success = QDir(picsPath).rmdir(dir);
601 if (!success) {
602 qInfo() << "Failed to remove inner directory" << picsPath;
603 } else {
604 qInfo() << "Removed" << currentPath;
605 }
606 }
607 }
608 if (outerSuccessRemove) {
609 QMessageBox::information(this, tr("Success"), tr("Downloaded card pictures have been reset."));
610 } else {
611 QMessageBox::critical(this, tr("Error"), tr("One or more downloaded card pictures could not be cleared."));
612 }
613 }
614
actAddURL()615 void DeckEditorSettingsPage::actAddURL()
616 {
617 bool ok;
618 QString msg = QInputDialog::getText(this, tr("Add URL"), tr("URL:"), QLineEdit::Normal, QString(), &ok);
619 if (ok) {
620 urlList->addItem(msg);
621 storeSettings();
622 }
623 }
624
actRemoveURL()625 void DeckEditorSettingsPage::actRemoveURL()
626 {
627 if (urlList->currentItem() != nullptr) {
628 delete urlList->takeItem(urlList->currentRow());
629 storeSettings();
630 }
631 }
632
actEditURL()633 void DeckEditorSettingsPage::actEditURL()
634 {
635 if (urlList->currentItem()) {
636 QString oldText = urlList->currentItem()->text();
637 bool ok;
638 QString msg = QInputDialog::getText(this, tr("Edit URL"), tr("URL:"), QLineEdit::Normal, oldText, &ok);
639 if (ok) {
640 urlList->currentItem()->setText(msg);
641 storeSettings();
642 }
643 }
644 }
645
storeSettings()646 void DeckEditorSettingsPage::storeSettings()
647 {
648 qInfo() << "URL Priority Reset";
649 SettingsCache::instance().downloads().clear();
650 for (int i = 0; i < urlList->count(); i++) {
651 qInfo() << "Priority" << i << ":" << urlList->item(i)->text();
652 SettingsCache::instance().downloads().setDownloadUrlAt(i, urlList->item(i)->text());
653 }
654 }
655
urlListChanged(const QModelIndex &,int,int,const QModelIndex &,int)656 void DeckEditorSettingsPage::urlListChanged(const QModelIndex &, int, int, const QModelIndex &, int)
657 {
658 storeSettings();
659 }
660
updateSpoilers()661 void DeckEditorSettingsPage::updateSpoilers()
662 {
663 // Disable the button so the user can only press it once at a time
664 updateNowButton->setDisabled(true);
665 updateNowButton->setText(tr("Updating..."));
666
667 // Create a new SBU that will act as if the client was just reloaded
668 auto *sbu = new SpoilerBackgroundUpdater();
669 connect(sbu, SIGNAL(spoilerCheckerDone()), this, SLOT(unlockSettings()));
670 connect(sbu, SIGNAL(spoilersUpdatedSuccessfully()), this, SLOT(unlockSettings()));
671 }
672
unlockSettings()673 void DeckEditorSettingsPage::unlockSettings()
674 {
675 updateNowButton->setDisabled(false);
676 updateNowButton->setText(tr("Update Spoilers"));
677 }
678
getLastUpdateTime()679 QString DeckEditorSettingsPage::getLastUpdateTime()
680 {
681 QString fileName = SettingsCache::instance().getSpoilerCardDatabasePath();
682 QFileInfo fi(fileName);
683 QDir fileDir(fi.path());
684 QFile file(fileName);
685
686 if (file.exists()) {
687 return fi.lastModified().toString("MMM d, hh:mm");
688 }
689
690 return QString();
691 }
692
spoilerPathButtonClicked()693 void DeckEditorSettingsPage::spoilerPathButtonClicked()
694 {
695 QString lsPath = QFileDialog::getExistingDirectory(this, tr("Choose path"), mpSpoilerSavePathLineEdit->text());
696 if (lsPath.isEmpty()) {
697 return;
698 }
699
700 mpSpoilerSavePathLineEdit->setText(lsPath + "/spoiler.xml");
701 SettingsCache::instance().setSpoilerDatabasePath(lsPath + "/spoiler.xml");
702 }
703
setSpoilersEnabled(bool anInput)704 void DeckEditorSettingsPage::setSpoilersEnabled(bool anInput)
705 {
706 msDownloadSpoilersLabel.setEnabled(anInput);
707 mcSpoilerSaveLabel.setEnabled(anInput);
708 mpSpoilerSavePathLineEdit->setEnabled(anInput);
709 mpSpoilerPathButton->setEnabled(anInput);
710 lastUpdatedLabel.setEnabled(anInput);
711 updateNowButton->setEnabled(anInput);
712 infoOnSpoilersLabel.setEnabled(anInput);
713
714 if (!anInput) {
715 SpoilerBackgroundUpdater::deleteSpoilerFile();
716 }
717 }
718
retranslateUi()719 void DeckEditorSettingsPage::retranslateUi()
720 {
721 mpGeneralGroupBox->setTitle(tr("URL Download Priority"));
722 mpSpoilerGroupBox->setTitle(tr("Spoilers"));
723 mcDownloadSpoilersCheckBox.setText(tr("Download Spoilers Automatically"));
724 mcSpoilerSaveLabel.setText(tr("Spoiler Location:"));
725 lastUpdatedLabel.setText(tr("Last Change") + ": " + getLastUpdateTime());
726 infoOnSpoilersLabel.setText(tr("Spoilers download automatically on launch") + "\n" +
727 tr("Press the button to manually update without relaunching") + "\n\n" +
728 tr("Do not close settings until manual update is complete"));
729 picDownloadCheckBox.setText(tr("Download card pictures on the fly"));
730 urlLinkLabel.setText(QString("<a href='%1'>%2</a>").arg(WIKI_CUSTOM_PIC_URL).arg(tr("How to add a custom URL")));
731 clearDownloadedPicsButton.setText(tr("Delete Downloaded Images"));
732 resetDownloadURLs.setText(tr("Reset Download URLs"));
733 }
734
MessagesSettingsPage()735 MessagesSettingsPage::MessagesSettingsPage()
736 {
737 chatMentionCheckBox.setChecked(SettingsCache::instance().getChatMention());
738 connect(&chatMentionCheckBox, SIGNAL(stateChanged(int)), &SettingsCache::instance(), SLOT(setChatMention(int)));
739
740 chatMentionCompleterCheckbox.setChecked(SettingsCache::instance().getChatMentionCompleter());
741 connect(&chatMentionCompleterCheckbox, SIGNAL(stateChanged(int)), &SettingsCache::instance(),
742 SLOT(setChatMentionCompleter(int)));
743
744 ignoreUnregUsersMainChat.setChecked(SettingsCache::instance().getIgnoreUnregisteredUsers());
745 ignoreUnregUserMessages.setChecked(SettingsCache::instance().getIgnoreUnregisteredUserMessages());
746 connect(&ignoreUnregUsersMainChat, SIGNAL(stateChanged(int)), &SettingsCache::instance(),
747 SLOT(setIgnoreUnregisteredUsers(int)));
748 connect(&ignoreUnregUserMessages, SIGNAL(stateChanged(int)), &SettingsCache::instance(),
749 SLOT(setIgnoreUnregisteredUserMessages(int)));
750
751 invertMentionForeground.setChecked(SettingsCache::instance().getChatMentionForeground());
752 connect(&invertMentionForeground, SIGNAL(stateChanged(int)), this, SLOT(updateTextColor(int)));
753
754 invertHighlightForeground.setChecked(SettingsCache::instance().getChatHighlightForeground());
755 connect(&invertHighlightForeground, SIGNAL(stateChanged(int)), this, SLOT(updateTextHighlightColor(int)));
756
757 mentionColor = new QLineEdit();
758 mentionColor->setText(SettingsCache::instance().getChatMentionColor());
759 updateMentionPreview();
760 connect(mentionColor, SIGNAL(textChanged(QString)), this, SLOT(updateColor(QString)));
761
762 messagePopups.setChecked(SettingsCache::instance().getShowMessagePopup());
763 connect(&messagePopups, SIGNAL(stateChanged(int)), &SettingsCache::instance(), SLOT(setShowMessagePopups(int)));
764
765 mentionPopups.setChecked(SettingsCache::instance().getShowMentionPopup());
766 connect(&mentionPopups, SIGNAL(stateChanged(int)), &SettingsCache::instance(), SLOT(setShowMentionPopups(int)));
767
768 roomHistory.setChecked(SettingsCache::instance().getRoomHistory());
769 connect(&roomHistory, SIGNAL(stateChanged(int)), &SettingsCache::instance(), SLOT(setRoomHistory(int)));
770
771 customAlertString = new QLineEdit();
772 customAlertString->setPlaceholderText(tr("Word1 Word2 Word3"));
773 customAlertString->setText(SettingsCache::instance().getHighlightWords());
774 connect(customAlertString, SIGNAL(textChanged(QString)), &SettingsCache::instance(),
775 SLOT(setHighlightWords(QString)));
776
777 auto *chatGrid = new QGridLayout;
778 chatGrid->addWidget(&chatMentionCheckBox, 0, 0);
779 chatGrid->addWidget(&invertMentionForeground, 0, 1);
780 chatGrid->addWidget(mentionColor, 0, 2);
781 chatGrid->addWidget(&chatMentionCompleterCheckbox, 1, 0);
782 chatGrid->addWidget(&ignoreUnregUsersMainChat, 2, 0);
783 chatGrid->addWidget(&hexLabel, 1, 2);
784 chatGrid->addWidget(&ignoreUnregUserMessages, 3, 0);
785 chatGrid->addWidget(&messagePopups, 4, 0);
786 chatGrid->addWidget(&mentionPopups, 5, 0);
787 chatGrid->addWidget(&roomHistory, 6, 0);
788 chatGroupBox = new QGroupBox;
789 chatGroupBox->setLayout(chatGrid);
790
791 highlightColor = new QLineEdit();
792 highlightColor->setText(SettingsCache::instance().getChatHighlightColor());
793 updateHighlightPreview();
794 connect(highlightColor, SIGNAL(textChanged(QString)), this, SLOT(updateHighlightColor(QString)));
795
796 auto *highlightNotice = new QGridLayout;
797 highlightNotice->addWidget(highlightColor, 0, 2);
798 highlightNotice->addWidget(&invertHighlightForeground, 0, 1);
799 highlightNotice->addWidget(&hexHighlightLabel, 1, 2);
800 highlightNotice->addWidget(customAlertString, 0, 0);
801 highlightNotice->addWidget(&customAlertStringLabel, 1, 0);
802 highlightGroupBox = new QGroupBox;
803 highlightGroupBox->setLayout(highlightNotice);
804
805 messageList = new QListWidget;
806
807 int count = SettingsCache::instance().messages().getCount();
808 for (int i = 0; i < count; i++)
809 messageList->addItem(SettingsCache::instance().messages().getMessageAt(i));
810
811 aAdd = new QAction(this);
812 aAdd->setIcon(QPixmap("theme:icons/increment"));
813 aAdd->setStatusTip(tr("Add New URL"));
814
815 connect(aAdd, SIGNAL(triggered()), this, SLOT(actAdd()));
816 aEdit = new QAction(this);
817 aEdit->setIcon(QPixmap("theme:icons/pencil"));
818 aEdit->setStatusTip(tr("Edit URL"));
819 connect(aEdit, SIGNAL(triggered()), this, SLOT(actEdit()));
820 aRemove = new QAction(this);
821 aRemove->setIcon(QPixmap("theme:icons/decrement"));
822 aRemove->setStatusTip(tr("Remove URL"));
823 connect(aRemove, SIGNAL(triggered()), this, SLOT(actRemove()));
824
825 auto *messageToolBar = new QToolBar;
826 messageToolBar->setOrientation(Qt::Vertical);
827 messageToolBar->addAction(aAdd);
828 messageToolBar->addAction(aRemove);
829 messageToolBar->addAction(aEdit);
830 messageToolBar->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
831
832 auto *messageListLayout = new QHBoxLayout;
833 messageListLayout->addWidget(messageToolBar);
834 messageListLayout->addWidget(messageList);
835
836 messageShortcuts = new QGroupBox;
837 messageShortcuts->setLayout(messageListLayout);
838
839 auto *mainLayout = new QVBoxLayout;
840
841 mainLayout->addWidget(messageShortcuts);
842 mainLayout->addWidget(chatGroupBox);
843 mainLayout->addWidget(highlightGroupBox);
844
845 setLayout(mainLayout);
846
847 retranslateUi();
848 }
849
updateColor(const QString & value)850 void MessagesSettingsPage::updateColor(const QString &value)
851 {
852 QColor colorToSet;
853 colorToSet.setNamedColor("#" + value);
854 if (colorToSet.isValid()) {
855 SettingsCache::instance().setChatMentionColor(value);
856 updateMentionPreview();
857 }
858 }
859
updateHighlightColor(const QString & value)860 void MessagesSettingsPage::updateHighlightColor(const QString &value)
861 {
862 QColor colorToSet;
863 colorToSet.setNamedColor("#" + value);
864 if (colorToSet.isValid()) {
865 SettingsCache::instance().setChatHighlightColor(value);
866 updateHighlightPreview();
867 }
868 }
869
updateTextColor(int value)870 void MessagesSettingsPage::updateTextColor(int value)
871 {
872 SettingsCache::instance().setChatMentionForeground(value);
873 updateMentionPreview();
874 }
875
updateTextHighlightColor(int value)876 void MessagesSettingsPage::updateTextHighlightColor(int value)
877 {
878 SettingsCache::instance().setChatHighlightForeground(value);
879 updateHighlightPreview();
880 }
881
updateMentionPreview()882 void MessagesSettingsPage::updateMentionPreview()
883 {
884 mentionColor->setStyleSheet(
885 "QLineEdit{background:#" + SettingsCache::instance().getChatMentionColor() +
886 ";color: " + (SettingsCache::instance().getChatMentionForeground() ? "white" : "black") + ";}");
887 }
888
updateHighlightPreview()889 void MessagesSettingsPage::updateHighlightPreview()
890 {
891 highlightColor->setStyleSheet(
892 "QLineEdit{background:#" + SettingsCache::instance().getChatHighlightColor() +
893 ";color: " + (SettingsCache::instance().getChatHighlightForeground() ? "white" : "black") + ";}");
894 }
895
storeSettings()896 void MessagesSettingsPage::storeSettings()
897 {
898 SettingsCache::instance().messages().setCount(messageList->count());
899 for (int i = 0; i < messageList->count(); i++)
900 SettingsCache::instance().messages().setMessageAt(i, messageList->item(i)->text());
901 }
902
actAdd()903 void MessagesSettingsPage::actAdd()
904 {
905 bool ok;
906 QString msg = QInputDialog::getText(this, tr("Add message"), tr("Message:"), QLineEdit::Normal, QString(), &ok);
907 if (ok) {
908 messageList->addItem(msg);
909 storeSettings();
910 }
911 }
912
actEdit()913 void MessagesSettingsPage::actEdit()
914 {
915 if (messageList->currentItem()) {
916 QString oldText = messageList->currentItem()->text();
917 bool ok;
918 QString msg = QInputDialog::getText(this, tr("Edit message"), tr("Message:"), QLineEdit::Normal, oldText, &ok);
919 if (ok) {
920 messageList->currentItem()->setText(msg);
921 storeSettings();
922 }
923 }
924 }
925
actRemove()926 void MessagesSettingsPage::actRemove()
927 {
928 if (messageList->currentItem() != nullptr) {
929 delete messageList->takeItem(messageList->currentRow());
930 storeSettings();
931 }
932 }
933
retranslateUi()934 void MessagesSettingsPage::retranslateUi()
935 {
936 chatGroupBox->setTitle(tr("Chat settings"));
937 highlightGroupBox->setTitle(tr("Custom alert words"));
938 chatMentionCheckBox.setText(tr("Enable chat mentions"));
939 chatMentionCompleterCheckbox.setText(tr("Enable mention completer"));
940 messageShortcuts->setTitle(tr("In-game message macros"));
941 ignoreUnregUsersMainChat.setText(tr("Ignore chat room messages sent by unregistered users"));
942 ignoreUnregUserMessages.setText(tr("Ignore private messages sent by unregistered users"));
943 invertMentionForeground.setText(tr("Invert text color"));
944 invertHighlightForeground.setText(tr("Invert text color"));
945 messagePopups.setText(tr("Enable desktop notifications for private messages"));
946 mentionPopups.setText(tr("Enable desktop notification for mentions"));
947 roomHistory.setText(tr("Enable room message history on join"));
948 hexLabel.setText(tr("(Color is hexadecimal)"));
949 hexHighlightLabel.setText(tr("(Color is hexadecimal)"));
950 customAlertStringLabel.setText(tr("Separate words with a space, alphanumeric characters only"));
951 }
952
SoundSettingsPage()953 SoundSettingsPage::SoundSettingsPage()
954 {
955 soundEnabledCheckBox.setChecked(SettingsCache::instance().getSoundEnabled());
956 connect(&soundEnabledCheckBox, SIGNAL(stateChanged(int)), &SettingsCache::instance(), SLOT(setSoundEnabled(int)));
957
958 QString themeName = SettingsCache::instance().getSoundThemeName();
959
960 QStringList themeDirs = soundEngine->getAvailableThemes().keys();
961 for (int i = 0; i < themeDirs.size(); i++) {
962 themeBox.addItem(themeDirs[i]);
963 if (themeDirs[i] == themeName)
964 themeBox.setCurrentIndex(i);
965 }
966
967 connect(&themeBox, SIGNAL(currentIndexChanged(int)), this, SLOT(themeBoxChanged(int)));
968 connect(&soundTestButton, SIGNAL(clicked()), soundEngine, SLOT(testSound()));
969
970 masterVolumeSlider = new QSlider(Qt::Horizontal);
971 masterVolumeSlider->setMinimum(0);
972 masterVolumeSlider->setMaximum(100);
973 masterVolumeSlider->setValue(SettingsCache::instance().getMasterVolume());
974 masterVolumeSlider->setToolTip(QString::number(SettingsCache::instance().getMasterVolume()));
975 connect(&SettingsCache::instance(), SIGNAL(masterVolumeChanged(int)), this, SLOT(masterVolumeChanged(int)));
976 connect(masterVolumeSlider, SIGNAL(sliderReleased()), soundEngine, SLOT(testSound()));
977 connect(masterVolumeSlider, SIGNAL(valueChanged(int)), &SettingsCache::instance(), SLOT(setMasterVolume(int)));
978
979 masterVolumeSpinBox = new QSpinBox();
980 masterVolumeSpinBox->setMinimum(0);
981 masterVolumeSpinBox->setMaximum(100);
982 masterVolumeSpinBox->setValue(SettingsCache::instance().getMasterVolume());
983 connect(masterVolumeSlider, SIGNAL(valueChanged(int)), masterVolumeSpinBox, SLOT(setValue(int)));
984 connect(masterVolumeSpinBox, SIGNAL(valueChanged(int)), masterVolumeSlider, SLOT(setValue(int)));
985
986 auto *soundGrid = new QGridLayout;
987 soundGrid->addWidget(&soundEnabledCheckBox, 0, 0, 1, 3);
988 soundGrid->addWidget(&masterVolumeLabel, 1, 0);
989 soundGrid->addWidget(masterVolumeSlider, 1, 1);
990 soundGrid->addWidget(masterVolumeSpinBox, 1, 2);
991 soundGrid->addWidget(&themeLabel, 2, 0);
992 soundGrid->addWidget(&themeBox, 2, 1);
993 soundGrid->addWidget(&soundTestButton, 3, 1);
994
995 soundGroupBox = new QGroupBox;
996 soundGroupBox->setLayout(soundGrid);
997
998 auto *mainLayout = new QVBoxLayout;
999 mainLayout->addWidget(soundGroupBox);
1000
1001 setLayout(mainLayout);
1002 }
1003
themeBoxChanged(int index)1004 void SoundSettingsPage::themeBoxChanged(int index)
1005 {
1006 QStringList themeDirs = soundEngine->getAvailableThemes().keys();
1007 if (index >= 0 && index < themeDirs.count())
1008 SettingsCache::instance().setSoundThemeName(themeDirs.at(index));
1009 }
1010
masterVolumeChanged(int value)1011 void SoundSettingsPage::masterVolumeChanged(int value)
1012 {
1013 masterVolumeSlider->setToolTip(QString::number(value));
1014 }
1015
retranslateUi()1016 void SoundSettingsPage::retranslateUi()
1017 {
1018 soundEnabledCheckBox.setText(tr("Enable &sounds"));
1019 themeLabel.setText(tr("Current sounds theme:"));
1020 soundTestButton.setText(tr("Test system sound engine"));
1021 soundGroupBox->setTitle(tr("Sound settings"));
1022 masterVolumeLabel.setText(tr("Master volume"));
1023 }
1024
ShortcutSettingsPage()1025 ShortcutSettingsPage::ShortcutSettingsPage()
1026 {
1027 // table
1028 shortcutsTable = new QTreeWidget();
1029 shortcutsTable->setColumnCount(2);
1030 shortcutsTable->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
1031 shortcutsTable->setUniformRowHeights(true);
1032 shortcutsTable->setAlternatingRowColors(true);
1033 shortcutsTable->header()->resizeSection(0, shortcutsTable->width() / 3 * 2);
1034
1035 // edit widget
1036 currentActionGroupLabel = new QLabel(this);
1037 currentActionGroupName = new QLabel(this);
1038 currentActionLabel = new QLabel(this);
1039 currentActionName = new QLabel(this);
1040 currentShortcutLabel = new QLabel(this);
1041 editTextBox = new SequenceEdit("", this);
1042 shortcutsTable->installEventFilter(editTextBox);
1043
1044 // buttons
1045 faqLabel = new QLabel(this);
1046 faqLabel->setTextInteractionFlags(Qt::LinksAccessibleByMouse);
1047 faqLabel->setOpenExternalLinks(true);
1048
1049 btnResetAll = new QPushButton(this);
1050 btnClearAll = new QPushButton(this);
1051
1052 btnResetAll->setIcon(QPixmap("theme:icons/update"));
1053 btnClearAll->setIcon(QPixmap("theme:icons/clearsearch"));
1054
1055 // layout
1056 auto *editLayout = new QGridLayout;
1057 editLayout->addWidget(currentActionGroupLabel, 0, 0);
1058 editLayout->addWidget(currentActionGroupName, 0, 1);
1059 editLayout->addWidget(currentActionLabel, 1, 0);
1060 editLayout->addWidget(currentActionName, 1, 1);
1061 editLayout->addWidget(currentShortcutLabel, 2, 0);
1062 editLayout->addWidget(editTextBox, 2, 1);
1063
1064 editShortcutGroupBox = new QGroupBox;
1065 editShortcutGroupBox->setLayout(editLayout);
1066
1067 auto *buttonsLayout = new QHBoxLayout;
1068 buttonsLayout->addWidget(faqLabel);
1069 buttonsLayout->addWidget(btnResetAll);
1070 buttonsLayout->addWidget(btnClearAll);
1071
1072 auto *mainLayout = new QVBoxLayout;
1073 mainLayout->addWidget(shortcutsTable);
1074 mainLayout->addWidget(editShortcutGroupBox);
1075 mainLayout->addLayout(buttonsLayout);
1076
1077 setLayout(mainLayout);
1078
1079 connect(btnResetAll, SIGNAL(clicked()), this, SLOT(resetShortcuts()));
1080 connect(btnClearAll, SIGNAL(clicked()), this, SLOT(clearShortcuts()));
1081 connect(shortcutsTable, SIGNAL(currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)), this,
1082 SLOT(currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)));
1083 connect(&SettingsCache::instance().shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts()));
1084
1085 createShortcuts();
1086 }
1087
currentItemChanged(QTreeWidgetItem * current,QTreeWidgetItem *)1088 void ShortcutSettingsPage::currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem * /*previous */)
1089 {
1090 if (current == nullptr) {
1091 currentActionGroupName->setText("");
1092 currentActionName->setText("");
1093 editTextBox->setShortcutName("");
1094 } else {
1095 QString key = current->data(2, Qt::DisplayRole).toString();
1096 QString group = SettingsCache::instance().shortcuts().getShortcut(key).getGroupName();
1097 QString action = SettingsCache::instance().shortcuts().getShortcut(key).getName();
1098 currentActionGroupName->setText(group);
1099 currentActionName->setText(action);
1100 editTextBox->setShortcutName(key);
1101 }
1102 }
1103
resetShortcuts()1104 void ShortcutSettingsPage::resetShortcuts()
1105 {
1106 if (QMessageBox::question(this, tr("Restore all default shortcuts"),
1107 tr("Do you really want to restore all default shortcuts?")) == QMessageBox::Yes) {
1108 SettingsCache::instance().shortcuts().resetAllShortcuts();
1109 }
1110 }
1111
createShortcuts()1112 void ShortcutSettingsPage::createShortcuts()
1113 {
1114 QHash<QString, QTreeWidgetItem *> parentItems;
1115 QTreeWidgetItem *curParent = nullptr;
1116 for (const auto &key : SettingsCache::instance().shortcuts().getAllShortcutKeys()) {
1117 QString name = SettingsCache::instance().shortcuts().getShortcut(key).getName();
1118 QString group = SettingsCache::instance().shortcuts().getShortcut(key).getGroupName();
1119 QString shortcut = SettingsCache::instance().shortcuts().getShortcutString(key);
1120
1121 if (parentItems.contains(group)) {
1122 curParent = parentItems.value(group);
1123 } else {
1124 curParent = new QTreeWidgetItem((QTreeWidget *)nullptr, QStringList({group, "", ""}));
1125 static QFont font = curParent->font(0);
1126 font.setBold(true);
1127 curParent->setFont(0, font);
1128 parentItems.insert(group, curParent);
1129 }
1130
1131 new QTreeWidgetItem(curParent, QStringList({name, shortcut, key}));
1132 }
1133 shortcutsTable->clear();
1134 shortcutsTable->insertTopLevelItems(0, parentItems.values());
1135 shortcutsTable->setCurrentItem(nullptr);
1136 shortcutsTable->expandAll();
1137 shortcutsTable->sortItems(0, Qt::AscendingOrder);
1138 }
1139
refreshShortcuts()1140 void ShortcutSettingsPage::refreshShortcuts()
1141 {
1142 QTreeWidgetItem *curParent = nullptr;
1143 QTreeWidgetItem *curChild = nullptr;
1144 for (int i = 0; i < shortcutsTable->topLevelItemCount(); ++i) {
1145 curParent = shortcutsTable->topLevelItem(i);
1146 for (int j = 0; j < curParent->childCount(); ++j) {
1147 curChild = curParent->child(j);
1148 QString key = curChild->data(2, Qt::DisplayRole).toString();
1149 QString name = SettingsCache::instance().shortcuts().getShortcut(key).getName();
1150 QString shortcut = SettingsCache::instance().shortcuts().getShortcutString(key);
1151 curChild->setText(0, name);
1152 curChild->setText(1, shortcut);
1153
1154 if (j == 0) {
1155 // the first child also updates the parent's group name
1156 QString group = SettingsCache::instance().shortcuts().getShortcut(key).getGroupName();
1157 curParent->setText(0, group);
1158 }
1159 }
1160 }
1161 shortcutsTable->sortItems(0, Qt::AscendingOrder);
1162 currentItemChanged(shortcutsTable->currentItem(), nullptr);
1163 }
1164
clearShortcuts()1165 void ShortcutSettingsPage::clearShortcuts()
1166 {
1167 if (QMessageBox::question(this, tr("Clear all default shortcuts"),
1168 tr("Do you really want to clear all shortcuts?")) == QMessageBox::Yes) {
1169 SettingsCache::instance().shortcuts().clearAllShortcuts();
1170 }
1171 }
1172
retranslateUi()1173 void ShortcutSettingsPage::retranslateUi()
1174 {
1175 shortcutsTable->setHeaderLabels(QStringList() << tr("Action") << tr("Shortcut"));
1176 refreshShortcuts();
1177
1178 currentActionGroupLabel->setText(tr("Section:"));
1179 currentActionLabel->setText(tr("Action:"));
1180 currentShortcutLabel->setText(tr("Shortcut:"));
1181 editTextBox->retranslateUi();
1182 faqLabel->setText(QString("<a href='%1'>%2</a>").arg(WIKI_CUSTOM_SHORTCUTS).arg(tr("How to set custom shortcuts")));
1183 btnResetAll->setText("Restore all default shortcuts");
1184 btnClearAll->setText("Clear all shortcuts");
1185 }
1186
DlgSettings(QWidget * parent)1187 DlgSettings::DlgSettings(QWidget *parent) : QDialog(parent)
1188 {
1189 #if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))
1190 QRect rec = qApp->primaryScreen()->availableGeometry();
1191 #else
1192 QRect rec = QApplication::desktop()->availableGeometry();
1193 #endif
1194 this->setMinimumSize(rec.width() / 2, rec.height() - 100);
1195 this->setBaseSize(rec.width(), rec.height());
1196
1197 connect(&SettingsCache::instance(), SIGNAL(langChanged()), this, SLOT(updateLanguage()));
1198
1199 contentsWidget = new QListWidget;
1200 contentsWidget->setViewMode(QListView::IconMode);
1201 contentsWidget->setIconSize(QSize(58, 50));
1202 contentsWidget->setMovement(QListView::Static);
1203 contentsWidget->setMinimumHeight(85);
1204 contentsWidget->setMaximumHeight(85);
1205 contentsWidget->setSpacing(5);
1206
1207 pagesWidget = new QStackedWidget;
1208 pagesWidget->addWidget(new GeneralSettingsPage);
1209 pagesWidget->addWidget(new AppearanceSettingsPage);
1210 pagesWidget->addWidget(new UserInterfaceSettingsPage);
1211 pagesWidget->addWidget(new DeckEditorSettingsPage);
1212 pagesWidget->addWidget(new MessagesSettingsPage);
1213 pagesWidget->addWidget(new SoundSettingsPage);
1214 pagesWidget->addWidget(new ShortcutSettingsPage);
1215
1216 createIcons();
1217 contentsWidget->setCurrentRow(0);
1218
1219 auto *vboxLayout = new QVBoxLayout;
1220 vboxLayout->addWidget(contentsWidget);
1221 vboxLayout->addWidget(pagesWidget);
1222
1223 auto *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok);
1224 connect(buttonBox, SIGNAL(accepted()), this, SLOT(close()));
1225
1226 auto *mainLayout = new QVBoxLayout;
1227 mainLayout->addLayout(vboxLayout);
1228 mainLayout->addSpacing(2);
1229 mainLayout->addWidget(buttonBox);
1230 setLayout(mainLayout);
1231
1232 retranslateUi();
1233
1234 adjustSize();
1235 }
1236
createIcons()1237 void DlgSettings::createIcons()
1238 {
1239 generalButton = new QListWidgetItem(contentsWidget);
1240 generalButton->setTextAlignment(Qt::AlignHCenter);
1241 generalButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
1242 generalButton->setIcon(QPixmap("theme:config/general"));
1243
1244 appearanceButton = new QListWidgetItem(contentsWidget);
1245 appearanceButton->setTextAlignment(Qt::AlignHCenter);
1246 appearanceButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
1247 appearanceButton->setIcon(QPixmap("theme:config/appearance"));
1248
1249 userInterfaceButton = new QListWidgetItem(contentsWidget);
1250 userInterfaceButton->setTextAlignment(Qt::AlignHCenter);
1251 userInterfaceButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
1252 userInterfaceButton->setIcon(QPixmap("theme:config/interface"));
1253
1254 deckEditorButton = new QListWidgetItem(contentsWidget);
1255 deckEditorButton->setTextAlignment(Qt::AlignHCenter);
1256 deckEditorButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
1257 deckEditorButton->setIcon(QPixmap("theme:config/deckeditor"));
1258
1259 messagesButton = new QListWidgetItem(contentsWidget);
1260 messagesButton->setTextAlignment(Qt::AlignHCenter);
1261 messagesButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
1262 messagesButton->setIcon(QPixmap("theme:config/messages"));
1263
1264 soundButton = new QListWidgetItem(contentsWidget);
1265 soundButton->setTextAlignment(Qt::AlignHCenter);
1266 soundButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
1267 soundButton->setIcon(QPixmap("theme:config/sound"));
1268
1269 shortcutsButton = new QListWidgetItem(contentsWidget);
1270 shortcutsButton->setTextAlignment(Qt::AlignHCenter);
1271 shortcutsButton->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
1272 shortcutsButton->setIcon(QPixmap("theme:config/shorcuts"));
1273
1274 connect(contentsWidget, SIGNAL(currentItemChanged(QListWidgetItem *, QListWidgetItem *)), this,
1275 SLOT(changePage(QListWidgetItem *, QListWidgetItem *)));
1276 }
1277
changePage(QListWidgetItem * current,QListWidgetItem * previous)1278 void DlgSettings::changePage(QListWidgetItem *current, QListWidgetItem *previous)
1279 {
1280 if (!current)
1281 current = previous;
1282
1283 pagesWidget->setCurrentIndex(contentsWidget->row(current));
1284 }
1285
setTab(int index)1286 void DlgSettings::setTab(int index)
1287 {
1288 if (index <= contentsWidget->count() - 1 && index >= 0) {
1289 changePage(contentsWidget->item(index), contentsWidget->currentItem());
1290 contentsWidget->setCurrentRow(index);
1291 }
1292 }
1293
updateLanguage()1294 void DlgSettings::updateLanguage()
1295 {
1296 qApp->removeTranslator(translator); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast)
1297 installNewTranslator();
1298 }
1299
changeEvent(QEvent * event)1300 void DlgSettings::changeEvent(QEvent *event)
1301 {
1302 if (event->type() == QEvent::LanguageChange)
1303 retranslateUi();
1304 QDialog::changeEvent(event);
1305 }
1306
closeEvent(QCloseEvent * event)1307 void DlgSettings::closeEvent(QCloseEvent *event)
1308 {
1309 bool showLoadError = true;
1310 QString loadErrorMessage = tr("Unknown Error loading card database");
1311 LoadStatus loadStatus = db->getLoadStatus();
1312 qDebug() << "Card Database load status: " << loadStatus;
1313 switch (loadStatus) {
1314 case Ok:
1315 showLoadError = false;
1316 break;
1317 case Invalid:
1318 loadErrorMessage = tr("Your card database is invalid.\n\n"
1319 "Cockatrice may not function correctly with an invalid database\n\n"
1320 "You may need to rerun oracle to update your card database.\n\n"
1321 "Would you like to change your database location setting?");
1322 break;
1323 case VersionTooOld:
1324 loadErrorMessage = tr("Your card database version is too old.\n\n"
1325 "This can cause problems loading card information or images\n\n"
1326 "Usually this can be fixed by rerunning oracle to to update your card database.\n\n"
1327 "Would you like to change your database location setting?");
1328 break;
1329 case NotLoaded:
1330 loadErrorMessage = tr("Your card database did not finish loading\n\n"
1331 "Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your "
1332 "cards.xml attached\n\n"
1333 "Would you like to change your database location setting?");
1334 break;
1335 case FileError:
1336 loadErrorMessage = tr("File Error loading your card database.\n\n"
1337 "Would you like to change your database location setting?");
1338 break;
1339 case NoCards:
1340 loadErrorMessage = tr("Your card database was loaded but contains no cards.\n\n"
1341 "Would you like to change your database location setting?");
1342 break;
1343 default:
1344 loadErrorMessage = tr("Unknown card database load status\n\n"
1345 "Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues\n\n"
1346 "Would you like to change your database location setting?");
1347
1348 break;
1349 }
1350
1351 if (showLoadError) {
1352 if (QMessageBox::critical(this, tr("Error"), loadErrorMessage, QMessageBox::Yes | QMessageBox::No) ==
1353 QMessageBox::Yes) {
1354 event->ignore();
1355 return;
1356 }
1357 }
1358
1359 if (!QDir(SettingsCache::instance().getDeckPath()).exists() || SettingsCache::instance().getDeckPath().isEmpty()) {
1360 // TODO: Prompt to create it
1361 if (QMessageBox::critical(
1362 this, tr("Error"),
1363 tr("The path to your deck directory is invalid. Would you like to go back and set the correct path?"),
1364 QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
1365 event->ignore();
1366 return;
1367 }
1368 }
1369
1370 if (!QDir(SettingsCache::instance().getPicsPath()).exists() || SettingsCache::instance().getPicsPath().isEmpty()) {
1371 // TODO: Prompt to create it
1372 if (QMessageBox::critical(this, tr("Error"),
1373 tr("The path to your card pictures directory is invalid. Would you like to go back "
1374 "and set the correct path?"),
1375 QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
1376 event->ignore();
1377 return;
1378 }
1379 }
1380 event->accept();
1381 }
1382
retranslateUi()1383 void DlgSettings::retranslateUi()
1384 {
1385 setWindowTitle(tr("Settings"));
1386
1387 generalButton->setText(tr("General"));
1388 appearanceButton->setText(tr("Appearance"));
1389 userInterfaceButton->setText(tr("User Interface"));
1390 deckEditorButton->setText(tr("Card Sources"));
1391 messagesButton->setText(tr("Chat"));
1392 soundButton->setText(tr("Sound"));
1393 shortcutsButton->setText(tr("Shortcuts"));
1394
1395 for (int i = 0; i < pagesWidget->count(); i++)
1396 dynamic_cast<AbstractSettingsPage *>(pagesWidget->widget(i))->retranslateUi();
1397
1398 contentsWidget->reset();
1399 }
1400