1 #include "common/common_pch.h"
2 
3 #include <QColorDialog>
4 #include <QFileDialog>
5 #include <QIcon>
6 #include <QInputDialog>
7 #include <QItemSelectionModel>
8 #include <QModelIndex>
9 #include <QStandardItem>
10 #include <QTabWidget>
11 #include <QVector>
12 
13 #include "common/chapters/chapters.h"
14 #include "common/qt.h"
15 #include "common/translation.h"
16 #include "mkvtoolnix-gui/app.h"
17 #include "mkvtoolnix-gui/chapter_editor/tool.h"
18 #include "mkvtoolnix-gui/forms/main_window/preferences_dialog.h"
19 #include "mkvtoolnix-gui/main_window/preferences_dialog.h"
20 #include "mkvtoolnix-gui/main_window/prefs_run_program_widget.h"
21 #include "mkvtoolnix-gui/merge/additional_command_line_options_dialog.h"
22 #include "mkvtoolnix-gui/merge/source_file.h"
23 #include "mkvtoolnix-gui/util/container.h"
24 #include "mkvtoolnix-gui/util/file_dialog.h"
25 #include "mkvtoolnix-gui/util/language_dialog.h"
26 #include "mkvtoolnix-gui/util/message_box.h"
27 #include "mkvtoolnix-gui/util/model.h"
28 #include "mkvtoolnix-gui/util/side_by_side_multi_select.h"
29 #include "mkvtoolnix-gui/util/string_list_configuration_widget.h"
30 #include "mkvtoolnix-gui/util/widget.h"
31 
32 namespace mtx::gui {
33 
34 PreferencesDialog::Page PreferencesDialog::ms_previouslySelectedPage{PreferencesDialog::Page::Gui};
35 
PreferencesDialog(QWidget * parent,Page pageToShow)36 PreferencesDialog::PreferencesDialog(QWidget *parent,
37                                      Page pageToShow)
38   : QDialog{parent, Qt::Dialog | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint}
39   , ui{new Ui::PreferencesDialog}
40   , m_cfg(Util::Settings::get())
41   , m_previousUiLocale{m_cfg.m_uiLocale}
42   , m_previousDisableToolTips{m_cfg.m_uiDisableToolTips}
43   , m_previousProbeRangePercentage{m_cfg.m_probeRangePercentage}
44   , m_ignoreNextCurrentChange{}
45 {
46   ui->setupUi(this);
47 
48   setupPageSelector(pageToShow == Page::PreviouslySelected ? ms_previouslySelectedPage : pageToShow);
49 
50   ui->tbOftenUsedXYZ->setTabPosition(m_cfg.m_tabPosition);
51 
52   Util::restoreWidgetGeometry(this);
53 
54   // GUI page
55   ui->cbGuiDisableToolTips->setChecked(m_cfg.m_uiDisableToolTips);
56   ui->cbGuiCheckForUpdates->setChecked(m_cfg.m_checkForUpdates);
57   ui->cbGuiShowDebuggingMenu->setChecked(m_cfg.m_showDebuggingMenu);
58   ui->cbGuiShowToolSelector->setChecked(m_cfg.m_showToolSelector);
59   ui->cbGuiShowMoveUpDownButtons->setChecked(m_cfg.m_showMoveUpDownButtons);
60   ui->cbGuiElideTabHeaderLabels->setChecked(m_cfg.m_elideTabHeaderLabels);
61   ui->cbGuiUseLegacyFontMIMETypes->setChecked(m_cfg.m_useLegacyFontMIMETypes);
62   ui->cbGuiWarnBeforeClosingModifiedTabs->setChecked(m_cfg.m_warnBeforeClosingModifiedTabs);
63   ui->cbGuiWarnBeforeAbortingJobs->setChecked(m_cfg.m_warnBeforeAbortingJobs);
64   ui->cbGuiWarnBeforeOverwriting->setChecked(m_cfg.m_warnBeforeOverwriting);
65   setupFontAndScaling();
66   setupInterfaceLanguage();
67   setupTabPositions();
68   setupBCP47LanguageEditMode();
69   setupDerivingTrackLanguagesFromFileName();
70   setupWhenToSetDefaultLanguage();
71   setupLanguageShortcuts();
72 
73   ui->cbGuiUseDefaultJobDescription->setChecked(m_cfg.m_useDefaultJobDescription);
74   ui->cbGuiShowOutputOfAllJobs->setChecked(m_cfg.m_showOutputOfAllJobs);
75   ui->cbGuiSwitchToJobOutputAfterStarting->setChecked(m_cfg.m_switchToJobOutputAfterStarting);
76   ui->cbGuiResetJobWarningErrorCountersOnExit->setChecked(m_cfg.m_resetJobWarningErrorCountersOnExit);
77   ui->cbGuiRemoveOutputFileOnJobFailure->setChecked(m_cfg.m_removeOutputFileOnJobFailure);
78   ui->cbGuiRemoveOldJobs->setChecked(m_cfg.m_removeOldJobs);
79   ui->sbGuiRemoveOldJobsDays->setValue(m_cfg.m_removeOldJobsDays);
80   ui->sbGuiMaximumConcurrentJobs->setValue(m_cfg.m_maximumConcurrentJobs);
81   adjustRemoveOldJobsControls();
82   setupJobRemovalPolicy();
83 
84   setupCommonLanguages(m_cfg.m_useISO639_3Languages);
85   setupCommonRegions();
86   setupCommonCharacterSets();
87 
88   ui->cbUseISO639_3Languages->setChecked(m_cfg.m_useISO639_3Languages);
89 
90   // Merge page
91   if (!m_cfg.m_mediaInfoExe.isEmpty())
92     ui->leMMediaInfoExe->setText(QDir::toNativeSeparators(m_cfg.m_mediaInfoExe));
93   ui->cbMSortFilesTracksByTypeWhenAdding->setChecked(m_cfg.m_mergeSortFilesTracksByTypeWhenAdding);
94   ui->cbMReconstructSequencesWhenAdding->setChecked(m_cfg.m_mergeReconstructSequencesWhenAdding);
95   ui->cbMAutoSetFileTitle->setChecked(m_cfg.m_autoSetFileTitle);
96   ui->cbMAutoClearFileTitle->setChecked(m_cfg.m_autoClearFileTitle);
97   ui->cbMSetAudioDelayFromFileName->setChecked(m_cfg.m_setAudioDelayFromFileName);
98   ui->cbMDisableCompressionForAllTrackTypes->setChecked(m_cfg.m_disableCompressionForAllTrackTypes);
99   ui->cbMDisableDefaultTrackForSubtitles->setChecked(m_cfg.m_disableDefaultTrackForSubtitles);
100   ui->cbMEnableDialogNormGainRemoval->setChecked(m_cfg.m_mergeEnableDialogNormGainRemoval);
101   ui->cbMAlwaysShowOutputFileControls->setChecked(m_cfg.m_mergeAlwaysShowOutputFileControls);
102   ui->cbMClearMergeSettings->setCurrentIndex(static_cast<int>(m_cfg.m_clearMergeSettings));
103   ui->ldwMDefaultAudioTrackLanguage->setLanguage(m_cfg.m_defaultAudioTrackLanguage);
104   ui->ldwMDefaultAudioTrackLanguage->registerBuddyLabel(*ui->lDefaultAudioTrackLanguage);
105   ui->ldwMDefaultVideoTrackLanguage->setLanguage(m_cfg.m_defaultVideoTrackLanguage);
106   ui->ldwMDefaultVideoTrackLanguage->registerBuddyLabel(*ui->lDefaultVideoTrackLanguage);
107   ui->ldwMDefaultSubtitleTrackLanguage->setLanguage(m_cfg.m_defaultSubtitleTrackLanguage);
108   ui->ldwMDefaultSubtitleTrackLanguage->registerBuddyLabel(*ui->lDefaultSubtitleTrackLanguage);
109   ui->cbMDefaultSubtitleCharset->setAdditionalItems(m_cfg.m_defaultSubtitleCharset).setup(true, QY("– No selection by default –")).setCurrentByData(m_cfg.m_defaultSubtitleCharset);
110   ui->leMDefaultAdditionalCommandLineOptions->setText(m_cfg.m_defaultAdditionalMergeOptions);
111   ui->cbMProbeRangePercentage->setValue(m_cfg.m_probeRangePercentage);
112   ui->cbMAddBlurayCovers->setChecked(m_cfg.m_mergeAddBlurayCovers);
113   ui->cbMAttachmentAlwaysSkipForExistingName->setChecked(m_cfg.m_mergeAttachmentsAlwaysSkipForExistingName);
114 
115   setupFileColorsControls();
116   setupProcessPriority();
117   setupPlaylistScanningPolicy();
118   setupOutputFileNamePolicy();
119   setupRecentDestinationDirectoryList();
120   setupEnableMuxingTracksByType();
121   setupEnableMuxingTracksByLanguage();
122   setupMergeAddingAppendingFilesPolicy();
123   setupMergeWarnMissingAudioTrack();
124   setupMergePredefinedItems();
125   setupTrackPropertiesLayout();
126 
127   // Info tool page
128   ui->wIDefaultJobSettings->setFileNameVisible(false);
129   ui->wIDefaultJobSettings->setSettings(m_cfg.m_defaultInfoJobSettings);
130 
131   // Chapter editor page
132   ui->cbCEDropLastFromBlurayPlaylist->setChecked(m_cfg.m_dropLastChapterFromBlurayPlaylist);
133   ui->cbCETextFileCharacterSet->setAdditionalItems(m_cfg.m_ceTextFileCharacterSet).setup(true, QY("Always ask the user")).setCurrentByData(m_cfg.m_ceTextFileCharacterSet);
134   ui->leCENameTemplate->setText(m_cfg.m_chapterNameTemplate);
135   ui->ldwCEDefaultLanguage->setLanguage(m_cfg.m_defaultChapterLanguage);
136 
137   // Header editor page
138   setupHeaderEditorDroppedFilesPolicy();
139   ui->cbHEDateTimeInUTC->setChecked(m_cfg.m_headerEditorDateTimeInUTC);
140 
141   setupJobsRunPrograms();
142 
143   setupToolTips();
144   setupConnections();
145 
146   Util::preventScrollingWithoutFocus(this);
147 }
148 
~PreferencesDialog()149 PreferencesDialog::~PreferencesDialog() {
150   Util::saveWidgetGeometry(this);
151 }
152 
153 bool
uiLocaleChanged() const154 PreferencesDialog::uiLocaleChanged()
155   const {
156   return m_previousUiLocale != m_cfg.m_uiLocale;
157 }
158 
159 bool
disableToolTipsChanged() const160 PreferencesDialog::disableToolTipsChanged()
161   const {
162   return m_previousDisableToolTips != m_cfg.m_uiDisableToolTips;
163 }
164 
165 bool
probeRangePercentageChanged() const166 PreferencesDialog::probeRangePercentageChanged()
167   const {
168   return m_previousProbeRangePercentage != m_cfg.m_probeRangePercentage;
169 }
170 
171 void
pageSelectionChanged(QItemSelection const & selection)172 PreferencesDialog::pageSelectionChanged(QItemSelection const &selection) {
173   if (selection.indexes().isEmpty())
174     return;
175 
176   auto current = selection.indexes().first();
177   if (!current.isValid())
178     return;
179 
180   if (m_ignoreNextCurrentChange) {
181     m_ignoreNextCurrentChange = false;
182     return;
183   }
184 
185   auto page = qobject_cast<QStandardItemModel *>(ui->pageSelector->model())
186     ->itemFromIndex(current.sibling(current.row(), 0))
187     ->data()
188     .value<int>();
189   ui->pages->setCurrentIndex(page);
190 }
191 
192 QModelIndex
modelIndexForPage(int page)193 PreferencesDialog::modelIndexForPage(int page) {
194   auto &model  = *qobject_cast<QStandardItemModel *>(ui->pageSelector->model());
195   auto pageIdx = Util::findIndex(model, [page, &model](QModelIndex const &idxToTest) {
196     return model.itemFromIndex(idxToTest)->data().value<int>() == page;
197   });
198 
199   return pageIdx;
200 }
201 
202 void
setupPageSelector(Page pageToShow)203 PreferencesDialog::setupPageSelector(Page pageToShow) {
204   m_cfg.handleSplitterSizes(ui->pagesSplitter);
205 
206   auto pageIndex = 0;
207   auto model     = new QStandardItemModel{this};
208   ui->pageSelector->setModel(model);
209 
210   auto addItem = [this, model, &pageIndex](Page pageType, QStandardItem *parent, QString const &text, QString const &icon = QString{}) -> QStandardItem * {
211     auto item = new QStandardItem{text};
212 
213     item->setData(pageIndex);
214     m_pageIndexes[pageType] = pageIndex++;
215 
216     if (!icon.isEmpty())
217       item->setIcon(QIcon{Q(":/icons/16x16/%1.png").arg(icon)});
218 
219     if (parent)
220       parent->appendRow(item);
221     else
222       model->appendRow(item);
223 
224     return item;
225   };
226 
227   auto pGui      = addItem(Page::Gui,                 nullptr, QY("GUI"),              "mkvtoolnix-gui");
228                    addItem(Page::OftenUsedSelections, pGui,    QY("Often used selections"));
229                    addItem(Page::LanguagesShortcuts,  pGui,    QY("Language shortcuts"));
230   auto pMerge    = addItem(Page::Merge,               nullptr, QY("Multiplexer"),      "merge");
231                    addItem(Page::PredefinedValues,    pMerge,  QY("Predefined values"));
232                    addItem(Page::DefaultValues,       pMerge,  QY("Default values"));
233                    addItem(Page::DeriveTrackLanguage, pMerge,  QY("Deriving track languages"));
234                    addItem(Page::Output,              pMerge,  QY("Destination file name"));
235                    addItem(Page::EnablingTracks,      pMerge,  QY("Enabling items"));
236                    addItem(Page::Playlists,           pMerge,  QY("Playlists & Blu-rays"));
237                    addItem(Page::Info,                nullptr, QY("Info tool"),        "document-preview-archive");
238                    addItem(Page::HeaderEditor,        nullptr, QY("Header editor"),    "document-edit");
239                    addItem(Page::ChapterEditor,       nullptr, QY("Chapter editor"),   "story-editor");
240   auto pJobs     = addItem(Page::Jobs,                nullptr, QY("Jobs & job queue"), "view-task");
241                    addItem(Page::RunPrograms,         pJobs,   QY("Executing actions"));
242 
243   for (auto row = 0, numRows = model->rowCount(); row < numRows; ++row)
244     ui->pageSelector->setExpanded(model->index(row, 0), true);
245 
246   ui->pageSelector->setMinimumSize(ui->pageSelector->minimumSizeHint());
247 
248   showPage(pageToShow);
249 
250   m_ignoreNextCurrentChange = false;
251 
252   connect(ui->pageSelector->selectionModel(), &QItemSelectionModel::selectionChanged, this, &PreferencesDialog::pageSelectionChanged);
253 }
254 
255 void
setupToolTips()256 PreferencesDialog::setupToolTips() {
257   if (m_cfg.m_uiDisableToolTips)
258     return;
259 
260   // GUI page
261   Util::setToolTip(ui->cbGuiDisableHighDPIScaling,
262                    Q("%1 %2")
263                    .arg(QY("If enabled, automatic scaling for high DPI displays will be disabled."))
264                    .arg(QY("Changes to this option will only take effect the next time the application is started.")));
265 
266   Util::setToolTip(ui->cbGuiDisableToolTips, QY("If checked, the GUI will not show helpful usage tips in popup windows while hovering over a control — such as this one."));
267   Util::setToolTip(ui->cbGuiDisableDarkStyleSheet,
268                    Q("%1 %2")
269                    .arg(QY("By default the GUI will start up with a dark color scheme if the color scheme in Windows is set to dark mode."))
270                    .arg(QY("If this option is enabled, the GUI will not use its dark mode and fall back to the default color scheme.")));
271 
272   Util::setToolTip(ui->cbGuiCheckForUpdates,
273                    Q("%1 %2 %3")
274                    .arg(QY("If enabled, the program will check online whether or not a new release of MKVToolNix is available on the home page."))
275                    .arg(QY("This is done at startup and at most once within 24 hours."))
276                    .arg(QY("No information is transmitted to the server.")));
277 
278   Util::setToolTip(ui->cbGuiWarnBeforeClosingModifiedTabs,
279                    Q("%1 %2")
280                    .arg(QY("If checked, the program will ask for confirmation before closing or reloading tabs that have been modified."))
281                    .arg(QY("This is also done when quitting the application.")));
282   Util::setToolTip(ui->cbGuiWarnBeforeAbortingJobs,
283                    Q("%1 %2")
284                    .arg(QY("If checked, the program will ask for confirmation before aborting a running job."))
285                    .arg(QY("This happens when clicking the \"abort job\" button in a \"job output\" tab and when quitting the application.")));
286 
287   Util::setToolTip(ui->cbGuiShowMoveUpDownButtons,
288                    Q("%1 %2")
289                    .arg(QY("Normally selected entries in list view can be moved around via drag & drop and with keyboard shortcuts (Ctrl+Up, Ctrl+Down)."))
290                    .arg(QY("If checked, additional buttons for moving selected entries up and down will be shown next to several list views.")));
291 
292   Util::setToolTip(ui->cbGuiElideTabHeaderLabels, QY("If enabled, the names of tab headers will be shortened so that all tab headers fit into the window's width."));
293 
294   Util::setToolTip(ui->cbGuiUseLegacyFontMIMETypes,
295                    Q("%1 %2")
296                    .arg(QY("If enabled, the GUI will use legacy MIME types when detecting the MIME type of font attachments instead of the current standard MIME types."))
297                    .arg(QY("This mostly affects TrueType fonts for which the legacy MIME type ('application/x-truetype-font') might be more widely supported than the standard MIME types ('font/sfnt' and 'font/ttf').")));
298 
299   Util::setToolTip(ui->cbGuiWarnBeforeOverwriting, QY("If enabled, the program will ask for confirmation before overwriting files and jobs."));
300 
301   Util::setToolTip(ui->cbGuiUseDefaultJobDescription, QY("If disabled, the GUI will let you enter a description for a job when adding it to the queue."));
302   Util::setToolTip(ui->cbGuiShowOutputOfAllJobs,      QY("If enabled, the first tab in the \"job output\" tool will not be cleared when a new job starts."));
303   Util::setToolTip(ui->cbGuiSwitchToJobOutputAfterStarting,     QY("If enabled, the GUI will automatically switch to the job output tool whenever you start a job (e.g. by pressing \"start multiplexing\")."));
304   Util::setToolTip(ui->cbGuiResetJobWarningErrorCountersOnExit, QY("If enabled, the warning and error counters of all jobs and the global counters in the status bar will be reset to 0 when the program exits."));
305   Util::setToolTip(ui->cbGuiRemoveOutputFileOnJobFailure,       QY("If enabled, the GUI will remove the output file created by a job if that job ends with an error or if the user aborts the job."));
306   Util::setToolTip(ui->cbGuiRemoveOldJobs,                      QY("If enabled, the GUI will remove completed jobs older than the configured number of days no matter their status on exit."));
307   Util::setToolTip(ui->sbGuiRemoveOldJobsDays,                  QY("If enabled, the GUI will remove completed jobs older than the configured number of days no matter their status on exit."));
308 
309   Util::setToolTip(ui->cbGuiRemoveJobs,
310                    Q("%1 %2")
311                    .arg(QY("Normally completed jobs stay in the queue even over restarts until the user clears them out manually."))
312                    .arg(QY("You can opt for having them removed automatically under certain conditions.")));
313 
314   Util::setToolTip(ui->leCENameTemplate, ChapterEditor::Tool::chapterNameTemplateToolTip());
315   Util::setToolTip(ui->ldwCEDefaultLanguage, QY("This is the language that newly added chapter names get assigned automatically."));
316   Util::setToolTip(ui->cbCEDropLastFromBlurayPlaylist,
317                    Q("%1 %2")
318                    .arg(QY("Blu-ray discs often contain a chapter entry very close to the end of the movie."))
319                    .arg(QY("If enabled, the last entry will be skipped when loading chapters from such playlists in the chapter editor if it is located within five seconds of the end of the movie.")));
320   Util::setToolTip(ui->cbCETextFileCharacterSet,
321                    Q("%1 %2 %3")
322                    .arg(QY("The chapter editor needs to know the character set a text chapter file uses in order to display all characters properly."))
323                    .arg(QY("By default it always asks the user which character set to use when opening a file for which it cannot be recognized automatically."))
324                    .arg(QY("If a character set is selected here, it will be used instead of asking the user.")));
325 
326   // Merge page
327   Util::setToolTip(ui->cbMAutoSetFileTitle,
328                    Q("<p>%1 %2</p><p>%3</p>")
329                    .arg(QYH("Certain file formats have 'title' property."))
330                    .arg(QYH("When the user adds a file containing such a title, the program will copy the title into the \"file title\" input box if this option is enabled."))
331                    .arg(QYH("Note that even if the option is disabled mkvmerge will copy a source file's title property unless a title is manually set by the user.")));
332   Util::setToolTip(ui->cbMAutoClearFileTitle, QY("If this option is enabled, the GUI will always clear the \"file title\" input box whenever the last source file is removed."));
333 
334   auto widgets = QList<mtx::gui::Util::StringListConfigurationWidget *>{} << ui->lwMPredefinedVideoTrackNames << ui->lwMPredefinedAudioTrackNames << ui->lwMPredefinedSubtitleTrackNames;
335   for (auto const &widget : widgets)
336     widget->setToolTips(Q("%1 %2 %3")
337                         .arg(QY("If you often use the same names for tracks, you can enter them here."))
338                         .arg(QY("The names will be available for easy selection in both the multiplexer and the header editor."))
339                         .arg(QY("You can still enter track names not present in this list manually in both tools.")));
340 
341   ui->lwMPredefinedSplitSizes->setToolTips(Q("%1 %2 %3")
342                                            .arg(QY("If you often use the same values when splitting by size, you can enter them here."))
343                                            .arg(QY("The values will be available for easy selection in the multiplexer."))
344                                            .arg(QY("You can still enter values not present in this list manually in the multiplexer.")));
345 
346   ui->lwMPredefinedSplitDurations->setToolTips(Q("%1 %2 %3")
347                                                .arg(QY("If you often use the same values when splitting by duration, you can enter them here."))
348                                                .arg(QY("The values will be available for easy selection in the multiplexer."))
349                                                .arg(QY("You can still enter values not present in this list manually in the multiplexer.")));
350 
351   Util::setToolTip(ui->cbMSetAudioDelayFromFileName,
352                    Q("%1 %2")
353                    .arg(QY("When a file is added its name is scanned."))
354                    .arg(QY("If it contains the word 'DELAY' followed by a number, this number is automatically put into the 'delay' input field for any audio track found in the file.")));
355 
356   Util::setToolTip(ui->cbMDisableDefaultTrackForSubtitles, QY("If enabled, all subtitle tracks will have their \"default track\" flag set to \"no\" when they're added."));
357 
358   Util::setToolTip(ui->cbMDisableCompressionForAllTrackTypes,
359                    Q("%1 %2")
360                    .arg(QY("Normally mkvmerge will apply additional lossless compression for subtitle tracks for certain codecs."))
361                    .arg(QY("Checking this option causes the GUI to set that compression to \"none\" by default for all track types when adding files.")));
362 
363   Util::setToolTip(ui->cbMEnableDialogNormGainRemoval, QY("If enabled, removal of dialog normalization gain will be enabled for all audio tracks for which removal is supported."));
364 
365   Util::setToolTip(ui->cbMProbeRangePercentage,
366                    Q("%1 %2 %3")
367                    .arg(QY("File types such as MPEG program and transport streams (.vob, .m2ts) require parsing a certain amount of data in order to detect all tracks contained in the file."))
368                    .arg(QY("This amount is 0.3% of the source file's size or 10 MB, whichever is higher."))
369                    .arg(QY("If tracks are known to be present but not found, the percentage to probe can be changed here.")));
370 
371   Util::setToolTip(ui->cbMSortFilesTracksByTypeWhenAdding,
372                    Q("<p>%1 %2</p><p>%3 %4</p><p>%5</p><p>%6</p>")
373                    .arg(QY("If enabled, files and tracks will be sorted by track types when they're added to multiplex settings."))
374                    .arg(QY("The order is: video first followed by audio, subtitles and other types."))
375                    .arg(QY("For example, a file containing audio tracks but no video tracks will be inserted before the first file that contains neither video nor audio tracks."))
376                    .arg(QY("Similarly an audio track will be inserted before the first track that's neither a video nor an audio track."))
377                    .arg(QY("If disabled, files and tracks will be inserted after all existing files and tracks."))
378                    .arg(QY("This only determines the initial order which can still be changed manually later.")));
379 
380   Util::setToolTip(ui->cbMReconstructSequencesWhenAdding,
381                    Q("<p>%1 %2 %3</p><p>%4</p><p>%5</p><p>%6</p>")
382                    .arg(QY("If enabled, the GUI will analyze the file names when you add multiple files at once."))
383                    .arg(QY("It tries to detect sequences of names that likely belong to the same movie by splitting the name into three parts: a prefix, a running number and a suffix that doesn't contain digits."))
384                    .arg(QY("Names are considered to be in sequence when the previous file name's prefix & suffix match the current file name's prefix & suffix and the running number is incremented by one."))
385                    .arg(QY("An example: 'movie.001.mp4', 'movie.002.mp4' and 'movie.003.mp4'"))
386                    .arg(QY("If such a sequence is detected, only the first file is added while the second and following files in the sequence are appended to the first one."))
387                    .arg(QY("If disabled, all files selected for adding will always be added regardless of their names.")));
388 
389   Util::setToolTip(ui->cbMAlwaysShowOutputFileControls,
390                    Q("%1 %2")
391                    .arg(QY("If enabled, the destination file name controls will always be visible no matter which tab is currently shown."))
392                    .arg(QY("Otherwise they're shown on the 'output' tab.")));
393 
394   auto controls = QWidgetList{} << ui->rbMTrackPropertiesLayoutHorizontalScrollArea << ui->rbMTrackPropertiesLayoutHorizontalTwoColumns << ui->rbMTrackPropertiesLayoutVerticalTabWidget;
395   for (auto const &control : controls)
396     Util::setToolTip(control,
397                      Q("<p>%1 %2</p><p>%3 %4</p>")
398                      .arg(QYH("The track properties on the \"input\" tab can be laid out in three different way in order to serve different workflows."))
399                      .arg(QYH("In the most compact layout the track properties are located to the right of the files and tracks lists in a scrollable single column."))
400                      .arg(QYH("The other two layouts available are: in two fixed columns to the right or in a tab widget below the files and tracks lists."))
401                      .arg(QYH("The horizontal layout with two fixed columns results in a wider window while the vertical tab widget layout results in a higher window.")));
402 
403   Util::setToolTip(ui->cbMUseFileAndTrackColors,
404                    Q("<p>%1 %2</p>")
405                    .arg(QYH("If enabled, small colored boxes will be shown in the file and track lists as a visual clue to help associating tracks with the files they come from."))
406                    .arg(QYH("If there are more entries than configured colors, random colors will be used temporarily.")));
407 
408 
409   Util::setToolTip(ui->cbMClearMergeSettings,
410                    Q("<p>%1</p><ol><li>%2 %3</li><li>%4 %5</li><li>%6</li></ol>")
411                    .arg(QYH("The GUI can help you start your next multiplex settings after having started a job or having added a one to the job queue."))
412                    .arg(QYH("With \"create new settings\" a new set of multiplex settings will be added."))
413                    .arg(QYH("The current multiplex settings will be closed."))
414                    .arg(QYH("With \"remove source files\" all source files will be removed."))
415                    .arg(QYH("Most of the other settings on the output tab will be kept intact, though."))
416                    .arg(QYH("With \"close current settings\" the current multiplex settings will be closed without opening new ones.")));
417 
418   Util::setToolTip(ui->cbMAddingAppendingFilesPolicy,
419                    Q("%1 %2 %3")
420                    .arg(QY("When the user drags & drops files from an external application onto the multiplex tool the GUI can take different actions."))
421                    .arg(QY("The default is to always add all the files to the current multiplex settings."))
422                    .arg(QY("The GUI can also ask the user what to do each time, e.g. appending them instead of adding them, or creating new multiplex settings and adding them to those.")));
423 
424   Util::setToolTip(ui->cbMWarnMissingAudioTrack, QY("The GUI can ask for confirmation when you're about to create a file without audio tracks in it."));
425 
426   Util::setToolTip(ui->cbMDeriveAudioTrackLanguageFromFileName,
427                    Q("<p>%1 %2</p><p>%3 %4</p>")
428                    .arg(QYH("Certain file formats have a 'language' property for their tracks."))
429                    .arg(QYH("When the user adds such a file the track's language input is set to the language property from the source file."))
430                    .arg(QYH("If the source file contains no such property for an audio track, then the language can be derived from the file name if it matches certain patterns (e.g. '…[ger]…' for German)."))
431                    .arg(QYH("Depending on this setting the language can also be derived from the file name if the language in the source file is 'undetermined' ('und').")));
432   Util::setToolTip(ui->cbMDeriveVideoTrackLanguageFromFileName,
433                    Q("<p>%1 %2</p><p>%3 %4</p>")
434                    .arg(QYH("Certain file formats have a 'language' property for their tracks."))
435                    .arg(QYH("When the user adds such a file the track's language input is set to the language property from the source file."))
436                    .arg(QYH("If the source file contains no such property for a video track, then the language can be derived from the file name if it matches certain patterns (e.g. '…[ger]…' for German)."))
437                    .arg(QYH("Depending on this setting the language can also be derived from the file name if the language in the source file is 'undetermined' ('und').")));
438   Util::setToolTip(ui->cbMDeriveSubtitleTrackLanguageFromFileName,
439                    Q("<p>%1 %2</p><p>%3 %4</p>")
440                    .arg(QYH("Certain file formats have a 'language' property for their tracks."))
441                    .arg(QYH("When the user adds such a file the track's language input is set to the language property from the source file."))
442                    .arg(QYH("If the source file contains no such property for a subtitle track, then the language can be derived from the file name if it matches certain patterns (e.g. '…[ger]…' for German)."))
443                    .arg(QYH("Depending on this setting the language can also be derived from the file name if the language in the source file is 'undetermined' ('und').")));
444   Util::setToolTip(ui->leMDeriveTrackLanguageBoundaryChars,
445                    Q("<p>%1 %2</p>")
446                    .arg(QYH("When deriving the track language from the file name, the file name is split into parts on the characters in this list."))
447                    .arg(QYH("Each part is then matched against the list of languages selected below to determine whether or not to use it as the track language.")));
448   Util::setToolTip(ui->pbMDeriveTrackLanguageRevertBoundaryChars, QY("Revert the entry to its default value."));
449   ui->tbMDeriveTrackLanguageRecognizedLanguages->setToolTips(QY("Only the languages in the 'selected' list on the right will be recognized as track languages in file names."));
450 
451   Util::setToolTip(ui->ldwMDefaultAudioTrackLanguage,
452                    Q("<p>%1 %2</p><p>%3 %4</p>")
453                    .arg(QYH("Certain file formats have a 'language' property for their tracks."))
454                    .arg(QYH("When the user adds such a file the track's language input is set to the language property from the source file."))
455                    .arg(QYH("The language selected here is used for audio tracks for which their source file contains no such property and for which the language has not been derived from the file name."))
456                    .arg(QYH("Depending on the setting below this language can also be used if the language in the source file is 'undetermined' ('und').")));
457   Util::setToolTip(ui->ldwMDefaultVideoTrackLanguage,
458                    Q("<p>%1 %2</p><p>%3 %4</p>")
459                    .arg(QYH("Certain file formats have a 'language' property for their tracks."))
460                    .arg(QYH("When the user adds such a file the track's language input is set to the language property from the source file."))
461                    .arg(QYH("The language selected here is used for video tracks for which their source file contains no such property and for which the language has not been derived from the file name."))
462                    .arg(QYH("Depending on the setting below this language can also be used if the language in the source file is 'undetermined' ('und').")));
463   Util::setToolTip(ui->ldwMDefaultSubtitleTrackLanguage,
464                    Q("<p>%1 %2</p><p>%3 %4</p>")
465                    .arg(QYH("Certain file formats have a 'language' property for their tracks."))
466                    .arg(QYH("When the user adds such a file the track's language input is set to the language property from the source file."))
467                    .arg(QYH("The language selected here is used for subtitle tracks for which their source file contains no such property and for which the language has not been derived from the file name."))
468                    .arg(QYH("Depending on the setting below this language can also be used if the language in the source file is 'undetermined' ('und').")));
469 
470   Util::setToolTip(ui->cbMDefaultSubtitleCharset, QY("If a character set is selected here, the program will automatically set the character set input to this value for newly added text subtitle tracks."));
471 
472   Util::setToolTip(ui->leMDefaultAdditionalCommandLineOptions, QY("The options entered here are set for all new multiplex jobs by default."));
473 
474   Util::setToolTip(ui->cbMScanPlaylistsPolicy,
475                    Q("<p>%1 %2</p><p>%3</p>")
476                    .arg(QYH("Whenever the user adds a playlist the program can automatically scan the directory for other playlists and present the user with a detailed list of the playlists found."))
477                    .arg(QYH("The user can then select which playlist to actually add."))
478                    .arg(QYH("This is useful for situations like Blu-ray discs on which a multitude of playlists exists in the same directory and where it is not obvious which feature (e.g. main movie, extras etc.) "
479                             "a playlist belongs to.")));
480 
481   Util::setToolTip(ui->sbMMinPlaylistDuration, QY("Only playlists whose duration are at least this long are considered and offered to the user for selection."));
482   Util::setToolTip(ui->cbMAddBlurayCovers, QY("If enabled, the largest cover image of a Blu-ray will be added as an attachment when adding a Blu-ray playlist."));
483   Util::setToolTip(ui->cbMAttachmentAlwaysSkipForExistingName,
484                    Q("<p>%1 %2 %3</p>")
485                    .arg(QY("When adding new files as attachments the GUI will check if there are other attachments with the same name."))
486                    .arg(QY("If one is found, the GUI will ask whether to skip the file or to add it anyway."))
487                    .arg(QY("If enabled, such files will always be skipped without asking.")));
488 
489   Util::setToolTip(ui->cbMAutoSetOutputFileName,
490                    Q("%1 %2")
491                    .arg(QY("If this option is enabled and if there is currently no destination file name set, the program will set one for you when you add a source file."))
492                    .arg(QY("The generated destination file name has the same base name as the source file name but with an extension based on the track types present in the file.")));
493 
494   Util::setToolTip(ui->cbMAutoDestinationOnlyForVideoFiles,
495                    Q("%1 %2")
496                    .arg(QY("If this option is enabled, only source files containing video tracks will be used for setting the destination file name."))
497                    .arg(QY("Other files are ignored when they're added.")));
498 
499   Util::setToolTip(ui->cbMSetDestinationFromTitle,
500                    Q("%1 %2")
501                    .arg(QY("If this option is enabled, the file title will be used as the basis for the destination file name if a file title is set."))
502                    .arg(QY("Otherwise the destination file name is derived from the source file names.")));
503 
504   Util::setToolTip(ui->cbMUniqueOutputFileNames,
505                    Q("%1 %2")
506                    .arg(QY("If checked, the program makes sure the suggested destination file name is unique by adding a number (e.g. ' (1)') to the end of the file name."))
507                    .arg(QY("This is done only if there is already a file whose name matches the unmodified destination file name.")));
508   Util::setToolTip(ui->cbMAutoClearOutputFileName, QY("If this option is enabled, the GUI will always clear the \"destination file name\" input box whenever the last source file is removed."));
509 
510   ui->tbMEnableMuxingTracksByType->setToolTips(QY("Only items whose type is in the 'selected' list on the right will be set to be copied by default."));
511   Util::setToolTip(ui->cbMEnableMuxingTracksByLanguage,
512                    Q("<p>%1 %2 %3</p><p>%4</p>")
513                    .arg(QYH("When adding source files all tracks are normally set to be copied into the destination file."))
514                    .arg(QYH("If this option is enabled, only those tracks will be set to be copied whose language is selected below."))
515                    .arg(QYH("You can exempt certain track types from this restriction by checking the corresponding check box below, e.g. for video tracks."))
516                    .arg(QYH("Note that the language \"Undetermined (und)\" is assumed for tracks for which no language is known (e.g. those read from SRT subtitle files).")));
517   Util::setToolTip(ui->cbMEnableMuxingAllVideoTracks,    QY("If enabled, tracks of this type will always be set to be copied regardless of their language."));
518   Util::setToolTip(ui->cbMEnableMuxingAllAudioTracks,    QY("If enabled, tracks of this type will always be set to be copied regardless of their language."));
519   Util::setToolTip(ui->cbMEnableMuxingAllSubtitleTracks, QY("If enabled, tracks of this type will always be set to be copied regardless of their language."));
520   ui->tbMEnableMuxingTracksByLanguage->setToolTips(QY("Tracks with a language in this list will be set not to be copied by default."),
521                                                    QY("Only tracks with a language in this list will be set to be copied by default."));
522 
523   // Often used XYZ page
524   ui->tbOftenUsedLanguages->setToolTips(QY("The languages in the 'selected' list on the right will be shown at the top of all the language drop-down boxes in the program."));
525   Util::setToolTip(ui->cbOftenUsedLanguagesOnly,
526                    Q("%1 %2")
527                    .arg(QYH("If checked, only the list of often used entries will be included in the selections in the program."))
528                    .arg(QYH("Otherwise the often used entries will be included first and the full list of all entries afterwards.")));
529 
530   ui->tbOftenUsedRegions->setToolTips(QY("The entries in the 'selected' list on the right will be shown at the top of all the drop-down boxes with countries and regions in the program."));
531   Util::setToolTip(ui->cbOftenUsedRegionsOnly,
532                    Q("%1 %2")
533                    .arg(QYH("If checked, only the list of often used entries will be included in the selections in the program."))
534                    .arg(QYH("Otherwise the often used entries will be included first and the full list of all entries afterwards.")));
535 
536   ui->tbOftenUsedCharacterSets->setToolTips(QY("The character sets in the 'selected' list on the right will be shown at the top of all the character set drop-down boxes in the program."));
537   Util::setToolTip(ui->cbOftenUsedCharacterSetsOnly,
538                    Q("%1 %2")
539                    .arg(QYH("If checked, only the list of often used entries will be included in the selections in the program."))
540                    .arg(QYH("Otherwise the often used entries will be included first and the full list of all entries afterwards.")));
541 
542   // Header editor  page
543   Util::setToolTip(ui->cbHEDroppedFilesPolicy,
544                    Q("%1 %2 %3")
545                    .arg(QY("When the user drags & drops files from an external application onto a header editor tab the GUI can take different actions."))
546                    .arg(QY("The default is to ask the user what to do with the dropped files."))
547                    .arg(QY("Apart from asking the GUI can always open the dropped files as new tabs or it can always add them as new attachments to the current tab.")));
548 }
549 
550 void
setupConnections()551 PreferencesDialog::setupConnections() {
552   connect(ui->cbUseISO639_3Languages,                     &QCheckBox::toggled,                                           this,                                 &PreferencesDialog::setupCommonLanguages);
553 
554   connect(ui->lwGuiLanguageShortcuts,                     &QListWidget::itemSelectionChanged,                            this,                                 &PreferencesDialog::enableLanguageShortcutControls);
555   connect(ui->lwGuiLanguageShortcuts,                     &QListWidget::itemDoubleClicked,                               this,                                 &PreferencesDialog::editLanguageShortcut);
556   connect(ui->pbGuiAddLanguageShortcut,                   &QPushButton::clicked,                                         this,                                 &PreferencesDialog::addLanguageShortcut);
557   connect(ui->pbGuiEditLanguageShortcut,                  &QPushButton::clicked,                                         this,                                 &PreferencesDialog::editLanguageShortcut);
558   connect(ui->pbGuiRemoveLanguageShortcuts,               &QPushButton::clicked,                                         this,                                 &PreferencesDialog::removeLanguageShortcuts);
559 
560   connect(ui->pbMEditDefaultAdditionalCommandLineOptions, &QPushButton::clicked,                                         this,                                 &PreferencesDialog::editDefaultAdditionalCommandLineOptions);
561 
562   connect(ui->pbMBrowseMediaInfoExe,                      &QPushButton::clicked,                                         this,                                 &PreferencesDialog::browseMediaInfoExe);
563   connect(ui->cbMAutoSetOutputFileName,                   &QCheckBox::toggled,                                           this,                                 &PreferencesDialog::enableOutputFileNameControls);
564   connect(ui->rbMAutoSetSameDirectory,                    &QRadioButton::toggled,                                        this,                                 &PreferencesDialog::enableOutputFileNameControls);
565   connect(ui->rbMAutoSetRelativeDirectory,                &QRadioButton::toggled,                                        this,                                 &PreferencesDialog::enableOutputFileNameControls);
566   connect(ui->rbMAutoSetPreviousDirectory,                &QRadioButton::toggled,                                        this,                                 &PreferencesDialog::enableOutputFileNameControls);
567   connect(ui->rbMAutoSetFixedDirectory,                   &QRadioButton::toggled,                                        this,                                 &PreferencesDialog::enableOutputFileNameControls);
568   connect(ui->pbMBrowseAutoSetFixedDirectory,             &QPushButton::clicked,                                         this,                                 &PreferencesDialog::browseFixedOutputDirectory);
569 
570   connect(ui->cbMEnableMuxingTracksByLanguage,            &QCheckBox::toggled,                                           ui->lMEnableMuxingAllTracksOfType,    &QLabel::setEnabled);
571   connect(ui->cbMEnableMuxingTracksByLanguage,            &QCheckBox::toggled,                                           ui->gbMEnableMuxingExceptions,        &QLabel::setEnabled);
572   connect(ui->cbMEnableMuxingTracksByLanguage,            &QCheckBox::toggled,                                           ui->cbMEnableMuxingAllVideoTracks,    &QLabel::setEnabled);
573   connect(ui->cbMEnableMuxingTracksByLanguage,            &QCheckBox::toggled,                                           ui->cbMEnableMuxingAllAudioTracks,    &QLabel::setEnabled);
574   connect(ui->cbMEnableMuxingTracksByLanguage,            &QCheckBox::toggled,                                           ui->cbMEnableMuxingAllSubtitleTracks, &QLabel::setEnabled);
575   connect(ui->cbMEnableMuxingTracksByLanguage,            &QCheckBox::toggled,                                           ui->tbMEnableMuxingTracksByLanguage,  &QLabel::setEnabled);
576 
577   connect(ui->pbMDeriveTrackLanguageRevertBoundaryChars,  &QPushButton::clicked,                                         this,                                 &PreferencesDialog::revertDeriveTrackLanguageFromFileNameChars);
578 
579   connect(ui->cbGuiRemoveJobs,                            &QCheckBox::toggled,                                           ui->cbGuiJobRemovalPolicy,            &QComboBox::setEnabled);
580   connect(ui->cbGuiRemoveJobsOnExit,                      &QCheckBox::toggled,                                           ui->cbGuiJobRemovalOnExitPolicy,      &QComboBox::setEnabled);
581   connect(ui->cbGuiRemoveOldJobs,                         &QCheckBox::toggled,                                           this,                                 &PreferencesDialog::adjustRemoveOldJobsControls);
582   connect(ui->sbGuiRemoveOldJobsDays,                     static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this,                                 &PreferencesDialog::adjustRemoveOldJobsControls);
583 
584   connect(ui->sbMMinPlaylistDuration,                     static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this,                                 &PreferencesDialog::adjustPlaylistControls);
585 
586   connect(ui->buttons,                                    &QDialogButtonBox::accepted,                                   this,                                 &PreferencesDialog::accept);
587   connect(ui->buttons,                                    &QDialogButtonBox::rejected,                                   this,                                 &PreferencesDialog::reject);
588 
589   connect(ui->tbOftenUsedLanguages,                       &Util::SideBySideMultiSelect::listsChanged,                    this,                                 &PreferencesDialog::enableOftendUsedLanguagesOnly);
590   connect(ui->tbOftenUsedRegions,                         &Util::SideBySideMultiSelect::listsChanged,                    this,                                 &PreferencesDialog::enableOftendUsedRegionsOnly);
591   connect(ui->tbOftenUsedCharacterSets,                   &Util::SideBySideMultiSelect::listsChanged,                    this,                                 &PreferencesDialog::enableOftendUsedCharacterSetsOnly);
592 }
593 
594 void
setupInterfaceLanguage()595 PreferencesDialog::setupInterfaceLanguage() {
596 #if defined(HAVE_LIBINTL_H)
597   using TranslationSorter = std::pair<QString, QString>;
598   auto translations       = std::vector<TranslationSorter>{};
599 
600   for (auto const &translation : translation_c::ms_available_translations)
601     translations.emplace_back(Q("%1 (%2)").arg(Q(translation.m_translated_name)).arg(Q(translation.m_english_name)), Q(translation.get_locale()));
602 
603   std::sort(translations.begin(), translations.end());
604 
605   for (auto const &translation : translations)
606     ui->cbGuiInterfaceLanguage->addItem(translation.first, translation.second);
607 
608   Util::setComboBoxTextByData(ui->cbGuiInterfaceLanguage, m_cfg.m_uiLocale);
609   Util::fixComboBoxViewWidth(*ui->cbGuiInterfaceLanguage);
610 #endif  // HAVE_LIBINTL_H
611 }
612 
613 void
setupJobRemovalPolicy()614 PreferencesDialog::setupJobRemovalPolicy() {
615   auto doIt = [](Util::Settings::JobRemovalPolicy policy,
616                  QCheckBox &checkBox,
617                  QComboBox &comboBox) {
618     auto doRemove = Util::Settings::JobRemovalPolicy::Never != policy;
619     auto idx      = std::max(static_cast<int>(policy), 1) - 1;
620 
621     checkBox.setChecked(doRemove);
622     comboBox.setEnabled(doRemove);
623     comboBox.setCurrentIndex(idx);
624   };
625 
626   doIt(m_cfg.m_jobRemovalPolicy,       *ui->cbGuiRemoveJobs,       *ui->cbGuiJobRemovalPolicy);
627   doIt(m_cfg.m_jobRemovalOnExitPolicy, *ui->cbGuiRemoveJobsOnExit, *ui->cbGuiJobRemovalOnExitPolicy);
628 }
629 
630 void
setupCommonLanguages(bool withISO639_3)631 PreferencesDialog::setupCommonLanguages(bool withISO639_3) {
632   auto &allLanguages = withISO639_3 ? App::iso639Languages() : App::iso639_2Languages();
633   auto languageItems = QList<Util::SideBySideMultiSelect::Item>::fromVector(Util::stdVectorToQVector<Util::SideBySideMultiSelect::Item>(allLanguages));
634 
635   ui->tbOftenUsedLanguages->setItems(languageItems, m_cfg.m_oftenUsedLanguages);
636   ui->cbOftenUsedLanguagesOnly->setChecked(m_cfg.m_oftenUsedLanguagesOnly && !m_cfg.m_oftenUsedLanguages.isEmpty());
637   enableOftendUsedLanguagesOnly();
638 
639   ui->tbMEnableMuxingTracksByLanguage->setItems(languageItems, m_cfg.m_enableMuxingTracksByTheseLanguages);
640   ui->tbMDeriveTrackLanguageRecognizedLanguages->setItems(languageItems, m_cfg.m_recognizedTrackLanguagesInFileNames);
641 }
642 
643 void
setupCommonRegions()644 PreferencesDialog::setupCommonRegions() {
645   auto &allRegions = App::regions();
646 
647   ui->tbOftenUsedRegions->setItems(QList<Util::SideBySideMultiSelect::Item>::fromVector(Util::stdVectorToQVector<Util::SideBySideMultiSelect::Item>(allRegions)), m_cfg.m_oftenUsedRegions);
648   ui->cbOftenUsedRegionsOnly->setChecked(m_cfg.m_oftenUsedRegionsOnly && !m_cfg.m_oftenUsedRegions.isEmpty());
649   enableOftendUsedRegionsOnly();
650 }
651 
652 void
setupCommonCharacterSets()653 PreferencesDialog::setupCommonCharacterSets() {
654   ui->tbOftenUsedCharacterSets->setItems(QList<QString>::fromVector(Util::stdVectorToQVector<QString>(App::characterSets())), m_cfg.m_oftenUsedCharacterSets);
655   ui->cbOftenUsedCharacterSetsOnly->setChecked(m_cfg.m_oftenUsedCharacterSetsOnly && !m_cfg.m_oftenUsedCharacterSets.isEmpty());
656   enableOftendUsedCharacterSetsOnly();
657 }
658 
659 void
setupProcessPriority()660 PreferencesDialog::setupProcessPriority() {
661 #if defined(SYS_WINDOWS)
662   ui->cbMProcessPriority->addItem(QY("Highest priority"), static_cast<int>(Util::Settings::HighestPriority)); // value 4, index 0
663   ui->cbMProcessPriority->addItem(QY("Higher priority"),  static_cast<int>(Util::Settings::HighPriority));    // value 3, index 1
664 #endif
665   ui->cbMProcessPriority->addItem(QY("Normal priority"),  static_cast<int>(Util::Settings::NormalPriority));  // value 2, index 2/0
666   ui->cbMProcessPriority->addItem(QY("Lower priority"),   static_cast<int>(Util::Settings::LowPriority));     // value 1, index 3/1
667   ui->cbMProcessPriority->addItem(QY("Lowest priority"),  static_cast<int>(Util::Settings::LowestPriority));  // value 0, index 4/2
668 
669   auto numPrios = ui->cbMProcessPriority->count();
670   auto selected = 4 - static_cast<int>(m_cfg.m_priority) - (5 - numPrios);
671   selected      = std::min<int>(std::max<int>(0, selected), numPrios);
672 
673   ui->cbMProcessPriority->setCurrentIndex(selected);
674 }
675 
676 void
setupPlaylistScanningPolicy()677 PreferencesDialog::setupPlaylistScanningPolicy() {
678   auto selected = std::max(std::min(static_cast<int>(m_cfg.m_scanForPlaylistsPolicy), 2), 0);
679 
680   ui->cbMScanPlaylistsPolicy->setCurrentIndex(selected);
681   ui->sbMMinPlaylistDuration->setValue(m_cfg.m_minimumPlaylistDuration);
682 
683   adjustPlaylistControls();
684 
685   Util::fixComboBoxViewWidth(*ui->cbMScanPlaylistsPolicy);
686 }
687 
688 void
setupOutputFileNamePolicy()689 PreferencesDialog::setupOutputFileNamePolicy() {
690   auto isChecked = Util::Settings::DontSetOutputFileName != m_cfg.m_outputFileNamePolicy;
691   auto rbToCheck = Util::Settings::ToPreviousDirectory        == m_cfg.m_outputFileNamePolicy ? ui->rbMAutoSetPreviousDirectory
692                  : Util::Settings::ToFixedDirectory           == m_cfg.m_outputFileNamePolicy ? ui->rbMAutoSetFixedDirectory
693                  : Util::Settings::ToRelativeOfFirstInputFile == m_cfg.m_outputFileNamePolicy ? ui->rbMAutoSetRelativeDirectory
694                  :                                                                              ui->rbMAutoSetSameDirectory;
695 
696   auto dFixed    = QDir::toNativeSeparators(m_cfg.m_fixedOutputDir.path());
697   auto dRelative = QDir::toNativeSeparators(m_cfg.m_relativeOutputDir.path());
698 
699   m_cfg.m_mergeLastFixedOutputDirs.add(dFixed);
700   m_cfg.m_mergeLastRelativeOutputDirs.add(dRelative);
701 
702   ui->cbMAutoSetOutputFileName->setChecked(isChecked);
703   ui->cbMAutoDestinationOnlyForVideoFiles->setChecked(m_cfg.m_autoDestinationOnlyForVideoFiles);
704   ui->cbMSetDestinationFromTitle->setChecked(m_cfg.m_mergeSetDestinationFromTitle);
705   rbToCheck->setChecked(true);
706   ui->cbMAutoSetRelativeDirectory->addItems(m_cfg.m_mergeLastRelativeOutputDirs.items());
707   ui->cbMAutoSetRelativeDirectory->setCurrentText(dRelative);
708   ui->cbMAutoSetRelativeDirectory->lineEdit()->setClearButtonEnabled(true);
709   ui->cbMAutoSetFixedDirectory->addItems(m_cfg.m_mergeLastFixedOutputDirs.items());
710   ui->cbMAutoSetFixedDirectory->setCurrentText(dFixed);
711   ui->cbMAutoSetFixedDirectory->lineEdit()->setClearButtonEnabled(true);
712   ui->cbMUniqueOutputFileNames->setChecked(m_cfg.m_uniqueOutputFileNames);
713   ui->cbMAutoClearOutputFileName->setChecked(m_cfg.m_autoClearOutputFileName);
714 
715   enableOutputFileNameControls();
716 }
717 
718 void
setupRecentDestinationDirectoryList()719 PreferencesDialog::setupRecentDestinationDirectoryList() {
720   ui->lwMRecentDestinationDirectories->setItems(m_cfg.m_mergeLastOutputDirs.items());
721   ui->lwMRecentDestinationDirectories->setMaximumNumItems(m_cfg.m_mergeLastOutputDirs.maximumNumItems());
722   ui->lwMRecentDestinationDirectories->setAddItemDialogTexts(QY("Select a directory"), {});
723   ui->lwMRecentDestinationDirectories->setItemType(Util::StringListConfigurationWidget::ItemType::Directory);
724 }
725 
726 void
setupEnableMuxingTracksByType()727 PreferencesDialog::setupEnableMuxingTracksByType() {
728   auto allTypes      = Util::SideBySideMultiSelect::ItemList{};
729   auto selectedTypes = QStringList{};
730 
731   for (auto type = static_cast<int>(Merge::TrackType::Min); type <= static_cast<int>(Merge::TrackType::Max); ++type)
732     allTypes << std::make_pair(Merge::Track::nameForType(static_cast<Merge::TrackType>(type)), QString::number(type));
733 
734   for (auto type : m_cfg.m_enableMuxingTracksByTheseTypes)
735     selectedTypes << QString::number(static_cast<int>(type));
736 
737   ui->tbMEnableMuxingTracksByType->setItems(allTypes, selectedTypes);
738 }
739 
740 void
setupEnableMuxingTracksByLanguage()741 PreferencesDialog::setupEnableMuxingTracksByLanguage() {
742   auto widgets = QList<QWidget *>{} << ui->lMEnableMuxingAllTracksOfType << ui->gbMEnableMuxingExceptions << ui->cbMEnableMuxingAllVideoTracks << ui->cbMEnableMuxingAllAudioTracks << ui->cbMEnableMuxingAllSubtitleTracks << ui->tbMEnableMuxingTracksByLanguage;
743   for (auto const &widget : widgets)
744     widget->setEnabled(m_cfg.m_enableMuxingTracksByLanguage);
745 
746   ui->cbMEnableMuxingTracksByLanguage->setChecked(m_cfg.m_enableMuxingTracksByLanguage);
747   ui->cbMEnableMuxingAllVideoTracks->setChecked(m_cfg.m_enableMuxingAllVideoTracks);
748   ui->cbMEnableMuxingAllAudioTracks->setChecked(m_cfg.m_enableMuxingAllAudioTracks);
749   ui->cbMEnableMuxingAllSubtitleTracks->setChecked(m_cfg.m_enableMuxingAllSubtitleTracks);
750 }
751 
752 void
setupMergeAddingAppendingFilesPolicy()753 PreferencesDialog::setupMergeAddingAppendingFilesPolicy() {
754   auto setup = [](QComboBox &cb, Util::Settings::MergeAddingAppendingFilesPolicy policy) {
755     cb.addItem(QY("Always ask the user"),                                           static_cast<int>(Util::Settings::MergeAddingAppendingFilesPolicy::Ask));
756     cb.addItem(QY("Add all files to the current multiplex settings"),               static_cast<int>(Util::Settings::MergeAddingAppendingFilesPolicy::Add));
757     cb.addItem(QY("Create one new multiplex settings tab and add all files there"), static_cast<int>(Util::Settings::MergeAddingAppendingFilesPolicy::AddToNew));
758     cb.addItem(QY("Create one new multiplex settings tab for each file"),           static_cast<int>(Util::Settings::MergeAddingAppendingFilesPolicy::AddEachToNew));
759 
760     Util::setComboBoxIndexIf(&cb, [policy](QString const &, QVariant const &data) {
761       return data.isValid() && (static_cast<Util::Settings::MergeAddingAppendingFilesPolicy>(data.toInt()) == policy);
762     });
763 
764     Util::fixComboBoxViewWidth(cb);
765   };
766 
767   setup(*ui->cbMAddingAppendingFilesPolicy, m_cfg.m_mergeAddingAppendingFilesPolicy);
768   setup(*ui->cbMDragAndDropFilesPolicy,     m_cfg.m_mergeDragAndDropFilesPolicy);
769 
770   ui->cbMAlwaysCreateSettingsForVideoFiles->setChecked(m_cfg.m_mergeAlwaysCreateNewSettingsForVideoFiles);
771 }
772 
773 void
setupMergeWarnMissingAudioTrack()774 PreferencesDialog::setupMergeWarnMissingAudioTrack() {
775   ui->cbMWarnMissingAudioTrack->addItem(QY("Never"),                                           static_cast<int>(Util::Settings::MergeMissingAudioTrackPolicy::Never));
776   ui->cbMWarnMissingAudioTrack->addItem(QY("If audio tracks are present but none is enabled"), static_cast<int>(Util::Settings::MergeMissingAudioTrackPolicy::IfAudioTrackPresent));
777   ui->cbMWarnMissingAudioTrack->addItem(QY("Even if no audio tracks are present"),             static_cast<int>(Util::Settings::MergeMissingAudioTrackPolicy::Always));
778 
779   Util::setComboBoxIndexIf(ui->cbMWarnMissingAudioTrack, [this](QString const &, QVariant const &data) {
780     return data.isValid() && (static_cast<Util::Settings::MergeMissingAudioTrackPolicy>(data.toInt()) == m_cfg.m_mergeWarnMissingAudioTrack);
781   });
782 
783   Util::fixComboBoxViewWidth(*ui->cbMWarnMissingAudioTrack);
784 }
785 
786 void
setupMergePredefinedItems()787 PreferencesDialog::setupMergePredefinedItems() {
788   auto &cfg = Util::Settings::get();
789 
790   ui->lwMPredefinedVideoTrackNames->setItems(cfg.m_mergePredefinedVideoTrackNames);
791   ui->lwMPredefinedAudioTrackNames->setItems(cfg.m_mergePredefinedAudioTrackNames);
792   ui->lwMPredefinedSubtitleTrackNames->setItems(cfg.m_mergePredefinedSubtitleTrackNames);
793   ui->lwMPredefinedSplitSizes->setItems(cfg.m_mergePredefinedSplitSizes);
794   ui->lwMPredefinedSplitDurations->setItems(cfg.m_mergePredefinedSplitDurations);
795 
796   auto widgets = QList<mtx::gui::Util::StringListConfigurationWidget *>{} << ui->lwMPredefinedVideoTrackNames << ui->lwMPredefinedAudioTrackNames << ui->lwMPredefinedSubtitleTrackNames;
797   for (auto const &widget : widgets)
798     widget->setAddItemDialogTexts(QY("Enter predefined track name"), QY("Please enter the new predefined track name."));
799 
800   ui->lwMPredefinedSplitSizes->setAddItemDialogTexts(QY("Enter predefined split size"), QY("Please enter the new predefined split size."));
801   ui->lwMPredefinedSplitDurations->setAddItemDialogTexts(QY("Enter predefined split duration"), QY("Please enter the new predefined split duration."));
802 }
803 
804 void
setupHeaderEditorDroppedFilesPolicy()805 PreferencesDialog::setupHeaderEditorDroppedFilesPolicy() {
806   ui->cbHEDroppedFilesPolicy->addItem(QY("Always ask the user"),                                 static_cast<int>(Util::Settings::HeaderEditorDroppedFilesPolicy::Ask));
807   ui->cbHEDroppedFilesPolicy->addItem(QY("Open all files as tabs in the header editor"),         static_cast<int>(Util::Settings::HeaderEditorDroppedFilesPolicy::Open));
808   ui->cbHEDroppedFilesPolicy->addItem(QY("Add all files as new attachments to the current tab"), static_cast<int>(Util::Settings::HeaderEditorDroppedFilesPolicy::AddAttachments));
809 
810   Util::setComboBoxIndexIf(ui->cbHEDroppedFilesPolicy, [this](QString const &, QVariant const &data) {
811     return data.isValid() && (static_cast<Util::Settings::HeaderEditorDroppedFilesPolicy>(data.toInt()) == m_cfg.m_headerEditorDroppedFilesPolicy);
812   });
813 
814   Util::fixComboBoxViewWidth(*ui->cbHEDroppedFilesPolicy);
815 }
816 
817 void
setupTrackPropertiesLayout()818 PreferencesDialog::setupTrackPropertiesLayout() {
819   auto rbToCheck = Util::Settings::TrackPropertiesLayout::HorizontalScrollArea == m_cfg.m_mergeTrackPropertiesLayout ? ui->rbMTrackPropertiesLayoutHorizontalScrollArea
820                  : Util::Settings::TrackPropertiesLayout::HorizontalTwoColumns == m_cfg.m_mergeTrackPropertiesLayout ? ui->rbMTrackPropertiesLayoutHorizontalTwoColumns
821                  :                                                                                                     ui->rbMTrackPropertiesLayoutVerticalTabWidget;
822 
823   rbToCheck->setChecked(true);
824 }
825 
826 void
setupBCP47LanguageEditMode()827 PreferencesDialog::setupBCP47LanguageEditMode() {
828   ui->cbGuiBCP47LanguageEditingMode->clear();
829   ui->cbGuiBCP47LanguageEditingMode->addItem(QY("Free-form input"),                  static_cast<int>(Util::Settings::BCP47LanguageEditingMode::FreeForm));
830   ui->cbGuiBCP47LanguageEditingMode->addItem(QY("Individually selected components"), static_cast<int>(Util::Settings::BCP47LanguageEditingMode::Components));
831 
832   Util::setComboBoxIndexIf(ui->cbGuiBCP47LanguageEditingMode, [this](auto const &, auto const &data) {
833     return data.toInt() == static_cast<int>(m_cfg.m_bcp47LanguageEditingMode);
834   });
835 }
836 
837 void
setupTabPositions()838 PreferencesDialog::setupTabPositions() {
839   ui->cbGuiTabPositions->clear();
840   ui->cbGuiTabPositions->addItem(QY("Top"),    static_cast<int>(QTabWidget::North));
841   ui->cbGuiTabPositions->addItem(QY("Bottom"), static_cast<int>(QTabWidget::South));
842   ui->cbGuiTabPositions->addItem(QY("Left"),   static_cast<int>(QTabWidget::West));
843   ui->cbGuiTabPositions->addItem(QY("Right"),  static_cast<int>(QTabWidget::East));
844 
845   Util::setComboBoxIndexIf(ui->cbGuiTabPositions, [this](QString const &, QVariant const &data) {
846     return data.toInt() == static_cast<int>(m_cfg.m_tabPosition);
847   });
848 }
849 
850 void
setupDerivingTrackLanguagesFromFileName()851 PreferencesDialog::setupDerivingTrackLanguagesFromFileName() {
852   auto setupComboBox = [](QComboBox &cb, Util::Settings::DeriveLanguageFromFileNamePolicy policy) {
853     cb.clear();
854     cb.addItem(QY("Never"),                                          static_cast<int>(Util::Settings::DeriveLanguageFromFileNamePolicy::Never));
855     cb.addItem(QY("Only if the source doesn't contain a language"),  static_cast<int>(Util::Settings::DeriveLanguageFromFileNamePolicy::OnlyIfAbsent));
856     cb.addItem(QY("Also if the language is 'undetermined' ('und')"), static_cast<int>(Util::Settings::DeriveLanguageFromFileNamePolicy::IfAbsentOrUndetermined));
857 
858     Util::fixComboBoxViewWidth(cb);
859 
860     Util::setComboBoxIndexIf(&cb, [policy](QString const &, QVariant const &data) {
861       return data.toInt() == static_cast<int>(policy);
862     });
863   };
864 
865   setupComboBox(*ui->cbMDeriveAudioTrackLanguageFromFileName,    m_cfg.m_deriveAudioTrackLanguageFromFileNamePolicy);
866   setupComboBox(*ui->cbMDeriveVideoTrackLanguageFromFileName,    m_cfg.m_deriveVideoTrackLanguageFromFileNamePolicy);
867   setupComboBox(*ui->cbMDeriveSubtitleTrackLanguageFromFileName, m_cfg.m_deriveSubtitleTrackLanguageFromFileNamePolicy);
868 
869   ui->leMDeriveTrackLanguageBoundaryChars->setText(m_cfg.m_boundaryCharsForDerivingTrackLanguagesFromFileNames);
870 }
871 
872 void
setupWhenToSetDefaultLanguage()873 PreferencesDialog::setupWhenToSetDefaultLanguage() {
874   ui->cbMWhenToSetDefaultLanguage->clear();
875   ui->cbMWhenToSetDefaultLanguage->addItem(QY("Only if the source doesn't contain a language"),  static_cast<int>(Util::Settings::SetDefaultLanguagePolicy::OnlyIfAbsent));
876   ui->cbMWhenToSetDefaultLanguage->addItem(QY("Also if the language is 'undetermined' ('und')"), static_cast<int>(Util::Settings::SetDefaultLanguagePolicy::IfAbsentOrUndetermined));
877 
878   Util::setComboBoxIndexIf(ui->cbMWhenToSetDefaultLanguage, [this](QString const &, QVariant const &data) {
879     return data.toInt() == static_cast<int>(m_cfg.m_whenToSetDefaultLanguage);
880   });
881 }
882 
883 void
setupJobsRunPrograms()884 PreferencesDialog::setupJobsRunPrograms() {
885   ui->twJobsPrograms->setTabPosition(m_cfg.m_tabPosition);
886 
887   for (auto const &runProgramConfig : m_cfg.m_runProgramConfigurations) {
888     auto widget = new PrefsRunProgramWidget{ui->twJobsPrograms, *runProgramConfig};
889     ui->twJobsPrograms->addTab(widget, {});
890 
891     setTabTitleForRunProgramWidget(ui->twJobsPrograms->count() - 1, runProgramConfig->name());
892 
893     connect(widget, &PrefsRunProgramWidget::titleChanged, this, &PreferencesDialog::setSendersTabTitleForRunProgramWidget);
894   }
895 
896   if (!m_cfg.m_runProgramConfigurations.isEmpty())
897     ui->swJobsPrograms->setCurrentIndex(1);
898 
899   connect(ui->pbJobsAddProgram, &QPushButton::clicked,          this, &PreferencesDialog::addProgramToExecute);
900   connect(ui->twJobsPrograms,   &QTabWidget::tabCloseRequested, this, &PreferencesDialog::removeProgramToExecute);
901 }
902 
903 void
setupFontAndScaling()904 PreferencesDialog::setupFontAndScaling() {
905   auto font = App::font();
906   ui->fcbGuiFontFamily->setCurrentFont(font);
907   ui->sbGuiFontPointSize->setValue(font.pointSize());
908 
909   ui->cbGuiDisableHighDPIScaling->setChecked(m_cfg.m_uiDisableHighDPIScaling);
910 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
911   ui->cbGuiDisableHighDPIScaling->setVisible(false);
912 #endif
913 
914   ui->cbGuiDisableDarkStyleSheet->setChecked(m_cfg.m_uiDisableDarkStyleSheet);
915 #if !defined(SYS_WINDOWS)
916   ui->cbGuiDisableDarkStyleSheet->setVisible(false);
917 #endif
918 
919   ui->cbGuiStayOnTop->setChecked(m_cfg.m_uiStayOnTop);
920 }
921 
922 void
setupFileColorsControls()923 PreferencesDialog::setupFileColorsControls() {
924   ui->cbMUseFileAndTrackColors->setChecked(m_cfg.m_mergeUseFileAndTrackColors);
925   setupFileColors(m_cfg.m_mergeFileColors);
926 
927   enableFileColorsControls();
928 
929   connect(ui->cbMUseFileAndTrackColors,     &QCheckBox::toggled,                this, &PreferencesDialog::enableFileColorsControls);
930   connect(ui->lwMFileColors,                &QListWidget::itemSelectionChanged, this, &PreferencesDialog::enableFileColorsControls);
931   connect(ui->lwMFileColors,                &QListWidget::itemDoubleClicked,    this, &PreferencesDialog::editFileColor);
932   connect(ui->pbMAddFileColor,              &QPushButton::clicked,              this, &PreferencesDialog::addFileColor);
933   connect(ui->pbMRemoveFileColors,          &QPushButton::clicked,              this, &PreferencesDialog::removeFileColors);
934   connect(ui->pbMEditFileColor,             &QPushButton::clicked,              this, &PreferencesDialog::editSelectedFileColor);
935   connect(ui->pbMRevertFileColorsToDefault, &QPushButton::clicked,              this, &PreferencesDialog::revertFileColorsToDefault);
936 }
937 
938 QListWidgetItem &
setupFileColorItem(QListWidgetItem & item,QColor const & color)939 PreferencesDialog::setupFileColorItem(QListWidgetItem &item,
940                                       QColor const &color) {
941   auto luminance  = (0.299 * color.red() + 0.587 * color.green() + 0.114 * color.blue()) / 255.0;
942   auto foreground = luminance > 0.5 ?  0 : 255;
943 
944   item.setForeground(QColor{foreground, foreground, foreground});
945   item.setBackground(color);
946   item.setText(Q(fmt::format("0x{0:02x}{1:02x}{2:02x}", color.red(), color.green(), color.blue())));
947 
948   return item;
949 }
950 
951 void
setupFileColors(QVector<QColor> const & colors)952 PreferencesDialog::setupFileColors(QVector<QColor> const &colors) {
953   ui->lwMFileColors->clear();
954 
955   for (auto const &color : colors)
956     ui->lwMFileColors->addItem(&setupFileColorItem(*new QListWidgetItem, color));
957 
958   enableFileColorsControls();
959 }
960 
961 void
saveFileColors()962 PreferencesDialog::saveFileColors() {
963   auto &colors = m_cfg.m_mergeFileColors;
964   auto numRows = ui->lwMFileColors->count();
965 
966   colors.clear();
967   colors.reserve(numRows);
968 
969   for (int row = 0; row < numRows; ++row)
970     colors << ui->lwMFileColors->item(row)->background().color();
971 
972   m_cfg.m_mergeUseFileAndTrackColors = ui->cbMUseFileAndTrackColors->isChecked();
973 }
974 
975 void
enableFileColorsControls()976 PreferencesDialog::enableFileColorsControls() {
977   auto useColors = ui->cbMUseFileAndTrackColors->isChecked();
978   auto items     = ui->lwMFileColors->selectedItems();
979 
980   ui->lwMFileColors->setEnabled(useColors);
981   ui->pbMAddFileColor->setEnabled(useColors);
982   ui->pbMRevertFileColorsToDefault->setEnabled(useColors);
983   ui->pbMRemoveFileColors->setEnabled(useColors && !items.isEmpty());
984   ui->pbMEditFileColor->setEnabled(useColors && (items.size() == 1));
985 }
986 
987 void
addFileColor()988 PreferencesDialog::addFileColor() {
989   QColorDialog dlg{this};
990 
991   if (dlg.exec())
992     ui->lwMFileColors->addItem(&setupFileColorItem(*new QListWidgetItem, dlg.currentColor()));
993 }
994 
995 void
removeFileColors()996 PreferencesDialog::removeFileColors() {
997   for (auto const &item : ui->lwMFileColors->selectedItems())
998     delete item;
999 
1000   enableFileColorsControls();
1001 }
1002 
1003 void
editFileColor(QListWidgetItem * item)1004 PreferencesDialog::editFileColor(QListWidgetItem *item) {
1005   if (!item)
1006     return;
1007 
1008   QColorDialog dlg{item->background().color(), this};
1009 
1010   if (dlg.exec())
1011     setupFileColorItem(*item, dlg.currentColor());
1012 }
1013 
1014 void
editSelectedFileColor()1015 PreferencesDialog::editSelectedFileColor() {
1016   auto items = ui->lwMFileColors->selectedItems();
1017   if (!items.isEmpty())
1018     editFileColor(items.first());
1019 }
1020 
1021 void
revertFileColorsToDefault()1022 PreferencesDialog::revertFileColorsToDefault() {
1023   setupFileColors(Util::Settings::defaultFileColors());
1024 }
1025 
1026 void
save()1027 PreferencesDialog::save() {
1028   // GUI page:
1029   m_cfg.m_uiLocale                                            = ui->cbGuiInterfaceLanguage->currentData().toString();
1030   m_cfg.m_tabPosition                                         = static_cast<QTabWidget::TabPosition>(ui->cbGuiTabPositions->currentData().toInt());
1031   m_cfg.m_bcp47LanguageEditingMode                            = static_cast<Util::Settings::BCP47LanguageEditingMode>(ui->cbGuiBCP47LanguageEditingMode->currentData().toInt());
1032   m_cfg.m_uiFontFamily                                        = ui->fcbGuiFontFamily->currentFont().family();
1033   m_cfg.m_uiFontPointSize                                     = ui->sbGuiFontPointSize->value();
1034   m_cfg.m_uiStayOnTop                                         = ui->cbGuiStayOnTop->isChecked();
1035   m_cfg.m_uiDisableHighDPIScaling                             = ui->cbGuiDisableHighDPIScaling->isChecked();
1036   m_cfg.m_uiDisableDarkStyleSheet                             = ui->cbGuiDisableDarkStyleSheet->isChecked();
1037   m_cfg.m_uiDisableToolTips                                   = ui->cbGuiDisableToolTips->isChecked();
1038   m_cfg.m_checkForUpdates                                     = ui->cbGuiCheckForUpdates->isChecked();
1039   m_cfg.m_showDebuggingMenu                                   = ui->cbGuiShowDebuggingMenu->isChecked();
1040   m_cfg.m_showToolSelector                                    = ui->cbGuiShowToolSelector->isChecked();
1041   m_cfg.m_showMoveUpDownButtons                               = ui->cbGuiShowMoveUpDownButtons->isChecked();
1042   m_cfg.m_elideTabHeaderLabels                                = ui->cbGuiElideTabHeaderLabels->isChecked();
1043   m_cfg.m_useLegacyFontMIMETypes                              = ui->cbGuiUseLegacyFontMIMETypes->isChecked();
1044   m_cfg.m_warnBeforeClosingModifiedTabs                       = ui->cbGuiWarnBeforeClosingModifiedTabs->isChecked();
1045   m_cfg.m_warnBeforeAbortingJobs                              = ui->cbGuiWarnBeforeAbortingJobs->isChecked();
1046   m_cfg.m_warnBeforeOverwriting                               = ui->cbGuiWarnBeforeOverwriting->isChecked();
1047   m_cfg.m_useDefaultJobDescription                            = ui->cbGuiUseDefaultJobDescription->isChecked();
1048   m_cfg.m_showOutputOfAllJobs                                 = ui->cbGuiShowOutputOfAllJobs->isChecked();
1049   m_cfg.m_switchToJobOutputAfterStarting                      = ui->cbGuiSwitchToJobOutputAfterStarting->isChecked();
1050   m_cfg.m_resetJobWarningErrorCountersOnExit                  = ui->cbGuiResetJobWarningErrorCountersOnExit->isChecked();
1051   m_cfg.m_removeOutputFileOnJobFailure                        = ui->cbGuiRemoveOutputFileOnJobFailure->isChecked();
1052   auto idx                                                    = !ui->cbGuiRemoveJobs      ->isChecked() ? 0 : ui->cbGuiJobRemovalPolicy      ->currentIndex() + 1;
1053   auto idxOnExit                                              = !ui->cbGuiRemoveJobsOnExit->isChecked() ? 0 : ui->cbGuiJobRemovalOnExitPolicy->currentIndex() + 1;
1054   m_cfg.m_jobRemovalPolicy                                    = static_cast<Util::Settings::JobRemovalPolicy>(idx);
1055   m_cfg.m_jobRemovalOnExitPolicy                              = static_cast<Util::Settings::JobRemovalPolicy>(idxOnExit);
1056   m_cfg.m_removeOldJobs                                       = ui->cbGuiRemoveOldJobs->isChecked();
1057   m_cfg.m_removeOldJobsDays                                   = ui->sbGuiRemoveOldJobsDays->value();
1058   m_cfg.m_maximumConcurrentJobs                               = ui->sbGuiMaximumConcurrentJobs->value();
1059 
1060   m_cfg.m_chapterNameTemplate                                 = ui->leCENameTemplate->text();
1061   m_cfg.m_ceTextFileCharacterSet                              = ui->cbCETextFileCharacterSet->currentData().toString();
1062   m_cfg.m_defaultChapterLanguage                              = ui->ldwCEDefaultLanguage->language();
1063   m_cfg.m_dropLastChapterFromBlurayPlaylist                   = ui->cbCEDropLastFromBlurayPlaylist->isChecked();
1064 
1065   // Merge page:
1066   m_cfg.m_mediaInfoExe                                        = ui->leMMediaInfoExe->text();
1067   m_cfg.m_autoSetFileTitle                                    = ui->cbMAutoSetFileTitle->isChecked();
1068   m_cfg.m_autoClearFileTitle                                  = ui->cbMAutoClearFileTitle->isChecked();
1069   m_cfg.m_setAudioDelayFromFileName                           = ui->cbMSetAudioDelayFromFileName->isChecked();
1070   m_cfg.m_disableCompressionForAllTrackTypes                  = ui->cbMDisableCompressionForAllTrackTypes->isChecked();
1071   m_cfg.m_disableDefaultTrackForSubtitles                     = ui->cbMDisableDefaultTrackForSubtitles->isChecked();
1072   m_cfg.m_mergeEnableDialogNormGainRemoval                    = ui->cbMEnableDialogNormGainRemoval->isChecked();
1073   m_cfg.m_mergeAddingAppendingFilesPolicy                     = static_cast<Util::Settings::MergeAddingAppendingFilesPolicy>(ui->cbMAddingAppendingFilesPolicy->currentData().toInt());
1074   m_cfg.m_mergeDragAndDropFilesPolicy                         = static_cast<Util::Settings::MergeAddingAppendingFilesPolicy>(ui->cbMDragAndDropFilesPolicy->currentData().toInt());
1075   m_cfg.m_mergeAlwaysCreateNewSettingsForVideoFiles           = ui->cbMAlwaysCreateSettingsForVideoFiles->isChecked();
1076   m_cfg.m_mergeSortFilesTracksByTypeWhenAdding                = ui->cbMSortFilesTracksByTypeWhenAdding->isChecked();
1077   m_cfg.m_mergeReconstructSequencesWhenAdding                 = ui->cbMReconstructSequencesWhenAdding->isChecked();
1078   m_cfg.m_mergeAlwaysShowOutputFileControls                   = ui->cbMAlwaysShowOutputFileControls->isChecked();
1079   m_cfg.m_mergeWarnMissingAudioTrack                          = static_cast<Util::Settings::MergeMissingAudioTrackPolicy>(ui->cbMWarnMissingAudioTrack->currentData().toInt());
1080   m_cfg.m_mergeTrackPropertiesLayout                          = ui->rbMTrackPropertiesLayoutHorizontalScrollArea->isChecked() ? Util::Settings::TrackPropertiesLayout::HorizontalScrollArea
1081                                                               : ui->rbMTrackPropertiesLayoutHorizontalTwoColumns->isChecked() ? Util::Settings::TrackPropertiesLayout::HorizontalTwoColumns
1082                                                               :                                                                 Util::Settings::TrackPropertiesLayout::VerticalTabWidget;
1083   m_cfg.m_clearMergeSettings                                  = static_cast<Util::Settings::ClearMergeSettingsAction>(ui->cbMClearMergeSettings->currentIndex());
1084   m_cfg.m_defaultAudioTrackLanguage                           = ui->ldwMDefaultAudioTrackLanguage->language();
1085   m_cfg.m_defaultVideoTrackLanguage                           = ui->ldwMDefaultVideoTrackLanguage->language();
1086   m_cfg.m_defaultSubtitleTrackLanguage                        = ui->ldwMDefaultSubtitleTrackLanguage->language();
1087   m_cfg.m_whenToSetDefaultLanguage                            = static_cast<Util::Settings::SetDefaultLanguagePolicy>(ui->cbMWhenToSetDefaultLanguage->currentData().toInt());
1088   m_cfg.m_defaultSubtitleCharset                              = ui->cbMDefaultSubtitleCharset->currentData().toString();
1089   m_cfg.m_priority                                            = static_cast<Util::Settings::ProcessPriority>(ui->cbMProcessPriority->currentData().toInt());
1090   m_cfg.m_defaultAdditionalMergeOptions                       = ui->leMDefaultAdditionalCommandLineOptions->text();
1091   m_cfg.m_probeRangePercentage                                = ui->cbMProbeRangePercentage->value();
1092 
1093   m_cfg.m_deriveAudioTrackLanguageFromFileNamePolicy          = static_cast<Util::Settings::DeriveLanguageFromFileNamePolicy>(ui->cbMDeriveAudioTrackLanguageFromFileName   ->currentData().toInt());
1094   m_cfg.m_deriveVideoTrackLanguageFromFileNamePolicy          = static_cast<Util::Settings::DeriveLanguageFromFileNamePolicy>(ui->cbMDeriveVideoTrackLanguageFromFileName   ->currentData().toInt());
1095   m_cfg.m_deriveSubtitleTrackLanguageFromFileNamePolicy       = static_cast<Util::Settings::DeriveLanguageFromFileNamePolicy>(ui->cbMDeriveSubtitleTrackLanguageFromFileName->currentData().toInt());
1096   m_cfg.m_boundaryCharsForDerivingTrackLanguagesFromFileNames = ui->leMDeriveTrackLanguageBoundaryChars->text();
1097   m_cfg.m_recognizedTrackLanguagesInFileNames                 = ui->tbMDeriveTrackLanguageRecognizedLanguages->selectedItemValues();
1098 
1099   m_cfg.m_scanForPlaylistsPolicy                              = static_cast<Util::Settings::ScanForPlaylistsPolicy>(ui->cbMScanPlaylistsPolicy->currentIndex());
1100   m_cfg.m_minimumPlaylistDuration                             = ui->sbMMinPlaylistDuration->value();
1101   m_cfg.m_mergeAddBlurayCovers                                = ui->cbMAddBlurayCovers->isChecked();
1102   m_cfg.m_mergeAttachmentsAlwaysSkipForExistingName           = ui->cbMAttachmentAlwaysSkipForExistingName->isChecked();
1103 
1104   m_cfg.m_outputFileNamePolicy                                = !ui->cbMAutoSetOutputFileName->isChecked()   ? Util::Settings::DontSetOutputFileName
1105                                                               : ui->rbMAutoSetRelativeDirectory->isChecked() ? Util::Settings::ToRelativeOfFirstInputFile
1106                                                               : ui->rbMAutoSetFixedDirectory->isChecked()    ? Util::Settings::ToFixedDirectory
1107                                                               : ui->rbMAutoSetPreviousDirectory->isChecked() ? Util::Settings::ToPreviousDirectory
1108                                                               :                                                Util::Settings::ToSameAsFirstInputFile;
1109   m_cfg.m_autoDestinationOnlyForVideoFiles                    = ui->cbMAutoDestinationOnlyForVideoFiles->isChecked();
1110   m_cfg.m_mergeSetDestinationFromTitle                        = ui->cbMSetDestinationFromTitle->isChecked();
1111   m_cfg.m_relativeOutputDir.setPath(ui->cbMAutoSetRelativeDirectory->currentText());
1112   m_cfg.m_fixedOutputDir.setPath(ui->cbMAutoSetFixedDirectory->currentText());
1113   m_cfg.m_uniqueOutputFileNames                               = ui->cbMUniqueOutputFileNames->isChecked();
1114   m_cfg.m_autoClearOutputFileName                             = ui->cbMAutoClearOutputFileName->isChecked();
1115 
1116   m_cfg.m_mergeLastFixedOutputDirs.add(QDir::toNativeSeparators(m_cfg.m_fixedOutputDir.path()));
1117   m_cfg.m_mergeLastRelativeOutputDirs.add(QDir::toNativeSeparators(m_cfg.m_relativeOutputDir.path()));
1118   m_cfg.m_mergeLastOutputDirs.setItems(ui->lwMRecentDestinationDirectories->items());
1119 
1120   m_cfg.m_enableMuxingTracksByLanguage                        = ui->cbMEnableMuxingTracksByLanguage->isChecked();
1121   m_cfg.m_enableMuxingAllVideoTracks                          = ui->cbMEnableMuxingAllVideoTracks->isChecked();
1122   m_cfg.m_enableMuxingAllAudioTracks                          = ui->cbMEnableMuxingAllAudioTracks->isChecked();
1123   m_cfg.m_enableMuxingAllSubtitleTracks                       = ui->cbMEnableMuxingAllSubtitleTracks->isChecked();
1124   m_cfg.m_enableMuxingTracksByTheseLanguages                  = ui->tbMEnableMuxingTracksByLanguage->selectedItemValues();
1125 
1126   // Often used selections page:
1127   m_cfg.m_oftenUsedLanguages                                  = ui->tbOftenUsedLanguages->selectedItemValues();
1128   m_cfg.m_oftenUsedRegions                                    = ui->tbOftenUsedRegions->selectedItemValues();
1129   m_cfg.m_oftenUsedCharacterSets                              = ui->tbOftenUsedCharacterSets->selectedItemValues();
1130 
1131   m_cfg.m_oftenUsedLanguagesOnly                              = ui->cbOftenUsedLanguagesOnly    ->isChecked() && !m_cfg.m_oftenUsedLanguages    .isEmpty();
1132   m_cfg.m_oftenUsedRegionsOnly                                = ui->cbOftenUsedRegionsOnly      ->isChecked() && !m_cfg.m_oftenUsedRegions      .isEmpty();
1133   m_cfg.m_oftenUsedCharacterSetsOnly                          = ui->cbOftenUsedCharacterSetsOnly->isChecked() && !m_cfg.m_oftenUsedCharacterSets.isEmpty();
1134   m_cfg.m_useISO639_3Languages                                = ui->cbUseISO639_3Languages      ->isChecked();
1135 
1136   // Info tool page
1137   m_cfg.m_defaultInfoJobSettings                              = ui->wIDefaultJobSettings->settings();
1138 
1139   // Header editor page
1140   m_cfg.m_headerEditorDroppedFilesPolicy                      = static_cast<Util::Settings::HeaderEditorDroppedFilesPolicy>(ui->cbHEDroppedFilesPolicy->currentData().toInt());
1141   m_cfg.m_headerEditorDateTimeInUTC                           = ui->cbHEDateTimeInUTC->isChecked();
1142 
1143   // Run programs page:
1144   m_cfg.m_runProgramConfigurations.clear();
1145   for (auto tabIdx = 0, numTabs = ui->twJobsPrograms->count(); tabIdx < numTabs; ++tabIdx) {
1146     auto widget = static_cast<PrefsRunProgramWidget *>(ui->twJobsPrograms->widget(tabIdx));
1147     auto cfg    = widget->config();
1148 
1149     if (!cfg->m_active || cfg->isValid())
1150       m_cfg.m_runProgramConfigurations << cfg;
1151   }
1152 
1153   m_cfg.m_enableMuxingTracksByTheseTypes.clear();
1154   for (auto const &type : ui->tbMEnableMuxingTracksByType->selectedItemValues())
1155     m_cfg.m_enableMuxingTracksByTheseTypes << static_cast<Merge::TrackType>(type.toInt());
1156 
1157   m_cfg.m_mergePredefinedVideoTrackNames    = ui->lwMPredefinedVideoTrackNames->items();
1158   m_cfg.m_mergePredefinedAudioTrackNames    = ui->lwMPredefinedAudioTrackNames->items();
1159   m_cfg.m_mergePredefinedSubtitleTrackNames = ui->lwMPredefinedSubtitleTrackNames->items();
1160   m_cfg.m_mergePredefinedSplitSizes         = ui->lwMPredefinedSplitSizes->items();
1161   m_cfg.m_mergePredefinedSplitDurations     = ui->lwMPredefinedSplitDurations->items();
1162 
1163   saveLanguageShortcuts();
1164   saveFileColors();
1165 
1166   m_cfg.save();
1167 
1168   mtx::chapters::g_chapter_generation_name_template.override(to_utf8(m_cfg.m_chapterNameTemplate));
1169 }
1170 
1171 void
rememberCurrentlySelectedPage()1172 PreferencesDialog::rememberCurrentlySelectedPage() {
1173   auto pageIndex = ui->pages->currentIndex();
1174   auto pageTypes = m_pageIndexes.keys(pageIndex);
1175 
1176   if (!pageTypes.isEmpty())
1177     ms_previouslySelectedPage = pageTypes[0];
1178 }
1179 
1180 void
enableOftendUsedLanguagesOnly()1181 PreferencesDialog::enableOftendUsedLanguagesOnly() {
1182   ui->cbOftenUsedLanguagesOnly->setEnabled(!ui->tbOftenUsedLanguages->selectedItemValues().isEmpty());
1183 }
1184 
1185 void
enableOftendUsedRegionsOnly()1186 PreferencesDialog::enableOftendUsedRegionsOnly() {
1187   ui->cbOftenUsedRegionsOnly->setEnabled(!ui->tbOftenUsedRegions->selectedItemValues().isEmpty());
1188 }
1189 
1190 void
enableOftendUsedCharacterSetsOnly()1191 PreferencesDialog::enableOftendUsedCharacterSetsOnly() {
1192   ui->cbOftenUsedCharacterSetsOnly->setEnabled(!ui->tbOftenUsedCharacterSets->selectedItemValues().isEmpty());
1193 }
1194 
1195 void
enableOutputFileNameControls()1196 PreferencesDialog::enableOutputFileNameControls() {
1197   bool isChecked        = ui->cbMAutoSetOutputFileName->isChecked();
1198   bool relativeSelected = ui->rbMAutoSetRelativeDirectory->isChecked();
1199   bool fixedSelected    = ui->rbMAutoSetFixedDirectory->isChecked();
1200 
1201   Util::enableWidgets(QList<QWidget *>{} << ui->gbDestinationDirectory   << ui->cbMUniqueOutputFileNames << ui->cbMAutoDestinationOnlyForVideoFiles << ui->cbMSetDestinationFromTitle, isChecked);
1202   Util::enableWidgets(QList<QWidget *>{} << ui->cbMAutoSetFixedDirectory << ui->pbMBrowseAutoSetFixedDirectory, isChecked && fixedSelected);
1203   ui->cbMAutoSetRelativeDirectory->setEnabled(isChecked && relativeSelected);
1204 }
1205 
1206 void
browseFixedOutputDirectory()1207 PreferencesDialog::browseFixedOutputDirectory() {
1208   auto dir = Util::getExistingDirectory(this, QY("Select destination directory"), ui->cbMAutoSetFixedDirectory->currentText());
1209   if (!dir.isEmpty())
1210     ui->cbMAutoSetFixedDirectory->setCurrentText(dir);
1211 }
1212 
1213 void
browseMediaInfoExe()1214 PreferencesDialog::browseMediaInfoExe() {
1215   auto filters = QStringList{};
1216 
1217 #if defined(SYS_WINDOWS)
1218   filters << QY("Executable files") + Q(" (*.exe)");
1219 #endif
1220   filters << QY("All files") + Q(" (*)");
1221 
1222   auto fileName = Util::getOpenFileName(this, QY("Select executable"), ui->leMMediaInfoExe->text(), filters.join(Q(";;")));
1223   if (!fileName.isEmpty())
1224     ui->leMMediaInfoExe->setText(fileName);
1225 }
1226 
1227 void
editDefaultAdditionalCommandLineOptions()1228 PreferencesDialog::editDefaultAdditionalCommandLineOptions() {
1229   Merge::AdditionalCommandLineOptionsDialog dlg{this, ui->leMDefaultAdditionalCommandLineOptions->text()};
1230   dlg.hideSaveAsDefaultCheckbox();
1231   if (dlg.exec())
1232     ui->leMDefaultAdditionalCommandLineOptions->setText(dlg.additionalOptions());
1233 }
1234 
1235 void
addProgramToExecute()1236 PreferencesDialog::addProgramToExecute() {
1237   auto programWidget = new PrefsRunProgramWidget{this, {}};
1238   ui->twJobsPrograms->addTab(programWidget, programWidget->config()->name());
1239   ui->twJobsPrograms->setCurrentIndex(ui->twJobsPrograms->count() - 1);
1240 
1241   ui->swJobsPrograms->setCurrentIndex(1);
1242 
1243   connect(programWidget, &PrefsRunProgramWidget::titleChanged, this, &PreferencesDialog::setSendersTabTitleForRunProgramWidget);
1244 }
1245 
1246 void
removeProgramToExecute(int index)1247 PreferencesDialog::removeProgramToExecute(int index) {
1248   if ((0  > index) || (ui->twJobsPrograms->count() <= index))
1249     return;
1250 
1251   auto tab = qobject_cast<QWidget *>(ui->twJobsPrograms->widget(index));
1252   if (!tab)
1253     return;
1254 
1255   ui->twJobsPrograms->removeTab(index);
1256   delete tab;
1257 
1258   if (!ui->twJobsPrograms->count())
1259     ui->swJobsPrograms->setCurrentIndex(0);
1260 }
1261 
1262 void
setSendersTabTitleForRunProgramWidget()1263 PreferencesDialog::setSendersTabTitleForRunProgramWidget() {
1264   auto widget = qobject_cast<PrefsRunProgramWidget *>(sender());
1265   auto title  = widget->config()->name();
1266 
1267   setTabTitleForRunProgramWidget(ui->twJobsPrograms->indexOf(widget), title);
1268 }
1269 
1270 void
setTabTitleForRunProgramWidget(int tabIdx,QString const & title)1271 PreferencesDialog::setTabTitleForRunProgramWidget(int tabIdx,
1272                                                   QString const &title) {
1273   if ((tabIdx < 0) || (tabIdx >= ui->twJobsPrograms->count()))
1274     return;
1275 
1276   ui->twJobsPrograms->setTabText(tabIdx, title);
1277 }
1278 
1279 void
adjustPlaylistControls()1280 PreferencesDialog::adjustPlaylistControls() {
1281   ui->sbMMinPlaylistDuration->setSuffix(QNY(" second", " seconds", ui->sbMMinPlaylistDuration->value()));
1282 }
1283 
1284 void
adjustRemoveOldJobsControls()1285 PreferencesDialog::adjustRemoveOldJobsControls() {
1286   ui->sbGuiRemoveOldJobsDays->setEnabled(ui->cbGuiRemoveOldJobs->isChecked());
1287   ui->sbGuiRemoveOldJobsDays->setSuffix(QNY(" day", " days", ui->sbGuiRemoveOldJobsDays->value()));
1288 }
1289 
1290 void
showPage(Page page)1291 PreferencesDialog::showPage(Page page) {
1292   auto pageIndex      = m_pageIndexes[page];
1293   auto pageModelIndex = modelIndexForPage(pageIndex);
1294 
1295   if (!pageModelIndex.isValid())
1296     return;
1297 
1298   m_ignoreNextCurrentChange = true;
1299 
1300   ui->pageSelector->selectionModel()->select(pageModelIndex, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Current);
1301   ui->pages->setCurrentIndex(pageIndex);
1302 }
1303 
1304 void
revertDeriveTrackLanguageFromFileNameChars()1305 PreferencesDialog::revertDeriveTrackLanguageFromFileNameChars() {
1306   ui->leMDeriveTrackLanguageBoundaryChars->setText(Util::Settings::defaultBoundaryCharsForDerivingLanguageFromFileName());
1307 }
1308 
1309 bool
verifyDeriveTrackLanguageSettings()1310 PreferencesDialog::verifyDeriveTrackLanguageSettings() {
1311   if (   (static_cast<Util::Settings::DeriveLanguageFromFileNamePolicy>(ui->cbMDeriveAudioTrackLanguageFromFileName   ->currentData().toInt()) == Util::Settings::DeriveLanguageFromFileNamePolicy::Never)
1312       && (static_cast<Util::Settings::DeriveLanguageFromFileNamePolicy>(ui->cbMDeriveVideoTrackLanguageFromFileName   ->currentData().toInt()) == Util::Settings::DeriveLanguageFromFileNamePolicy::Never)
1313       && (static_cast<Util::Settings::DeriveLanguageFromFileNamePolicy>(ui->cbMDeriveSubtitleTrackLanguageFromFileName->currentData().toInt()) == Util::Settings::DeriveLanguageFromFileNamePolicy::Never))
1314     return true;
1315 
1316   if (ui->leMDeriveTrackLanguageBoundaryChars->text().isEmpty()) {
1317     Util::MessageBox::critical(this)
1318       ->title(QY("Invalid settings"))
1319       .text(QY("The list of boundary characters for deriving the track language from file names must not be empty."))
1320       .exec();
1321 
1322     return false;
1323   }
1324 
1325   if (ui->tbMDeriveTrackLanguageRecognizedLanguages->selectedItemValues().isEmpty()) {
1326     showPage(Page::DeriveTrackLanguage);
1327     ui->tbMDeriveTrackLanguageRecognizedLanguages->setFocus();
1328 
1329     Util::MessageBox::critical(this)
1330       ->title(QY("Invalid settings"))
1331       .text(QY("The list of recognized track languages in file names must not be empty."))
1332       .exec();
1333 
1334     return false;
1335   }
1336 
1337   return true;
1338 }
1339 
1340 bool
verifyRunProgramConfigurations()1341 PreferencesDialog::verifyRunProgramConfigurations() {
1342   for (auto tabIdx = 0, numTabs = ui->twJobsPrograms->count(); tabIdx < numTabs; ++tabIdx) {
1343     auto tab   = qobject_cast<PrefsRunProgramWidget *>(ui->twJobsPrograms->widget(tabIdx));
1344     auto error = tab->validate();
1345 
1346     if (error.isEmpty())
1347       continue;
1348 
1349     showPage(Page::RunPrograms);
1350     ui->twJobsPrograms->setCurrentIndex(tabIdx);
1351 
1352     Util::MessageBox::critical(this)
1353       ->title(QY("Invalid settings"))
1354       .text(Q("<p>%1 %2</p>"
1355               "<p>%3</p>")
1356             .arg(QY("This configuration is currently invalid.").toHtmlEscaped())
1357             .arg(error.toHtmlEscaped())
1358             .arg(QY("Either fix the error or remove the configuration before closing the preferences dialog.").toHtmlEscaped()))
1359       .exec();
1360 
1361     return false;
1362   }
1363 
1364   return true;
1365 }
1366 
1367 void
accept()1368 PreferencesDialog::accept() {
1369   if (   verifyDeriveTrackLanguageSettings()
1370       && verifyRunProgramConfigurations()) {
1371     rememberCurrentlySelectedPage();
1372     QDialog::accept();
1373   }
1374 }
1375 
1376 void
reject()1377 PreferencesDialog::reject() {
1378   rememberCurrentlySelectedPage();
1379   QDialog::reject();
1380 }
1381 
1382 void
setupLanguageShortcuts()1383 PreferencesDialog::setupLanguageShortcuts() {
1384   for (auto const &formattedLanguage : m_cfg.m_languageShortcuts) {
1385     auto language = mtx::bcp47::language_c::parse(to_utf8(formattedLanguage));
1386     if (!language.is_valid())
1387       continue;
1388 
1389     auto item = new QListWidgetItem{Q(language.format_long())};
1390     item->setData(Qt::UserRole, Q(language.format()));
1391 
1392     ui->lwGuiLanguageShortcuts->addItem(item);
1393   }
1394 
1395   enableLanguageShortcutControls();
1396 }
1397 
1398 void
saveLanguageShortcuts()1399 PreferencesDialog::saveLanguageShortcuts() {
1400   m_cfg.m_languageShortcuts.clear();
1401 
1402   for (auto row = 0, numItems = ui->lwGuiLanguageShortcuts->count(); row < numItems; ++row)
1403     m_cfg.m_languageShortcuts << ui->lwGuiLanguageShortcuts->item(row)->data(Qt::UserRole).toString();
1404 }
1405 
1406 void
enableLanguageShortcutControls()1407 PreferencesDialog::enableLanguageShortcutControls() {
1408   auto items = ui->lwGuiLanguageShortcuts->selectedItems();
1409 
1410   ui->pbGuiEditLanguageShortcut->setEnabled(items.size() == 1);
1411   ui->pbGuiRemoveLanguageShortcuts->setEnabled(items.size() > 0);
1412 }
1413 
1414 void
addLanguageShortcut()1415 PreferencesDialog::addLanguageShortcut() {
1416   mtx::gui::Util::LanguageDialog dlg{this};
1417 
1418   if (!dlg.exec())
1419     return;
1420 
1421   auto language = dlg.language();
1422   auto item     = new QListWidgetItem{Q(language.format_long())};
1423 
1424   item->setData(Qt::UserRole, Q(language.format()));
1425 
1426   ui->lwGuiLanguageShortcuts->addItem(item);
1427 }
1428 
1429 void
editLanguageShortcut()1430 PreferencesDialog::editLanguageShortcut() {
1431   auto items = ui->lwGuiLanguageShortcuts->selectedItems();
1432   if (items.isEmpty())
1433     return;
1434 
1435   auto item     = items.first();
1436   auto language = mtx::bcp47::language_c::parse(to_utf8(item->data(Qt::UserRole).toString()));
1437 
1438   mtx::gui::Util::LanguageDialog dlg{this};
1439 
1440   if (language.is_valid())
1441     dlg.setLanguage(language);
1442 
1443   if (!dlg.exec())
1444     return;
1445 
1446   language = dlg.language();
1447   item->setText(Q(language.format_long()));
1448   item->setData(Qt::UserRole, Q(language.format()));
1449 }
1450 
1451 void
removeLanguageShortcuts()1452 PreferencesDialog::removeLanguageShortcuts() {
1453   for (auto const &item : ui->lwGuiLanguageShortcuts->selectedItems())
1454     delete item;
1455 
1456   enableLanguageShortcutControls();
1457 }
1458 
1459 }
1460