1 #include "common/common_pch.h"
2 
3 #include <QDebug>
4 #include <QDir>
5 #include <QDragEnterEvent>
6 #include <QDropEvent>
7 #include <QFileInfo>
8 #include <QList>
9 #include <QMenu>
10 #include <QMessageBox>
11 #include <QProcess>
12 #include <QRegularExpression>
13 #include <QSettings>
14 #include <QSignalBlocker>
15 #include <QString>
16 #include <QThread>
17 #include <QTimer>
18 
19 #include <matroska/KaxSemantic.h>
20 
21 #include "common/iso639.h"
22 #include "common/logger.h"
23 #include "common/qt.h"
24 #include "common/stereo_mode.h"
25 #include "mkvtoolnix-gui/app.h"
26 #include "mkvtoolnix-gui/forms/merge/tab.h"
27 #include "mkvtoolnix-gui/main_window/main_window.h"
28 #include "mkvtoolnix-gui/main_window/select_character_set_dialog.h"
29 #include "mkvtoolnix-gui/merge/enums.h"
30 #include "mkvtoolnix-gui/merge/executable_location_dialog.h"
31 #include "mkvtoolnix-gui/merge/tab.h"
32 #include "mkvtoolnix-gui/merge/tab_p.h"
33 #include "mkvtoolnix-gui/merge/tool.h"
34 #include "mkvtoolnix-gui/util/file.h"
35 #include "mkvtoolnix-gui/util/file_identifier.h"
36 #include "mkvtoolnix-gui/util/file_dialog.h"
37 #include "mkvtoolnix-gui/util/file_type_filter.h"
38 #include "mkvtoolnix-gui/util/header_view_manager.h"
39 #include "mkvtoolnix-gui/util/message_box.h"
40 #include "mkvtoolnix-gui/util/model.h"
41 #include "mkvtoolnix-gui/util/settings.h"
42 #include "mkvtoolnix-gui/util/widget.h"
43 
44 namespace mtx::gui::Merge {
45 
46 using namespace mtx::gui;
47 
48 void
setupControlLists()49 Tab::setupControlLists() {
50   auto &p = *p_func();
51 
52   p.typeIndependentControls << p.ui->generalOptionsBox << p.ui->muxThisLabel << p.ui->muxThis << p.ui->miscellaneousBox << p.ui->additionalTrackOptionsLabel << p.ui->additionalTrackOptions;
53 
54   p.audioControls << p.ui->trackNameLabel << p.ui->trackName << p.ui->trackLanguageLabel << p.ui->trackLanguage << p.ui->defaultTrackFlagLabel << p.ui->defaultTrackFlag << p.ui->forcedTrackFlagLabel << p.ui->forcedTrackFlag
55                   << p.ui->trackEnabledFlagLabel << p.ui->trackEnabledFlag << p.ui->hearingImpairedFlagLabel << p.ui->hearingImpairedFlag << p.ui->visualImpairedFlagLabel << p.ui->visualImpairedFlag
56                   << p.ui->originalFlagLabel << p.ui->originalFlag << p.ui->commentaryFlagLabel << p.ui->commentaryFlag
57                   << p.ui->compressionLabel << p.ui->compression << p.ui->trackTagsLabel << p.ui->trackTags << p.ui->browseTrackTags << p.ui->timestampsAndDefaultDurationBox
58                   << p.ui->delayLabel << p.ui->delay << p.ui->stretchByLabel << p.ui->stretchBy << p.ui->timestampsLabel << p.ui->timestamps << p.ui->browseTimestamps << p.ui->audioPropertiesBox << p.ui->aacIsSBR << p.ui->aacIsSBRLabel
59                   << p.ui->cuesLabel << p.ui->cues << p.ui->propertiesLabel << p.ui->generalOptionsBox << p.ui->reduceToAudioCore << p.ui->removeDialogNormalizationGain;
60 
61   p.videoControls << p.ui->trackNameLabel << p.ui->trackName << p.ui->trackLanguageLabel << p.ui->trackLanguage << p.ui->defaultTrackFlagLabel << p.ui->defaultTrackFlag << p.ui->forcedTrackFlagLabel << p.ui->forcedTrackFlag
62                   << p.ui->trackEnabledFlagLabel << p.ui->trackEnabledFlag << p.ui->hearingImpairedFlagLabel << p.ui->hearingImpairedFlag << p.ui->visualImpairedFlagLabel << p.ui->visualImpairedFlag
63                   << p.ui->originalFlagLabel << p.ui->originalFlag << p.ui->commentaryFlagLabel << p.ui->commentaryFlag
64                   << p.ui->compressionLabel << p.ui->compression << p.ui->trackTagsLabel << p.ui->trackTags << p.ui->browseTrackTags << p.ui->timestampsAndDefaultDurationBox
65                   << p.ui->delayLabel << p.ui->delay << p.ui->stretchByLabel << p.ui->stretchBy << p.ui->defaultDurationLabel << p.ui->defaultDuration << p.ui->timestampsLabel << p.ui->timestamps << p.ui->browseTimestamps
66                   << p.ui->videoPropertiesBox << p.ui->setAspectRatio << p.ui->aspectRatio << p.ui->setDisplayWidthHeight << p.ui->displayWidth << p.ui->displayDimensionsXLabel << p.ui->displayHeight << p.ui->stereoscopyLabel
67                   << p.ui->stereoscopy << p.ui->croppingLabel << p.ui->cropping << p.ui->cuesLabel << p.ui->cues
68                   << p.ui->propertiesLabel << p.ui->generalOptionsBox << p.ui->fixBitstreamTimingInfo;
69 
70   p.subtitleControls << p.ui->trackNameLabel << p.ui->trackName << p.ui->trackLanguageLabel << p.ui->trackLanguage << p.ui->defaultTrackFlagLabel << p.ui->defaultTrackFlag << p.ui->forcedTrackFlagLabel << p.ui->forcedTrackFlag
71                      << p.ui->trackEnabledFlagLabel << p.ui->trackEnabledFlag << p.ui->hearingImpairedFlagLabel << p.ui->hearingImpairedFlag << p.ui->visualImpairedFlagLabel << p.ui->visualImpairedFlag << p.ui->textDescriptionsFlagLabel << p.ui->textDescriptionsFlag
72                      << p.ui->originalFlagLabel << p.ui->originalFlag << p.ui->commentaryFlagLabel << p.ui->commentaryFlag
73                      << p.ui->compressionLabel << p.ui->compression << p.ui->trackTagsLabel << p.ui->trackTags << p.ui->browseTrackTags << p.ui->timestampsAndDefaultDurationBox
74                      << p.ui->delayLabel << p.ui->delay << p.ui->stretchByLabel << p.ui->stretchBy << p.ui->timestampsLabel << p.ui->timestamps << p.ui->browseTimestamps
75                      << p.ui->subtitleAndChapterPropertiesBox << p.ui->characterSetLabel << p.ui->subtitleCharacterSet << p.ui->cuesLabel << p.ui->cues
76                      << p.ui->propertiesLabel << p.ui->generalOptionsBox;
77 
78   p.chapterControls << p.ui->timestampsAndDefaultDurationBox << p.ui->delayLabel << p.ui->delay << p.ui->stretchByLabel << p.ui->stretchBy
79                     << p.ui->subtitleAndChapterPropertiesBox << p.ui->characterSetLabel << p.ui->subtitleCharacterSet << p.ui->propertiesLabel << p.ui->generalOptionsBox;
80 
81   p.allInputControls << p.ui->muxThisLabel << p.ui->muxThis << p.ui->trackNameLabel << p.ui->trackName << p.ui->trackLanguageLabel << p.ui->trackLanguage << p.ui->defaultTrackFlagLabel << p.ui->defaultTrackFlag
82                      << p.ui->forcedTrackFlagLabel << p.ui->forcedTrackFlag << p.ui->trackEnabledFlagLabel << p.ui->trackEnabledFlag
83                      << p.ui->hearingImpairedFlagLabel << p.ui->hearingImpairedFlag << p.ui->visualImpairedFlagLabel << p.ui->visualImpairedFlag << p.ui->textDescriptionsFlagLabel << p.ui->textDescriptionsFlag
84                      << p.ui->originalFlagLabel << p.ui->originalFlag << p.ui->commentaryFlagLabel << p.ui->commentaryFlag
85                      << p.ui->compressionLabel << p.ui->compression << p.ui->trackTagsLabel << p.ui->trackTags << p.ui->browseTrackTags << p.ui->timestampsAndDefaultDurationBox
86                      << p.ui->delayLabel << p.ui->delay << p.ui->stretchByLabel << p.ui->stretchBy << p.ui->defaultDurationLabel << p.ui->defaultDuration << p.ui->timestampsLabel << p.ui->timestamps << p.ui->browseTimestamps
87                      << p.ui->videoPropertiesBox << p.ui->setAspectRatio << p.ui->aspectRatio << p.ui->setDisplayWidthHeight << p.ui->displayWidth << p.ui->displayDimensionsXLabel << p.ui->displayHeight << p.ui->stereoscopyLabel
88                      << p.ui->stereoscopy << p.ui->croppingLabel << p.ui->cropping << p.ui->audioPropertiesBox << p.ui->aacIsSBR << p.ui->subtitleAndChapterPropertiesBox << p.ui->characterSetLabel << p.ui->subtitleCharacterSet
89                      << p.ui->miscellaneousBox << p.ui->cuesLabel << p.ui->cues << p.ui->additionalTrackOptionsLabel << p.ui->additionalTrackOptions
90                      << p.ui->propertiesLabel << p.ui->generalOptionsBox << p.ui->fixBitstreamTimingInfo << p.ui->reduceToAudioCore << p.ui->removeDialogNormalizationGain;
91 
92   p.comboBoxControls << p.ui->muxThis << p.ui->defaultTrackFlag << p.ui->forcedTrackFlag << p.ui->trackEnabledFlag << p.ui->compression << p.ui->cues << p.ui->stereoscopy << p.ui->aacIsSBR << p.ui->subtitleCharacterSet
93                      << p.ui->hearingImpairedFlag << p.ui->visualImpairedFlag << p.ui->textDescriptionsFlag<< p.ui->originalFlag << p.ui->commentaryFlag;
94 
95   p.notIfAppendingControls << p.ui->trackLanguageLabel       << p.ui->trackLanguage           << p.ui->trackNameLabel              << p.ui->trackName          << p.ui->defaultTrackFlagLabel     << p.ui->defaultTrackFlag
96                            << p.ui->trackEnabledFlagLabel    << p.ui->trackEnabledFlag
97                            << p.ui->forcedTrackFlagLabel     << p.ui->forcedTrackFlag         << p.ui->originalFlagLabel           << p.ui->originalFlag       << p.ui->commentaryFlagLabel       << p.ui->commentaryFlag
98                            << p.ui->hearingImpairedFlagLabel << p.ui->hearingImpairedFlag     << p.ui->visualImpairedFlagLabel     << p.ui->visualImpairedFlag << p.ui->textDescriptionsFlagLabel << p.ui->textDescriptionsFlag
99                            << p.ui->compressionLabel         << p.ui->compression             << p.ui->trackTagsLabel              << p.ui->trackTags          << p.ui->browseTrackTags
100                            << p.ui->defaultDurationLabel     << p.ui->defaultDuration         << p.ui->fixBitstreamTimingInfo      << p.ui->setAspectRatio     << p.ui->setDisplayWidthHeight     << p.ui->aspectRatio
101                            << p.ui->displayWidth             << p.ui->displayDimensionsXLabel << p.ui->displayHeight               << p.ui->stereoscopyLabel   << p.ui->stereoscopy
102                            << p.ui->croppingLabel            << p.ui->cropping                << p.ui->aacIsSBR
103                            << p.ui->cuesLabel                << p.ui->cues                    << p.ui->additionalTrackOptionsLabel << p.ui->additionalTrackOptions;
104 }
105 
106 void
setupMoveUpDownButtons()107 Tab::setupMoveUpDownButtons() {
108   auto &p      = *p_func();
109   auto show    = Util::Settings::get().m_showMoveUpDownButtons;
110   auto widgets = QList<QWidget *>{} << p.ui->moveFilesButtons << p.ui->moveTracksButtons << p.ui->moveAttachmentsButtons;
111 
112   for (auto const &widget : widgets)
113     widget->setVisible(show);
114 }
115 
116 void
setupInputLayout()117 Tab::setupInputLayout() {
118   auto const layout = Util::Settings::get().m_mergeTrackPropertiesLayout;
119 
120   if (layout == Util::Settings::TrackPropertiesLayout::HorizontalScrollArea)
121     setupHorizontalScrollAreaInputLayout();
122 
123   else if (layout == Util::Settings::TrackPropertiesLayout::HorizontalTwoColumns)
124     setupHorizontalTwoColumnsInputLayout();
125 
126   else
127     setupVerticalTabWidgetInputLayout();
128 }
129 
130 void
setupHorizontalScrollAreaInputLayout()131 Tab::setupHorizontalScrollAreaInputLayout() {
132   auto &p = *p_func();
133 
134   if (p.ui->wProperties->isVisible() && (0 == p.ui->propertiesStack->currentIndex()))
135     return;
136 
137   p.ui->twProperties->hide();
138   p.ui->wProperties->show();
139 
140   auto layout = qobject_cast<QBoxLayout *>(p.ui->scrollAreaWidgetContents->layout());
141 
142   Q_ASSERT(!!layout);
143 
144   auto widgets = QWidgetList{} << p.ui->generalOptionsBox << p.ui->timestampsAndDefaultDurationBox << p.ui->videoPropertiesBox << p.ui->audioPropertiesBox << p.ui->subtitleAndChapterPropertiesBox << p.ui->miscellaneousBox;
145   for (auto const &widget : widgets)
146     widget->setParent(p.ui->scrollAreaWidgetContents);
147 
148   layout->insertWidget(0, p.ui->generalOptionsBox);
149   layout->insertWidget(1, p.ui->timestampsAndDefaultDurationBox);
150   layout->insertWidget(2, p.ui->videoPropertiesBox);
151   layout->insertWidget(3, p.ui->audioPropertiesBox);
152   layout->insertWidget(4, p.ui->subtitleAndChapterPropertiesBox);
153   layout->insertWidget(5, p.ui->miscellaneousBox);
154 
155   p.ui->propertiesColumn1->updateGeometry();
156   p.ui->propertiesColumn2->updateGeometry();
157 
158   p.ui->propertiesStack->setCurrentIndex(0);
159 
160   Util::autoGroupBoxGridLayout(*p.ui->generalOptionsBox, 1);
161 }
162 
163 void
setupHorizontalTwoColumnsInputLayout()164 Tab::setupHorizontalTwoColumnsInputLayout() {
165   auto &p = *p_func();
166 
167   if (p.ui->wProperties->isVisible() && (1 == p.ui->propertiesStack->currentIndex()))
168     return;
169 
170   p.ui->twProperties->hide();
171   p.ui->wProperties->show();
172   p.ui->propertiesStack->setCurrentIndex(1);
173 
174   auto moveTo = [](QWidget *column, int position, QWidget *widget) {
175     auto layout = qobject_cast<QBoxLayout *>(column->layout());
176     Q_ASSERT(!!layout);
177 
178     widget->setParent(column);
179     layout->insertWidget(position, widget);
180   };
181 
182   moveTo(p.ui->propertiesColumn1, 0, p.ui->generalOptionsBox);
183   moveTo(p.ui->propertiesColumn1, 1, p.ui->timestampsAndDefaultDurationBox);
184   moveTo(p.ui->propertiesColumn2, 0, p.ui->videoPropertiesBox);
185   moveTo(p.ui->propertiesColumn2, 1, p.ui->audioPropertiesBox);
186   moveTo(p.ui->propertiesColumn2, 2, p.ui->subtitleAndChapterPropertiesBox);
187   moveTo(p.ui->propertiesColumn2, 3, p.ui->miscellaneousBox);
188 
189   Util::autoGroupBoxGridLayout(*p.ui->generalOptionsBox, 1);
190 }
191 
192 void
setupVerticalTabWidgetInputLayout()193 Tab::setupVerticalTabWidgetInputLayout() {
194   auto &p = *p_func();
195 
196   if (p.ui->twProperties->isVisible())
197     return;
198 
199   p.ui->twProperties->show();
200   p.ui->wProperties->hide();
201 
202   auto moveTo = [](QWidget *page, int position, QWidget *widget) {
203     auto layout = qobject_cast<QBoxLayout *>(page->layout());
204     Q_ASSERT(!!layout);
205 
206     widget->setParent(page);
207     layout->insertWidget(position, widget);
208   };
209 
210   moveTo(p.ui->generalOptionsPage,                 0, p.ui->generalOptionsBox);
211   moveTo(p.ui->timestampsAndDefaultDurationPage,   0, p.ui->timestampsAndDefaultDurationBox);
212   moveTo(p.ui->videoPropertiesPage,                0, p.ui->videoPropertiesBox);
213   moveTo(p.ui->audioSubtitleChapterPropertiesPage, 0, p.ui->audioPropertiesBox);
214   moveTo(p.ui->audioSubtitleChapterPropertiesPage, 1, p.ui->subtitleAndChapterPropertiesBox);
215   moveTo(p.ui->miscellaneousPage,                  0, p.ui->miscellaneousBox);
216 
217   p.ui->propertiesColumn1->updateGeometry();
218   p.ui->propertiesColumn2->updateGeometry();
219 
220   Util::autoGroupBoxGridLayout(*p.ui->generalOptionsBox, 2);
221 }
222 
223 void
setupInputControls()224 Tab::setupInputControls() {
225   auto &p   = *p_func();
226   auto &cfg = Util::Settings::get();
227 
228   p.ui->twProperties->hide();
229 
230   setupControlLists();
231   setupMoveUpDownButtons();
232   setupInputLayout();
233 
234   p.ui->files->setModel(p.filesModel);
235   p.ui->tracks->setModel(p.tracksModel);
236   p.ui->tracks->enterActivatesAllSelected(true);
237 
238   cfg.handleSplitterSizes(p.ui->mergeInputSplitter);
239   cfg.handleSplitterSizes(p.ui->mergeFilesTracksSplitter);
240 
241   p.ui->trackName->lineEdit()->setClearButtonEnabled(true);
242   p.ui->defaultDuration->lineEdit()->setClearButtonEnabled(true);
243   p.ui->aspectRatio->lineEdit()->setClearButtonEnabled(true);
244 
245   // Track & chapter character set
246   p.ui->subtitleCharacterSet->setup(true);
247   p.ui->chapterCharacterSet->setup(true);
248 
249   // Stereoscopy
250   p.ui->stereoscopy->addItem(Q(""), 0);
251   for (auto idx = 0u, end = stereo_mode_c::max_index(); idx <= end; ++idx)
252     p.ui->stereoscopy->addItem(QString{}, idx + 1);
253 
254   p.ui->defaultTrackFlag->addItem(QString{}, true);
255   p.ui->defaultTrackFlag->addItem(QString{}, false);
256 
257   // Originally the "forced display" flag's options where ordered "off,
258   // on"; now they're ordered "yes, no" for consistency with other
259   // flags, requiring the values to be reversed.
260   p.ui->forcedTrackFlag->addItem(QString{}, 1);
261   p.ui->forcedTrackFlag->addItem(QString{}, 0);
262   p.ui->forcedTrackFlag->setCurrentIndex(1);
263 
264   for (auto &comboBox : std::vector<QComboBox *>{p.ui->muxThis, p.ui->trackEnabledFlag, p.ui->hearingImpairedFlag, p.ui->visualImpairedFlag, p.ui->textDescriptionsFlag, p.ui->originalFlag, p.ui->commentaryFlag})
265     for (auto idx = 0; idx < 2; ++idx)
266       comboBox->addItem(QString{}, idx == 0);
267 
268   for (auto idx = 0; idx < 3; ++idx)
269     p.ui->compression->addItem(QString{}, idx);
270 
271   for (auto idx = 0; idx < 4; ++idx)
272     p.ui->cues->addItem(QString{}, idx);
273 
274   for (auto idx = 0; idx < 3; ++idx)
275     p.ui->aacIsSBR->addItem(QString{}, idx);
276 
277   for (auto const &control : p.comboBoxControls) {
278     control->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
279     Util::fixComboBoxViewWidth(*control);
280   }
281 
282   // "files" context menu
283   p.filesMenu->addAction(p.addFilesAction);
284   p.filesMenu->addAction(p.appendFilesAction);
285   p.filesMenu->addAction(p.addAdditionalPartsAction);
286   p.filesMenu->addSeparator();
287   p.filesMenu->addAction(p.removeFilesAction);
288   p.filesMenu->addAction(p.removeAllFilesAction);
289   p.filesMenu->addSeparator();
290   p.filesMenu->addAction(p.setDestinationFileNameAction);
291   p.filesMenu->addSeparator();
292   p.filesMenu->addAction(p.openFilesInMediaInfoAction);
293   p.filesMenu->addSeparator();
294   p.filesMenu->addAction(p.selectTracksFromFilesAction);
295 
296   p.addFilesAction->setIcon(QIcon{Q(":/icons/16x16/list-add.png")});
297   p.appendFilesAction->setIcon(QIcon{Q(":/icons/16x16/distribute-horizontal-x.png")});
298   p.addAdditionalPartsAction->setIcon(QIcon{Q(":/icons/16x16/distribute-horizontal-margin.png")});
299   p.removeFilesAction->setIcon(QIcon{Q(":/icons/16x16/list-remove.png")});
300   p.openFilesInMediaInfoAction->setIcon(QIcon{Q(":/icons/16x16/documentinfo.png")});
301 
302   // "tracks" context menu
303   p.tracksMenu->addAction(p.selectAllTracksAction);
304   p.tracksMenu->addMenu(p.selectTracksOfTypeMenu);
305   p.tracksMenu->addAction(p.enableAllTracksAction);
306   p.tracksMenu->addAction(p.disableAllTracksAction);
307   p.tracksMenu->addSeparator();
308   p.tracksMenu->addAction(p.openTracksInMediaInfoAction);
309 
310   p.selectTracksOfTypeMenu->addAction(p.selectAllVideoTracksAction);
311   p.selectTracksOfTypeMenu->addAction(p.selectAllAudioTracksAction);
312   p.selectTracksOfTypeMenu->addAction(p.selectAllSubtitlesTracksAction);
313 
314   p.selectAllTracksAction->setIcon(QIcon{Q(":/icons/16x16/edit-select-all.png")});
315   p.enableAllTracksAction->setIcon(QIcon{Q(":/icons/16x16/checkbox.png")});
316   p.disableAllTracksAction->setIcon(QIcon{Q(":/icons/16x16/checkbox-unchecked.png")});
317   p.openTracksInMediaInfoAction->setIcon(QIcon{Q(":/icons/16x16/documentinfo.png")});
318 
319   p.selectAllVideoTracksAction->setIcon(QIcon{Q(":/icons/16x16/tool-animator.png")});
320   p.selectAllAudioTracksAction->setIcon(QIcon{Q(":/icons/16x16/knotify.png")});
321   p.selectAllSubtitlesTracksAction->setIcon(QIcon{Q(":/icons/16x16/subtitles.png")});
322 
323   // "add source files" menu
324   p.addFilesMenu->addAction(p.addFilesAction2);
325   p.addFilesMenu->addAction(p.appendFilesAction2);
326   p.addFilesMenu->addAction(p.addAdditionalPartsAction2);
327   p.ui->addFiles->setMenu(p.addFilesMenu);
328 
329   // "start muxing" menu
330   p.startMuxingMenu->addAction(p.startMuxingLeaveAsIs);
331   p.startMuxingMenu->addAction(p.startMuxingCreateNewSettings);
332   p.startMuxingMenu->addAction(p.startMuxingRemoveInputFiles);
333   p.startMuxingMenu->addAction(p.startMuxingCloseSettings);
334   p.ui->startMuxing->setMenu(p.startMuxingMenu);
335 
336   // "add to job queue" menu
337   p.addToJobQueueMenu->addAction(p.addToJobQueueLeaveAsIs);
338   p.addToJobQueueMenu->addAction(p.addToJobQueueCreateNewSettings);
339   p.addToJobQueueMenu->addAction(p.addToJobQueueRemoveInputFiles);
340   p.addToJobQueueMenu->addAction(p.addToJobQueueCloseSettings);
341   p.ui->addToJobQueue->setMenu(p.addToJobQueueMenu);
342 
343   p.addFilesAction2->setIcon(QIcon{Q(":/icons/16x16/list-add.png")});
344   p.appendFilesAction2->setIcon(QIcon{Q(":/icons/16x16/distribute-horizontal-x.png")});
345   p.addAdditionalPartsAction2->setIcon(QIcon{Q(":/icons/16x16/distribute-horizontal-margin.png")});
346 
347   // Connect Q_SIGNALS & Q_SLOTS.
348   auto mw = MainWindow::get();
349   using CMSAction = Util::Settings::ClearMergeSettingsAction;
350 
351   connect(p.ui->aacIsSBR,                      static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,                       &Tab::onAacIsSBRChanged);
352   connect(p.ui->addFiles,                      &QToolButton::clicked,                                                  this,                       &Tab::onAddFiles);
353   connect(p.ui->addToJobQueue,                 &QPushButton::clicked,                                                  this,                       [=]() { addToJobQueue(false); });
354   connect(p.ui->additionalTrackOptions,        &QLineEdit::textChanged,                                                this,                       &Tab::onAdditionalTrackOptionsChanged);
355   connect(p.ui->aspectRatio,                   &QComboBox::currentTextChanged,                                         this,                       &Tab::onAspectRatioChanged);
356   connect(p.ui->aspectRatio,                   &QComboBox::editTextChanged,                                            this,                       &Tab::onAspectRatioChanged);
357   connect(p.ui->browseTimestamps,              &QPushButton::clicked,                                                  this,                       &Tab::onBrowseTimestamps);
358   connect(p.ui->browseTrackTags,               &QPushButton::clicked,                                                  this,                       &Tab::onBrowseTrackTags);
359   connect(p.ui->compression,                   static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,                       &Tab::onCompressionChanged);
360   connect(p.ui->cropping,                      &QLineEdit::textChanged,                                                this,                       &Tab::onCroppingChanged);
361   connect(p.ui->cues,                          static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,                       &Tab::onCuesChanged);
362   connect(p.ui->defaultDuration,               &QComboBox::currentTextChanged,                                         this,                       &Tab::onDefaultDurationChanged);
363   connect(p.ui->defaultDuration,               &QComboBox::editTextChanged,                                            this,                       &Tab::onDefaultDurationChanged);
364   connect(p.ui->defaultTrackFlag,              static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,                       &Tab::onDefaultTrackFlagChanged);
365   connect(p.ui->delay,                         &QLineEdit::textChanged,                                                this,                       &Tab::onDelayChanged);
366   connect(p.ui->displayHeight,                 &QLineEdit::textChanged,                                                this,                       &Tab::onDisplayHeightChanged);
367   connect(p.ui->displayWidth,                  &QLineEdit::textChanged,                                                this,                       &Tab::onDisplayWidthChanged);
368   connect(p.ui->editAdditionalOptions,         &QPushButton::clicked,                                                  this,                       &Tab::onEditAdditionalOptions);
369   connect(p.ui->files,                         &Util::BasicTreeView::ctrlDownPressed,                                  this,                       &Tab::onMoveFilesDown);
370   connect(p.ui->files,                         &Util::BasicTreeView::ctrlUpPressed,                                    this,                       &Tab::onMoveFilesUp);
371   connect(p.ui->files,                         &Util::BasicTreeView::customContextMenuRequested,                       this,                       &Tab::showFilesContextMenu);
372   connect(p.ui->files,                         &Util::BasicTreeView::deletePressed,                                    this,                       &Tab::onRemoveFiles);
373   connect(p.ui->files,                         &Util::BasicTreeView::insertPressed,                                    this,                       &Tab::onAddFiles);
374   connect(p.ui->files->selectionModel(),       &QItemSelectionModel::selectionChanged,                                 p.filesModel,               &SourceFileModel::updateSelectionStatus);
375   connect(p.ui->files->selectionModel(),       &QItemSelectionModel::selectionChanged,                                 this,                       &Tab::enableMoveFilesButtons);
376   connect(p.ui->fixBitstreamTimingInfo,        &QCheckBox::toggled,                                                    this,                       &Tab::onFixBitstreamTimingInfoChanged);
377   connect(p.ui->forcedTrackFlag,               static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,                       &Tab::onForcedTrackFlagChanged);
378   connect(p.ui->trackEnabledFlag,              static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,                       &Tab::onTrackEnabledFlagChanged);
379   connect(p.ui->hearingImpairedFlag,           static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,                       &Tab::onHearingImpairedFlagChanged);
380   connect(p.ui->visualImpairedFlag,            static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,                       &Tab::onVisualImpairedFlagChanged);
381   connect(p.ui->textDescriptionsFlag,          static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,                       &Tab::onTextDescriptionsFlagChanged);
382   connect(p.ui->originalFlag,                  static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,                       &Tab::onOriginalFlagChanged);
383   connect(p.ui->commentaryFlag,                static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,                       &Tab::onCommentaryFlagChanged);
384   connect(p.ui->moveFilesDown,                 &QPushButton::clicked,                                                  this,                       &Tab::onMoveFilesDown);
385   connect(p.ui->moveFilesUp,                   &QPushButton::clicked,                                                  this,                       &Tab::onMoveFilesUp);
386   connect(p.ui->moveTracksDown,                &QPushButton::clicked,                                                  this,                       &Tab::onMoveTracksDown);
387   connect(p.ui->moveTracksUp,                  &QPushButton::clicked,                                                  this,                       &Tab::onMoveTracksUp);
388   connect(p.ui->muxThis,                       static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,                       &Tab::onMuxThisChanged);
389   connect(p.ui->reduceToAudioCore,             &QCheckBox::toggled,                                                    this,                       &Tab::onReduceAudioToCoreChanged);
390   connect(p.ui->removeDialogNormalizationGain, &QCheckBox::toggled,                                                    this,                       &Tab::onRemoveDialogNormalizationGainChanged);
391   connect(p.ui->setAspectRatio,                &QPushButton::clicked,                                                  this,                       &Tab::onSetAspectRatio);
392   connect(p.ui->setDisplayWidthHeight,         &QPushButton::clicked,                                                  this,                       &Tab::onSetDisplayDimensions);
393   connect(p.ui->startMuxing,                   &QPushButton::clicked,                                                  this,                       [=]() { addToJobQueue(true); });
394   connect(p.ui->stereoscopy,                   static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,                       &Tab::onStereoscopyChanged);
395   connect(p.ui->stretchBy,                     &QLineEdit::textChanged,                                                this,                       &Tab::onStretchByChanged);
396   connect(p.ui->subtitleCharacterSet,          static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,                       &Tab::onSubtitleCharacterSetChanged);
397   connect(p.ui->subtitleCharacterSetPreview,   &QPushButton::clicked,                                                  this,                       &Tab::onPreviewSubtitleCharacterSet);
398   connect(p.ui->timestamps,                    &QLineEdit::textChanged,                                                this,                       &Tab::onTimestampsChanged);
399   connect(p.ui->trackLanguage,                 &Util::LanguageDisplayWidget::languageChanged,                          this,                       &Tab::onTrackLanguageChanged);
400   connect(p.ui->trackName,                     &QComboBox::editTextChanged,                                            this,                       &Tab::onTrackNameChanged);
401   connect(p.ui->trackTags,                     &QLineEdit::textChanged,                                                this,                       &Tab::onTrackTagsChanged);
402   connect(p.ui->tracks,                        &QTreeView::doubleClicked,                                              this,                       &Tab::toggleMuxThisForSelectedTracks);
403   connect(p.ui->tracks,                        &Util::BasicTreeView::allSelectedActivated,                             this,                       &Tab::toggleMuxThisForSelectedTracks);
404   connect(p.ui->tracks,                        &Util::BasicTreeView::ctrlDownPressed,                                  this,                       &Tab::onMoveTracksDown);
405   connect(p.ui->tracks,                        &Util::BasicTreeView::ctrlUpPressed,                                    this,                       &Tab::onMoveTracksUp);
406   connect(p.ui->tracks,                        &Util::BasicTreeView::customContextMenuRequested,                       this,                       &Tab::showTracksContextMenu);
407   connect(p.ui->tracks->selectionModel(),      &QItemSelectionModel::selectionChanged,                                 p.tracksModel,              &TrackModel::updateSelectionStatus);
408   connect(p.ui->tracks->selectionModel(),      &QItemSelectionModel::selectionChanged,                                 this,                       &Tab::onTrackSelectionChanged);
409 
410   connect(p.addFilesAction,                    &QAction::triggered,                                                    this,                       &Tab::onAddFiles);
411   connect(p.addFilesAction2,                   &QAction::triggered,                                                    this,                       &Tab::onAddFiles);
412   connect(p.appendFilesAction,                 &QAction::triggered,                                                    this,                       &Tab::onAppendFiles);
413   connect(p.appendFilesAction2,                &QAction::triggered,                                                    this,                       &Tab::onAppendFiles);
414   connect(p.addAdditionalPartsAction,          &QAction::triggered,                                                    this,                       &Tab::onAddAdditionalParts);
415   connect(p.addAdditionalPartsAction2,         &QAction::triggered,                                                    this,                       &Tab::onAddAdditionalParts);
416   connect(p.removeFilesAction,                 &QAction::triggered,                                                    this,                       &Tab::onRemoveFiles);
417   connect(p.removeAllFilesAction,              &QAction::triggered,                                                    this,                       &Tab::onRemoveAllFiles);
418   connect(p.setDestinationFileNameAction,      &QAction::triggered,                                                    this,                       &Tab::setDestinationFileNameFromSelectedFile);
419   connect(p.openFilesInMediaInfoAction,        &QAction::triggered,                                                    this,                       &Tab::onOpenFilesInMediaInfo);
420   connect(p.openTracksInMediaInfoAction,       &QAction::triggered,                                                    this,                       &Tab::onOpenTracksInMediaInfo);
421 
422   connect(p.selectAllTracksAction,             &QAction::triggered,                                                    this,                       &Tab::selectAllTracks);
423   connect(p.selectAllVideoTracksAction,        &QAction::triggered,                                                    this,                       &Tab::selectAllVideoTracks);
424   connect(p.selectAllAudioTracksAction,        &QAction::triggered,                                                    this,                       &Tab::selectAllAudioTracks);
425   connect(p.selectAllSubtitlesTracksAction,    &QAction::triggered,                                                    this,                       &Tab::selectAllSubtitlesTracks);
426   connect(p.selectTracksFromFilesAction,       &QAction::triggered,                                                    this,                       &Tab::selectAllTracksFromSelectedFiles);
427   connect(p.enableAllTracksAction,             &QAction::triggered,                                                    this,                       &Tab::enableAllTracks);
428   connect(p.disableAllTracksAction,            &QAction::triggered,                                                    this,                       &Tab::disableAllTracks);
429 
430   connect(p.startMuxingLeaveAsIs,              &QAction::triggered,                                                    this,                       [=]() { addToJobQueue(true,  CMSAction::None); });
431   connect(p.startMuxingCreateNewSettings,      &QAction::triggered,                                                    this,                       [=]() { addToJobQueue(true,  CMSAction::NewSettings); });
432   connect(p.startMuxingCloseSettings,          &QAction::triggered,                                                    this,                       [=]() { addToJobQueue(true,  CMSAction::CloseSettings); });
433   connect(p.startMuxingRemoveInputFiles,       &QAction::triggered,                                                    this,                       [=]() { addToJobQueue(true,  CMSAction::RemoveInputFiles); });
434 
435   connect(p.addToJobQueueLeaveAsIs,            &QAction::triggered,                                                    this,                       [=]() { addToJobQueue(false, CMSAction::None); });
436   connect(p.addToJobQueueCreateNewSettings,    &QAction::triggered,                                                    this,                       [=]() { addToJobQueue(false, CMSAction::NewSettings); });
437   connect(p.addToJobQueueCloseSettings,        &QAction::triggered,                                                    this,                       [=]() { addToJobQueue(false, CMSAction::CloseSettings); });
438   connect(p.addToJobQueueRemoveInputFiles,     &QAction::triggered,                                                    this,                       [=]() { addToJobQueue(false, CMSAction::RemoveInputFiles); });
439 
440   connect(p.addFilesMenu,                      &QMenu::aboutToShow,                                                    this,                       &Tab::enableFilesActions);
441 
442   connect(p.filesModel,                        &SourceFileModel::rowsInserted,                                         this,                       &Tab::onFileRowsInserted);
443   connect(p.tracksModel,                       &TrackModel::rowsInserted,                                              this,                       &Tab::onTrackRowsInserted);
444   connect(p.tracksModel,                       &TrackModel::itemChanged,                                               this,                       &Tab::onTrackItemChanged);
445 
446   connect(mw,                                  &MainWindow::preferencesChanged,                                        this,                       &Tab::setupMoveUpDownButtons);
447   connect(mw,                                  &MainWindow::preferencesChanged,                                        this,                       &Tab::setupInputLayout);
448   connect(mw,                                  &MainWindow::preferencesChanged,                                        this,                       &Tab::setupPredefinedTrackNames);
449   connect(mw,                                  &MainWindow::preferencesChanged,                                        p.ui->subtitleCharacterSet, &Util::ComboBoxBase::reInitialize);
450   connect(mw,                                  &MainWindow::preferencesChanged,                                        p.ui->chapterCharacterSet,  &Util::ComboBoxBase::reInitialize);
451 
452   enableMoveFilesButtons();
453   onTrackSelectionChanged();
454 
455   Util::HeaderViewManager::create(*p.ui->files,  "Merge::Files") .setDefaultSizes({ { Q("fileName"), 200 }, { Q("container"), 100 }, { Q("fileSize"),  60 } });
456   Util::HeaderViewManager::create(*p.ui->tracks, "Merge::Tracks").setDefaultSizes({ { Q("codec"),    150 }, { Q("type"),       80 }, { Q("name"),     150 }, { Q("properties"), 150 } });
457 }
458 
459 void
setupInputToolTips()460 Tab::setupInputToolTips() {
461   auto &p = *p_func();
462 
463   Util::setToolTip(p.ui->files,     QY("Right-click to add, append and remove files"));
464   Util::setToolTip(p.ui->tracks,    QY("Right-click for actions for all items"));
465 
466   Util::setToolTip(p.ui->muxThis,   QY("If set to 'no' then the selected tracks will not be copied to the destination file."));
467   Util::setToolTip(p.ui->trackName, QY("A name for this track that players can display helping the user choose the right track to play, e.g. \"director's comments\"."));
468   Util::setToolTip(p.ui->trackLanguage, QY("The language for this track that players can use for automatic track selection and display for the user."));
469   Util::setToolTip(p.ui->defaultTrackFlag,
470                    Q("%1 %2")
471                    .arg(QY("Make this track eligible to be played by default."))
472                    .arg(QY("Players should prefer tracks with the default track flag set while taking into account user preferences such as the track's language.")));
473   Util::setToolTip(p.ui->forcedTrackFlag,
474                    Q("%1 %2")
475                    .arg(QY("Mark this track as \"forced display\"."))
476                    .arg(QY("Use this for tracks containing onscreen text or foreign-language dialogue.")));
477   Util::setToolTip(p.ui->trackEnabledFlag,
478                    Q("%1 %2")
479                    .arg(QY("Mark this track as \"enabled\" (the default) or \"disabled\"."))
480                    .arg(QY("Players should only consider enabled tracks for playback.")));
481   Util::setToolTip(p.ui->hearingImpairedFlag,  QY("Can be set if the track is suitable for users with hearing impairments."));
482   Util::setToolTip(p.ui->visualImpairedFlag,   QY("Can be set if the track is suitable for users with visual impairments."));
483   Util::setToolTip(p.ui->textDescriptionsFlag, QY("Can be set if the track contains textual descriptions of video content suitable for playback via a text-to-speech system for a visually-impaired user."));
484   Util::setToolTip(p.ui->originalFlag,         QY("Can be set if the track is in the content's original language (not a translation)."));
485   Util::setToolTip(p.ui->commentaryFlag,       QY("Can be set if the track contains commentary."));
486   Util::setToolTip(p.ui->compression,
487                    Q("%1 %2 %3")
488                    .arg(QY("Sets the lossless compression algorithm to be used for this track."))
489                    .arg(QY("If set to 'determine automatically' then mkvmerge will decide whether or not to compress and which algorithm to use based on the track type."))
490                    .arg(QY("Currently only certain subtitle formats are compressed with the zlib algorithm.")));
491   Util::setToolTip(p.ui->delay,
492                    Q("<p>%1 %2 %3</p><p>%4</p>")
493                    .arg(QYH("Delay this track's timestamps by a couple of ms."))
494                    .arg(QYH("The value can be negative, but keep in mind that any frame whose timestamp is negative after this calculation is dropped."))
495                    .arg(QYH("This works with all track types."))
496                    .arg(QYH("This option can also be used for chapters.")));
497   Util::setToolTip(p.ui->stretchBy,
498                    Q("<p>%1 %2</p><p>%3</p><p>%4</p>")
499                    .arg(QYH("Multiply this track's timestamps with a factor."))
500                    .arg(QYH("The value can be given either as a floating point number (e.g. 12.345) or a fraction of numbers (e.g. 123/456.78)."))
501                    .arg(QYH("This works well for video and subtitle tracks but should not be used with audio tracks."))
502                    .arg(QYH("This option can also be used for chapters.")));
503   Util::setToolTip(p.ui->defaultDuration,
504                    Q("%1 %2 %3 %4")
505                    .arg(QY("Forces the default duration or number of frames per second for a track."))
506                    .arg(QY("The value can be given either as a floating point number (e.g. 12.345) or a fraction of integer values (e.g. 123/456)."))
507                    .arg(QY("You can specify one of the units 's', 'ms', 'us', 'ns', 'fps', 'i' or 'p'."))
508                    .arg(QY("If no unit is given, 'fps' will be used.")));
509   Util::setToolTip(p.ui->fixBitstreamTimingInfo,
510                    Q("%1 %2 %3")
511                    .arg(QY("Normally mkvmerge does not change the timing information (frame/field rate) stored in the video bitstream."))
512                    .arg(QY("With this option that information is adjusted to match the container's timing information."))
513                    .arg(QY("There are several potential sources for the container's timing information: a value given on the command line with the '--default-duration' option, "
514                            "the source container or the video bitstream.")));
515   Util::setToolTip(p.ui->aspectRatio,
516                    Q("<p>%1 %2 %3</p><p>%4</p>")
517                    .arg(QYH("The Matroska container format can store the display width/height for a video track."))
518                    .arg(QYH("This option tells mkvmerge the display aspect ratio to use when it calculates the display width/height."))
519                    .arg(QYH("Note that many players don't use the display width/height values directly but only use the ratio given by these values when setting the initial window size."))
520                    .arg(QYH("The value can be given either as a floating point number (e.g. 12.345) or a fraction of integer values (e.g. 123/456).")));
521   Util::setToolTip(p.ui->displayWidth,
522                    Q("<p>%1 %2</p><p>%3</p>")
523                    .arg(QYH("The Matroska container format can store the display width/height for a video track."))
524                    .arg(QYH("This parameter is the display width in pixels."))
525                    .arg(QYH("Note that many players don't use the display width/height values directly but only use the ratio given by these values when setting the initial window size.")));
526   Util::setToolTip(p.ui->displayHeight,
527                    Q("<p>%1 %2</p><p>%3</p>")
528                    .arg(QYH("The Matroska container format can store the display width/height for a video track."))
529                    .arg(QYH("This parameter is the display height in pixels."))
530                    .arg(QYH("Note that many players don't use the display width/height values directly but only use the ratio given by these values when setting the initial window size.")));
531   Util::setToolTip(p.ui->cropping,
532                    Q("<p>%1 %2</p><p>%3 %4</p><p>%5</p>")
533                    .arg(QYH("Sets the cropping parameters which tell a player to omit a certain number of pixels on the four sides during playback."))
534                    .arg(QYH("This must be a comma-separated list of four numbers for the cropping to be used at the left, top, right and bottom, e.g. '0,20,0,20'."))
535                    .arg(QYH("Note that the video content is not modified by this option."))
536                    .arg(QYH("The values are only stored in the track headers."))
537                    .arg(QYH("Note also that there are not a lot of players that support the cropping parameters.")));
538   Util::setToolTip(p.ui->stereoscopy,
539                    Q("%1 %2")
540                    .arg(QY("Sets the stereo mode of the video track to this value."))
541                    .arg(QY("If left empty then the track's original stereo mode will be kept or, if it didn't have one, none will be set at all.")));
542   Util::setToolTip(p.ui->aacIsSBR,
543                    Q("%1 %2 %3")
544                    .arg(QY("This track contains SBR AAC/HE-AAC/AAC+ data."))
545                    .arg(QY("Only needed for AAC source files as SBR AAC cannot be detected automatically for these files."))
546                    .arg(QY("Not needed for AAC tracks read from other container formats like MP4 or Matroska files.")));
547   Util::setToolTip(p.ui->reduceToAudioCore,
548                    Q("%1 %2")
549                    .arg(QY("Drops all HD extensions from an audio track and keeps only its lossy core."))
550                    .arg(QY("This only works with DTS audio tracks.")));
551   Util::setToolTip(p.ui->removeDialogNormalizationGain,
552                    Q("%1 %2")
553                    .arg(QY("Removes or at least minimizes the dialog normalization gain by modifying audio headers."))
554                    .arg(QY("This only works with AC-3, DTS & TrueHD audio tracks.")));
555   Util::setToolTip(p.ui->subtitleCharacterSet,
556                    Q("<p>%1 %2</p><p><ol><li>%3</li><li>%4</li></p>")
557                    .arg(QYH("Selects the character set a subtitle file or chapter information was written with."))
558                    .arg(QYH("Only needed in certain situations:"))
559                    .arg(QYH("for subtitle files that do not use a byte order marker (BOM) and that are not encoded in the system's current character set (%1)").arg(Q(g_cc_local_utf8->get_charset())))
560                    .arg(QYH("for files with chapter information (e.g. OGM, MP4) for which mkvmerge does not detect the encoding correctly")));
561   Util::setToolTip(p.ui->cues,
562                    Q("%1 %2")
563                    .arg(QY("Selects for which blocks mkvmerge will produce index entries ( = cue entries)."))
564                    .arg(QY("\"Determine automatically\" is a good choice for almost all situations.")));
565   Util::setToolTip(p.ui->additionalTrackOptions,
566                    Q("%1 %2 %3")
567                    .arg(QY("Free-form edit field for user defined options for this track."))
568                    .arg(QY("What you input here is added after all the other options the GUI adds so that you could overwrite any of the GUI's options for this track."))
569                    .arg(QY("All occurrences of the string \"<TID>\" will be replaced by the track's track ID.")));
570 }
571 
572 void
setupPredefinedTrackNames()573 Tab::setupPredefinedTrackNames() {
574   auto &p = *p_func();
575 
576   QSignalBlocker const blocker{p.ui->trackName};
577 
578   auto name = p.ui->trackName->currentText();
579   QMap<TrackType, bool> haveType;
580 
581   Util::withSelectedIndexes(p.ui->tracks, [&](QModelIndex const &idx) {
582     auto track = p.tracksModel->fromIndex(idx);
583     if (track)
584       haveType[track->m_type] = true;
585   });
586 
587   QStringList items;
588   QMap<QString, bool> haveItem;
589 
590   auto addItemsMaybe = [&items, &haveItem](QStringList const &newItems) {
591     for (auto const &newItem : newItems)
592       if (!haveItem[newItem]) {
593         haveItem[newItem] = true;
594         items += newItem;
595       }
596   };
597 
598   auto &settings = Util::Settings::get();
599 
600   if (haveType[TrackType::Audio])
601     addItemsMaybe(settings.m_mergePredefinedAudioTrackNames);
602   if (haveType[TrackType::Video])
603     addItemsMaybe(settings.m_mergePredefinedVideoTrackNames);
604   if (haveType[TrackType::Subtitles])
605     addItemsMaybe(settings.m_mergePredefinedSubtitleTrackNames);
606 
607   p.ui->trackName->clear();
608   p.ui->trackName->addItems(items);
609   p.ui->trackName->setCurrentText(name);
610 }
611 
612 void
onFileRowsInserted(QModelIndex const & parentIdx,int,int)613 Tab::onFileRowsInserted(QModelIndex const &parentIdx,
614                         int,
615                         int) {
616   if (parentIdx.isValid())
617     p_func()->ui->files->setExpanded(parentIdx, true);
618 }
619 
620 void
onTrackRowsInserted(QModelIndex const & parentIdx,int,int)621 Tab::onTrackRowsInserted(QModelIndex const &parentIdx,
622                          int,
623                          int) {
624   if (parentIdx.isValid())
625     p_func()->ui->tracks->setExpanded(parentIdx, true);
626 }
627 
628 void
onTrackSelectionChanged()629 Tab::onTrackSelectionChanged() {
630   auto &p = *p_func();
631 
632   Util::enableWidgets(p.allInputControls, false);
633   p.ui->moveTracksUp->setEnabled(false);
634   p.ui->moveTracksDown->setEnabled(false);
635   p.ui->subtitleCharacterSetPreview->setEnabled(false);
636 
637   auto selection = p.ui->tracks->selectionModel()->selection();
638   auto numRows   = Util::numSelectedRows(selection);
639   if (!numRows) {
640     clearInputControlValues();
641     return;
642   }
643 
644   p.ui->moveTracksUp->setEnabled(true);
645   p.ui->moveTracksDown->setEnabled(true);
646 
647   if (1 < numRows) {
648     setInputControlValues(nullptr);
649     setupPredefinedTrackNames();
650     Util::enableWidgets(p.allInputControls, true);
651     return;
652   }
653 
654   Util::enableWidgets(p.typeIndependentControls, true);
655 
656   auto idxs = selection[0].indexes();
657   if (idxs.isEmpty() || !idxs[0].isValid())
658     return;
659 
660   auto track = p.tracksModel->fromIndex(idxs[0]);
661   if (!track)
662     return;
663 
664   setInputControlValues(track);
665   setupPredefinedTrackNames();
666 
667   if (track->isAudio()) {
668     Util::enableWidgets(p.audioControls, true);
669     Util::enableWidgets({ p.ui->aacIsSBRLabel, p.ui->aacIsSBR }, track->canSetAacToSbr());
670     p.ui->reduceToAudioCore->setEnabled(track->canReduceToAudioCore());
671     p.ui->removeDialogNormalizationGain->setEnabled(track->canRemoveDialogNormalizationGain());
672 
673   } else if (track->isVideo())
674     Util::enableWidgets(p.videoControls, true);
675 
676   else if (track->isSubtitles()) {
677     Util::enableWidgets(p.subtitleControls, true);
678     if (!track->canChangeSubCharset())
679       Util::enableWidgets(QList<QWidget *>{} << p.ui->characterSetLabel << p.ui->subtitleCharacterSet, false);
680 
681     else if (track->m_file->isTextSubtitleContainer())
682       p.ui->subtitleCharacterSetPreview->setEnabled(true);
683 
684   } else if (track->isChapters()) {
685     Util::enableWidgets(p.chapterControls, true);
686 
687     if (!track->canChangeSubCharset())
688       Util::enableWidgets(QList<QWidget *>{} << p.ui->characterSetLabel << p.ui->subtitleCharacterSet, false);
689   }
690 
691   if (track->isAppended())
692     Util::enableWidgets(p.notIfAppendingControls, false);
693 }
694 
695 void
addOrRemoveEmptyComboBoxItem(bool add)696 Tab::addOrRemoveEmptyComboBoxItem(bool add) {
697   for (auto &comboBox : p_func()->comboBoxControls)
698     if (add && comboBox->itemData(0).isValid())
699       comboBox->insertItem(0, QY("<Do not change>"));
700     else if (!add && !comboBox->itemData(0).isValid())
701       comboBox->removeItem(0);
702 }
703 
704 void
clearInputControlValues()705 Tab::clearInputControlValues() {
706   auto &p = *p_func();
707 
708   p.ui->trackLanguage->setLanguage({});
709 
710   for (auto comboBox : p.comboBoxControls)
711     comboBox->setCurrentIndex(0);
712 
713   for (auto control : std::vector<QLineEdit *>{p.ui->trackTags, p.ui->delay, p.ui->stretchBy, p.ui->timestamps, p.ui->displayWidth, p.ui->displayHeight, p.ui->cropping, p.ui->additionalTrackOptions})
714     control->setText(Q(""));
715 
716   for (auto control : std::vector<QComboBox *>{p.ui->trackName, p.ui->defaultDuration, p.ui->aspectRatio})
717     control->setEditText(Q(""));
718 
719   p.ui->setAspectRatio->setChecked(false);
720   p.ui->setDisplayWidthHeight->setChecked(false);
721 }
722 
723 void
setInputControlValues(Track * track)724 Tab::setInputControlValues(Track *track) {
725   auto &p = *p_func();
726 
727   p.currentlySettingInputControlValues = true;
728 
729   addOrRemoveEmptyComboBoxItem(!track);
730 
731   auto additionalLanguages     = QSet<QString>{};
732   auto additionalCharacterSets = QSet<QString>{};
733 
734   for (auto const &sourceFile : p.config.m_files)
735     for (auto const &sourceTrack : sourceFile->m_tracks) {
736       additionalLanguages     << Q(sourceTrack->m_language.format());
737       additionalCharacterSets << sourceTrack->m_characterSet;
738     }
739 
740   p.ui->trackLanguage->setAdditionalLanguages(additionalLanguages.values());
741   p.ui->subtitleCharacterSet->setAdditionalItems(additionalCharacterSets.values()).reInitializeIfNecessary();
742 
743   if (!track) {
744     clearInputControlValues();
745     p.currentlySettingInputControlValues = false;
746     return;
747   }
748 
749   Util::setComboBoxIndexIf(p.ui->muxThis,              [&track](auto const &, auto const &data) { return data.isValid() && (data.toBool() == track->m_muxThis);                                });
750   Util::setComboBoxIndexIf(p.ui->defaultTrackFlag,     [&track](auto const &, auto const &data) { return data.isValid() && (data.toBool() == track->m_defaultTrackFlag);                       });
751   Util::setComboBoxIndexIf(p.ui->forcedTrackFlag,      [&track](auto const &, auto const &data) { return data.isValid() && (data.toUInt() == track->m_forcedTrackFlag);                        });
752   Util::setComboBoxIndexIf(p.ui->trackEnabledFlag,     [&track](auto const &, auto const &data) { return data.isValid() && (data.toUInt() == track->m_trackEnabledFlag);                       });
753   Util::setComboBoxIndexIf(p.ui->hearingImpairedFlag,  [&track](auto const &, auto const &data) { return data.isValid() && (data.toBool() == track->m_hearingImpairedFlag);                    });
754   Util::setComboBoxIndexIf(p.ui->visualImpairedFlag,   [&track](auto const &, auto const &data) { return data.isValid() && (data.toBool() == track->m_visualImpairedFlag);                     });
755   Util::setComboBoxIndexIf(p.ui->textDescriptionsFlag, [&track](auto const &, auto const &data) { return data.isValid() && (data.toBool() == track->m_textDescriptionsFlag);                   });
756   Util::setComboBoxIndexIf(p.ui->originalFlag,         [&track](auto const &, auto const &data) { return data.isValid() && (data.toBool() == track->m_originalFlag);                           });
757   Util::setComboBoxIndexIf(p.ui->commentaryFlag,       [&track](auto const &, auto const &data) { return data.isValid() && (data.toBool() == track->m_commentaryFlag);                         });
758   Util::setComboBoxIndexIf(p.ui->compression,          [&track](auto const &, auto const &data) { return data.isValid() && (data.toUInt() == static_cast<unsigned int>(track->m_compression)); });
759   Util::setComboBoxIndexIf(p.ui->cues,                 [&track](auto const &, auto const &data) { return data.isValid() && (data.toUInt() == track->m_cues);                                   });
760   Util::setComboBoxIndexIf(p.ui->stereoscopy,          [&track](auto const &, auto const &data) { return data.isValid() && (data.toUInt() == track->m_stereoscopy);                            });
761   Util::setComboBoxIndexIf(p.ui->aacIsSBR,             [&track](auto const &, auto const &data) { return data.isValid() && (data.toUInt() == track->m_aacIsSBR);                               });
762 
763   p.ui->trackLanguage->setLanguage(track->m_language);
764   p.ui->subtitleCharacterSet->setCurrentByData(track->m_characterSet);
765 
766   p.ui->trackName->setEditText(                    track->m_name);
767   p.ui->trackTags->setText(                        track->m_tags);
768   p.ui->delay->setText(                            track->m_delay);
769   p.ui->stretchBy->setText(                        track->m_stretchBy);
770   p.ui->timestamps->setText(                       track->m_timestamps);
771   p.ui->displayWidth->setText(                     track->m_displayWidth);
772   p.ui->displayHeight->setText(                    track->m_displayHeight);
773   p.ui->cropping->setText(                         track->m_cropping);
774   p.ui->additionalTrackOptions->setText(           track->m_additionalOptions);
775   p.ui->defaultDuration->setEditText(              track->m_defaultDuration);
776   p.ui->aspectRatio->setEditText(                  track->m_aspectRatio);
777 
778   p.ui->setAspectRatio->setChecked(                track->m_setAspectRatio);
779   p.ui->setDisplayWidthHeight->setChecked(        !track->m_setAspectRatio);
780   p.ui->fixBitstreamTimingInfo->setChecked(        track->m_fixBitstreamTimingInfo);
781   p.ui->reduceToAudioCore->setChecked(             track->m_reduceAudioToCore);
782   p.ui->removeDialogNormalizationGain->setChecked( track->m_removeDialogNormalizationGain);
783 
784   p.currentlySettingInputControlValues = false;
785 }
786 
787 QList<SourceFile *>
selectedSourceFiles() const788 Tab::selectedSourceFiles()
789   const {
790   auto &p          = *p_func();
791   auto sourceFiles = QList<SourceFile *>{};
792   Util::withSelectedIndexes(p.ui->files, [&sourceFiles, &p](QModelIndex const &idx) {
793       auto sourceFile = p.filesModel->fromIndex(idx);
794       if (sourceFile)
795         sourceFiles << sourceFile.get();
796   });
797 
798   return sourceFiles;
799 }
800 
801 QList<Track *>
selectedTracks() const802 Tab::selectedTracks()
803   const {
804   auto &p     = *p_func();
805   auto tracks = QList<Track *>{};
806   Util::withSelectedIndexes(p.ui->tracks, [&tracks, &p](QModelIndex const &idx) {
807       auto track = p.tracksModel->fromIndex(idx);
808       if (track)
809         tracks << track;
810     });
811 
812   return tracks;
813 }
814 
815 void
withSelectedTracks(std::function<void (Track &)> code,bool notIfAppending,QWidget * widget)816 Tab::withSelectedTracks(std::function<void(Track &)> code,
817                         bool notIfAppending,
818                         QWidget *widget) {
819   auto &p = *p_func();
820 
821   if (p.currentlySettingInputControlValues)
822     return;
823 
824   auto tracks = selectedTracks();
825   if (tracks.isEmpty())
826     return;
827 
828   if (!widget)
829     widget = static_cast<QWidget *>(QObject::sender());
830 
831   bool withAudio     = p.audioControls.contains(widget);
832   bool withVideo     = p.videoControls.contains(widget);
833   bool withSubtitles = p.subtitleControls.contains(widget);
834   bool withChapters  = p.chapterControls.contains(widget);
835   bool withAll       = p.typeIndependentControls.contains(widget);
836 
837   for (auto &track : tracks) {
838     if (track->m_appendedTo && notIfAppending)
839       continue;
840 
841     if (   withAll
842         || (track->isAudio()     && withAudio)
843         || (track->isVideo()     && withVideo)
844         || (track->isSubtitles() && withSubtitles)
845         || (track->isChapters()  && withChapters)) {
846       code(*track);
847       p.tracksModel->trackUpdated(track);
848     }
849   }
850 }
851 
852 bool
hasSelectedNotAppendedRegularTracks() const853 Tab::hasSelectedNotAppendedRegularTracks()
854   const {
855   auto &p     = *p_func();
856   auto result = false;
857 
858   Util::withSelectedIndexes(p.ui->tracks, [&p, &result](QModelIndex const &idx) {
859     auto track = p.tracksModel->fromIndex(idx);
860     if (track && !track->isAppended() && track->isRegular())
861       result = true;
862   });
863 
864   return result;
865 }
866 
867 void
onTrackNameChanged(QString newValue)868 Tab::onTrackNameChanged(QString newValue) {
869   withSelectedTracks([&newValue](auto &track) { track.m_name = newValue; }, true);
870 }
871 
872 void
onTrackItemChanged(QStandardItem * item)873 Tab::onTrackItemChanged(QStandardItem *item) {
874   auto &p = *p_func();
875 
876   if (!item)
877     return;
878 
879   auto idx = p.tracksModel->indexFromItem(item);
880   if (idx.column())
881     return;
882 
883   auto track = p.tracksModel->fromIndex(idx);
884   if (!track)
885     return;
886 
887   auto newWantedMuxThis = (item->checkState() == Qt::Checked);
888   auto newMuxThis       = newWantedMuxThis && (!track->m_appendedTo || track->m_appendedTo->m_muxThis);
889 
890   if (newWantedMuxThis != newMuxThis)
891     item->setCheckState(newMuxThis ? Qt::Checked : Qt::Unchecked);
892 
893   if (newMuxThis == track->m_muxThis)
894     return;
895 
896   track->m_muxThis = newMuxThis;
897   p.tracksModel->trackUpdated(track);
898 
899   for (auto appendedTrack : track->m_appendedTracks) {
900     appendedTrack->m_muxThis = newMuxThis;
901     p.tracksModel->trackUpdated(appendedTrack);
902   }
903 
904   auto selected = selectedTracks();
905   if ((1 == selected.count()) && selected.contains(track))
906     Util::setComboBoxIndexIf(p.ui->muxThis, [newMuxThis](QString const &, QVariant const &data) { return data.isValid() && (data.toBool() == newMuxThis); });
907 
908   setOutputFileNameMaybe();
909 }
910 
911 void
onMuxThisChanged(int selected)912 Tab::onMuxThisChanged(int selected) {
913   auto &p   = *p_func();
914   auto data = p.ui->muxThis->itemData(selected);
915   if (!data.isValid())
916     return;
917 
918   auto muxThis = data.toBool();
919   QList<Track *> appendedTracks;
920 
921   withSelectedTracks([muxThis, &appendedTracks](auto &track) {
922     if (!track.m_appendedTo || track.m_appendedTo->m_muxThis)
923       track.m_muxThis = muxThis;
924     appendedTracks += track.m_appendedTracks;
925   });
926 
927   for (auto track : appendedTracks) {
928     track->m_muxThis = muxThis;
929     p.tracksModel->trackUpdated(track);
930   }
931 
932   setOutputFileNameMaybe();
933 
934   auto tracks = selectedTracks();
935   if (1 == tracks.count())
936     Util::setComboBoxIndexIf(p.ui->muxThis, [&tracks](QString const &, QVariant const &eltData) { return eltData.isValid() && (eltData.toBool() == tracks[0]->m_muxThis); });
937 }
938 
939 void
toggleMuxThisForSelectedTracks()940 Tab::toggleMuxThisForSelectedTracks() {
941   auto &p             = *p_func();
942   auto allEnabled     = true;
943   auto tracksSelected = false;
944 
945   withSelectedTracks([&allEnabled, &tracksSelected](auto &track) {
946     tracksSelected = true;
947 
948     if (!track.m_muxThis)
949       allEnabled = false;
950   }, false, p.ui->muxThis);
951 
952   if (!tracksSelected)
953     return;
954 
955   auto newEnabled = !allEnabled;
956   QList<Track *> appendedTracks;
957 
958   withSelectedTracks([newEnabled, &appendedTracks](auto &track) {
959     if (!track.m_appendedTo || track.m_appendedTo->m_muxThis)
960       track.m_muxThis = newEnabled;
961     appendedTracks += track.m_appendedTracks;
962   }, false, p.ui->muxThis);
963 
964   for (auto track : appendedTracks) {
965     track->m_muxThis = newEnabled;
966     p.tracksModel->trackUpdated(track);
967   }
968 
969   Util::setComboBoxIndexIf(p.ui->muxThis, [newEnabled](auto const &, auto const &data) { return data.isValid() && (data.toBool() == newEnabled); });
970 }
971 
972 void
onTrackLanguageChanged(mtx::bcp47::language_c const & newLanguage)973 Tab::onTrackLanguageChanged(mtx::bcp47::language_c const &newLanguage) {
974   withSelectedTracks([&newLanguage](auto &track) { track.m_language = newLanguage; }, true, p_func()->ui->trackLanguage);
975 }
976 
977 void
onDefaultTrackFlagChanged(int newValue)978 Tab::onDefaultTrackFlagChanged(int newValue) {
979   auto &p   = *p_func();
980   auto data = p.ui->defaultTrackFlag->itemData(newValue);
981 
982   if (!data.isValid())
983     return;
984 
985   newValue = data.toBool();
986 
987   withSelectedTracks([newValue](auto &track) { track.m_defaultTrackFlag = newValue; }, true);
988 }
989 
990 void
onForcedTrackFlagChanged(int newValue)991 Tab::onForcedTrackFlagChanged(int newValue) {
992   auto &p   = *p_func();
993   auto data = p.ui->forcedTrackFlag->itemData(newValue);
994 
995   if (!data.isValid())
996     return;
997 
998   newValue = data.toInt();
999 
1000   withSelectedTracks([&newValue](auto &track) { track.m_forcedTrackFlag = newValue; }, true);
1001 }
1002 
1003 void
onTrackEnabledFlagChanged(int newValue)1004 Tab::onTrackEnabledFlagChanged(int newValue) {
1005   auto &p   = *p_func();
1006   auto data = p.ui->trackEnabledFlag->itemData(newValue);
1007 
1008   if (!data.isValid())
1009     return;
1010 
1011   newValue = data.toInt();
1012 
1013   withSelectedTracks([&newValue](auto &track) { track.m_trackEnabledFlag = newValue; }, true);
1014 }
1015 
1016 void
onHearingImpairedFlagChanged(int newValue)1017 Tab::onHearingImpairedFlagChanged(int newValue) {
1018   auto &p   = *p_func();
1019   auto data = p.ui->hearingImpairedFlag->itemData(newValue);
1020 
1021   if (!data.isValid())
1022     return;
1023 
1024   newValue = data.toBool();
1025 
1026   withSelectedTracks([&newValue](auto &track) { track.m_hearingImpairedFlag = newValue; }, true);
1027 }
1028 
1029 void
onVisualImpairedFlagChanged(int newValue)1030 Tab::onVisualImpairedFlagChanged(int newValue) {
1031   auto &p   = *p_func();
1032   auto data = p.ui->visualImpairedFlag->itemData(newValue);
1033 
1034   if (!data.isValid())
1035     return;
1036 
1037   newValue = data.toBool();
1038 
1039   withSelectedTracks([&newValue](auto &track) { track.m_visualImpairedFlag = newValue; }, true);
1040 }
1041 
1042 void
onTextDescriptionsFlagChanged(int newValue)1043 Tab::onTextDescriptionsFlagChanged(int newValue) {
1044   auto &p   = *p_func();
1045   auto data = p.ui->textDescriptionsFlag->itemData(newValue);
1046 
1047   if (!data.isValid())
1048     return;
1049 
1050   newValue = data.toBool();
1051 
1052   withSelectedTracks([&newValue](auto &track) { track.m_textDescriptionsFlag = newValue; }, true);
1053 }
1054 
1055 void
onOriginalFlagChanged(int newValue)1056 Tab::onOriginalFlagChanged(int newValue) {
1057   auto &p   = *p_func();
1058   auto data = p.ui->originalFlag->itemData(newValue);
1059 
1060   if (!data.isValid())
1061     return;
1062 
1063   newValue = data.toBool();
1064 
1065   withSelectedTracks([&newValue](auto &track) { track.m_originalFlag = newValue; }, true);
1066 }
1067 
1068 void
onCommentaryFlagChanged(int newValue)1069 Tab::onCommentaryFlagChanged(int newValue) {
1070   auto &p   = *p_func();
1071   auto data = p.ui->commentaryFlag->itemData(newValue);
1072 
1073   if (!data.isValid())
1074     return;
1075 
1076   newValue = data.toBool();
1077 
1078   withSelectedTracks([&newValue](auto &track) { track.m_commentaryFlag = newValue; }, true);
1079 }
1080 
1081 void
onCompressionChanged(int newValue)1082 Tab::onCompressionChanged(int newValue) {
1083   auto data = p_func()->ui->compression->itemData(newValue);
1084   if (!data.isValid())
1085     return;
1086   newValue = data.toInt();
1087 
1088   auto compression = 1 == newValue ? TrackCompression::None
1089                    : 2 == newValue ? TrackCompression::Zlib
1090                    :                 TrackCompression::Default;
1091 
1092   withSelectedTracks([compression](auto &track) { track.m_compression = compression; }, true);
1093 }
1094 
1095 void
onTrackTagsChanged(QString newValue)1096 Tab::onTrackTagsChanged(QString newValue) {
1097   withSelectedTracks([&newValue](auto &track) { track.m_tags = newValue; }, true);
1098 }
1099 
1100 void
onDelayChanged(QString newValue)1101 Tab::onDelayChanged(QString newValue) {
1102   withSelectedTracks([&newValue](auto &track) { track.m_delay = newValue; });
1103 }
1104 
1105 void
onStretchByChanged(QString newValue)1106 Tab::onStretchByChanged(QString newValue) {
1107   withSelectedTracks([&newValue](auto &track) { track.m_stretchBy = newValue; });
1108 }
1109 
1110 void
onDefaultDurationChanged(QString newValue)1111 Tab::onDefaultDurationChanged(QString newValue) {
1112   withSelectedTracks([&newValue](auto &track) { track.m_defaultDuration = newValue; }, true);
1113 }
1114 
1115 void
onTimestampsChanged(QString newValue)1116 Tab::onTimestampsChanged(QString newValue) {
1117   withSelectedTracks([&newValue](auto &track) { track.m_timestamps = newValue; });
1118 }
1119 
1120 void
onBrowseTimestamps()1121 Tab::onBrowseTimestamps() {
1122   auto fileName = getOpenFileName(QY("Select timestamp file"), QY("Text files") + Q(" (*.txt)"), p_func()->ui->timestamps);
1123   if (!fileName.isEmpty())
1124     withSelectedTracks([&fileName](auto &track) { track.m_timestamps = fileName; });
1125 }
1126 
1127 void
onFixBitstreamTimingInfoChanged(bool newValue)1128 Tab::onFixBitstreamTimingInfoChanged(bool newValue) {
1129   withSelectedTracks([&newValue](auto &track) { track.m_fixBitstreamTimingInfo = newValue; }, true);
1130 }
1131 
1132 void
onBrowseTrackTags()1133 Tab::onBrowseTrackTags() {
1134   auto fileName = getOpenFileName(QY("Select tags file"), QY("XML tag files") + Q(" (*.xml)"), p_func()->ui->trackTags);
1135   if (!fileName.isEmpty())
1136     withSelectedTracks([&fileName](auto &track) { track.m_tags = fileName; }, true);
1137 }
1138 
1139 void
onSetAspectRatio()1140 Tab::onSetAspectRatio() {
1141   withSelectedTracks([](auto &track) { track.m_setAspectRatio = true; }, true);
1142 }
1143 
1144 void
onSetDisplayDimensions()1145 Tab::onSetDisplayDimensions() {
1146   withSelectedTracks([](auto &track) { track.m_setAspectRatio = false; }, true);
1147 }
1148 
1149 void
onAspectRatioChanged(QString newValue)1150 Tab::onAspectRatioChanged(QString newValue) {
1151   if (!newValue.isEmpty()) {
1152     p_func()->ui->setAspectRatio->setChecked(true);
1153     onSetAspectRatio();
1154   }
1155 
1156   withSelectedTracks([&newValue](auto &track) { track.m_aspectRatio = newValue; }, true);
1157 }
1158 
1159 void
onDisplayWidthChanged(QString newValue)1160 Tab::onDisplayWidthChanged(QString newValue) {
1161   if (!newValue.isEmpty()) {
1162     p_func()->ui->setDisplayWidthHeight->setChecked(true);
1163     onSetDisplayDimensions();
1164   }
1165 
1166   withSelectedTracks([&newValue](auto &track) { track.m_displayWidth = newValue; }, true);
1167 }
1168 
1169 void
onDisplayHeightChanged(QString newValue)1170 Tab::onDisplayHeightChanged(QString newValue) {
1171   if (!newValue.isEmpty()) {
1172     p_func()->ui->setDisplayWidthHeight->setChecked(true);
1173     onSetDisplayDimensions();
1174   }
1175 
1176   withSelectedTracks([&newValue](auto &track) { track.m_displayHeight = newValue; }, true);
1177 }
1178 
1179 void
onStereoscopyChanged(int newValue)1180 Tab::onStereoscopyChanged(int newValue) {
1181   auto data = p_func()->ui->stereoscopy->itemData(newValue);
1182   if (!data.isValid())
1183     return;
1184   newValue = data.toInt();
1185 
1186   withSelectedTracks([&newValue](auto &track) { track.m_stereoscopy = newValue; }, true);
1187 }
1188 
1189 void
onCroppingChanged(QString newValue)1190 Tab::onCroppingChanged(QString newValue) {
1191   withSelectedTracks([&newValue](auto &track) { track.m_cropping = newValue; }, true);
1192 }
1193 
1194 void
onAacIsSBRChanged(int newValue)1195 Tab::onAacIsSBRChanged(int newValue) {
1196   auto data = p_func()->ui->aacIsSBR->itemData(newValue);
1197   if (!data.isValid())
1198     return;
1199   newValue = data.toInt();
1200 
1201   withSelectedTracks([&newValue](auto &track) { track.m_aacIsSBR = newValue; }, true);
1202 }
1203 
1204 void
onReduceAudioToCoreChanged(bool newValue)1205 Tab::onReduceAudioToCoreChanged(bool newValue) {
1206   withSelectedTracks([&newValue](auto &track) {
1207     if (track.canReduceToAudioCore())
1208       track.m_reduceAudioToCore = newValue;
1209   });
1210 }
1211 
1212 void
onRemoveDialogNormalizationGainChanged(bool newValue)1213 Tab::onRemoveDialogNormalizationGainChanged(bool newValue) {
1214   withSelectedTracks([&newValue](auto &track) {
1215     if (track.canRemoveDialogNormalizationGain())
1216       track.m_removeDialogNormalizationGain = newValue;
1217   });
1218 }
1219 
1220 void
onSubtitleCharacterSetChanged(int newValue)1221 Tab::onSubtitleCharacterSetChanged(int newValue) {
1222   auto data = p_func()->ui->subtitleCharacterSet->itemData(newValue);
1223   if (!data.isValid())
1224     return;
1225 
1226   withSelectedTracks([&data](auto &track) {
1227     if (track.canChangeSubCharset())
1228       track.m_characterSet = data.toString();
1229   });
1230 }
1231 
1232 void
onCuesChanged(int newValue)1233 Tab::onCuesChanged(int newValue) {
1234   auto data = p_func()->ui->cues->itemData(newValue);
1235   if (!data.isValid())
1236     return;
1237   newValue = data.toInt();
1238 
1239   withSelectedTracks([&newValue](auto &track) { track.m_cues = newValue; }, true);
1240 }
1241 
1242 void
onAdditionalTrackOptionsChanged(QString newValue)1243 Tab::onAdditionalTrackOptionsChanged(QString newValue) {
1244   withSelectedTracks([&newValue](auto &track) { track.m_additionalOptions = newValue; }, true);
1245 }
1246 
1247 void
onAddFiles()1248 Tab::onAddFiles() {
1249   selectFilesAndIdentifyForAddingOrAppending(IdentificationPack::AddMode::Add);
1250 }
1251 
1252 void
onAppendFiles()1253 Tab::onAppendFiles() {
1254   selectFilesAndIdentifyForAddingOrAppending(IdentificationPack::AddMode::Append);
1255 }
1256 
1257 void
selectFilesAndIdentifyForAddingOrAppending(IdentificationPack::AddMode addMode)1258 Tab::selectFilesAndIdentifyForAddingOrAppending(IdentificationPack::AddMode addMode) {
1259   auto fileNames = selectFilesToAdd(addMode == IdentificationPack::AddMode::Append ? QY("Append media files") : QY("Add media files"));
1260   if (fileNames.isEmpty())
1261     return;
1262 
1263   IdentificationPack pack;
1264   pack.m_tabId         = reinterpret_cast<uint64_t>(this);
1265   pack.m_addMode       = addMode;
1266   pack.m_fileNames     = fileNames;
1267   // pack.m_sourceFileIdx = sourceFileIdx;
1268   pack.m_sourceFileIdx = selectedSourceFile();
1269 
1270   Tool::identifier().addPackToIdentify(pack);
1271 }
1272 
1273 void
addOrAppendIdentifiedFiles(QVector<SourceFilePtr> const & identifiedFiles,QModelIndex const & fileModelIdx,IdentificationPack::AddMode addMode)1274 Tab::addOrAppendIdentifiedFiles(QVector<SourceFilePtr> const &identifiedFiles,
1275                                 QModelIndex const &fileModelIdx,
1276                                 IdentificationPack::AddMode addMode) {
1277   auto &p = *p_func();
1278 
1279   if (identifiedFiles.isEmpty())
1280     return;
1281 
1282   if (p.debugTrackModel) {
1283     log_it(fmt::format("### BEFORE adding/appending ###\n"));
1284     p.config.debugDumpFileList();
1285     p.config.debugDumpTrackList();
1286   }
1287 
1288   setDefaultsFromSettingsForAddedFiles(identifiedFiles);
1289 
1290   p.filesModel->addOrAppendFilesAndTracks(identifiedFiles, fileModelIdx, addMode == IdentificationPack::AddMode::Append);
1291 
1292   if (p.debugTrackModel) {
1293     log_it(fmt::format("### AFTER adding/appending ###\n"));
1294     p.config.debugDumpFileList();
1295     p.config.debugDumpTrackList();
1296   }
1297 
1298   reinitFilesTracksControls();
1299 
1300   if (p.config.m_firstInputFileName.isEmpty())
1301     p.config.m_firstInputFileName = p.config.determineFirstInputFileName(identifiedFiles);
1302 
1303   setTitleMaybe(identifiedFiles);
1304   addDataFromIdentifiedBlurayFiles(identifiedFiles);
1305   setOutputFileNameMaybe();
1306 
1307   p.config.verifyStructure();
1308 }
1309 
1310 void
addIdentifiedFilesAsAdditionalParts(QVector<SourceFilePtr> const & identifiedFiles,QModelIndex const & fileModelIdx)1311 Tab::addIdentifiedFilesAsAdditionalParts(QVector<SourceFilePtr> const &identifiedFiles,
1312                                          QModelIndex const &fileModelIdx) {
1313   QStringList fileNames;
1314 
1315   for (auto const &identifiedFile : identifiedFiles)
1316     fileNames << identifiedFile->m_fileName;
1317 
1318   p_func()->filesModel->addAdditionalParts(fileNames, fileModelIdx);
1319 }
1320 
1321 void
addDataFromIdentifiedBlurayFiles(QVector<SourceFilePtr> const & files)1322 Tab::addDataFromIdentifiedBlurayFiles(QVector<SourceFilePtr> const &files) {
1323   for (auto const &file : files) {
1324     if (!file->m_discLibraryInfoToAdd)
1325       continue;
1326 
1327     auto &info = *file->m_discLibraryInfoToAdd;
1328 
1329     if (!info.m_title.empty())
1330       setTitleMaybe(Q(info.m_title));
1331 
1332     if (Util::Settings::get().m_mergeAddBlurayCovers)
1333       addAttachmentsFromIdentifiedBluray(info);
1334   }
1335 }
1336 
1337 void
setDefaultsFromSettingsForAddedFiles(QVector<SourceFilePtr> const & files)1338 Tab::setDefaultsFromSettingsForAddedFiles(QVector<SourceFilePtr> const &files) {
1339   auto &cfg = Util::Settings::get();
1340 
1341   for (auto const &file : files)
1342     for (auto const &track : file->m_tracks) {
1343       if (cfg.m_disableCompressionForAllTrackTypes)
1344         track->m_compression = TrackCompression::None;
1345 
1346       if (cfg.m_disableDefaultTrackForSubtitles && track->isSubtitles())
1347         track->m_defaultTrackFlag = false;
1348     }
1349 }
1350 
1351 QStringList
selectFilesToAdd(QString const & title)1352 Tab::selectFilesToAdd(QString const &title) {
1353   auto &settings = Util::Settings::get();
1354   auto fileNames = Util::getOpenFileNames(this, title, settings.lastOpenDirPath(), Util::FileTypeFilter::get().join(Q(";;")), nullptr, QFileDialog::HideNameFilterDetails);
1355 
1356   if (!fileNames.isEmpty()) {
1357     settings.m_lastOpenDir.setPath(QFileInfo{fileNames[0]}.path());
1358     settings.save();
1359   }
1360 
1361   return fileNames;
1362 }
1363 
1364 void
onAddAdditionalParts()1365 Tab::onAddAdditionalParts() {
1366   auto &p         = *p_func();
1367   auto currentIdx = selectedSourceFile();
1368   auto sourceFile = p.filesModel->fromIndex(currentIdx);
1369 
1370   if (sourceFile && !sourceFile->m_tracks.size()) {
1371     Util::MessageBox::critical(this)->title(QY("Unable to append files")).text(QY("You cannot add additional parts to files that don't contain tracks.")).exec();
1372     return;
1373   }
1374 
1375   p.filesModel->addAdditionalParts(selectFilesToAdd(QY("Add media files as additional parts")), currentIdx);
1376 }
1377 
1378 void
onRemoveFiles()1379 Tab::onRemoveFiles() {
1380   auto &p            = *p_func();
1381   auto selectedFiles = selectedSourceFiles();
1382 
1383   if (selectedFiles.isEmpty())
1384     return;
1385 
1386   p.filesModel->removeFiles(selectedFiles);
1387 
1388   reinitFilesTracksControls();
1389 
1390   if (!p.filesModel->rowCount()) {
1391     p.config.m_firstInputFileName.clear();
1392     clearDestinationMaybe();
1393     clearTitleMaybe();
1394   }
1395 }
1396 
1397 void
onRemoveAllFiles()1398 Tab::onRemoveAllFiles() {
1399   auto &p = *p_func();
1400 
1401   if (p.config.m_files.isEmpty())
1402     return;
1403 
1404   p.attachedFilesModel->reset();
1405   p.filesModel->removeRows(0, p.filesModel->rowCount());
1406   p.tracksModel->removeRows(0, p.tracksModel->rowCount());
1407   p.config.m_files.clear();
1408   p.config.m_tracks.clear();
1409   p.config.m_firstInputFileName.clear();
1410 
1411   reinitFilesTracksControls();
1412   clearDestinationMaybe();
1413   clearTitleMaybe();
1414 }
1415 
1416 void
reinitFilesTracksControls()1417 Tab::reinitFilesTracksControls() {
1418   onTrackSelectionChanged();
1419 }
1420 
1421 void
enableMoveFilesButtons()1422 Tab::enableMoveFilesButtons() {
1423   auto &p          = *p_func();
1424   auto hasSelected = !p.ui->files->selectionModel()->selection().isEmpty();
1425 
1426   p.ui->moveFilesUp->setEnabled(hasSelected);
1427   p.ui->moveFilesDown->setEnabled(hasSelected);
1428 }
1429 
1430 void
enableFilesActions()1431 Tab::enableFilesActions() {
1432   auto &p              = *p_func();
1433   int numSelected      = selectedSourceFiles().size();
1434   bool hasRegularTrack = false;
1435 
1436   if (1 == numSelected)
1437     hasRegularTrack = p.config.m_files.end() != std::find_if(p.config.m_files.begin(), p.config.m_files.end(), [](SourceFilePtr const &file) { return file->hasRegularTrack(); });
1438 
1439   p.addFilesAction->setEnabled(true);
1440   p.addFilesAction2->setEnabled(true);
1441   p.appendFilesAction->setEnabled((1 == numSelected) && hasRegularTrack);
1442   p.appendFilesAction2->setEnabled((1 == numSelected) && hasRegularTrack);
1443   p.addAdditionalPartsAction->setEnabled(1 == numSelected);
1444   p.addAdditionalPartsAction2->setEnabled(1 == numSelected);
1445   p.removeFilesAction->setEnabled(0 < numSelected);
1446   p.removeAllFilesAction->setEnabled(!p.config.m_files.isEmpty());
1447   p.setDestinationFileNameAction->setEnabled(1 == numSelected);
1448   p.openFilesInMediaInfoAction->setEnabled(0 < numSelected);
1449   p.selectTracksFromFilesAction->setEnabled(0 < numSelected);
1450 
1451   p.removeFilesAction->setText(QNY("&Remove file", "&Remove files", numSelected));
1452   p.openFilesInMediaInfoAction->setText(QNY("Open file in &MediaInfo", "Open files in &MediaInfo", numSelected));
1453   p.selectTracksFromFilesAction->setText(QNY("Select all &items from selected file", "Select all &items from selected files", numSelected));
1454 }
1455 
1456 void
enableTracksActions()1457 Tab::enableTracksActions() {
1458   auto &p         = *p_func();
1459   int numSelected = selectedTracks().size();
1460   bool hasTracks  = !!p.tracksModel->rowCount();
1461 
1462   p.selectAllTracksAction->setEnabled(hasTracks);
1463   p.selectTracksOfTypeMenu->setEnabled(hasTracks);
1464   p.enableAllTracksAction->setEnabled(hasTracks);
1465   p.disableAllTracksAction->setEnabled(hasTracks);
1466 
1467   p.selectAllVideoTracksAction->setEnabled(hasTracks);
1468   p.selectAllAudioTracksAction->setEnabled(hasTracks);
1469   p.selectAllSubtitlesTracksAction->setEnabled(hasTracks);
1470 
1471   p.openTracksInMediaInfoAction->setEnabled(0 < numSelected);
1472 
1473   p.openTracksInMediaInfoAction->setText(QNY("Open corresponding file in &MediaInfo", "Open corresponding files in &MediaInfo", numSelected));
1474 }
1475 
1476 void
retranslateInputUI()1477 Tab::retranslateInputUI() {
1478   auto &p = *p_func();
1479 
1480   p.ui->trackLanguage->setClearTitle(QY("<Do not change>"));
1481 
1482   p.filesModel->retranslateUi();
1483   p.tracksModel->retranslateUi();
1484 
1485   p.addFilesAction ->setText(QY("&Add files"));
1486   p.addFilesAction2->setText(QY("&Add files"));
1487   p.appendFilesAction ->setText(QY("A&ppend files"));
1488   p.appendFilesAction2->setText(QY("A&ppend files"));
1489   p.addAdditionalPartsAction ->setText(QY("Add files as a&dditional parts"));
1490   p.addAdditionalPartsAction2->setText(QY("Add files as a&dditional parts"));
1491 
1492   p.removeAllFilesAction->setText(QY("Remove a&ll files"));
1493 
1494   p.setDestinationFileNameAction->setText(QY("Set destination &file name from selected file's name"));
1495 
1496   p.selectAllTracksAction->setText(QY("&Select all items"));
1497   p.selectTracksOfTypeMenu->setTitle(QY("Select all tracks of specific &type"));
1498   p.enableAllTracksAction->setText(QY("&Enable all items"));
1499   p.disableAllTracksAction->setText(QY("&Disable all items"));
1500 
1501   p.selectAllVideoTracksAction->setText(QY("&Video"));
1502   p.selectAllAudioTracksAction->setText(QY("&Audio"));
1503   p.selectAllSubtitlesTracksAction->setText(QY("&Subtitles"));
1504 
1505   p.startMuxingLeaveAsIs->setText(QY("Afterwards &leave the settings as they are."));
1506   p.startMuxingCreateNewSettings->setText(QY("Afterwards create &new multiplex settings and close the current ones."));
1507   p.startMuxingCloseSettings->setText(QY("Afterwards &close the current multiplex settings."));
1508   p.startMuxingRemoveInputFiles->setText(QY("Afterwards &remove all source files."));
1509 
1510   p.addToJobQueueLeaveAsIs->setText(QY("Afterwards &leave the settings as they are."));
1511   p.addToJobQueueCreateNewSettings->setText(QY("Afterwards create &new multiplex settings and close the current ones."));
1512   p.addToJobQueueCloseSettings->setText(QY("Afterwards &close the current multiplex settings."));
1513   p.addToJobQueueRemoveInputFiles->setText(QY("Afterwards &remove all source files."));
1514 
1515   for (auto idx = 0u, end = stereo_mode_c::max_index(); idx <= end; ++idx)
1516     p.ui->stereoscopy->setItemText(idx + 1, to_qs(stereo_mode_c::translate(idx)));
1517 
1518   Util::fixComboBoxViewWidth(*p.ui->stereoscopy);
1519 
1520   for (auto &comboBox : p.comboBoxControls)
1521     if (comboBox->count() && !comboBox->itemData(0).isValid())
1522       comboBox->setItemText(0, QY("<Do not change>"));
1523 
1524   for (auto &comboBox : std::vector<QComboBox *>{p.ui->muxThis, p.ui->forcedTrackFlag, p.ui->trackEnabledFlag, p.ui->hearingImpairedFlag, p.ui->visualImpairedFlag, p.ui->textDescriptionsFlag, p.ui->originalFlag, p.ui->commentaryFlag})
1525     Util::setComboBoxTexts(comboBox, QStringList{} << QY("Yes") << QY("No"));
1526 
1527   Util::setComboBoxTexts(p.ui->defaultTrackFlag, QStringList{}                                  << QY("Yes")                  << QY("No"));
1528   Util::setComboBoxTexts(p.ui->compression,      QStringList{} << QY("Determine automatically") << QY("No extra compression") << Q("zlib"));
1529   Util::setComboBoxTexts(p.ui->cues,             QStringList{} << QY("Determine automatically") << QY("Only for I frames")    << QY("For all frames") << QY("No cues"));
1530   Util::setComboBoxTexts(p.ui->aacIsSBR,         QStringList{} << QY("Determine automatically") << QY("Yes")                  << QY("No"));
1531 
1532   setupInputToolTips();
1533 }
1534 
1535 QModelIndex
selectedSourceFile() const1536 Tab::selectedSourceFile()
1537   const {
1538   auto &p  = *p_func();
1539   auto idx = p.ui->files->selectionModel()->currentIndex();
1540   return p.filesModel->index(idx.row(), 0, idx.parent());
1541 }
1542 
1543 void
setTitleMaybe(QVector<SourceFilePtr> const & files)1544 Tab::setTitleMaybe(QVector<SourceFilePtr> const &files) {
1545   for (auto const &file : files) {
1546     setTitleMaybe(file->m_properties.value("title").toString());
1547 
1548     if (mtx::file_type_e::ogm != file->m_type)
1549       continue;
1550 
1551     for (auto const &track : file->m_tracks)
1552       if (track->isVideo() && !track->m_name.isEmpty()) {
1553         setTitleMaybe(track->m_name);
1554         break;
1555       }
1556   }
1557 }
1558 
1559 void
setTitleMaybe(QString const & title)1560 Tab::setTitleMaybe(QString const &title) {
1561   auto &p = *p_func();
1562 
1563   if (!Util::Settings::get().m_autoSetFileTitle || title.isEmpty() || !p.config.m_title.isEmpty())
1564     return;
1565 
1566   p.ui->title->setText(title);
1567   p.config.m_title = title;
1568 }
1569 
1570 QString
suggestOutputFileNameExtension() const1571 Tab::suggestOutputFileNameExtension()
1572   const {
1573   auto &p        = *p_func();
1574   auto hasTracks = false, hasVideo = false, hasAudio = false, hasStereoscopy = false;
1575 
1576   for (auto const &t : p.config.m_tracks) {
1577     if (!t->m_muxThis)
1578       continue;
1579 
1580     hasTracks = true;
1581 
1582     if (t->isVideo()) {
1583       hasVideo = true;
1584       if (t->m_stereoscopy >= 2)
1585         hasStereoscopy = true;
1586 
1587     } else if (t->isAudio())
1588       hasAudio = true;
1589   }
1590 
1591   return p.config.m_webmMode ? "webm"
1592        : hasStereoscopy      ? "mk3d"
1593        : hasVideo            ? "mkv"
1594        : hasAudio            ? "mka"
1595        : hasTracks           ? "mks"
1596        :                       "mkv";
1597 }
1598 
1599 void
setOutputFileNameMaybe(bool force)1600 Tab::setOutputFileNameMaybe(bool force) {
1601   auto &p        = *p_func();
1602   auto &settings = Util::Settings::get();
1603   auto policy    = settings.m_outputFileNamePolicy;
1604 
1605   if (   !force
1606       && (   (Util::Settings::DontSetOutputFileName == policy)
1607           || p.config.m_firstInputFileName.isEmpty()))
1608     return;
1609 
1610   auto currentOutput = p.ui->output->text();
1611   QDir outputDir;
1612 
1613   // Don't override custom changes to the destination file name.
1614   if (   !force
1615       && !currentOutput.isEmpty()
1616       && !p.config.m_destinationAuto.isEmpty()
1617       && (QDir::toNativeSeparators(currentOutput) != QDir::toNativeSeparators(p.config.m_destinationAuto)))
1618     return;
1619 
1620   if (Util::Settings::ToPreviousDirectory == policy)
1621     outputDir = settings.m_lastOutputDir;
1622 
1623   else if (Util::Settings::ToFixedDirectory == policy)
1624     outputDir = settings.m_fixedOutputDir;
1625 
1626   else if (Util::Settings::ToRelativeOfFirstInputFile == policy)
1627     outputDir = QDir{ QFileInfo{p.config.m_firstInputFileName}.absoluteDir().path() + Q("/") + settings.m_relativeOutputDir.path() };
1628 
1629   else if (   (Util::Settings::ToSameAsFirstInputFile == policy)
1630            || force)
1631     outputDir = QFileInfo{p.config.m_firstInputFileName}.absoluteDir();
1632 
1633   else
1634     Q_ASSERT_X(false, "setOutputFileNameMaybe", "Untested destination file name policy");
1635 
1636   auto cleanedTitle          = Util::replaceInvalidFileNameCharacters(p.config.m_title);
1637   auto firstInputBaseName    = QFileInfo{ p.config.m_firstInputFileName }.completeBaseName();
1638   auto baseName              = !settings.m_mergeSetDestinationFromTitle ? firstInputBaseName
1639                              : !cleanedTitle.isEmpty()                  ? cleanedTitle
1640                              :                                            firstInputBaseName;
1641   p.config.m_destinationAuto = generateUniqueOutputFileName(baseName, outputDir);
1642 
1643   p.ui->output->setText(p.config.m_destinationAuto);
1644   setDestination(p.config.m_destinationAuto);
1645 }
1646 
1647 QString
generateUniqueOutputFileName(QString const & baseName,QDir const & outputDir,bool removeUniquenessSuffix)1648 Tab::generateUniqueOutputFileName(QString const &baseName,
1649                                   QDir const &outputDir,
1650                                   bool removeUniquenessSuffix) {
1651   auto &p              = *p_func();
1652   auto &settings       = Util::Settings::get();
1653   auto cleanedBaseName = baseName;
1654   auto suffix          = suggestOutputFileNameExtension();
1655   auto needToRemove    =    removeUniquenessSuffix
1656                          && !p.config.m_destinationUniquenessSuffix.isEmpty()
1657                          && cleanedBaseName.endsWith(p.config.m_destinationUniquenessSuffix);
1658 
1659   qDebug() << "generateUniqueOutputFileName: baseName" << baseName << "suffix" << suffix << "destinationUniquenessSuffix" << p.config.m_destinationUniquenessSuffix
1660            << "removeUniquenessSuffix" << removeUniquenessSuffix << "needToRemove" << needToRemove;
1661 
1662   if (needToRemove)
1663     cleanedBaseName.remove(cleanedBaseName.length() - p.config.m_destinationUniquenessSuffix.length(), p.config.m_destinationUniquenessSuffix.length());
1664 
1665   auto idx = 0;
1666 
1667   while (true) {
1668     auto uniquenessSuffix = idx ? QString{" (%1)"}.arg(idx) : QString{};
1669     auto currentBaseName  = QString{"%1%2.%3"}.arg(cleanedBaseName).arg(uniquenessSuffix).arg(suffix);
1670     currentBaseName       = Util::removeInvalidPathCharacters(currentBaseName);
1671     auto outputFileName   = QFileInfo{outputDir, currentBaseName};
1672 
1673     if (!settings.m_uniqueOutputFileNames || !outputFileName.exists()) {
1674       p.config.m_destinationUniquenessSuffix = uniquenessSuffix;
1675       return QDir::toNativeSeparators(outputFileName.absoluteFilePath());
1676     }
1677 
1678     ++idx;
1679   }
1680 }
1681 
1682 void
setDestinationFileNameFromSelectedFile()1683 Tab::setDestinationFileNameFromSelectedFile() {
1684   auto &p            = *p_func();
1685   auto selectedFiles = selectedSourceFiles();
1686   if (selectedFiles.isEmpty())
1687     return;
1688 
1689   auto selectedFileName = selectedFiles[0]->m_fileName;
1690 
1691   if (!p.config.m_destination.isEmpty()) {
1692     p.config.m_destinationUniquenessSuffix.clear();
1693 
1694     auto baseName       = QFileInfo{selectedFileName}.completeBaseName();
1695     auto destinationDir = QDir{ QFileInfo{ p.config.m_destination }.path() };
1696     auto newFileName    = generateUniqueOutputFileName(baseName, destinationDir);
1697 
1698     p.ui->output->setText(QDir::toNativeSeparators(newFileName));
1699 
1700     return;
1701   }
1702 
1703   p.config.m_destinationAuto.clear();
1704   p.config.m_firstInputFileName = QDir::toNativeSeparators(selectedFileName);
1705 
1706   setOutputFileNameMaybe(true);
1707 }
1708 
1709 void
selectAllTracksOfType(std::optional<TrackType> type)1710 Tab::selectAllTracksOfType(std::optional<TrackType> type) {
1711   auto &p      = *p_func();
1712   auto numRows = p.tracksModel->rowCount();
1713   if (!numRows)
1714     return;
1715 
1716   auto lastColumn = p.tracksModel->columnCount() - 1;
1717   auto selection  = QItemSelection{};
1718 
1719   for (auto row = 0; row < numRows; ++row) {
1720     auto &track      = *p.config.m_tracks[row];
1721     auto numAppended = track.m_appendedTracks.count();
1722 
1723     if (!type || (track.m_type == *type))
1724       selection.select(p.tracksModel->index(row, 0), p.tracksModel->index(row, lastColumn));
1725 
1726     if (!numAppended)
1727       continue;
1728 
1729     auto rowIdx = p.tracksModel->index(row, 0);
1730 
1731     for (auto appendedRow = 0; appendedRow < numAppended; ++appendedRow) {
1732       auto &appendedTrack = *track.m_appendedTracks[appendedRow];
1733       if (!type || (appendedTrack.m_type == *type))
1734         selection.select(p.tracksModel->index(appendedRow, 0, rowIdx), p.tracksModel->index(appendedRow, lastColumn, rowIdx));
1735     }
1736   }
1737 
1738   p.ui->tracks->selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect);
1739 }
1740 
1741 void
selectAllTracks()1742 Tab::selectAllTracks() {
1743   selectAllTracksOfType({});
1744 }
1745 
1746 void
selectAllVideoTracks()1747 Tab::selectAllVideoTracks() {
1748   selectAllTracksOfType(TrackType::Video);
1749 }
1750 
1751 void
selectAllAudioTracks()1752 Tab::selectAllAudioTracks() {
1753   selectAllTracksOfType(TrackType::Audio);
1754 }
1755 
1756 void
selectAllSubtitlesTracks()1757 Tab::selectAllSubtitlesTracks() {
1758   selectAllTracksOfType(TrackType::Subtitles);
1759 }
1760 
1761 void
selectAllTracksFromSelectedFiles()1762 Tab::selectAllTracksFromSelectedFiles() {
1763   if (!p_func()->tracksModel->rowCount())
1764     return;
1765 
1766   auto tracksToSelect = QList<Track *>{};
1767   for (auto const &sourceFile : selectedSourceFiles())
1768     for (auto const &track : sourceFile->m_tracks)
1769       tracksToSelect << track.get();
1770 
1771   selectTracks(tracksToSelect);
1772 }
1773 
1774 void
selectTracks(QList<Track * > const & tracks)1775 Tab::selectTracks(QList<Track *> const &tracks) {
1776   auto &p         = *p_func();
1777   auto numColumns = p.tracksModel->columnCount() - 1;
1778   auto selection  = QItemSelection{};
1779 
1780   for (auto const &track : tracks) {
1781     auto idx = p.tracksModel->indexFromTrack(track);
1782     selection.select(idx.sibling(idx.row(), 0), idx.sibling(idx.row(), numColumns));
1783   }
1784 
1785   p.ui->tracks->selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect);
1786 }
1787 
1788 void
selectSourceFiles(QList<SourceFile * > const & files)1789 Tab::selectSourceFiles(QList<SourceFile *> const &files) {
1790   auto &p         = *p_func();
1791   auto numColumns = p.filesModel->columnCount() - 1;
1792   auto selection  = QItemSelection{};
1793 
1794   for (auto const &file : files) {
1795     auto idx = p.filesModel->indexFromSourceFile(file);
1796     selection.select(idx.sibling(idx.row(), 0), idx.sibling(idx.row(), numColumns));
1797   }
1798 
1799   p.ui->files->selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect);
1800 }
1801 
1802 void
enableAllTracks()1803 Tab::enableAllTracks() {
1804   enableDisableAllTracks(true);
1805 }
1806 
1807 void
disableAllTracks()1808 Tab::disableAllTracks() {
1809   enableDisableAllTracks(false);
1810 }
1811 
1812 void
enableDisableAllTracks(bool enable)1813 Tab::enableDisableAllTracks(bool enable) {
1814   auto &p = *p_func();
1815 
1816   for (auto row = 0, numRows = p.tracksModel->rowCount(); row < numRows; ++row) {
1817     auto track       = p.tracksModel->fromIndex(p.tracksModel->index(row, 0));
1818     track->m_muxThis = enable;
1819     p.tracksModel->trackUpdated(track);
1820 
1821     for (auto const &appendedTrack : track->m_appendedTracks) {
1822       appendedTrack->m_muxThis = enable;
1823       p.tracksModel->trackUpdated(appendedTrack);
1824     }
1825   }
1826 
1827   auto base = p.ui->muxThis->itemData(0).isValid() ? 0 : 1;
1828   p.ui->muxThis->setCurrentIndex(base + (enable ? 0 : 1));
1829 }
1830 
1831 void
moveSourceFilesUpOrDown(bool up)1832 Tab::moveSourceFilesUpOrDown(bool up) {
1833   auto &p    = *p_func();
1834   auto focus = App::instance()->focusWidget();
1835   auto files = selectedSourceFiles();
1836 
1837   p.filesModel->moveSourceFilesUpOrDown(files, up);
1838 
1839   for (auto const &file : files)
1840     if (file->isRegular())
1841       p.ui->files->setExpanded(p.filesModel->indexFromSourceFile(file), true);
1842 
1843   selectSourceFiles(files);
1844 
1845   if (focus)
1846     focus->setFocus();
1847 
1848   p.ui->files->scrollToFirstSelected();
1849 }
1850 
1851 void
onMoveFilesUp()1852 Tab::onMoveFilesUp() {
1853   moveSourceFilesUpOrDown(true);
1854 }
1855 
1856 void
onMoveFilesDown()1857 Tab::onMoveFilesDown() {
1858   moveSourceFilesUpOrDown(false);
1859 }
1860 
1861 void
moveTracksUpOrDown(bool up)1862 Tab::moveTracksUpOrDown(bool up) {
1863   auto &p     = *p_func();
1864   auto focus  = App::instance()->focusWidget();
1865   auto tracks = selectedTracks();
1866 
1867   p.tracksModel->moveTracksUpOrDown(tracks, up);
1868 
1869   for (auto const &track : tracks)
1870     if (track->isRegular() && !track->m_appendedTo)
1871       p.ui->tracks->setExpanded(p.tracksModel->indexFromTrack(track), true);
1872 
1873   selectTracks(tracks);
1874 
1875   if (focus)
1876     focus->setFocus();
1877 
1878   p.ui->tracks->scrollToFirstSelected();
1879 }
1880 
1881 void
onMoveTracksUp()1882 Tab::onMoveTracksUp() {
1883   moveTracksUpOrDown(true);
1884 }
1885 
1886 void
onMoveTracksDown()1887 Tab::onMoveTracksDown() {
1888   moveTracksUpOrDown(false);
1889 }
1890 
1891 void
showFilesContextMenu(QPoint const & pos)1892 Tab::showFilesContextMenu(QPoint const &pos) {
1893   auto &p = *p_func();
1894 
1895   enableFilesActions();
1896   p.filesMenu->exec(p.ui->files->viewport()->mapToGlobal(pos));
1897 }
1898 
1899 void
showTracksContextMenu(QPoint const & pos)1900 Tab::showTracksContextMenu(QPoint const &pos) {
1901   auto &p = *p_func();
1902 
1903   enableTracksActions();
1904   p.tracksMenu->exec(p.ui->tracks->viewport()->mapToGlobal(pos));
1905 }
1906 
1907 void
onOpenFilesInMediaInfo()1908 Tab::onOpenFilesInMediaInfo() {
1909   auto fileNames = QStringList{};
1910   for (auto const &sourceFile : selectedSourceFiles())
1911     fileNames << sourceFile->m_fileName;
1912 
1913   openFilesInMediaInfo(fileNames);
1914 }
1915 
1916 void
onOpenTracksInMediaInfo()1917 Tab::onOpenTracksInMediaInfo() {
1918   auto fileNames = QStringList{};
1919   for (auto const &track : selectedTracks()) {
1920     auto const &fileName = track->m_file->m_fileName;
1921     if (!fileNames.contains(fileName))
1922       fileNames << fileName;
1923   }
1924 
1925   openFilesInMediaInfo(fileNames);
1926 }
1927 
1928 QString
mediaInfoLocation()1929 Tab::mediaInfoLocation() {
1930   auto &cfg = Util::Settings::get();
1931   auto exe  = Util::Settings::determineMediaInfoExePath();
1932 
1933   if (!exe.isEmpty() && QFileInfo{exe}.exists())
1934     return exe;
1935 
1936   ExecutableLocationDialog dlg{this};
1937   auto result = dlg
1938     .setInfo(QY("Executable not found"),
1939              Q("<p>%1 %2 %3</p><p>%4</p>")
1940              .arg(QYH("This function requires the application %1.").arg("MediaInfo"))
1941              .arg(QYH("Its installation location could not be determined automatically."))
1942              .arg(QYH("Please select its location below."))
1943              .arg(QYH("You can download the application from the following URL:")))
1944     .setURL(Q("https://mediaarea.net/en/MediaInfo"))
1945     .exec();
1946 
1947   if (QDialog::Rejected == result)
1948     return {};
1949 
1950   exe = dlg.executable();
1951   if (exe.isEmpty() || !QFileInfo{exe}.exists())
1952     return {};
1953 
1954   cfg.m_mediaInfoExe = exe;
1955 
1956   return exe;
1957 }
1958 
1959 void
openFilesInMediaInfo(QStringList const & fileNames)1960 Tab::openFilesInMediaInfo(QStringList const &fileNames) {
1961   if (fileNames.isEmpty())
1962     return;
1963 
1964   auto exe = mediaInfoLocation();
1965   if (!exe.isEmpty())
1966     QProcess::startDetached(exe, fileNames);
1967 }
1968 
1969 void
onPreviewSubtitleCharacterSet()1970 Tab::onPreviewSubtitleCharacterSet() {
1971   auto selection = selectedTracks();
1972   auto track     = selection.count() ? selection[0] : nullptr;
1973 
1974   if ((selection.count() != 1) || !track->m_file->isTextSubtitleContainer())
1975     return;
1976 
1977   auto additionalCharacterSets = QSet<QString>{};
1978 
1979   for (auto const &sourceFile : p_func()->config.m_files)
1980     for (auto const &sourceTrack : sourceFile->m_tracks)
1981       additionalCharacterSets << sourceTrack->m_characterSet;
1982 
1983   auto dlg = new SelectCharacterSetDialog{this, track->m_file->m_fileName, track->m_characterSet, additionalCharacterSets.values()};
1984   dlg->setUserData(reinterpret_cast<qulonglong>(track));
1985 
1986   connect(dlg, &SelectCharacterSetDialog::characterSetSelected, this, &Tab::setSubtitleCharacterSet);
1987 
1988   dlg->show();
1989 }
1990 
1991 void
setSubtitleCharacterSet(QString const & characterSet)1992 Tab::setSubtitleCharacterSet(QString const &characterSet) {
1993   auto &p  = *p_func();
1994   auto dlg = qobject_cast<SelectCharacterSetDialog *>(QObject::sender());
1995   if (!dlg)
1996     return;
1997 
1998   auto track = reinterpret_cast<Track *>(dlg->userData().toULongLong());
1999 
2000   if (!p.config.m_tracks.contains(track))
2001     return;
2002 
2003   track->m_characterSet = characterSet;
2004   auto selection        = selectedTracks();
2005 
2006   if ((selection.count() == 1) && (selection[0] == track))
2007     Util::setComboBoxTextByData(p.ui->subtitleCharacterSet, characterSet);
2008 }
2009 
2010 bool
hasSourceFiles() const2011 Tab::hasSourceFiles()
2012   const {
2013   return !p_func()->config.m_files.isEmpty();
2014 }
2015 
2016 void
toggleSpecificTrackFlag(unsigned int wantedId)2017 Tab::toggleSpecificTrackFlag(unsigned int wantedId) {
2018   auto &p       = *p_func();
2019   auto comboBox = wantedId == libmatroska::KaxTrackFlagDefault    ::ClassInfos.GlobalId.GetValue() ? p.ui->defaultTrackFlag
2020                 : wantedId == libmatroska::KaxTrackFlagForced     ::ClassInfos.GlobalId.GetValue() ? p.ui->forcedTrackFlag
2021                 : wantedId == libmatroska::KaxTrackFlagEnabled    ::ClassInfos.GlobalId.GetValue() ? p.ui->trackEnabledFlag
2022                 : wantedId == libmatroska::KaxFlagCommentary      ::ClassInfos.GlobalId.GetValue() ? p.ui->commentaryFlag
2023                 : wantedId == libmatroska::KaxFlagOriginal        ::ClassInfos.GlobalId.GetValue() ? p.ui->originalFlag
2024                 : wantedId == libmatroska::KaxFlagHearingImpaired ::ClassInfos.GlobalId.GetValue() ? p.ui->hearingImpairedFlag
2025                 : wantedId == libmatroska::KaxFlagVisualImpaired  ::ClassInfos.GlobalId.GetValue() ? p.ui->visualImpairedFlag
2026                 : wantedId == libmatroska::KaxFlagTextDescriptions::ClassInfos.GlobalId.GetValue() ? p.ui->textDescriptionsFlag
2027                 :                                                                                    static_cast<QComboBox *>(nullptr);
2028 
2029   if (!comboBox || !comboBox->isEnabled())
2030     return;
2031 
2032   auto newIndex = (comboBox->currentIndex() + 1) % comboBox->count();
2033   if (!comboBox->itemData(newIndex).isValid())
2034     ++newIndex;
2035 
2036   comboBox->setCurrentIndex(newIndex);
2037 }
2038 
2039 void
changeTrackLanguage(QString const & formattedLanguage)2040 Tab::changeTrackLanguage(QString const &formattedLanguage) {
2041   auto &p = *p_func();
2042 
2043   auto language = mtx::bcp47::language_c::parse(to_utf8(formattedLanguage));
2044 
2045   if (!language.is_valid() || !p.ui->trackLanguage->isEnabled())
2046     return;
2047 
2048   p.ui->trackLanguage->setLanguage(language);
2049   onTrackLanguageChanged(language);
2050 }
2051 
2052 }
2053