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