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 ® = *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 ®) {
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 ®) {
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 ®) {
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 ®) {
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 ®) {
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 ®) {
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 ®,
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 ®) {
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 ® = *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 ®)
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 ®)
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 ®)
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 ®)
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 ®)
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 ®)
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 ®) {
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 ®) {
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