1 #include "common/common_pch.h"
2 
3 #include <QColor>
4 #include <QDebug>
5 #include <QRegularExpression>
6 #include <QScreen>
7 #include <QSettings>
8 #include <QSplitter>
9 #include <QStandardPaths>
10 
11 #include "common/chapters/chapters.h"
12 #include "common/character_sets.h"
13 #include "common/fs_sys_helpers.h"
14 #include "common/iso639.h"
15 #include "common/iso3166.h"
16 #include "common/path.h"
17 #include "common/qt.h"
18 #include "common/random.h"
19 #include "common/sorting.h"
20 #include "common/version.h"
21 #include "mkvtoolnix-gui/app.h"
22 #include "mkvtoolnix-gui/jobs/program_runner.h"
23 #include "mkvtoolnix-gui/merge/enums.h"
24 #include "mkvtoolnix-gui/util/file_dialog.h"
25 #include "mkvtoolnix-gui/util/font.h"
26 #include "mkvtoolnix-gui/util/settings.h"
27 #include "mkvtoolnix-gui/util/settings_names.h"
28 #include "mkvtoolnix-gui/util/string.h"
29 #include "mkvtoolnix-gui/util/widget.h"
30 
31 namespace mtx::gui::Util {
32 
33 QString
validate() const34 Settings::RunProgramConfig::validate()
35   const {
36   return (m_type == RunProgramType::ExecuteProgram) && (m_commandLine.isEmpty() || m_commandLine.value(0).isEmpty()) ? QY("The program to execute hasn't been set yet.")
37        : (m_type == RunProgramType::PlayAudioFile)  && m_audioFile.isEmpty()                                         ? QY("The audio file to play hasn't been set yet.")
38        :                                                                                                               QString{};
39 }
40 
41 bool
isValid() const42 Settings::RunProgramConfig::isValid()
43   const {
44   return validate().isEmpty();
45 }
46 
47 QString
name() const48 Settings::RunProgramConfig::name()
49   const {
50   if (!m_name.isEmpty())
51     return m_name;
52 
53   return m_type == RunProgramType::ExecuteProgram    ? nameForExternalProgram()
54        : m_type == RunProgramType::PlayAudioFile     ? nameForPlayAudioFile()
55        : m_type == RunProgramType::ShutDownComputer  ? QY("Shut down the computer")
56        : m_type == RunProgramType::HibernateComputer ? QY("Hibernate the computer")
57        : m_type == RunProgramType::SleepComputer     ? QY("Sleep the computer")
58        : m_type == RunProgramType::DeleteSourceFiles ? QY("Delete source files for multiplexer jobs")
59        :                                               Q("unknown");
60 }
61 
62 QString
nameForExternalProgram() const63 Settings::RunProgramConfig::nameForExternalProgram()
64   const {
65   if (m_commandLine.isEmpty())
66     return QY("Execute a program");
67 
68   auto program = m_commandLine.value(0);
69   program.replace(QRegularExpression{Q(".*[/\\\\]")}, Q(""));
70 
71   return QY("Execute program '%1'").arg(program);
72 }
73 
74 QString
nameForPlayAudioFile() const75 Settings::RunProgramConfig::nameForPlayAudioFile()
76   const {
77   if (m_audioFile.isEmpty())
78     return QY("Play an audio file");
79 
80   auto audioFile = m_audioFile;
81   audioFile.replace(QRegularExpression{Q(".*[/\\\\]")}, Q(""));
82 
83   return QY("Play audio file '%1'").arg(audioFile);
84 }
85 
86 Settings Settings::s_settings;
87 
Settings()88 Settings::Settings() {
89 }
90 
91 void
change(std::function<void (Settings &)> worker)92 Settings::change(std::function<void(Settings &)> worker) {
93   worker(s_settings);
94   s_settings.save();
95 }
96 
97 Settings &
get()98 Settings::get() {
99   return s_settings;
100 }
101 
102 QString
iniFileLocation()103 Settings::iniFileLocation() {
104 #if defined(SYS_WINDOWS)
105   if (!App::isInstalled())
106     // QApplication::applicationDirPath() cannot be used here as the
107     // Util::Settings class might be used before QCoreApplication's
108     // been instantiated, which QApplication::applicationDirPath()
109     // requires.
110     return Q(mtx::sys::get_current_exe_path("").u8string());
111 
112   return QStandardPaths::writableLocation(QStandardPaths::ConfigLocation);
113 #else
114   return Q("%1/%2/%3").arg(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation)).arg(App::organizationName()).arg(App::applicationName());
115 #endif
116 }
117 
118 QString
cacheDirLocation(QString const & subDir)119 Settings::cacheDirLocation(QString const &subDir) {
120   QString dir;
121 
122 #if defined(SYS_WINDOWS)
123   if (!App::isInstalled())
124     dir = Q("%1/cache").arg(App::applicationDirPath());
125 #endif
126 
127   if (dir.isEmpty())
128     dir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
129 
130   if (!subDir.isEmpty())
131     dir = Q("%1/%2").arg(dir).arg(subDir);
132 
133   return QDir::toNativeSeparators(dir);
134 }
135 
136 QString
prepareCacheDir(QString const & subDir)137 Settings::prepareCacheDir(QString const &subDir) {
138   auto dirName = cacheDirLocation(subDir);
139   QDir{dirName}.mkpath(dirName);
140 
141   return QDir::toNativeSeparators(dirName);
142 }
143 
144 QString
iniFileName()145 Settings::iniFileName() {
146   return Q("%1/mkvtoolnix-gui.ini").arg(iniFileLocation());
147 }
148 
149 void
migrateFromRegistry()150 Settings::migrateFromRegistry() {
151 #if defined(SYS_WINDOWS)
152   // Migration of settings from the Windows registry into an .ini file
153   // due to performance issues.
154 
155   // If this is the portable version the no settings have to be migrated.
156   if (!App::isInstalled())
157     return;
158 
159   // Don't do anything if such a file exists already.
160   auto targetFileName = iniFileName();
161   if (QFileInfo{targetFileName}.exists())
162     return;
163 
164   // Copy all settings.
165   QSettings target{targetFileName, QSettings::IniFormat};
166   QSettings source{};
167 
168   for (auto const &key : source.allKeys())
169     target.setValue(key, source.value(key));
170 
171   // Ensure the new file is written and remove the keys from the
172   // registry.
173   target.sync();
174   source.clear();
175   source.sync();
176 
177 #else
178   // Rename file from .conf to .ini in order to be consistent.
179   auto target = QFileInfo{ iniFileName() };
180   auto source = QFileInfo{ Q("%1/%2.conf").arg(target.absolutePath()).arg(target.baseName()) };
181 
182   if (source.exists())
183     QFile{ source.absoluteFilePath() }.rename(target.filePath());
184 #endif
185 }
186 
187 std::unique_ptr<QSettings>
registry()188 Settings::registry() {
189   return std::make_unique<QSettings>(iniFileName(), QSettings::IniFormat);
190 }
191 
192 void
convertOldSettings()193 Settings::convertOldSettings() {
194   auto reg = registry();
195 
196   // Read the version number used for writing the settings.
197   reg->beginGroup(s_grpInfo);
198   auto writtenByVersion = version_number_t{to_utf8(reg->value(s_valGuiVersion).toString())};
199   if (!writtenByVersion.valid)
200     writtenByVersion = version_number_t{"8.1.0"}; // 8.1.0 was the last version not writing the version number field.
201   reg->endGroup();
202 
203   reg->beginGroup(s_grpDefaults);
204   if (   (writtenByVersion == version_number_t{"8.1.0"})
205       && (reg->value(s_valDefaultSubtitleCharset).toString() == Q("ISO-8859-15"))) {
206     // Fix for a bug in versions prior to 8.2.0.
207     reg->remove(s_valDefaultSubtitleCharset);
208   }
209   reg->endGroup();
210 
211   // defaultTrackLanguage → defaultAudioTrackLanguage, defaultVideoTrackLanguage, defaultSubtitleTrackLanguage;
212   reg->beginGroup(s_grpSettings);
213 
214   auto defaultTrackLanguage = reg->value(s_valDefaultTrackLanguage);
215   reg->remove(s_valDefaultTrackLanguage);
216 
217   if (defaultTrackLanguage.isValid()) {
218     reg->setValue(s_valDefaultAudioTrackLanguage,    defaultTrackLanguage.toString());
219     reg->setValue(s_valDefaultVideoTrackLanguage,    defaultTrackLanguage.toString());
220     reg->setValue(s_valDefaultSubtitleTrackLanguage, defaultTrackLanguage.toString());
221   }
222 
223   // mergeUseVerticalInputLayout → mergeTrackPropertiesLayout
224   auto mergeUseVerticalInputLayout = reg->value(s_valMergeUseVerticalInputLayout);
225   reg->remove(s_valMergeUseVerticalInputLayout);
226 
227   if (mergeUseVerticalInputLayout.isValid())
228     reg->setValue(s_valMergeTrackPropertiesLayout, static_cast<int>(mergeUseVerticalInputLayout.toBool() ? TrackPropertiesLayout::VerticalTabWidget : TrackPropertiesLayout::HorizontalScrollArea));
229 
230   reg->endGroup();
231 
232   // v55 uses boundary chars instead of a whole regex.
233   reg->beginGroup(s_grpSettings);
234   reg->beginGroup(s_grpDerivingTrackLanguagesFromFileNames);
235   reg->remove("customRegex");
236   reg->endGroup();
237   reg->endGroup();
238 
239   // Update list of often-used language codes when updating from v37
240   // or earlier due to the change that only often used languages are
241   // shown by default. Unfortunately that list was rather short in
242   // earlier releases, leading to confused users missing their
243   // language.
244   if (writtenByVersion <= version_number_t{"37"}) {
245     reg->beginGroup(s_grpSettings);
246     reg->remove(s_valOftenUsedLanguages);
247     reg->endGroup();
248   }
249 
250   // Predefined track names have been split up into lists for each
251   // track type after v40.
252   reg->beginGroup(s_grpSettings);
253   auto value = reg->value(s_valMergePredefinedTrackNames);
254   if (value.isValid()) {
255     reg->remove(s_valMergePredefinedTrackNames);
256     reg->setValue(s_valMergePredefinedVideoTrackNames,    value.toStringList());
257     reg->setValue(s_valMergePredefinedAudioTrackNames,    value.toStringList());
258     reg->setValue(s_valMergePredefinedSubtitleTrackNames, value.toStringList());
259   }
260   reg->endGroup();
261 
262   // Update program runner event types: v46 splits "run if job
263   // successful or with warnings" into two separate events.
264   if (writtenByVersion <= version_number_t{"45.0.0.26"}) {
265     reg->beginGroup(s_grpRunProgramConfigurations);
266 
267     for (auto const &group : reg->childGroups()) {
268       reg->beginGroup(group);
269       auto forEvents = reg->value(s_valForEvents).toInt();
270       if (forEvents & 2)
271         reg->setValue(s_valForEvents, forEvents | 8);
272       reg->endGroup();
273     }
274 
275     reg->endGroup();            // runProgramConfigurations
276   }
277 
278   // "Often used countries" was renamed to "often used regions" in
279   // v51.
280   reg->beginGroup(s_grpSettings);
281   if (!reg->value(s_valOftenUsedRegions).isValid()) {
282     reg->setValue(s_valOftenUsedRegions, reg->value(s_valOftenUsedCountries));
283     reg->remove(s_valOftenUsedCountries);
284   }
285   if (!reg->value(s_valOftenUsedRegionsOnly).isValid()) {
286     reg->setValue(s_valOftenUsedRegionsOnly, reg->value(s_valOftenUsedCountriesOnly));
287     reg->remove(s_valOftenUsedCountriesOnly);
288   }
289   reg->endGroup();
290 
291   // After v54: the setting mergeAddingAppendingFilesPolicy which was
292   // only used for dragging & dropping in < v54 but for both dragging
293   // & dropping and using the "add source files" button in v54 is
294   // split up into two settings afterwards.
295   reg->beginGroup(s_grpSettings);
296   if (!reg->value(s_valMergeDragAndDropFilesPolicy).isValid()) {
297     if (reg->value(s_valMergeAddingAppendingFilesPolicy).isValid())
298       reg->setValue(s_valMergeDragAndDropFilesPolicy,       reg->value(s_valMergeAddingAppendingFilesPolicy));
299     if (reg->value(s_valMergeLastAddingAppendingDecision).isValid())
300       reg->setValue(s_valMergeLastDragAndDropFilesDecision, reg->value(s_valMergeLastAddingAppendingDecision));
301 
302     reg->remove(s_valMergeAddingAppendingFilesPolicy);
303     reg->remove(s_valMergeLastAddingAppendingDecision);
304   }
305   reg->endGroup();
306 
307   // After v60: boundary characters for detecting track languages have changed.
308   if (writtenByVersion <= version_number_t{"60.0.0.2"}) {
309     reg->beginGroup(s_grpSettings);
310     reg->beginGroup(s_grpDerivingTrackLanguagesFromFileNames);
311     if (reg->value(s_valBoundaryChars) == Q("[](){}.+=#"))
312       reg->setValue(s_valBoundaryChars, defaultBoundaryCharsForDerivingLanguageFromFileName());
313     reg->endGroup();
314     reg->endGroup();
315   }
316 
317   // v60 changed default process priority to "lowest", which isn't a
318   // good idea; 60.0.0.18 amended it to "low".
319   if ((writtenByVersion >= version_number_t{"60.0.0.0"}) && (writtenByVersion < version_number_t{"60.0.0.18"})) {
320     reg->beginGroup(s_grpSettings);
321     if (reg->value(s_valPriority).toInt() == static_cast<int>(LowestPriority))
322       reg->setValue(s_valPriority, static_cast<int>(LowPriority));
323     reg->endGroup();
324   }
325 }
326 
327 void
load()328 Settings::load() {
329   convertOldSettings();
330 
331 #if defined(SYS_APPLE)
332   auto defaultElideTabHeaderLabels = true;
333 #else
334   auto defaultElideTabHeaderLabels = false;
335 #endif
336 
337   auto regPtr      = registry();
338   auto &reg        = *regPtr;
339   auto defaultFont = defaultUiFont();
340   std::optional<QVariant> enableMuxingTracksByTheseTypes;
341 
342   reg.beginGroup(s_grpSettings);
343   m_priority                                  = static_cast<ProcessPriority>(reg.value(s_valPriority,                                          static_cast<int>(LowPriority)).toInt());
344   m_probeRangePercentage                      = reg.value(s_valProbeRangePercentage,                                                           0.3).toDouble();
345   m_tabPosition                               = static_cast<QTabWidget::TabPosition>(reg.value(s_valTabPosition,                               static_cast<int>(QTabWidget::North)).toInt());
346   m_elideTabHeaderLabels                      = reg.value(s_valElideTabHeaderLabels,                                                           defaultElideTabHeaderLabels).toBool();
347   m_useLegacyFontMIMETypes                    = reg.value(s_valUseLegacyFontMIMETypes,                                                         false).toBool();
348   m_lastOpenDir                               = QDir{reg.value(s_valLastOpenDir).toString()};
349   m_lastOutputDir                             = QDir{reg.value(s_valLastOutputDir).toString()};
350   m_lastConfigDir                             = QDir{reg.value(s_valLastConfigDir).toString()};
351 
352   m_languageShortcuts                         = reg.value(s_valLanguageShortcuts).toStringList();
353   m_oftenUsedLanguages                        = reg.value(s_valOftenUsedLanguages).toStringList();
354   m_oftenUsedRegions                          = reg.value(s_valOftenUsedRegions).toStringList();
355   m_oftenUsedCharacterSets                    = reg.value(s_valOftenUsedCharacterSets).toStringList();
356 
357   m_oftenUsedLanguagesOnly                    = reg.value(s_valOftenUsedLanguagesOnly,                                                         false).toBool();
358   m_oftenUsedRegionsOnly                      = reg.value(s_valOftenUsedRegionsOnly,                                                           false).toBool();
359   m_oftenUsedCharacterSetsOnly                = reg.value(s_valOftenUsedCharacterSetsOnly,                                                     false).toBool();
360   m_useISO639_3Languages                      = reg.value(s_valUseISO639_3Languages,                                                           false).toBool();
361 
362   m_scanForPlaylistsPolicy                    = static_cast<ScanForPlaylistsPolicy>(reg.value(s_valScanForPlaylistsPolicy,                     static_cast<int>(AskBeforeScanning)).toInt());
363   m_minimumPlaylistDuration                   = reg.value(s_valMinimumPlaylistDuration,                                                        120).toUInt();
364 
365   m_setAudioDelayFromFileName                 = reg.value(s_valSetAudioDelayFromFileName,                                                      true).toBool();
366   m_autoSetFileTitle                          = reg.value(s_valAutoSetFileTitle,                                                               true).toBool();
367   m_autoClearFileTitle                        = reg.value(s_valAutoClearFileTitle,                                                             m_autoSetFileTitle).toBool();
368   m_clearMergeSettings                        = static_cast<ClearMergeSettingsAction>(reg.value(s_valClearMergeSettings,                       static_cast<int>(ClearMergeSettingsAction::None)).toInt());
369   m_disableCompressionForAllTrackTypes        = reg.value(s_valDisableCompressionForAllTrackTypes,                                             false).toBool();
370   m_disableDefaultTrackForSubtitles           = reg.value(s_valDisableDefaultTrackForSubtitles,                                                false).toBool();
371   m_mergeEnableDialogNormGainRemoval          = reg.value(s_valMergeEnableDialogNormGainRemoval,                                               false).toBool();
372   m_mergeAddBlurayCovers                      = reg.value(s_valMergeAddBlurayCovers,                                                           true).toBool();
373   m_mergeAttachmentsAlwaysSkipForExistingName = reg.value(s_valMergeAttachmentsAlwaysSkipForExistingName,                                      true).toBool();
374   m_mergeAlwaysCreateNewSettingsForVideoFiles = reg.value(s_valMergeAlwaysCreateNewSettingsForVideoFiles).toBool();
375   m_mergeSortFilesTracksByTypeWhenAdding      = reg.value(s_valMergeSortFilesTracksByTypeWhenAdding,                                           true).toBool();
376   m_mergeReconstructSequencesWhenAdding       = reg.value(s_valMergeReconstructSequencesWhenAdding,                                            true).toBool();
377   m_mergeAlwaysShowOutputFileControls         = reg.value(s_valMergeAlwaysShowOutputFileControls,                                              true).toBool();
378   m_mergePredefinedVideoTrackNames            = reg.value(s_valMergePredefinedVideoTrackNames).toStringList();
379   m_mergePredefinedAudioTrackNames            = reg.value(s_valMergePredefinedAudioTrackNames).toStringList();
380   m_mergePredefinedSubtitleTrackNames         = reg.value(s_valMergePredefinedSubtitleTrackNames).toStringList();
381   m_mergePredefinedSplitSizes                 = reg.value(s_valMergePredefinedSplitSizes).toStringList();
382   m_mergePredefinedSplitDurations             = reg.value(s_valMergePredefinedSplitDurations).toStringList();
383   m_mergeTrackPropertiesLayout                = static_cast<TrackPropertiesLayout>(reg.value(s_valMergeTrackPropertiesLayout,                  static_cast<int>(TrackPropertiesLayout::HorizontalScrollArea)).toInt());
384   m_mergeAddingAppendingFilesPolicy           = static_cast<MergeAddingAppendingFilesPolicy>(reg.value(s_valMergeAddingAppendingFilesPolicy,   static_cast<int>(MergeAddingAppendingFilesPolicy::Ask)).toInt());
385   m_mergeLastAddingAppendingDecision          = static_cast<MergeAddingAppendingFilesPolicy>(reg.value(s_valMergeLastAddingAppendingDecision,  static_cast<int>(MergeAddingAppendingFilesPolicy::Add)).toInt());
386   m_mergeDragAndDropFilesPolicy               = static_cast<MergeAddingAppendingFilesPolicy>(reg.value(s_valMergeDragAndDropFilesPolicy,       static_cast<int>(MergeAddingAppendingFilesPolicy::Ask)).toInt());
387   m_mergeLastDragAndDropFilesDecision         = static_cast<MergeAddingAppendingFilesPolicy>(reg.value(s_valMergeLastDragAndDropFilesDecision, static_cast<int>(MergeAddingAppendingFilesPolicy::Add)).toInt());
388   m_mergeWarnMissingAudioTrack                = static_cast<MergeMissingAudioTrackPolicy>(reg.value(s_valMergeWarnMissingAudioTrack,           static_cast<int>(MergeMissingAudioTrackPolicy::IfAudioTrackPresent)).toInt());
389   m_headerEditorDroppedFilesPolicy            = static_cast<HeaderEditorDroppedFilesPolicy>(reg.value(s_valHeaderEditorDroppedFilesPolicy,     static_cast<int>(HeaderEditorDroppedFilesPolicy::Ask)).toInt());
390   m_headerEditorDateTimeInUTC                 = reg.value(s_valHeaderEditorDateTimeInUTC).toBool();
391 
392   m_outputFileNamePolicy                      = static_cast<OutputFileNamePolicy>(reg.value(s_valOutputFileNamePolicy,                         static_cast<int>(ToSameAsFirstInputFile)).toInt());
393   m_autoDestinationOnlyForVideoFiles          = reg.value(s_valAutoDestinationOnlyForVideoFiles,                                               false).toBool();
394   m_mergeSetDestinationFromTitle              = reg.value(s_valMergeSetDestinationFromTitle,                                                   true).toBool();
395   m_relativeOutputDir                         = QDir{reg.value(s_valRelativeOutputDir).toString()};
396   m_fixedOutputDir                            = QDir{reg.value(s_valFixedOutputDir).toString()};
397   m_uniqueOutputFileNames                     = reg.value(s_valUniqueOutputFileNames,                                                          true).toBool();
398   m_autoClearOutputFileName                   = reg.value(s_valAutoClearOutputFileName,                                                        m_outputFileNamePolicy != DontSetOutputFileName).toBool();
399 
400   m_enableMuxingTracksByLanguage              = reg.value(s_valEnableMuxingTracksByLanguage,                                                   false).toBool();
401   m_enableMuxingAllVideoTracks                = reg.value(s_valEnableMuxingAllVideoTracks,                                                     true).toBool();
402   m_enableMuxingAllAudioTracks                = reg.value(s_valEnableMuxingAllAudioTracks,                                                     false).toBool();
403   m_enableMuxingAllSubtitleTracks             = reg.value(s_valEnableMuxingAllSubtitleTracks,                                                  false).toBool();
404   m_enableMuxingTracksByTheseLanguages        = reg.value(s_valEnableMuxingTracksByTheseLanguages).toStringList();
405 
406   if (reg.contains("enableMuxingTracksByTheseTypes"))
407     enableMuxingTracksByTheseTypes            = reg.value(s_valEnableMuxingTracksByTheseTypes);
408 
409   m_useDefaultJobDescription                  = reg.value(s_valUseDefaultJobDescription,                                                       false).toBool();
410   m_showOutputOfAllJobs                       = reg.value(s_valShowOutputOfAllJobs,                                                            true).toBool();
411   m_switchToJobOutputAfterStarting            = reg.value(s_valSwitchToJobOutputAfterStarting,                                                 false).toBool();
412   m_resetJobWarningErrorCountersOnExit        = reg.value(s_valResetJobWarningErrorCountersOnExit,                                             false).toBool();
413   m_removeOutputFileOnJobFailure              = reg.value(s_valRemoveOutputFileOnJobFailure,                                                   false).toBool();
414   m_jobRemovalPolicy                          = static_cast<JobRemovalPolicy>(reg.value(s_valJobRemovalPolicy,                                 static_cast<int>(JobRemovalPolicy::Never)).toInt());
415   m_jobRemovalOnExitPolicy                    = static_cast<JobRemovalPolicy>(reg.value(s_valJobRemovalOnExitPolicy,                           static_cast<int>(JobRemovalPolicy::Never)).toInt());
416   m_maximumConcurrentJobs                     = reg.value(s_valMaximumConcurrentJobs,                                                          1).toUInt();
417   m_removeOldJobs                             = reg.value(s_valRemoveOldJobs,                                                                  true).toBool();
418   m_removeOldJobsDays                         = reg.value(s_valRemoveOldJobsDays,                                                              14).toInt();
419 
420   m_showToolSelector                          = reg.value(s_valShowToolSelector,                                                               true).toBool();
421   m_warnBeforeClosingModifiedTabs             = reg.value(s_valWarnBeforeClosingModifiedTabs,                                                  true).toBool();
422   m_warnBeforeAbortingJobs                    = reg.value(s_valWarnBeforeAbortingJobs,                                                         true).toBool();
423   m_warnBeforeOverwriting                     = reg.value(s_valWarnBeforeOverwriting,                                                          true).toBool();
424   m_showMoveUpDownButtons                     = reg.value(s_valShowMoveUpDownButtons,                                                          false).toBool();
425   m_bcp47LanguageEditingMode                  = static_cast<BCP47LanguageEditingMode>(reg.value(s_valBCP47LanguageEditingMode,                 static_cast<int>(BCP47LanguageEditingMode::Components)).toInt());
426 
427   m_chapterNameTemplate                       = reg.value(s_valChapterNameTemplate,                                                            QY("Chapter <NUM:2>")).toString();
428   m_dropLastChapterFromBlurayPlaylist         = reg.value(s_valDropLastChapterFromBlurayPlaylist,                                              true).toBool();
429   m_ceTextFileCharacterSet                    = reg.value(s_valCeTextFileCharacterSet).toString();
430 
431   m_mediaInfoExe                              = reg.value(s_valMediaInfoExe,                                                                   Q("mediainfo-gui")).toString();
432   m_mediaInfoExe                              = determineMediaInfoExePath();
433 
434 #if defined(HAVE_LIBINTL_H)
435   m_uiLocale                                  = reg.value(s_valUiLocale).toString();
436 #endif
437   m_uiDisableHighDPIScaling                   = reg.value(s_valUiDisableHighDPIScaling).toBool();
438   m_uiDisableDarkStyleSheet                   = reg.value(s_valUiDisableDarkStyleSheet).toBool();
439   m_uiDisableToolTips                         = reg.value(s_valUiDisableToolTips).toBool();
440   m_uiFontFamily                              = reg.value(s_valUiFontFamily,                                                                   defaultFont.family()).toString();
441   m_uiFontPointSize                           = reg.value(s_valUiFontPointSize,                                                                defaultFont.pointSize()).toInt();
442   m_uiStayOnTop                               = reg.value(s_valUiStayOnTop,                                                                    false).toBool();
443 
444   m_showDebuggingMenu                         = reg.value(s_valShowDebuggingMenu,                                                              false).toBool();
445 
446   reg.beginGroup(s_grpUpdates);
447   m_checkForUpdates                           = reg.value(s_valCheckForUpdates,                                                                true).toBool();
448   m_lastUpdateCheck                           = reg.value(s_valLastUpdateCheck,                                                                QDateTime{}).toDateTime();
449 
450   reg.endGroup();               // settings.updates
451 
452   m_mergeLastFixedOutputDirs   .setItems(reg.value(s_valMergeLastFixedOutputDirs).toStringList());
453   m_mergeLastOutputDirs        .setItems(reg.value(s_valMergeLastOutputDirs).toStringList());
454   m_mergeLastRelativeOutputDirs.setItems(reg.value(s_valMergeLastRelativeOutputDirs).toStringList());
455 
456   reg.endGroup();               // settings
457 
458   loadDefaults(reg);
459   loadDerivingTrackLanguagesSettings(reg);
460   loadSplitterSizes(reg);
461   loadDefaultInfoJobSettings(reg);
462   loadFileColors(reg);
463   loadRunProgramConfigurations(reg);
464   addDefaultRunProgramConfigurations(reg);
465   setDefaults(enableMuxingTracksByTheseTypes);
466 
467   mtx::chapters::g_chapter_generation_name_template.override(to_utf8(m_chapterNameTemplate));
468 }
469 
470 void
setDefaults(std::optional<QVariant> enableMuxingTracksByTheseTypes)471 Settings::setDefaults(std::optional<QVariant> enableMuxingTracksByTheseTypes) {
472   auto iso639UiLanguage = Q(translation_c::ms_default_iso639_ui_language);
473 
474   if (m_languageShortcuts.isEmpty()) {
475     if (!iso639UiLanguage.isEmpty() && !m_oftenUsedLanguages.contains(iso639UiLanguage))
476       m_languageShortcuts << iso639UiLanguage;
477 
478     m_languageShortcuts
479       << Q("en")                // English
480       << Q("und")               // undetermined
481       << Q("mul")               // multiple languages
482       << Q("zxx");              // no linguistic content
483   }
484 
485   if (m_oftenUsedLanguages.isEmpty()) {
486     m_oftenUsedLanguages
487       << Q("mul")               // multiple languages
488       << Q("zxx")               // no linguistic content
489       << Q("qaa")               // reserved for local use
490       << Q("mis")               // uncoded languages
491       << Q("und")               // undetermined
492       << Q("eng");              // English
493 
494     if (!iso639UiLanguage.isEmpty() && !m_oftenUsedLanguages.contains(iso639UiLanguage))
495       m_oftenUsedLanguages << iso639UiLanguage;
496 
497     m_oftenUsedLanguages.sort();
498   }
499 
500   if (m_oftenUsedCharacterSets.isEmpty())
501     for (auto const &characterSet : g_popular_character_sets)
502       m_oftenUsedCharacterSets << Q(characterSet);
503 
504   if (m_recognizedTrackLanguagesInFileNames.isEmpty())
505     for (auto const &language : mtx::iso639::g_languages)
506       if (!language.alpha_2_code.empty())
507         m_recognizedTrackLanguagesInFileNames << Q(language.alpha_3_code);
508 
509   if (ToParentOfFirstInputFile == m_outputFileNamePolicy) {
510     m_outputFileNamePolicy = ToRelativeOfFirstInputFile;
511     m_relativeOutputDir.setPath(Q(".."));
512   }
513 
514   m_enableMuxingTracksByTheseTypes.clear();
515   if (enableMuxingTracksByTheseTypes)
516     for (auto const &type : enableMuxingTracksByTheseTypes->toList())
517       m_enableMuxingTracksByTheseTypes << static_cast<Merge::TrackType>(type.toInt());
518 
519   else
520     for (int type = static_cast<int>(Merge::TrackType::Min); type <= static_cast<int>(Merge::TrackType::Max); ++type)
521       m_enableMuxingTracksByTheseTypes << static_cast<Merge::TrackType>(type);
522 
523   if (m_mergePredefinedSplitSizes.isEmpty())
524     m_mergePredefinedSplitSizes
525       << Q("350M")
526       << Q("650M")
527       << Q("700M")
528       << Q("703M")
529       << Q("800M")
530       << Q("1000M")
531       << Q("4483M")
532       << Q("8142M");
533 
534   if (m_mergePredefinedSplitDurations.isEmpty())
535     m_mergePredefinedSplitDurations
536       << Q("01:00:00")
537       << Q("1800s");
538 }
539 
540 void
loadDefaults(QSettings & reg)541 Settings::loadDefaults(QSettings &reg) {
542   reg.beginGroup(s_grpDefaults);
543   m_defaultAudioTrackLanguage          = mtx::bcp47::language_c::parse(to_utf8(reg.value(s_valDefaultAudioTrackLanguage,    Q("und")).toString()));
544   m_defaultVideoTrackLanguage          = mtx::bcp47::language_c::parse(to_utf8(reg.value(s_valDefaultVideoTrackLanguage,    Q("und")).toString()));
545   m_defaultSubtitleTrackLanguage       = mtx::bcp47::language_c::parse(to_utf8(reg.value(s_valDefaultSubtitleTrackLanguage, Q("und")).toString()));
546   m_whenToSetDefaultLanguage           = static_cast<SetDefaultLanguagePolicy>(reg.value(s_valWhenToSetDefaultLanguage,     static_cast<int>(SetDefaultLanguagePolicy::IfAbsentOrUndetermined)).toInt());
547   m_defaultChapterLanguage             = mtx::bcp47::language_c::parse(to_utf8(reg.value(s_valDefaultChapterLanguage,       Q("und")).toString()));
548   m_defaultSubtitleCharset             = reg.value(s_valDefaultSubtitleCharset).toString();
549   m_defaultAdditionalMergeOptions      = reg.value(s_valDefaultAdditionalMergeOptions).toString();
550   reg.endGroup();               // defaults
551 }
552 
553 void
loadDerivingTrackLanguagesSettings(QSettings & reg)554 Settings::loadDerivingTrackLanguagesSettings(QSettings &reg) {
555   reg.beginGroup(s_grpSettings);
556   reg.beginGroup(s_grpDerivingTrackLanguagesFromFileNames);
557 
558   m_deriveAudioTrackLanguageFromFileNamePolicy          = static_cast<DeriveLanguageFromFileNamePolicy>(reg.value(s_valAudioPolicy,    static_cast<int>(DeriveLanguageFromFileNamePolicy::IfAbsentOrUndetermined)).toInt());
559   m_deriveVideoTrackLanguageFromFileNamePolicy          = static_cast<DeriveLanguageFromFileNamePolicy>(reg.value(s_valVideoPolicy,    static_cast<int>(DeriveLanguageFromFileNamePolicy::Never)).toInt());
560   m_deriveSubtitleTrackLanguageFromFileNamePolicy       = static_cast<DeriveLanguageFromFileNamePolicy>(reg.value(s_valSubtitlePolicy, static_cast<int>(DeriveLanguageFromFileNamePolicy::IfAbsentOrUndetermined)).toInt());
561   m_boundaryCharsForDerivingTrackLanguagesFromFileNames = reg.value(s_valBoundaryChars,                                                defaultBoundaryCharsForDerivingLanguageFromFileName()).toString();
562   m_recognizedTrackLanguagesInFileNames                 = reg.value(s_valRecognizedTrackLanguagesInFileNames).toStringList();
563 
564   reg.endGroup();
565   reg.endGroup();
566 }
567 
568 void
loadSplitterSizes(QSettings & reg)569 Settings::loadSplitterSizes(QSettings &reg) {
570   reg.beginGroup(s_grpSplitterSizes);
571 
572   m_splitterSizes.clear();
573   for (auto const &name : reg.childKeys()) {
574     auto sizes = reg.value(name).toList();
575     for (auto const &size : sizes)
576       m_splitterSizes[name] << size.toInt();
577   }
578 
579   reg.endGroup();               // splitterSizes
580 }
581 
582 void
loadDefaultInfoJobSettings(QSettings & reg)583 Settings::loadDefaultInfoJobSettings(QSettings &reg) {
584   reg.beginGroup(s_grpSettings);
585   reg.beginGroup(s_grpInfo);
586   reg.beginGroup(s_grpDefaultJobSettings);
587 
588   auto &s          = m_defaultInfoJobSettings;
589   s.m_mode         = static_cast<Info::JobSettings::Mode>(     reg.value(s_valMode,      static_cast<int>(Info::JobSettings::Mode::Tree))                   .toInt());
590   s.m_verbosity    = static_cast<Info::JobSettings::Verbosity>(reg.value(s_valVerbosity, static_cast<int>(Info::JobSettings::Verbosity::StopAtFirstCluster)).toInt());
591   s.m_hexDumps     = static_cast<Info::JobSettings::HexDumps>( reg.value(s_valHexDumps,  static_cast<int>(Info::JobSettings::HexDumps::None))               .toInt());
592   s.m_checksums    = reg.value(s_valChecksums).toBool();
593   s.m_trackInfo    = reg.value(s_valTrackInfo).toBool();
594   s.m_hexPositions = reg.value(s_valHexPositions).toBool();
595 
596   reg.endGroup();
597   reg.endGroup();
598   reg.endGroup();
599 }
600 
601 void
loadRunProgramConfigurations(QSettings & reg)602 Settings::loadRunProgramConfigurations(QSettings &reg) {
603   m_runProgramConfigurations.clear();
604 
605   reg.beginGroup(s_grpRunProgramConfigurations);
606 
607   auto groups = reg.childGroups();
608   groups.sort();
609 
610   for (auto const &group : groups) {
611     auto cfg = std::make_shared<RunProgramConfig>();
612 
613     reg.beginGroup(group);
614     cfg->m_active      = reg.value(s_valActive, true).toBool();
615     cfg->m_name        = reg.value(s_valName).toString();
616     auto type          = reg.value(s_valType, static_cast<int>(RunProgramType::ExecuteProgram)).toInt();
617     cfg->m_type        = (type > static_cast<int>(RunProgramType::Min)) && (type < static_cast<int>(RunProgramType::Max)) ? static_cast<RunProgramType>(type) : RunProgramType::Default;
618     cfg->m_forEvents   = static_cast<RunProgramForEvents>(reg.value(s_valForEvents).toInt());
619     cfg->m_commandLine = reg.value(s_valCommandLine).toStringList();
620     cfg->m_audioFile   = reg.value(s_valAudioFile).toString();
621     cfg->m_volume      = std::min(reg.value(s_valVolume, 50).toUInt(), 100u);
622     reg.endGroup();
623 
624     if (!cfg->m_active || cfg->isValid())
625       m_runProgramConfigurations << cfg;
626   }
627 
628   reg.endGroup();               // runProgramConfigurations
629 }
630 
631 void
loadFileColors(QSettings & reg)632 Settings::loadFileColors(QSettings &reg) {
633   reg.beginGroup(s_grpSettings);
634 
635   m_mergeUseFileAndTrackColors = reg.value(s_valMergeUseFileAndTrackColors, true).toBool();
636 
637   reg.beginGroup(s_grpFileColors);
638 
639   auto childKeys = reg.childKeys();
640   mtx::sort::naturally(childKeys.begin(), childKeys.end());
641 
642   m_mergeFileColors.clear();
643   m_mergeFileColors.reserve(childKeys.size());
644 
645   for (auto const &key : childKeys)
646     m_mergeFileColors << reg.value(key).value<QColor>();
647 
648   reg.endGroup();               // fileColors
649   reg.endGroup();               // settings
650 
651   if (m_mergeFileColors.isEmpty())
652     m_mergeFileColors = defaultFileColors();
653 }
654 
655 void
addDefaultRunProgramConfigurationForType(QSettings & reg,RunProgramType type,std::function<void (RunProgramConfig &)> const & modifier)656 Settings::addDefaultRunProgramConfigurationForType(QSettings &reg,
657                                                    RunProgramType type,
658                                                    std::function<void(RunProgramConfig &)> const &modifier) {
659   auto guard = Q("addedDefaultConfigurationType%1").arg(static_cast<int>(type));
660 
661   if (reg.value(guard).toBool() || !App::programRunner().isRunProgramTypeSupported(type))
662     return;
663 
664   auto cfg      = std::make_shared<RunProgramConfig>();
665 
666   cfg->m_active = true;
667   cfg->m_type   = type;
668 
669   if (modifier)
670     modifier(*cfg);
671 
672   if (cfg->isValid()) {
673     m_runProgramConfigurations << cfg;
674     reg.setValue(guard, true);
675   }
676 }
677 
678 void
addDefaultRunProgramConfigurations(QSettings & reg)679 Settings::addDefaultRunProgramConfigurations(QSettings &reg) {
680   reg.beginGroup(s_grpRunProgramConfigurations);
681 
682   auto numConfigurationsBefore = m_runProgramConfigurations.count();
683 
684   addDefaultRunProgramConfigurationForType(reg, RunProgramType::PlayAudioFile, [](RunProgramConfig &cfg) { cfg.m_audioFile = App::programRunner().defaultAudioFileName(); });
685   addDefaultRunProgramConfigurationForType(reg, RunProgramType::SleepComputer);
686   addDefaultRunProgramConfigurationForType(reg, RunProgramType::HibernateComputer);
687   addDefaultRunProgramConfigurationForType(reg, RunProgramType::ShutDownComputer);
688   addDefaultRunProgramConfigurationForType(reg, RunProgramType::DeleteSourceFiles, [](RunProgramConfig &cfg) { cfg.m_active = false; });
689 
690   auto changed = fixDefaultAudioFileNameBug();
691 
692   if ((numConfigurationsBefore != m_runProgramConfigurations.count()) || changed)
693     saveRunProgramConfigurations(reg);
694 
695   reg.endGroup();               // runProgramConfigurations
696 }
697 
698 bool
fixDefaultAudioFileNameBug()699 Settings::fixDefaultAudioFileNameBug() {
700 #if defined(SYS_WINDOWS)
701   // In version v11.0.0 the default audio file name is wrong:
702   // <MTX_INSTALLATION_DIRECTORY>\sounds\… instead of
703   // <MTX_INSTALLATION_DIRECTORY>\data\sounds\… where the default
704   // sound files are actually installed. As each configuration is only
705   // added once, update an existing configuration to the actual path.
706   QRegularExpression wrongFileNameRE{"<MTX_INSTALLATION_DIRECTORY>[/\\\\]sounds[/\\\\]finished-1\\.ogg"};
707   auto changed = false;
708 
709   for (auto const &config : m_runProgramConfigurations) {
710     if (   (config->m_type != RunProgramType::PlayAudioFile)
711         || !config->m_audioFile.contains(wrongFileNameRE))
712       continue;
713 
714     config->m_audioFile = App::programRunner().defaultAudioFileName();
715     changed             = true;
716   }
717 
718   return changed;
719 
720 #else
721   return false;
722 #endif
723 }
724 
725 QString
actualMkvmergeExe() const726 Settings::actualMkvmergeExe()
727   const {
728   return exeWithPath(Q("mkvmerge"));
729 }
730 
731 void
save() const732 Settings::save()
733   const {
734   auto regPtr = registry();
735   auto &reg   = *regPtr;
736 
737   QVariantList enableMuxingTracksByTheseTypes;
738   for (auto type : m_enableMuxingTracksByTheseTypes)
739     enableMuxingTracksByTheseTypes << static_cast<int>(type);
740 
741   reg.beginGroup(s_grpInfo);
742   reg.setValue(s_valGuiVersion,                                Q(get_current_version().to_string()));
743   reg.endGroup();
744 
745   reg.beginGroup(s_grpSettings);
746   reg.setValue(s_valPriority,                                  static_cast<int>(m_priority));
747   reg.setValue(s_valProbeRangePercentage,                      m_probeRangePercentage);
748   reg.setValue(s_valTabPosition,                               static_cast<int>(m_tabPosition));
749   reg.setValue(s_valElideTabHeaderLabels,                      m_elideTabHeaderLabels);
750   reg.setValue(s_valUseLegacyFontMIMETypes,                    m_useLegacyFontMIMETypes);
751   reg.setValue(s_valLastOpenDir,                               m_lastOpenDir.path());
752   reg.setValue(s_valLastOutputDir,                             m_lastOutputDir.path());
753   reg.setValue(s_valLastConfigDir,                             m_lastConfigDir.path());
754 
755   reg.setValue(s_valLanguageShortcuts,                         m_languageShortcuts);
756   reg.setValue(s_valOftenUsedLanguages,                        m_oftenUsedLanguages);
757   reg.setValue(s_valOftenUsedRegions,                          m_oftenUsedRegions);
758   reg.setValue(s_valOftenUsedCharacterSets,                    m_oftenUsedCharacterSets);
759 
760   reg.setValue(s_valOftenUsedLanguagesOnly,                    m_oftenUsedLanguagesOnly);
761   reg.setValue(s_valOftenUsedRegionsOnly,                      m_oftenUsedRegionsOnly);
762   reg.setValue(s_valOftenUsedCharacterSetsOnly,                m_oftenUsedCharacterSetsOnly);
763   reg.setValue(s_valUseISO639_3Languages,                      m_useISO639_3Languages);
764 
765   reg.setValue(s_valScanForPlaylistsPolicy,                    static_cast<int>(m_scanForPlaylistsPolicy));
766   reg.setValue(s_valMinimumPlaylistDuration,                   m_minimumPlaylistDuration);
767 
768   reg.setValue(s_valSetAudioDelayFromFileName,                 m_setAudioDelayFromFileName);
769   reg.setValue(s_valAutoSetFileTitle,                          m_autoSetFileTitle);
770   reg.setValue(s_valAutoClearFileTitle,                        m_autoClearFileTitle);
771   reg.setValue(s_valClearMergeSettings,                        static_cast<int>(m_clearMergeSettings));
772   reg.setValue(s_valDisableCompressionForAllTrackTypes,        m_disableCompressionForAllTrackTypes);
773   reg.setValue(s_valDisableDefaultTrackForSubtitles,           m_disableDefaultTrackForSubtitles);
774   reg.setValue(s_valMergeEnableDialogNormGainRemoval,          m_mergeEnableDialogNormGainRemoval);
775   reg.setValue(s_valMergeAddBlurayCovers,                      m_mergeAddBlurayCovers);
776   reg.setValue(s_valMergeAttachmentsAlwaysSkipForExistingName, m_mergeAttachmentsAlwaysSkipForExistingName);
777   reg.setValue(s_valMergeAlwaysCreateNewSettingsForVideoFiles, m_mergeAlwaysCreateNewSettingsForVideoFiles);
778   reg.setValue(s_valMergeSortFilesTracksByTypeWhenAdding,      m_mergeSortFilesTracksByTypeWhenAdding);
779   reg.setValue(s_valMergeReconstructSequencesWhenAdding,       m_mergeReconstructSequencesWhenAdding);
780   reg.setValue(s_valMergeAlwaysShowOutputFileControls,         m_mergeAlwaysShowOutputFileControls);
781   reg.setValue(s_valMergePredefinedVideoTrackNames,            m_mergePredefinedVideoTrackNames);
782   reg.setValue(s_valMergePredefinedAudioTrackNames,            m_mergePredefinedAudioTrackNames);
783   reg.setValue(s_valMergePredefinedSubtitleTrackNames,         m_mergePredefinedSubtitleTrackNames);
784   reg.setValue(s_valMergePredefinedSplitSizes,                 m_mergePredefinedSplitSizes);
785   reg.setValue(s_valMergePredefinedSplitDurations,             m_mergePredefinedSplitDurations);
786   reg.setValue(s_valMergeLastFixedOutputDirs,                  m_mergeLastFixedOutputDirs.items());
787   reg.setValue(s_valMergeLastOutputDirs,                       m_mergeLastOutputDirs.items());
788   reg.setValue(s_valMergeLastRelativeOutputDirs,               m_mergeLastRelativeOutputDirs.items());
789   reg.setValue(s_valMergeTrackPropertiesLayout,                static_cast<int>(m_mergeTrackPropertiesLayout));
790   reg.setValue(s_valMergeAddingAppendingFilesPolicy,           static_cast<int>(m_mergeAddingAppendingFilesPolicy));
791   reg.setValue(s_valMergeLastAddingAppendingDecision,          static_cast<int>(m_mergeLastAddingAppendingDecision));
792   reg.setValue(s_valMergeDragAndDropFilesPolicy,               static_cast<int>(m_mergeDragAndDropFilesPolicy));
793   reg.setValue(s_valMergeLastDragAndDropFilesDecision,         static_cast<int>(m_mergeLastDragAndDropFilesDecision));
794   reg.setValue(s_valMergeWarnMissingAudioTrack,                static_cast<int>(m_mergeWarnMissingAudioTrack));
795   reg.setValue(s_valHeaderEditorDroppedFilesPolicy,            static_cast<int>(m_headerEditorDroppedFilesPolicy));
796   reg.setValue(s_valHeaderEditorDateTimeInUTC,                 m_headerEditorDateTimeInUTC);
797 
798   reg.setValue(s_valOutputFileNamePolicy,                      static_cast<int>(m_outputFileNamePolicy));
799   reg.setValue(s_valAutoDestinationOnlyForVideoFiles,          m_autoDestinationOnlyForVideoFiles);
800   reg.setValue(s_valMergeSetDestinationFromTitle,              m_mergeSetDestinationFromTitle);
801   reg.setValue(s_valRelativeOutputDir,                         m_relativeOutputDir.path());
802   reg.setValue(s_valFixedOutputDir,                            m_fixedOutputDir.path());
803   reg.setValue(s_valUniqueOutputFileNames,                     m_uniqueOutputFileNames);
804   reg.setValue(s_valAutoClearOutputFileName,                   m_autoClearOutputFileName);
805 
806   reg.setValue(s_valEnableMuxingTracksByLanguage,              m_enableMuxingTracksByLanguage);
807   reg.setValue(s_valEnableMuxingAllVideoTracks,                m_enableMuxingAllVideoTracks);
808   reg.setValue(s_valEnableMuxingAllAudioTracks,                m_enableMuxingAllAudioTracks);
809   reg.setValue(s_valEnableMuxingAllSubtitleTracks,             m_enableMuxingAllSubtitleTracks);
810   reg.setValue(s_valEnableMuxingTracksByTheseLanguages,        m_enableMuxingTracksByTheseLanguages);
811   reg.setValue(s_valEnableMuxingTracksByTheseTypes,            enableMuxingTracksByTheseTypes);
812 
813   reg.setValue(s_valUseDefaultJobDescription,                  m_useDefaultJobDescription);
814   reg.setValue(s_valShowOutputOfAllJobs,                       m_showOutputOfAllJobs);
815   reg.setValue(s_valSwitchToJobOutputAfterStarting,            m_switchToJobOutputAfterStarting);
816   reg.setValue(s_valResetJobWarningErrorCountersOnExit,        m_resetJobWarningErrorCountersOnExit);
817   reg.setValue(s_valRemoveOutputFileOnJobFailure,              m_removeOutputFileOnJobFailure);
818   reg.setValue(s_valJobRemovalPolicy,                          static_cast<int>(m_jobRemovalPolicy));
819   reg.setValue(s_valJobRemovalOnExitPolicy,                    static_cast<int>(m_jobRemovalOnExitPolicy));
820   reg.setValue(s_valMaximumConcurrentJobs,                     m_maximumConcurrentJobs);
821   reg.setValue(s_valRemoveOldJobs,                             m_removeOldJobs);
822   reg.setValue(s_valRemoveOldJobsDays,                         m_removeOldJobsDays);
823 
824   reg.setValue(s_valShowToolSelector,                          m_showToolSelector);
825   reg.setValue(s_valWarnBeforeClosingModifiedTabs,             m_warnBeforeClosingModifiedTabs);
826   reg.setValue(s_valWarnBeforeAbortingJobs,                    m_warnBeforeAbortingJobs);
827   reg.setValue(s_valWarnBeforeOverwriting,                     m_warnBeforeOverwriting);
828   reg.setValue(s_valShowMoveUpDownButtons,                     m_showMoveUpDownButtons);
829   reg.setValue(s_valBCP47LanguageEditingMode,                  static_cast<int>(m_bcp47LanguageEditingMode));
830 
831   reg.setValue(s_valChapterNameTemplate,                       m_chapterNameTemplate);
832   reg.setValue(s_valDropLastChapterFromBlurayPlaylist,         m_dropLastChapterFromBlurayPlaylist);
833   reg.setValue(s_valCeTextFileCharacterSet,                    m_ceTextFileCharacterSet);
834 
835   reg.setValue(s_valUiLocale,                                  m_uiLocale);
836   reg.setValue(s_valUiDisableHighDPIScaling,                   m_uiDisableHighDPIScaling);
837   reg.setValue(s_valUiDisableDarkStyleSheet,                   m_uiDisableDarkStyleSheet);
838   reg.setValue(s_valUiDisableToolTips,                         m_uiDisableToolTips);
839   reg.setValue(s_valUiFontFamily,                              m_uiFontFamily);
840   reg.setValue(s_valUiFontPointSize,                           m_uiFontPointSize);
841   reg.setValue(s_valUiStayOnTop,                               m_uiStayOnTop);
842 
843   reg.setValue(s_valMediaInfoExe,                              m_mediaInfoExe);
844 
845   reg.setValue(s_valShowDebuggingMenu,                         m_showDebuggingMenu);
846 
847   reg.beginGroup(s_grpUpdates);
848   reg.setValue(s_valCheckForUpdates,                           m_checkForUpdates);
849   reg.setValue(s_valLastUpdateCheck,                           m_lastUpdateCheck);
850   reg.endGroup();               // settings.updates
851   reg.endGroup();               // settings
852 
853   saveDefaults(reg);
854   saveDerivingTrackLanguagesSettings(reg);
855   saveSplitterSizes(reg);
856   saveDefaultInfoJobSettings(reg);
857   saveFileColors(reg);
858   saveRunProgramConfigurations(reg);
859 }
860 
861 void
saveDefaults(QSettings & reg) const862 Settings::saveDefaults(QSettings &reg)
863   const {
864   reg.beginGroup(s_grpDefaults);
865   reg.setValue(s_valDefaultAudioTrackLanguage,     Q(m_defaultAudioTrackLanguage.format()));
866   reg.setValue(s_valDefaultVideoTrackLanguage,     Q(m_defaultVideoTrackLanguage.format()));
867   reg.setValue(s_valDefaultSubtitleTrackLanguage,  Q(m_defaultSubtitleTrackLanguage.format()));
868   reg.setValue(s_valWhenToSetDefaultLanguage,      static_cast<int>(m_whenToSetDefaultLanguage));
869   reg.setValue(s_valDefaultChapterLanguage,        Q(m_defaultChapterLanguage.format()));
870   reg.setValue(s_valDefaultSubtitleCharset,        m_defaultSubtitleCharset);
871   reg.setValue(s_valDefaultAdditionalMergeOptions, m_defaultAdditionalMergeOptions);
872   reg.endGroup();               // defaults
873 }
874 
875 void
saveDerivingTrackLanguagesSettings(QSettings & reg) const876 Settings::saveDerivingTrackLanguagesSettings(QSettings &reg)
877   const {
878   reg.beginGroup(s_grpSettings);
879   reg.beginGroup(s_grpDerivingTrackLanguagesFromFileNames);
880 
881   reg.setValue(s_valAudioPolicy,                         static_cast<int>(m_deriveAudioTrackLanguageFromFileNamePolicy));
882   reg.setValue(s_valVideoPolicy,                         static_cast<int>(m_deriveVideoTrackLanguageFromFileNamePolicy));
883   reg.setValue(s_valSubtitlePolicy,                      static_cast<int>(m_deriveSubtitleTrackLanguageFromFileNamePolicy));
884   reg.setValue(s_valBoundaryChars,                       m_boundaryCharsForDerivingTrackLanguagesFromFileNames);
885   reg.setValue(s_valRecognizedTrackLanguagesInFileNames, m_recognizedTrackLanguagesInFileNames);
886 
887   reg.endGroup();
888   reg.endGroup();
889 }
890 
891 void
saveSplitterSizes(QSettings & reg) const892 Settings::saveSplitterSizes(QSettings &reg)
893   const {
894   reg.beginGroup(s_grpSplitterSizes);
895   for (auto const &name : m_splitterSizes.keys()) {
896     auto sizes = QVariantList{};
897     for (auto const &size : m_splitterSizes[name])
898       sizes << size;
899     reg.setValue(name, sizes);
900   }
901   reg.endGroup();               // splitterSizes
902 
903   saveRunProgramConfigurations(reg);
904 }
905 
906 void
saveDefaultInfoJobSettings(QSettings & reg) const907 Settings::saveDefaultInfoJobSettings(QSettings &reg)
908   const {
909   reg.beginGroup(s_grpSettings);
910   reg.beginGroup(s_grpInfo);
911   reg.beginGroup(s_grpDefaultJobSettings);
912 
913   auto &s = m_defaultInfoJobSettings;
914   reg.setValue(s_valMode,         static_cast<int>(s.m_mode));
915   reg.setValue(s_valVerbosity,    static_cast<int>(s.m_verbosity));
916   reg.setValue(s_valHexDumps,     static_cast<int>(s.m_hexDumps));
917   reg.setValue(s_valChecksums,    s.m_checksums);
918   reg.setValue(s_valTrackInfo,    s.m_trackInfo);
919   reg.setValue(s_valHexPositions, s.m_hexPositions);
920 
921   reg.endGroup();
922   reg.endGroup();
923   reg.endGroup();
924 }
925 
926 void
saveRunProgramConfigurations(QSettings & reg) const927 Settings::saveRunProgramConfigurations(QSettings &reg)
928   const {
929   reg.beginGroup(s_grpRunProgramConfigurations);
930 
931   auto groups = reg.childGroups();
932   groups.sort();
933 
934   for (auto const &group : groups)
935     reg.remove(group);
936 
937   auto idx = 0;
938   for (auto const &cfg : m_runProgramConfigurations) {
939     reg.beginGroup(Q("%1").arg(++idx, 4, 10, Q('0')));
940     reg.setValue(s_valActive,      cfg->m_active);
941     reg.setValue(s_valName,        cfg->m_name);
942     reg.setValue(s_valType,        static_cast<int>(cfg->m_type));
943     reg.setValue(s_valForEvents,   static_cast<int>(cfg->m_forEvents));
944     reg.setValue(s_valCommandLine, cfg->m_commandLine);
945     reg.setValue(s_valAudioFile,   cfg->m_audioFile);
946     reg.setValue(s_valVolume,      cfg->m_volume);
947     reg.endGroup();
948   }
949 
950   reg.endGroup();               // runProgramConfigurations
951 }
952 
953 void
saveFileColors(QSettings & reg) const954 Settings::saveFileColors(QSettings &reg)
955   const {
956   reg.beginGroup(s_grpSettings);
957 
958   reg.setValue(s_valMergeUseFileAndTrackColors, m_mergeUseFileAndTrackColors);
959 
960   reg.remove(s_grpFileColors);
961 
962   reg.beginGroup(s_grpFileColors);
963 
964   auto idx = 0;
965   for (auto const &color : m_mergeFileColors)
966     reg.setValue(Q("color%1").arg(idx++), color);
967 
968   reg.endGroup();               // fileColors
969   reg.endGroup();               // settings
970 }
971 
972 QString
priorityAsString() const973 Settings::priorityAsString()
974   const {
975   return LowestPriority == m_priority ? Q("lowest")
976        : LowPriority    == m_priority ? Q("lower")
977        : NormalPriority == m_priority ? Q("normal")
978        : HighPriority   == m_priority ? Q("higher")
979        :                                Q("highest");
980 }
981 
982 QString
exeWithPath(QString const & exe)983 Settings::exeWithPath(QString const &exe) {
984   auto path          = mtx::fs::to_path( to_utf8(exe) );
985   auto program       = path.filename();
986   auto installPath   = mtx::fs::to_path( to_utf8(App::applicationDirPath()) );
987   auto potentialExes = QList<std::filesystem::path>{} << path << (installPath / path) << (installPath / ".." / path);
988 
989 #if defined(SYS_WINDOWS)
990   for (auto &potentialExe : potentialExes)
991     potentialExe.replace_extension(mtx::fs::to_path("exe"));
992 
993   program.replace_extension(mtx::fs::to_path("exe"));
994 #endif  // SYS_WINDOWS
995 
996   for (auto const &potentialExe : potentialExes)
997     if (std::filesystem::is_regular_file(potentialExe))
998       return QDir::toNativeSeparators(to_qs(potentialExe.u8string()));
999 
1000   auto location = QStandardPaths::findExecutable(to_qs(program.u8string()));
1001   if (!location.isEmpty())
1002     return QDir::toNativeSeparators(location);
1003 
1004   return QDir::toNativeSeparators(exe);
1005 }
1006 
1007 void
setValue(QString const & group,QString const & key,QVariant const & value)1008 Settings::setValue(QString const &group,
1009                    QString const &key,
1010                    QVariant const &value) {
1011   withGroup(group, [&key, &value](QSettings &reg) {
1012     reg.setValue(key, value);
1013   });
1014 }
1015 
1016 QVariant
value(QString const & group,QString const & key,QVariant const & defaultValue) const1017 Settings::value(QString const &group,
1018                 QString const &key,
1019                 QVariant const &defaultValue)
1020   const {
1021   auto result = QVariant{};
1022 
1023   withGroup(group, [&key, &defaultValue, &result](QSettings &reg) {
1024     result = reg.value(key, defaultValue);
1025   });
1026 
1027   return result;
1028 }
1029 
1030 void
withGroup(QString const & group,std::function<void (QSettings &)> worker)1031 Settings::withGroup(QString const &group,
1032                     std::function<void(QSettings &)> worker) {
1033   auto reg    = registry();
1034   auto groups = group.split(Q("/"));
1035 
1036   for (auto const &subGroup : groups)
1037     reg->beginGroup(subGroup);
1038 
1039   worker(*reg);
1040 
1041   for (auto idx = groups.size(); idx > 0; --idx)
1042     reg->endGroup();
1043 }
1044 
1045 void
handleSplitterSizes(QSplitter * splitter)1046 Settings::handleSplitterSizes(QSplitter *splitter) {
1047   restoreSplitterSizes(splitter);
1048   connect(splitter, &QSplitter::splitterMoved, this, &Settings::storeSplitterSizes);
1049 }
1050 
1051 void
restoreSplitterSizes(QSplitter * splitter)1052 Settings::restoreSplitterSizes(QSplitter *splitter) {
1053  auto name   = splitter->objectName();
1054  auto &sizes = m_splitterSizes[name];
1055 
1056   if (sizes.isEmpty())
1057     for (auto idx = 0, numWidgets = splitter->count(); idx < numWidgets; ++idx)
1058       sizes << 1;
1059 
1060   splitter->setSizes(sizes);
1061 }
1062 
1063 void
storeSplitterSizes()1064 Settings::storeSplitterSizes() {
1065   auto splitter = dynamic_cast<QSplitter *>(sender());
1066   if (splitter)
1067     m_splitterSizes[ splitter->objectName() ] = splitter->sizes();
1068   else
1069     qDebug() << "storeSplitterSize() signal from non-splitter" << sender() << sender()->objectName();
1070 }
1071 
1072 QString
localeToUse(QString const & requestedLocale) const1073 Settings::localeToUse(QString const &requestedLocale)
1074   const {
1075   auto locale = to_utf8(requestedLocale);
1076 
1077 #if defined(HAVE_LIBINTL_H)
1078   translation_c::initialize_available_translations();
1079 
1080   if (locale.empty())
1081     locale = to_utf8(m_uiLocale);
1082 
1083   if (-1 == translation_c::look_up_translation(locale))
1084     locale = "";
1085 
1086   if (locale.empty()) {
1087     locale = to_utf8(Q(translation_c::get_default_ui_locale()).replace(QRegularExpression{"\\..*"}, {}));
1088     if (-1 == translation_c::look_up_translation(locale))
1089       locale = "";
1090   }
1091 #endif
1092 
1093   return to_qs(locale);
1094 }
1095 
1096 QString
lastConfigDirPath() const1097 Settings::lastConfigDirPath()
1098   const {
1099   return Util::dirPath(m_lastConfigDir);
1100 }
1101 
1102 QString
lastOpenDirPath() const1103 Settings::lastOpenDirPath()
1104   const {
1105   return Util::dirPath(m_lastOpenDir);
1106 }
1107 
1108 void
runOncePerVersion(QString const & topic,std::function<void ()> worker)1109 Settings::runOncePerVersion(QString const &topic,
1110                             std::function<void()> worker) {
1111   auto reg = registry();
1112   auto key = Q("runOncePerVersion/%1").arg(topic);
1113 
1114   auto lastRunInVersion       = reg->value(key).toString();
1115   auto lastRunInVersionNumber = version_number_t{to_utf8(lastRunInVersion)};
1116   auto currentVersionNumber   = get_current_version();
1117 
1118   if (   lastRunInVersionNumber.valid
1119       && !(lastRunInVersionNumber < currentVersionNumber))
1120     return;
1121 
1122   reg->setValue(key, Q(currentVersionNumber.to_string()));
1123 
1124   worker();
1125 }
1126 
1127 QString
determineMediaInfoExePath()1128 Settings::determineMediaInfoExePath() {
1129   auto &cfg          = get();
1130   auto potentialExes = QStringList{exeWithPath(cfg.m_mediaInfoExe.isEmpty() ? Q("mediainfo-gui") : cfg.m_mediaInfoExe)};
1131 
1132 #if defined(SYS_WINDOWS)
1133   potentialExes << QSettings{Q("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\MediaInfo.exe"), QSettings::NativeFormat}.value("Default").toString();
1134 #endif
1135 
1136   potentialExes << exeWithPath(Q("mediainfo"));
1137 
1138   for (auto const &exe : potentialExes)
1139     if (!exe.isEmpty() && QFileInfo{exe}.exists())
1140       return QDir::toNativeSeparators(exe);
1141 
1142   return {};
1143 }
1144 
1145 QString
defaultBoundaryCharsForDerivingLanguageFromFileName()1146 Settings::defaultBoundaryCharsForDerivingLanguageFromFileName() {
1147   return Q("[](){}.+-=#");
1148 }
1149 
1150 QVector<QColor>
defaultFileColors()1151 Settings::defaultFileColors() {
1152   QVector<QColor> colors;
1153 
1154   auto step = 64;
1155 
1156   colors.clear();
1157   colors.reserve(6 * (256 / step));
1158 
1159   for (int value = 255; value > 0; value -= step) {
1160     colors << QColor{0,     value, 0};
1161     colors << QColor{0,     0,     value};
1162     colors << QColor{value, 0,     0};
1163     colors << QColor{value, value, 0};
1164     colors << QColor{value, 0,     value};
1165     colors << QColor{0,     value, value};
1166   }
1167 
1168   return colors;
1169 }
1170 
1171 QColor
nthFileColor(int idx) const1172 Settings::nthFileColor(int idx)
1173   const {
1174   static QVector<QColor> s_additionalColors;
1175 
1176   if (idx < 0)
1177     return {};
1178 
1179   if (idx < m_mergeFileColors.size())
1180     return m_mergeFileColors.at(idx);
1181 
1182   idx -= m_mergeFileColors.size();
1183 
1184   while (idx >= s_additionalColors.size())
1185     s_additionalColors << QColor{random_c::generate_8bits(), random_c::generate_8bits(), random_c::generate_8bits()};
1186 
1187   return s_additionalColors.at(idx);
1188 }
1189 
1190 }
1191