1 #include "common/common_pch.h"
2 
3 #include <QMenu>
4 
5 #include "common/bluray/disc_library.h"
6 #include "common/mime.h"
7 #include "common/qt.h"
8 #include "common/strings/formatting.h"
9 #include "mkvtoolnix-gui/merge/tab.h"
10 #include "mkvtoolnix-gui/merge/tab_p.h"
11 #include "mkvtoolnix-gui/forms/merge/tab.h"
12 #include "mkvtoolnix-gui/util/file_dialog.h"
13 #include "mkvtoolnix-gui/util/files_drag_drop_widget.h"
14 #include "mkvtoolnix-gui/util/header_view_manager.h"
15 #include "mkvtoolnix-gui/util/message_box.h"
16 #include "mkvtoolnix-gui/util/model.h"
17 #include "mkvtoolnix-gui/util/settings.h"
18 #include "mkvtoolnix-gui/util/widget.h"
19 
20 namespace mtx::gui::Merge {
21 
22 using namespace mtx::gui;
23 
24 void
setupAttachmentsControls()25 Tab::setupAttachmentsControls() {
26   auto &p = *p_func();
27 
28   p.ui->attachedFiles->setModel(p.attachedFilesModel);
29   p.ui->attachments->setModel(p.attachmentsModel);
30 
31   p.ui->attachedFiles->enterActivatesAllSelected(true);
32 
33   // Attached files context menu
34   p.attachedFilesMenu->addAction(p.enableSelectedAttachedFilesAction);
35   p.attachedFilesMenu->addAction(p.disableSelectedAttachedFilesAction);
36   p.attachedFilesMenu->addSeparator();
37   p.attachedFilesMenu->addAction(p.enableAllAttachedFilesAction);
38   p.attachedFilesMenu->addAction(p.disableAllAttachedFilesAction);
39 
40   // Attachments context menu
41   p.attachmentsMenu->addAction(p.addAttachmentsAction);
42   p.attachmentsMenu->addSeparator();
43   p.attachmentsMenu->addAction(p.removeAttachmentsAction);
44   p.attachmentsMenu->addAction(p.removeAllAttachmentsAction);
45   p.attachmentsMenu->addSeparator();
46   p.attachmentsMenu->addAction(p.selectAllAttachmentsAction);
47 
48   // MIME type
49   for (auto const &name : mtx::mime::sorted_type_names())
50     p.ui->attachmentMIMEType->addItem(Q(name), Q(name));
51 
52   p.ui->attachmentMIMEType->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
53   p.ui->attachmentStyle   ->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
54 
55   Util::fixComboBoxViewWidth(*p.ui->attachmentMIMEType);
56   Util::fixComboBoxViewWidth(*p.ui->attachmentStyle);
57 
58   p.ui->attachmentMIMEType->lineEdit()->setClearButtonEnabled(true);
59 
60   auto &cfg = Util::Settings::get();
61   cfg.handleSplitterSizes(p.ui->mergeAttachmentsSplitter);
62 
63   Util::HeaderViewManager::create(*p.ui->attachedFiles, "Merge::AttachedFiles").setDefaultSizes({ { Q("name"), 180 }, { Q("mimeType"), 180 }, { Q("size"), 100 } });
64 
65   p.enableSelectedAttachedFilesAction->setIcon(QIcon{Q(":/icons/16x16/checkbox.png")});
66   p.disableSelectedAttachedFilesAction->setIcon(QIcon{Q(":/icons/16x16/checkbox-unchecked.png")});
67   p.enableAllAttachedFilesAction->setIcon(QIcon{Q(":/icons/16x16/checkbox.png")});
68   p.disableAllAttachedFilesAction->setIcon(QIcon{Q(":/icons/16x16/checkbox-unchecked.png")});
69 
70   p.addAttachmentsAction->setIcon(QIcon{Q(":/icons/16x16/list-add.png")});
71   p.removeAttachmentsAction->setIcon(QIcon{Q(":/icons/16x16/list-remove.png")});
72   p.selectAllAttachmentsAction->setIcon(QIcon{Q(":/icons/16x16/edit-select-all.png")});
73 
74   // Q_SIGNALS & Q_SLOTS
75   connect(p.addAttachmentsAction,               &QAction::triggered,                                                    this, &Tab::onAddAttachments);
76   connect(p.removeAttachmentsAction,            &QAction::triggered,                                                    this, &Tab::onRemoveAttachments);
77   connect(p.removeAllAttachmentsAction,         &QAction::triggered,                                                    this, &Tab::onRemoveAllAttachments);
78   connect(p.selectAllAttachmentsAction,         &QAction::triggered,                                                    this, &Tab::onSelectAllAttachments);
79 
80   connect(p.enableAllAttachedFilesAction,       &QAction::triggered,                                                    this, [=]() { enableDisableAllAttachedFiles(true); });
81   connect(p.disableAllAttachedFilesAction,      &QAction::triggered,                                                    this, [=]() { enableDisableAllAttachedFiles(false); });
82   connect(p.enableSelectedAttachedFilesAction,  &QAction::triggered,                                                    this, [=]() { enableDisableSelectedAttachedFiles(true); });
83   connect(p.disableSelectedAttachedFilesAction, &QAction::triggered,                                                    this, [=]() { enableDisableSelectedAttachedFiles(false); });
84 
85   connect(p.ui->attachmentDescription,            &QLineEdit::textChanged,                                                this, &Tab::onAttachmentDescriptionChanged);
86   connect(p.ui->attachmentMIMEType,               &QComboBox::currentTextChanged,                                         this, &Tab::onAttachmentMIMETypeChanged);
87   connect(p.ui->attachmentMIMEType,               &QComboBox::editTextChanged,                                            this, &Tab::onAttachmentMIMETypeChanged);
88   connect(p.ui->attachmentName,                   &QLineEdit::textChanged,                                                this, &Tab::onAttachmentNameChanged);
89   connect(p.ui->attachmentStyle,                  static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &Tab::onAttachmentStyleChanged);
90   connect(p.ui->attachments,                      &Util::BasicTreeView::ctrlDownPressed,                                  this, &Tab::onMoveAttachmentsDown);
91   connect(p.ui->attachments,                      &Util::BasicTreeView::ctrlUpPressed,                                    this, &Tab::onMoveAttachmentsUp);
92   connect(p.ui->attachments,                      &Util::BasicTreeView::customContextMenuRequested,                       this, &Tab::showAttachmentsContextMenu);
93   connect(p.ui->attachments,                      &Util::BasicTreeView::deletePressed,                                    this, &Tab::onRemoveAttachments);
94   connect(p.ui->attachments,                      &Util::BasicTreeView::insertPressed,                                    this, &Tab::onAddAttachments);
95   connect(p.ui->attachments->selectionModel(),    &QItemSelectionModel::selectionChanged,                                 this, &Tab::onAttachmentSelectionChanged);
96   connect(p.ui->attachmentsTab,                   &Util::FilesDragDropWidget::filesDropped,                               this, &Tab::addAttachments);
97   connect(p.ui->moveAttachmentsDown,              &QPushButton::clicked,                                                  this, &Tab::onMoveAttachmentsDown);
98   connect(p.ui->moveAttachmentsUp,                &QPushButton::clicked,                                                  this, &Tab::onMoveAttachmentsUp);
99   connect(p.ui->attachedFiles,                    &Util::BasicTreeView::doubleClicked,                                    this, &Tab::toggleMuxThisForSelectedAttachedFiles);
100   connect(p.ui->attachedFiles,                    &Util::BasicTreeView::allSelectedActivated,                             this, &Tab::toggleMuxThisForSelectedAttachedFiles);
101   connect(p.ui->attachedFiles,                    &Util::BasicTreeView::customContextMenuRequested,                       this, &Tab::showAttachedFilesContextMenu);
102   connect(p.attachedFilesModel,                 &AttachedFileModel::itemChanged,                                        this, &Tab::attachedFileItemChanged);
103 
104   onAttachmentSelectionChanged();
105 
106   Util::HeaderViewManager::create(*p.ui->attachments, "Merge::Attachments").setDefaultSizes({ { Q("name"), 180 }, { Q("mimeType"), 180 }, { Q("attachTo"), 130 }, { Q("size"), 100 } });
107 }
108 
109 void
withSelectedAttachedFiles(std::function<void (Track &)> code)110 Tab::withSelectedAttachedFiles(std::function<void(Track &)> code) {
111   auto &p = *p_func();
112 
113   if (p.currentlySettingInputControlValues)
114     return;
115 
116   for (auto const &attachedFile : selectedAttachedFiles()) {
117     code(*attachedFile);
118     p.attachedFilesModel->attachedFileUpdated(*attachedFile);
119   }
120 }
121 
122 void
withSelectedAttachments(std::function<void (Attachment &)> code)123 Tab::withSelectedAttachments(std::function<void(Attachment &)> code) {
124   auto &p = *p_func();
125 
126   if (p.currentlySettingInputControlValues)
127     return;
128 
129   for (auto const &attachment : selectedAttachments()) {
130     code(*attachment);
131     p.attachmentsModel->attachmentUpdated(*attachment);
132   }
133 }
134 
135 QList<Track *>
selectedAttachedFiles() const136 Tab::selectedAttachedFiles()
137   const {
138   auto &p = *p_func();
139 
140   auto attachedFiles = QList<Track *>{};
141   Util::withSelectedIndexes(p.ui->attachedFiles, [&attachedFiles, &p](QModelIndex const &idx) {
142     auto attachedFile = p.attachedFilesModel->attachedFileForRow(idx.row());
143     if (attachedFile)
144       attachedFiles << attachedFile.get();
145   });
146 
147   return attachedFiles;
148 }
149 
150 QList<Attachment *>
selectedAttachments() const151 Tab::selectedAttachments()
152   const {
153   auto &p          = *p_func();
154   auto attachments = QList<Attachment *>{};
155   Util::withSelectedIndexes(p.ui->attachments, [&attachments, &p](QModelIndex const &idx) {
156     auto attachment = p.attachmentsModel->attachmentForRow(idx.row());
157     if (attachment)
158       attachments << attachment.get();
159   });
160 
161   return attachments;
162 }
163 
164 void
selectAttachments(QList<Attachment * > const & attachments)165 Tab::selectAttachments(QList<Attachment *> const &attachments) {
166   auto &p         = *p_func();
167   auto numColumns = p.attachmentsModel->columnCount() - 1;
168   auto selection  = QItemSelection{};
169 
170   for (auto const &attachment : attachments) {
171     auto row = p.attachmentsModel->rowForAttachment(*attachment);
172     selection.select(p.attachmentsModel->index(row, 0), p.attachmentsModel->index(row, numColumns));
173   }
174 
175   p.ui->attachments->selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect);
176 }
177 
178 void
enableDisableSelectedAttachedFiles(bool enable)179 Tab::enableDisableSelectedAttachedFiles(bool enable) {
180   withSelectedAttachedFiles([=](Track &attachedFile) {
181     attachedFile.m_muxThis = enable;
182   });
183 }
184 
185 void
enableDisableAllAttachedFiles(bool enable)186 Tab::enableDisableAllAttachedFiles(bool enable) {
187   auto &p = *p_func();
188 
189   if (p.currentlySettingInputControlValues)
190     return;
191 
192   for (int row = 0, numRows = p.attachedFilesModel->rowCount(); row < numRows; ++row) {
193     auto attachedFile = p.attachedFilesModel->attachedFileForRow(row);
194     if (attachedFile) {
195       attachedFile->m_muxThis = enable;
196       p.attachedFilesModel->attachedFileUpdated(*attachedFile);
197     }
198   }
199 }
200 
201 void
onAttachmentNameChanged(QString newValue)202 Tab::onAttachmentNameChanged(QString newValue) {
203   withSelectedAttachments([&newValue](auto &attachment) { attachment.m_name = newValue; });
204 }
205 
206 void
onAttachmentDescriptionChanged(QString newValue)207 Tab::onAttachmentDescriptionChanged(QString newValue) {
208   withSelectedAttachments([&newValue](auto &attachment) { attachment.m_description = newValue; });
209 }
210 
211 void
onAttachmentMIMETypeChanged(QString newValue)212 Tab::onAttachmentMIMETypeChanged(QString newValue) {
213   withSelectedAttachments([&newValue](auto &attachment) { attachment.m_MIMEType = newValue; });
214 }
215 
216 void
onAttachmentStyleChanged(int newValue)217 Tab::onAttachmentStyleChanged(int newValue) {
218   auto &p   = *p_func();
219   auto data = p.ui->attachmentStyle->itemData(newValue);
220   if (!data.isValid())
221     return;
222 
223   auto style = data.toInt() == Attachment::ToAllFiles ? Attachment::ToAllFiles : Attachment::ToFirstFile;
224   withSelectedAttachments([style](auto &attachment) { attachment.m_style = style; });
225 }
226 
227 AttachmentPtr
prepareFileForAttaching(QString const & fileName,bool alwaysAdd)228 Tab::prepareFileForAttaching(QString const &fileName,
229                              bool alwaysAdd) {
230   auto info = QFileInfo{fileName};
231   if (info.size() > 0x7fffffff) {
232     Util::MessageBox::critical(this)
233       ->title(QY("Reading failed"))
234       .text(Q("%1 %2")
235             .arg(QY("The file (%1) is too big (%2).").arg(fileName).arg(Q(mtx::string::format_file_size(info.size()))))
236             .arg(QY("Only files smaller than 2 GiB are supported.")))
237       .exec();
238     return {};
239   }
240 
241   auto &cfg = Util::Settings::get();
242 
243   if (!alwaysAdd) {
244     auto existingFileName = findExistingAttachmentFileName(info.fileName());
245     if (existingFileName) {
246       if (cfg.m_mergeAttachmentsAlwaysSkipForExistingName)
247         return {};
248 
249       auto const answer = Util::MessageBox::question(this)
250         ->title(QY("Attachment with same name present"))
251         .text(Q("%1 %2")
252               .arg(QY("An attachment with the name '%1' is already present.").arg(*existingFileName))
253               .arg(QY("Do you really want to add '%1' as another attachment?").arg(QDir::toNativeSeparators(fileName))))
254         .buttons(QMessageBox::Yes | QMessageBox::No | QMessageBox::NoToAll)
255         .buttonLabel(QMessageBox::Yes,     QY("&Add attachment"))
256         .buttonLabel(QMessageBox::No,      QY("&Skip file"))
257         .buttonLabel(QMessageBox::NoToAll, QY("Always s&kip files"))
258         .defaultButton(QMessageBox::No)
259         .exec();
260 
261       if (answer == QMessageBox::NoToAll) {
262         cfg.m_mergeAttachmentsAlwaysSkipForExistingName = true;
263         cfg.save();
264       }
265 
266       if (answer != QMessageBox::Yes)
267         return {};
268     }
269   }
270 
271   auto attachment = std::make_shared<Attachment>(fileName);
272   attachment->guessMIMEType();
273 
274   return attachment;
275 }
276 
277 void
addAttachments(QStringList const & fileNames)278 Tab::addAttachments(QStringList const &fileNames) {
279   QList<AttachmentPtr> attachmentsToAdd;
280   for (auto &fileName : fileNames) {
281     auto attachment = prepareFileForAttaching(fileName, false);
282     if (attachment)
283       attachmentsToAdd << attachment;
284   }
285 
286   p_func()->attachmentsModel->addAttachments(attachmentsToAdd);
287 }
288 
289 void
onAddAttachments()290 Tab::onAddAttachments() {
291   auto fileNames = selectAttachmentsToAdd();
292   if (!fileNames.isEmpty())
293     addAttachments(fileNames);
294 }
295 
296 QStringList
selectAttachmentsToAdd()297 Tab::selectAttachmentsToAdd() {
298   auto fileNames = Util::getOpenFileNames(this, QY("Add attachments"), Util::Settings::get().lastOpenDirPath(), QY("All files") + Q(" (*)"));
299 
300   if (!fileNames.isEmpty())
301     Util::Settings::get().m_lastOpenDir.setPath(QFileInfo{fileNames[0]}.path());
302 
303   return fileNames;
304 }
305 
306 void
onRemoveAttachments()307 Tab::onRemoveAttachments() {
308   auto &p = *p_func();
309 
310   p.attachmentsModel->removeSelectedAttachments(p.ui->attachments->selectionModel()->selection());
311 
312   onAttachmentSelectionChanged();
313 }
314 
315 void
onRemoveAllAttachments()316 Tab::onRemoveAllAttachments() {
317   p_func()->attachmentsModel->reset();
318   onAttachmentSelectionChanged();
319 }
320 
321 void
onSelectAllAttachments()322 Tab::onSelectAllAttachments() {
323   auto &p      = *p_func();
324   auto numRows = p.attachmentsModel->rowCount();
325   if (!numRows)
326     return;
327 
328   auto selection = QItemSelection{};
329   selection.select(p.attachmentsModel->index(0, 0), p.attachmentsModel->index(numRows - 1, p.attachmentsModel->columnCount() - 1));
330 
331   p.ui->attachments->selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect);
332 }
333 
334 void
onAttachmentSelectionChanged()335 Tab::onAttachmentSelectionChanged() {
336   auto &p        = *p_func();
337   auto selection = p.ui->attachments->selectionModel()->selection();
338   auto numRows   = Util::numSelectedRows(selection);
339 
340   p.ui->moveAttachmentsUp->setEnabled(!!numRows);
341   p.ui->moveAttachmentsDown->setEnabled(!!numRows);
342 
343   if (!numRows) {
344     setAttachmentControlValues(nullptr);
345     enableAttachmentControls(false);
346     return;
347   }
348 
349   enableAttachmentControls(true);
350 
351   if (1 < numRows) {
352     setAttachmentControlValues(nullptr);
353     return;
354   }
355 
356   auto idxs = selection.at(0).indexes();
357   if (idxs.isEmpty() || !idxs.at(0).isValid())
358     return;
359 
360   auto attachment = p.attachmentsModel->attachmentForRow(idxs.at(0).row());
361   if (!attachment)
362     return;
363 
364   setAttachmentControlValues(attachment.get());
365 }
366 
367 void
enableAttachmentControls(bool enable)368 Tab::enableAttachmentControls(bool enable) {
369   auto &p       = *p_func();
370   auto controls = std::vector<QWidget *>{p.ui->attachmentName,     p.ui->attachmentNameLabel,     p.ui->attachmentDescription, p.ui->attachmentDescriptionLabel,
371                                          p.ui->attachmentMIMEType, p.ui->attachmentMIMETypeLabel, p.ui->attachmentStyle,       p.ui->attachmentStyleLabel,};
372   for (auto &control : controls)
373     control->setEnabled(enable);
374 }
375 
376 void
enableAttachedFilesActions()377 Tab::enableAttachedFilesActions() {
378   auto &p           = *p_func();
379   auto numSelected  = selectedAttachedFiles().size();
380   auto hasSelection = !!numSelected;
381   auto hasEntries   = !!p.attachedFilesModel->rowCount();
382 
383   p.enableSelectedAttachedFilesAction->setEnabled(hasSelection);
384   p.disableSelectedAttachedFilesAction->setEnabled(hasSelection);
385   p.enableAllAttachedFilesAction->setEnabled(hasEntries);
386   p.disableAllAttachedFilesAction->setEnabled(hasEntries);
387 
388   p.enableSelectedAttachedFilesAction->setText(QNY("&Enable selected attachment", "&Enable selected attachments", numSelected));
389   p.disableSelectedAttachedFilesAction->setText(QNY("&Disable selected attachment", "&Disable selected attachments", numSelected));
390 }
391 
392 void
enableAttachmentsActions()393 Tab::enableAttachmentsActions() {
394   auto &p           = *p_func();
395   auto numSelected  = selectedAttachments().size();
396   auto hasSelection = !!numSelected;
397   auto hasEntries   = !!p.attachmentsModel->rowCount();
398 
399   p.removeAttachmentsAction->setEnabled(hasSelection);
400   p.removeAllAttachmentsAction->setEnabled(hasEntries);
401   p.selectAllAttachmentsAction->setEnabled(hasEntries);
402 
403   p.removeAttachmentsAction->setText(QNY("&Remove attachment", "&Remove attachments", numSelected));
404 }
405 
406 void
setAttachmentControlValues(Attachment * attachment)407 Tab::setAttachmentControlValues(Attachment *attachment) {
408   auto &p = *p_func();
409 
410   p.currentlySettingInputControlValues = true;
411 
412   if (!attachment && p.ui->attachmentStyle->itemData(0).isValid())
413     p.ui->attachmentStyle->insertItem(0, QY("<Do not change>"));
414 
415   else if (attachment && !p.ui->attachmentStyle->itemData(0).isValid())
416     p.ui->attachmentStyle->removeItem(0);
417 
418   if (!attachment) {
419     p.ui->attachmentName->setText(Q(""));
420     p.ui->attachmentDescription->setText(Q(""));
421     p.ui->attachmentMIMEType->setEditText(Q(""));
422     p.ui->attachmentStyle->setCurrentIndex(0);
423 
424   } else {
425     p.ui->attachmentName->setText(        attachment->m_name);
426     p.ui->attachmentDescription->setText( attachment->m_description);
427     p.ui->attachmentMIMEType->setEditText(attachment->m_MIMEType);
428 
429     Util::setComboBoxIndexIf(p.ui->attachmentStyle, [&attachment](auto const &, auto const &data) { return data.isValid() && (data.toInt() == static_cast<int>(attachment->m_style)); });
430   }
431 
432   p.currentlySettingInputControlValues = false;
433 }
434 
435 void
retranslateAttachmentsUI()436 Tab::retranslateAttachmentsUI() {
437   auto &p = *p_func();
438 
439   p.attachedFilesModel->retranslateUi();
440   p.attachmentsModel->retranslateUi();
441 
442   p.enableAllAttachedFilesAction->setText(QY("E&nable all attachments"));
443   p.disableAllAttachedFilesAction->setText(QY("Di&sable all attachments"));
444 
445   p.addAttachmentsAction->setText(QY("&Add attachments"));
446   p.removeAllAttachmentsAction->setText(QY("Remove a&ll attachments"));
447   p.selectAllAttachmentsAction->setText(QY("&Select all attachments"));
448 
449   // Attachment style
450   p.ui->attachmentStyle->setItemData(0, static_cast<int>(Attachment::ToAllFiles));
451   p.ui->attachmentStyle->setItemData(1, static_cast<int>(Attachment::ToFirstFile));
452 
453   setupAttachmentsToolTips();
454 }
455 
456 void
setupAttachmentsToolTips()457 Tab::setupAttachmentsToolTips() {
458   Util::setToolTip(p_func()->ui->attachments, QY("Right-click for attachment actions"));
459 }
460 
461 void
moveAttachmentsUpOrDown(bool up)462 Tab::moveAttachmentsUpOrDown(bool up) {
463   auto &p          = *p_func();
464   auto attachments = selectedAttachments();
465 
466   p.attachmentsModel->moveAttachmentsUpOrDown(attachments, up);
467 
468   selectAttachments(attachments);
469 
470   p.ui->attachments->scrollToFirstSelected();
471 }
472 
473 void
onMoveAttachmentsUp()474 Tab::onMoveAttachmentsUp() {
475   moveAttachmentsUpOrDown(true);
476 }
477 
478 void
onMoveAttachmentsDown()479 Tab::onMoveAttachmentsDown() {
480   moveAttachmentsUpOrDown(false);
481 }
482 
483 void
showAttachmentsContextMenu(QPoint const & pos)484 Tab::showAttachmentsContextMenu(QPoint const &pos) {
485   auto &p = *p_func();
486 
487   enableAttachmentsActions();
488   p.attachmentsMenu->exec(p.ui->attachments->viewport()->mapToGlobal(pos));
489 }
490 
491 void
showAttachedFilesContextMenu(QPoint const & pos)492 Tab::showAttachedFilesContextMenu(QPoint const &pos) {
493   auto &p = *p_func();
494 
495   enableAttachedFilesActions();
496   p.attachedFilesMenu->exec(p.ui->attachedFiles->viewport()->mapToGlobal(pos));
497 }
498 
499 void
toggleMuxThisForSelectedAttachedFiles()500 Tab::toggleMuxThisForSelectedAttachedFiles() {
501   auto allEnabled            = true;
502   auto attachedFilesSelected = false;
503 
504   withSelectedAttachedFiles([&allEnabled, &attachedFilesSelected](Track const &attachedFile) {
505     attachedFilesSelected = true;
506 
507     if (!attachedFile.m_muxThis)
508       allEnabled = false;
509   });
510 
511   if (!attachedFilesSelected)
512     return;
513 
514   auto newEnabled = !allEnabled;
515 
516   withSelectedAttachedFiles([newEnabled](Track &attachedFile) { attachedFile.m_muxThis = newEnabled; });
517 }
518 
519 void
attachedFileItemChanged(QStandardItem * item)520 Tab::attachedFileItemChanged(QStandardItem *item) {
521   auto &p = *p_func();
522 
523   if (!item)
524     return;
525 
526   auto idx = p.attachedFilesModel->indexFromItem(item);
527   if (idx.column())
528     return;
529 
530   auto attachedFile = p.attachedFilesModel->attachedFileForRow(idx.row());
531   if (!attachedFile)
532     return;
533 
534   auto newMuxThis = item->checkState() == Qt::Checked;
535   if (newMuxThis == attachedFile->m_muxThis)
536     return;
537 
538   attachedFile->m_muxThis = newMuxThis;
539   p.attachedFilesModel->attachedFileUpdated(*attachedFile);
540 }
541 
542 std::optional<QString>
findExistingAttachmentFileName(QString const & fileName)543 Tab::findExistingAttachmentFileName(QString const &fileName) {
544   auto &p            = *p_func();
545   auto lowerFileName = fileName.toLower();
546 
547   for (int row = 0, numRows = p.attachedFilesModel->rowCount(); row < numRows; ++row) {
548     auto attachedFile = p.attachedFilesModel->attachedFileForRow(row);
549 
550     if (!attachedFile || !attachedFile->m_muxThis)
551       continue;
552 
553     auto existingFileName = QFileInfo{attachedFile->m_name}.fileName();
554     if (lowerFileName == existingFileName.toLower())
555       return existingFileName;
556   }
557 
558   for (int row = 0, numRows = p.attachmentsModel->rowCount(); row < numRows; ++row) {
559     auto attachment = p.attachmentsModel->attachmentForRow(row);
560 
561     if (!attachment)
562       continue;
563 
564     auto existingFileName = QFileInfo{attachment->m_fileName}.fileName();
565     if (lowerFileName == existingFileName.toLower())
566       return existingFileName;
567   }
568 
569   return {};
570 }
571 
572 void
addAttachmentsFromIdentifiedBluray(mtx::bluray::disc_library::info_t const & info)573 Tab::addAttachmentsFromIdentifiedBluray(mtx::bluray::disc_library::info_t const &info) {
574   unsigned int maxSize = 0;
575   std::filesystem::path fileName;
576 
577   for (auto const &thumbnail : info.m_thumbnails) {
578     auto size = thumbnail.m_width * thumbnail.m_height;
579     if (size < maxSize)
580       continue;
581 
582     maxSize  = size;
583     fileName = thumbnail.m_file_name;
584   }
585 
586   if (fileName.empty() || !std::filesystem::is_regular_file(fileName))
587     return;
588 
589   auto attachment = prepareFileForAttaching(Q(fileName.u8string()), true);
590   if (!attachment)
591     return;
592 
593   attachment->m_name = Q("cover%2").arg(Q(fileName.extension().u8string()).toLower());
594 
595   p_func()->attachmentsModel->addAttachments(QList<AttachmentPtr>{} << attachment);
596 }
597 
598 }
599