diff --git a/src/mkvtoolnix-gui/forms/header_editor/attached_file_page.ui b/src/mkvtoolnix-gui/forms/header_editor/attached_file_page.ui
new file mode 100644
index 000000000..1bab6e639
--- /dev/null
+++ b/src/mkvtoolnix-gui/forms/header_editor/attached_file_page.ui
@@ -0,0 +1,161 @@
+
+
+ mtx::gui::HeaderEditor::AttachedFilePage
+
+
+
+ 0
+ 0
+ 546
+ 361
+
+
+
+ Form
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+ -
+
+
+ Attachment
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
-
+
+
+ -
+
+
+ -
+
+
+ Fi&le name:
+
+
+ name
+
+
+
+ -
+
+
+ &Description:
+
+
+ description
+
+
+
+ -
+
+
+ TextLabel
+
+
+
+ -
+
+
+ true
+
+
+
+ -
+
+
+ Size:
+
+
+
+ -
+
+
+ &MIME type:
+
+
+ mimeType
+
+
+
+ -
+
+
+ &UID:
+
+
+ uid
+
+
+
+ -
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 0
+
+
+
+
+ -
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ &Reset
+
+
+
+
+
+
+
+
+ name
+ description
+ mimeType
+ uid
+
+
+
+
diff --git a/src/mkvtoolnix-gui/forms/header_editor/attachments_page.ui b/src/mkvtoolnix-gui/forms/header_editor/attachments_page.ui
new file mode 100644
index 000000000..a8676ae0d
--- /dev/null
+++ b/src/mkvtoolnix-gui/forms/header_editor/attachments_page.ui
@@ -0,0 +1,133 @@
+
+
+ mtx::gui::HeaderEditor::AttachmentsPage
+
+
+
+ 0
+ 0
+ 457
+ 472
+
+
+
+ true
+
+
+ Form
+
+
+ -
+
+
+ Attachments
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 136
+
+
+
+
+ -
+
+
+ You can add attachments by clicking on the button below, by right-clicking on a node in the tree and selecting "Add attachments" from the popup menu or by dragging & dropping files here or onto the tree.
+
+
+ true
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+ QSizePolicy::Fixed
+
+
+
+ 20
+ 20
+
+
+
+
+ -
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 0
+ 20
+
+
+
+
+ -
+
+
+ &Add attachments
+
+
+
+ :/icons/16x16/list-add.png:/icons/16x16/list-add.png
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 0
+ 20
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 135
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/mkvtoolnix-gui/forms/header_editor/tab.ui b/src/mkvtoolnix-gui/forms/header_editor/tab.ui
index d900882bb..a4ad05bce 100644
--- a/src/mkvtoolnix-gui/forms/header_editor/tab.ui
+++ b/src/mkvtoolnix-gui/forms/header_editor/tab.ui
@@ -85,7 +85,7 @@
false
-
+
0
@@ -99,7 +99,10 @@
- Qt::ActionsContextMenu
+ Qt::CustomContextMenu
+
+
+ true
QAbstractItemView::NoEditTriggers
@@ -130,6 +133,11 @@
QLabel
mkvtoolnix-gui/util/elide_label.h
+
+ mtx::gui::Util::BasicTreeView
+ QTreeView
+ mkvtoolnix-gui/util/basic_tree_view.h
+
diff --git a/src/mkvtoolnix-gui/header_editor/attached_file_page.cpp b/src/mkvtoolnix-gui/header_editor/attached_file_page.cpp
new file mode 100644
index 000000000..931f18c8e
--- /dev/null
+++ b/src/mkvtoolnix-gui/header_editor/attached_file_page.cpp
@@ -0,0 +1,229 @@
+#include "common/common_pch.h"
+
+#include
+#include
+#include
+
+#include
+
+#include "common/ebml.h"
+#include "common/extern_data.h"
+#include "common/qt.h"
+#include "common/strings/formatting.h"
+#include "mkvtoolnix-gui/forms/header_editor/attached_file_page.h"
+#include "mkvtoolnix-gui/forms/header_editor/tab.h"
+#include "mkvtoolnix-gui/header_editor/attached_file_page.h"
+#include "mkvtoolnix-gui/util/file_dialog.h"
+#include "mkvtoolnix-gui/util/message_box.h"
+#include "mkvtoolnix-gui/util/settings.h"
+#include "mkvtoolnix-gui/util/widget.h"
+
+namespace mtx { namespace gui { namespace HeaderEditor {
+
+using namespace mtx::gui;
+
+AttachedFilePage::AttachedFilePage(Tab &parent,
+ PageBase &topLevelPage,
+ KaxAttachedPtr const &attachment)
+ : PageBase{parent, "todo"}
+ , ui{new Ui::AttachedFilePage}
+ , m_filesDDHandler{Util::FilesDragDropHandler::Mode::Remember}
+ , m_topLevelPage(topLevelPage)
+ , m_attachment{attachment}
+{
+ ui->setupUi(this);
+
+ connect(ui->reset, &QPushButton::clicked, this, &AttachedFilePage::setControlsFromAttachment);
+}
+
+AttachedFilePage::~AttachedFilePage() {
+}
+
+void
+AttachedFilePage::retranslateUi() {
+ ui->retranslateUi(this);
+
+ ui->size->setText(formatSize());
+
+ Util::setToolTip(ui->name, Q("%1 %2").arg(QY("Other parts of the file (e.g. a subtitle track) may refer to this attachment via this name.")).arg(QY("The name must not be left empty.")));
+ Util::setToolTip(ui->description, Q("%1 %2").arg(QY("An arbitrary description meant for the user.")).arg(QY("The description can be left empty.")));
+ Util::setToolTip(ui->mimeType, Q("%1 %2").arg(QY("The MIME type determines which program can be used for handling its content.")).arg(QY("The MIME type must not be left empty.")));
+ Util::setToolTip(ui->uid, Q("%1 %2").arg(QY("A unique, positive number unambiguously identifying the attachment within the Matroska file.")).arg(QY("The UID must not be left empty.")));
+ Util::setToolTip(ui->reset, QY("Reset the attachment values on this page to how they're saved in the file."));
+}
+
+void
+AttachedFilePage::init() {
+ for (auto &mimeType : mime_types)
+ ui->mimeType->addItem(Q(mimeType.name), Q(mimeType.name));
+
+ retranslateUi();
+
+ setControlsFromAttachment();
+
+ m_parent.appendPage(this, m_topLevelPage.m_pageIdx);
+
+ m_topLevelPage.m_children << this;
+}
+
+void
+AttachedFilePage::setControlsFromAttachment() {
+ auto mimeType = Q(FindChildValue(*m_attachment));
+
+ ui->name->setText(Q(FindChildValue(*m_attachment)));
+ ui->description->setText(Q(FindChildValue(*m_attachment)));
+ ui->mimeType->setEditText(mimeType);
+ ui->uid->setText(QString::number(FindChildValue(*m_attachment)));
+
+ Util::setComboBoxTextByData(ui->mimeType, mimeType);
+
+ ui->size->setText(formatSize());
+
+ m_newFileContent.reset();
+}
+
+void
+AttachedFilePage::dragEnterEvent(QDragEnterEvent *event) {
+ m_filesDDHandler.handle(event, false);
+}
+
+void
+AttachedFilePage::dropEvent(QDropEvent *event) {
+ if (m_filesDDHandler.handle(event, true))
+ emit filesDropped(m_filesDDHandler.fileNames());
+}
+
+QString
+AttachedFilePage::title()
+ const {
+ return Q(FindChildValue(*m_attachment, to_wide(Y(""))));
+}
+
+void
+AttachedFilePage::setItems(QList const &items)
+ const {
+ PageBase::setItems(items);
+
+ items.at(1)->setText(Q(FindChildValue(*m_attachment)));
+ items.at(3)->setText(Q(FindChildValue(*m_attachment)));
+ items.at(4)->setText(QString::number(FindChildValue(*m_attachment)));
+ items.at(7)->setText(formatSize());
+}
+
+QString
+AttachedFilePage::formatSize()
+ const {
+ if (m_newFileContent)
+ return QNY("%1 byte (%2)", "%1 bytes (%2)", m_newFileContent->get_size()).arg(m_newFileContent->get_size()).arg(Q(format_file_size(m_newFileContent->get_size())));
+
+ auto content = FindChild(*m_attachment);
+ if (content)
+ return QNY("%1 byte (%2)", "%1 bytes (%2)", content->GetSize()).arg(content->GetSize()).arg(Q(format_file_size(content->GetSize())));
+
+ return {};
+}
+
+bool
+AttachedFilePage::hasThisBeenModified()
+ const {
+ return m_newFileContent
+ || (Q(FindChildValue(*m_attachment)) != ui->name->text())
+ || (Q(FindChildValue(*m_attachment)) != ui->description->text())
+ || (Q(FindChildValue(*m_attachment)) != ui->mimeType->currentText())
+ || (QString::number(FindChildValue(*m_attachment)) != ui->uid->text());
+}
+
+void
+AttachedFilePage::modifyThis() {
+ auto description = ui->description->text();
+
+ GetChild(*m_attachment).SetValueUTF8(to_utf8(ui->name->text()));
+ GetChild(*m_attachment).SetValue(to_utf8(ui->mimeType->currentText()));
+ GetChild(*m_attachment).SetValue(ui->uid->text().toULongLong());
+
+ if (description.isEmpty())
+ DeleteChildren(*m_attachment);
+ else
+ GetChild(*m_attachment).SetValueUTF8(to_utf8(description));
+
+ if (m_newFileContent)
+ GetChild(*m_attachment).CopyBuffer(m_newFileContent->get_buffer(), m_newFileContent->get_size());
+}
+
+bool
+AttachedFilePage::validateThis()
+ const {
+ auto ok = false;
+
+ ui->uid->text().toULongLong(&ok);
+
+ ok = ok
+ && !ui->name->text().isEmpty()
+ && !ui->mimeType->currentText().isEmpty();
+
+ return ok;
+}
+
+void
+AttachedFilePage::saveContent() {
+ auto content = FindChild(*m_attachment);
+ if (!content)
+ return;
+
+ auto &settings = Util::Settings::get();
+ auto fileName = Util::getSaveFileName(this, QY("Save attachment"), Q("%1/%2").arg(Util::dirPath(settings.m_lastOutputDir.path())).arg(ui->name->text()), QY("All files") + Q(" (*)"));
+
+ if (fileName.isEmpty())
+ return;
+
+ settings.m_lastOutputDir = QFileInfo{ fileName }.absoluteDir();
+ settings.save();
+
+ QSaveFile file{fileName};
+ auto ok = true;
+
+ if (file.open(QIODevice::WriteOnly)) {
+ file.write(reinterpret_cast(content->GetBuffer()), content->GetSize());
+ ok = file.commit();
+ } else
+ ok = false;
+
+ if (!ok)
+ Util::MessageBox::critical(this)->title(QY("Saving failed")).text(QY("Creating the file failed. Check to make sure you have permission to write to that directory and that the drive is not full.")).exec();
+}
+
+void
+AttachedFilePage::replaceContent(bool deriveNameAndMimeType) {
+ auto &settings = Util::Settings::get();
+ auto fileName = Util::getOpenFileName(this, QY("Replace attachment"), Util::dirPath(settings.m_lastOpenDir.path()), QY("All files") + Q(" (*)"));
+
+ if (fileName.isEmpty())
+ return;
+
+ auto fileInfo = QFileInfo{ fileName };
+ settings.m_lastOpenDir = fileInfo.absoluteDir();
+ settings.save();
+
+ QFile file{fileName};
+
+ if (!file.open(QIODevice::ReadOnly)) {
+ Util::MessageBox::critical(this)->title(QY("Reading failed")).text(QY("The file you tried to open (%1) could not be read successfully.").arg(fileName)).exec();
+ return;
+ }
+
+ auto newContent = file.readAll();
+ m_newFileContent = memory_c::clone(newContent.data(), newContent.count());
+
+ ui->size->setText(formatSize());
+
+ if (!deriveNameAndMimeType)
+ return;
+
+ auto mimeType = Q(guess_mime_type(to_utf8(fileName), true));
+
+ ui->name->setText(fileInfo.fileName());
+ ui->mimeType->setEditText(mimeType);
+ Util::setComboBoxTextByData(ui->mimeType, mimeType);
+}
+
+}}}
diff --git a/src/mkvtoolnix-gui/header_editor/attached_file_page.h b/src/mkvtoolnix-gui/header_editor/attached_file_page.h
new file mode 100644
index 000000000..63ef5fde0
--- /dev/null
+++ b/src/mkvtoolnix-gui/header_editor/attached_file_page.h
@@ -0,0 +1,62 @@
+#ifndef MTX_MKVTOOLNIX_GUI_HEADER_EDITOR_ATTACHED_FILE_PAGE_H
+#define MTX_MKVTOOLNIX_GUI_HEADER_EDITOR_ATTACHED_FILE_PAGE_H
+
+#include "common/common_pch.h"
+
+#include "mkvtoolnix-gui/header_editor/page_base.h"
+#include "mkvtoolnix-gui/util/files_drag_drop_handler.h"
+
+namespace libmatroska {
+class KaxAttached;
+};
+
+namespace mtx { namespace gui { namespace HeaderEditor {
+
+namespace Ui {
+class AttachedFilePage;
+}
+
+using KaxAttachedPtr = std::shared_ptr;
+
+class AttachedFilePage: public PageBase {
+ Q_OBJECT;
+
+public:
+ std::unique_ptr ui;
+ mtx::gui::Util::FilesDragDropHandler m_filesDDHandler;
+ PageBase &m_topLevelPage;
+ KaxAttachedPtr m_attachment;
+ memory_cptr m_newFileContent;
+
+public:
+ AttachedFilePage(Tab &parent, PageBase &topLevelPage, KaxAttachedPtr const &attachment);
+ virtual ~AttachedFilePage();
+
+ virtual void init();
+
+ virtual void retranslateUi() override;
+
+ virtual QString title() const override;
+ virtual void setItems(QList const &items) const override;
+
+ virtual bool hasThisBeenModified() const;
+ virtual void modifyThis();
+ virtual bool validateThis() const;
+
+ virtual void setControlsFromAttachment();
+ virtual void saveContent();
+ virtual void replaceContent(bool deriveNameAndMimeType);
+
+signals:
+ void filesDropped(QStringList const &fileNames);
+
+protected:
+ virtual void dragEnterEvent(QDragEnterEvent *event) override;
+ virtual void dropEvent(QDropEvent *event) override;
+
+ virtual QString formatSize() const;
+};
+
+}}}
+
+#endif // MTX_MKVTOOLNIX_GUI_HEADER_EDITOR_ATTACHED_FILE_PAGE_H
diff --git a/src/mkvtoolnix-gui/header_editor/attachments_page.cpp b/src/mkvtoolnix-gui/header_editor/attachments_page.cpp
new file mode 100644
index 000000000..252d6768b
--- /dev/null
+++ b/src/mkvtoolnix-gui/header_editor/attachments_page.cpp
@@ -0,0 +1,79 @@
+#include "common/common_pch.h"
+
+#include
+#include
+
+#include
+
+#include "common/qt.h"
+#include "mkvtoolnix-gui/forms/header_editor/attachments_page.h"
+#include "mkvtoolnix-gui/forms/header_editor/tab.h"
+#include "mkvtoolnix-gui/header_editor/attached_file_page.h"
+#include "mkvtoolnix-gui/header_editor/attachments_page.h"
+
+namespace mtx { namespace gui { namespace HeaderEditor {
+
+using namespace mtx::gui;
+
+AttachmentsPage::AttachmentsPage(Tab &parent,
+ KaxAttachedList const &attachments)
+ : TopLevelPage{parent, YT("Attachments"), true}
+ , ui{new Ui::AttachmentsPage}
+ , m_filesDDHandler{Util::FilesDragDropHandler::Mode::Remember}
+ , m_initialAttachments{attachments}
+{
+ ui->setupUi(this);
+
+ connect(ui->addAttachments, &QPushButton::clicked, &parent, &Tab::selectAttachmentsAndAdd);
+ connect(this, &AttachmentsPage::filesDropped, &parent, &Tab::addAttachments);
+}
+
+AttachmentsPage::~AttachmentsPage() {
+}
+
+void
+AttachmentsPage::init() {
+ TopLevelPage::init();
+
+ for (auto const &attachment : m_initialAttachments)
+ m_parent.addAttachment(attachment);
+}
+
+bool
+AttachmentsPage::hasThisBeenModified()
+ const {
+ auto numChildren = m_children.count();
+
+ if (m_initialAttachments.count() != numChildren)
+ return true;
+
+ for (auto idx = 0; idx < numChildren; ++idx)
+ if (m_initialAttachments[idx] != dynamic_cast(*m_children[idx]).m_attachment)
+ return true;
+
+ return false;
+}
+
+void
+AttachmentsPage::retranslateUi() {
+ ui->retranslateUi(this);
+}
+
+void
+AttachmentsPage::dragEnterEvent(QDragEnterEvent *event) {
+ m_filesDDHandler.handle(event, false);
+}
+
+void
+AttachmentsPage::dropEvent(QDropEvent *event) {
+ if (m_filesDDHandler.handle(event, true))
+ emit filesDropped(m_filesDDHandler.fileNames());
+}
+
+QString
+AttachmentsPage::internalIdentifier()
+ const {
+ return Q("attachments");
+}
+
+}}}
diff --git a/src/mkvtoolnix-gui/header_editor/attachments_page.h b/src/mkvtoolnix-gui/header_editor/attachments_page.h
new file mode 100644
index 000000000..fc882177d
--- /dev/null
+++ b/src/mkvtoolnix-gui/header_editor/attachments_page.h
@@ -0,0 +1,51 @@
+#ifndef MTX_MKVTOOLNIX_GUI_HEADER_EDITOR_ATTACHMENTS_PAGE_H
+#define MTX_MKVTOOLNIX_GUI_HEADER_EDITOR_ATTACHMENTS_PAGE_H
+
+#include "common/common_pch.h"
+
+#include "mkvtoolnix-gui/header_editor/top_level_page.h"
+#include "mkvtoolnix-gui/util/files_drag_drop_handler.h"
+
+namespace libmatroska {
+class KaxAttached;
+};
+
+namespace mtx { namespace gui { namespace HeaderEditor {
+
+namespace Ui {
+class AttachmentsPage;
+}
+
+using KaxAttachedPtr = std::shared_ptr;
+using KaxAttachedList = QList< std::shared_ptr >;
+
+class AttachmentsPage: public TopLevelPage {
+ Q_OBJECT;
+
+protected:
+ std::unique_ptr ui;
+ mtx::gui::Util::FilesDragDropHandler m_filesDDHandler;
+ KaxAttachedList m_initialAttachments;
+
+public:
+ AttachmentsPage(Tab &parent, KaxAttachedList const &attachments);
+ virtual ~AttachmentsPage();
+
+ virtual void init() override;
+ virtual bool hasThisBeenModified() const override;
+ virtual QString internalIdentifier() const override;
+
+signals:
+ void filesDropped(QStringList const &fileNames);
+
+public slots:
+ virtual void retranslateUi() override;
+
+protected:
+ virtual void dragEnterEvent(QDragEnterEvent *event) override;
+ virtual void dropEvent(QDropEvent *event) override;
+};
+
+}}}
+
+#endif // MTX_MKVTOOLNIX_GUI_HEADER_EDITOR_ATTACHMENTS_PAGE_H
diff --git a/src/mkvtoolnix-gui/header_editor/page_model.cpp b/src/mkvtoolnix-gui/header_editor/page_model.cpp
index 2b8d129a4..c8db83ef5 100644
--- a/src/mkvtoolnix-gui/header_editor/page_model.cpp
+++ b/src/mkvtoolnix-gui/header_editor/page_model.cpp
@@ -96,17 +96,25 @@ PageModel::itemsForIndex(QModelIndex const &idx) {
return items;
}
+QModelIndex
+PageModel::indexFromPage(PageBase *page)
+ const {
+ return Util::findIndex(*this, [this, page](QModelIndex const &idx) -> bool {
+ return selectedPage(idx) == page;
+ });
+}
+
void
PageModel::retranslateUi() {
Util::setDisplayableAndSymbolicColumnNames(*this, {
- { QY("Type"), Q("type") },
- { QY("Codec"), Q("codec") },
- { QY("Language"), Q("language") },
- { QY("Name"), Q("name") },
- { QY("UID"), Q("uid") },
- { QY("Default track"), Q("defaultTrackFlag") },
- { QY("Forced track"), Q("forcedTrackFlag") },
- { QY("Properties"), Q("properties") },
+ { QY("Type"), Q("type") },
+ { QY("Codec/MIME type"), Q("codec") },
+ { QY("Language"), Q("language") },
+ { QY("Name/Description"), Q("name") },
+ { QY("UID"), Q("uid") },
+ { QY("Default track"), Q("defaultTrackFlag") },
+ { QY("Forced track"), Q("forcedTrackFlag") },
+ { QY("Properties"), Q("properties") },
});
horizontalHeaderItem(4)->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
diff --git a/src/mkvtoolnix-gui/header_editor/page_model.h b/src/mkvtoolnix-gui/header_editor/page_model.h
index 5e7e17456..a84cf17e0 100644
--- a/src/mkvtoolnix-gui/header_editor/page_model.h
+++ b/src/mkvtoolnix-gui/header_editor/page_model.h
@@ -35,6 +35,7 @@ public:
void retranslateUi();
QList itemsForIndex(QModelIndex const &idx);
+ QModelIndex indexFromPage(PageBase *page) const;
};
}}}
diff --git a/src/mkvtoolnix-gui/header_editor/tab.cpp b/src/mkvtoolnix-gui/header_editor/tab.cpp
index c7b9c29ca..1948bbce5 100644
--- a/src/mkvtoolnix-gui/header_editor/tab.cpp
+++ b/src/mkvtoolnix-gui/header_editor/tab.cpp
@@ -1,17 +1,25 @@
#include "common/common_pch.h"
#include
+#include
#include
+#include
+#include
#include
#include
+#include "common/construct.h"
#include "common/ebml.h"
+#include "common/extern_data.h"
#include "common/qt.h"
#include "common/segmentinfo.h"
#include "common/segment_tracks.h"
+#include "common/unique_numbers.h"
#include "mkvtoolnix-gui/forms/header_editor/tab.h"
#include "mkvtoolnix-gui/header_editor/ascii_string_value_page.h"
+#include "mkvtoolnix-gui/header_editor/attached_file_page.h"
+#include "mkvtoolnix-gui/header_editor/attachments_page.h"
#include "mkvtoolnix-gui/header_editor/bit_value_page.h"
#include "mkvtoolnix-gui/header_editor/bool_value_page.h"
#include "mkvtoolnix-gui/header_editor/float_value_page.h"
@@ -23,6 +31,8 @@
#include "mkvtoolnix-gui/header_editor/track_type_page.h"
#include "mkvtoolnix-gui/header_editor/unsigned_integer_value_page.h"
#include "mkvtoolnix-gui/main_window/main_window.h"
+#include "mkvtoolnix-gui/util/basic_tree_view.h"
+#include "mkvtoolnix-gui/util/file_dialog.h"
#include "mkvtoolnix-gui/util/header_view_manager.h"
#include "mkvtoolnix-gui/util/model.h"
#include "mkvtoolnix-gui/util/message_box.h"
@@ -39,8 +49,14 @@ Tab::Tab(QWidget *parent,
, ui{new Ui::Tab}
, m_fileName{fileName}
, m_model{new PageModel{this}}
+ , m_treeContextMenu{new QMenu{this}}
, m_expandAllAction{new QAction{this}}
, m_collapseAllAction{new QAction{this}}
+ , m_addAttachmentsAction{new QAction{this}}
+ , m_removeAttachmentAction{new QAction{this}}
+ , m_saveAttachmentContentAction{new QAction{this}}
+ , m_replaceAttachmentContentAction{new QAction{this}}
+ , m_replaceAttachmentContentSetValuesAction{new QAction{this}}
{
// Setup UI controls.
ui->setupUi(this);
@@ -75,7 +91,7 @@ Tab::load() {
auto expansionStatus = QHash{};
for (auto const &page : m_model->topLevelPages()) {
- auto key = page == m_segmentinfoPage ? Q("segmentinfo") : QString::number(static_cast(*page).m_trackNumber);
+ auto key = dynamic_cast(*page).internalIdentifier();
expansionStatus[key] = ui->elements->isExpanded(page->m_pageIdx);
}
@@ -102,7 +118,7 @@ Tab::load() {
m_analyzer->close_file();
for (auto const &page : m_model->topLevelPages()) {
- auto key = page == m_segmentinfoPage ? Q("segmentinfo") : QString::number(static_cast(*page).m_trackNumber);
+ auto key = dynamic_cast(*page).internalIdentifier();
ui->elements->setExpanded(page->m_pageIdx, expansionStatus[key]);
}
@@ -115,7 +131,8 @@ Tab::load() {
if (-1 != selected2ndLevelRow)
selectedIdx = m_model->index(selected2ndLevelRow, 0, selectedIdx);
- ui->elements->selectionModel()->select(selectedIdx, QItemSelectionModel::ClearAndSelect);
+ auto selection = QItemSelection{selectedIdx, selectedIdx.sibling(selectedIdx.row(), m_model->columnCount() - 1)};
+ ui->elements->selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Current);
selectionChanged(selectedIdx, QModelIndex{});
}
@@ -123,6 +140,7 @@ void
Tab::save() {
auto segmentinfoModified = false;
auto tracksModified = false;
+ auto attachmentsModified = false;
for (auto const &page : m_model->topLevelPages()) {
if (!page->hasBeenModified())
@@ -130,11 +148,15 @@ Tab::save() {
if (page == m_segmentinfoPage)
segmentinfoModified = true;
+
+ else if (page == m_attachmentsPage)
+ attachmentsModified = true;
+
else
tracksModified = true;
}
- if (!segmentinfoModified && !tracksModified) {
+ if (!segmentinfoModified && !tracksModified && !attachmentsModified) {
Util::MessageBox::information(this)->title(QY("File has not been modified")).text(QY("The header values have not been modified. There is nothing to save.")).exec();
return;
}
@@ -167,6 +189,21 @@ Tab::save() {
QtKaxAnalyzer::displayUpdateElementResult(this, result, QY("Saving the modified track headers failed."));
}
+ if (attachmentsModified) {
+ auto attachments = std::make_shared();
+
+ for (auto const &attachedFilePage : m_attachmentsPage->m_children)
+ attachments->PushElement(*dynamic_cast(*attachedFilePage).m_attachment.get());
+
+ auto result = attachments->ListSize() ? m_analyzer->update_element(attachments.get(), true)
+ : m_analyzer->remove_elements(KaxAttachments::ClassInfos.GlobalId);
+
+ attachments->RemoveAll();
+
+ if (kax_analyzer_c::uer_success != result)
+ QtKaxAnalyzer::displayUpdateElementResult(this, result, QY("Saving the modified attachments failed."));
+ }
+
m_analyzer->close_file();
load();
@@ -183,15 +220,21 @@ Tab::setupUi() {
ui->directory->setText(info.path());
ui->elements->setModel(m_model);
- ui->elements->addAction(m_expandAllAction);
- ui->elements->addAction(m_collapseAllAction);
+ ui->elements->acceptDroppedFiles(true);
Util::HeaderViewManager::create(*ui->elements, "HeaderEditor::Elements");
Util::preventScrollingWithoutFocus(this);
- connect(ui->elements->selectionModel(), &QItemSelectionModel::currentChanged, this, &Tab::selectionChanged);
- connect(m_expandAllAction, &QAction::triggered, this, &Tab::expandAll);
- connect(m_collapseAllAction, &QAction::triggered, this, &Tab::collapseAll);
+ connect(ui->elements, &Util::BasicTreeView::customContextMenuRequested, this, &Tab::showTreeContextMenu);
+ connect(ui->elements, &Util::BasicTreeView::filesDropped, this, &Tab::addAttachments);
+ connect(ui->elements->selectionModel(), &QItemSelectionModel::currentChanged, this, &Tab::selectionChanged);
+ connect(m_expandAllAction, &QAction::triggered, this, &Tab::expandAll);
+ connect(m_collapseAllAction, &QAction::triggered, this, &Tab::collapseAll);
+ connect(m_addAttachmentsAction, &QAction::triggered, this, &Tab::selectAttachmentsAndAdd);
+ connect(m_removeAttachmentAction, &QAction::triggered, this, &Tab::removeSelectedAttachment);
+ connect(m_saveAttachmentContentAction, &QAction::triggered, this, &Tab::saveAttachmentContent);
+ connect(m_replaceAttachmentContentAction, &QAction::triggered, [this]() { replaceAttachmentContent(false); });
+ connect(m_replaceAttachmentContentSetValuesAction, &QAction::triggered, [this]() { replaceAttachmentContent(true); });
}
void
@@ -207,6 +250,12 @@ Tab::model()
return m_model;
}
+PageBase *
+Tab::currentlySelectedPage()
+ const {
+ return m_model->selectedPage(ui->elements->selectionModel()->currentIndex());
+}
+
void
Tab::retranslateUi() {
ui->fileNameLabel->setText(QY("File name:"));
@@ -214,6 +263,11 @@ Tab::retranslateUi() {
m_expandAllAction->setText(QY("&Expand all"));
m_collapseAllAction->setText(QY("&Collapse all"));
+ m_addAttachmentsAction->setText(QY("&Add attachments"));
+ m_removeAttachmentAction->setText(QY("&Remove selected attachment"));
+ m_saveAttachmentContentAction->setText(QY("Save attachment content to a &file"));
+ m_replaceAttachmentContentAction->setText(QY("Replace attachment with a new &file"));
+ m_replaceAttachmentContentSetValuesAction->setText(QY("Replace attachment with new a file and &derive name && MIME type from it"));
auto &pages = m_model->pages();
for (auto const &page : pages)
@@ -233,6 +287,8 @@ Tab::populateTree() {
m_analyzer->with_elements(KaxTracks::ClassInfos.GlobalId, [this](kax_analyzer_data_c const &data) {
handleTracks(data);
});
+
+ handleAttachments();
}
void
@@ -288,8 +344,9 @@ Tab::handleSegmentInfo(kax_analyzer_data_c const &data) {
if (!m_eSegmentInfo)
return;
- auto &info = static_cast(*m_eSegmentInfo.get());
+ auto &info = dynamic_cast(*m_eSegmentInfo.get());
auto page = new TopLevelPage{*this, YT("Segment information")};
+ page->setInternalIdentifier("segmentInfo");
page->init();
(new StringValuePage{*this, *page, info, KaxTitle::ClassInfos, YT("Title"), YT("The title for the whole movie.")})->init();
@@ -311,7 +368,7 @@ Tab::handleTracks(kax_analyzer_data_c const &data) {
auto trackIdxMkvmerge = 0u;
- for (auto const &element : static_cast(*m_eTracks)) {
+ for (auto const &element : dynamic_cast(*m_eTracks)) {
auto kTrackEntry = dynamic_cast(element);
if (!kTrackEntry)
continue;
@@ -426,9 +483,34 @@ Tab::handleTracks(kax_analyzer_data_c const &data) {
}
}
+void
+Tab::handleAttachments() {
+ auto attachments = KaxAttachedList{};
+
+ m_analyzer->with_elements(KaxAttachments::ClassInfos.GlobalId, [this, &attachments](kax_analyzer_data_c const &data) {
+ auto master = std::dynamic_pointer_cast(m_analyzer->read_element(data));
+ if (!master)
+ return;
+
+ auto idx = 0u;
+ while (idx < master->ListSize()) {
+ auto attached = dynamic_cast((*master)[idx]);
+ if (attached) {
+ attachments << KaxAttachedPtr{attached};
+ master->Remove(idx);
+ } else
+ ++idx;
+ }
+ });
+
+ m_attachmentsPage = new AttachmentsPage{*this, attachments};
+ m_attachmentsPage->init();
+}
+
void
Tab::validate() {
auto pageIdx = m_model->validate();
+ // TODO: Tab::validate: handle attachments
if (!pageIdx.isValid()) {
Util::MessageBox::information(this)->title(QY("Header validation")).text(QY("All header values are OK.")).exec();
@@ -467,4 +549,121 @@ Tab::expandCollapseAll(bool expand) {
ui->elements->setExpanded(page->m_pageIdx, expand);
}
+void
+Tab::showTreeContextMenu(QPoint const &pos) {
+ auto selectedPage = currentlySelectedPage();
+ auto isAttachmentsPage = !!dynamic_cast(selectedPage);
+ auto isAttachedFilePage = !!dynamic_cast(selectedPage);
+ auto isAttachments = isAttachmentsPage || isAttachedFilePage;
+ auto actions = m_treeContextMenu->actions();
+
+ for (auto const &action : actions)
+ if (!action->isSeparator())
+ m_treeContextMenu->removeAction(action);
+
+ m_treeContextMenu->clear();
+
+ m_treeContextMenu->addAction(m_expandAllAction);
+ m_treeContextMenu->addAction(m_collapseAllAction);
+ m_treeContextMenu->addSeparator();
+ m_treeContextMenu->addAction(m_addAttachmentsAction);
+
+ if (isAttachments) {
+ m_treeContextMenu->addAction(m_removeAttachmentAction);
+ m_treeContextMenu->addSeparator();
+ m_treeContextMenu->addAction(m_saveAttachmentContentAction);
+ m_treeContextMenu->addAction(m_replaceAttachmentContentAction);
+ m_treeContextMenu->addAction(m_replaceAttachmentContentSetValuesAction);
+
+ m_removeAttachmentAction->setEnabled(isAttachedFilePage);
+ m_saveAttachmentContentAction->setEnabled(isAttachedFilePage);
+ m_replaceAttachmentContentAction->setEnabled(isAttachedFilePage);
+ m_replaceAttachmentContentSetValuesAction->setEnabled(isAttachedFilePage);
+ }
+
+ m_treeContextMenu->exec(ui->elements->viewport()->mapToGlobal(pos));
+}
+
+void
+Tab::selectAttachmentsAndAdd() {
+ auto &settings = Util::Settings::get();
+ auto fileNames = Util::getOpenFileNames(this, QY("Add attachments"), Util::dirPath(settings.lastOpenDirPath()), QY("All files") + Q(" (*)"));
+
+ if (fileNames.isEmpty())
+ return;
+
+ settings.m_lastOpenDir = QFileInfo{fileNames[0]}.path();
+ settings.save();
+
+ addAttachments(fileNames);
+}
+
+void
+Tab::addAttachment(KaxAttachedPtr const &attachment) {
+ if (!attachment)
+ return;
+
+ auto page = new AttachedFilePage{*this, *m_attachmentsPage, attachment};
+ page->init();
+}
+
+void
+Tab::addAttachments(QStringList const &fileNames) {
+ for (auto const &fileName : fileNames)
+ addAttachment(createAttachmentFromFile(fileName));
+
+ ui->elements->setExpanded(m_attachmentsPage->m_pageIdx, true);
+}
+
+void
+Tab::removeSelectedAttachment() {
+ auto selectedPage = currentlySelectedPage();
+ if (!selectedPage)
+ return;
+
+ auto idx = m_model->indexFromPage(selectedPage);
+ if (idx.isValid())
+ m_model->removeRow(idx.row(), idx.parent());
+
+ m_attachmentsPage->m_children.removeAll(selectedPage);
+
+ delete selectedPage;
+}
+
+KaxAttachedPtr
+Tab::createAttachmentFromFile(QString const &fileName) {
+ QByteArray content;
+ QFile file{fileName};
+
+ if (!file.open(QIODevice::ReadOnly)) {
+ Util::MessageBox::critical(this)->title(QY("Reading failed")).text(QY("The file you tried to open (%1) could not be read successfully.").arg(fileName)).exec();
+ return {};
+ }
+
+ auto mimeType = guess_mime_type(to_utf8(fileName), true);
+ auto contentAsMem = std::make_shared(content.data(), content.count(), false);
+ auto uid = create_unique_number(UNIQUE_ATTACHMENT_IDS);
+
+ return KaxAttachedPtr{
+ mtx::construct::cons(new KaxFileName, to_wide(QFileInfo{fileName}.fileName()),
+ new KaxMimeType, mimeType,
+ new KaxFileUID, uid,
+ new KaxFileData, contentAsMem)
+ };
+}
+
+void
+Tab::saveAttachmentContent() {
+ auto page = dynamic_cast(currentlySelectedPage());
+ if (page)
+ page->saveContent();
+}
+
+void
+Tab::replaceAttachmentContent(bool deriveNameAndMimeType) {
+ auto page = dynamic_cast(currentlySelectedPage());
+ if (page)
+ page->replaceContent(deriveNameAndMimeType);
+}
+
}}}
diff --git a/src/mkvtoolnix-gui/header_editor/tab.h b/src/mkvtoolnix-gui/header_editor/tab.h
index c189f6734..fdc209481 100644
--- a/src/mkvtoolnix-gui/header_editor/tab.h
+++ b/src/mkvtoolnix-gui/header_editor/tab.h
@@ -9,6 +9,7 @@
#include "mkvtoolnix-gui/header_editor/page_model.h"
class QAction;
+class QMenu;
namespace mtx { namespace gui { namespace HeaderEditor {
@@ -16,6 +17,10 @@ namespace Ui {
class Tab;
}
+using KaxAttachedPtr = std::shared_ptr;
+
+class AttachmentsPage;
+
class Tab : public QWidget {
Q_OBJECT;
@@ -29,8 +34,10 @@ protected:
PageModel *m_model;
PageBase *m_segmentinfoPage{};
+ AttachmentsPage *m_attachmentsPage{};
- QAction *m_expandAllAction, *m_collapseAllAction;
+ QMenu *m_treeContextMenu;
+ QAction *m_expandAllAction, *m_collapseAllAction, *m_addAttachmentsAction, *m_removeAttachmentAction, *m_saveAttachmentContentAction, *m_replaceAttachmentContentAction, *m_replaceAttachmentContentSetValuesAction;
std::shared_ptr m_eSegmentInfo, m_eTracks;
@@ -46,26 +53,38 @@ public:
virtual QString const &fileName() const;
virtual QString title() const;
virtual void validate();
+ virtual void addAttachment(KaxAttachedPtr const &attachment);
signals:
void removeThisTab();
public slots:
+ virtual void showTreeContextMenu(QPoint const &pos);
virtual void selectionChanged(QModelIndex const ¤t, QModelIndex const &previous);
virtual void load();
virtual void save();
virtual void expandAll();
virtual void collapseAll();
+ virtual void selectAttachmentsAndAdd();
+ virtual void addAttachments(QStringList const &fileNames);
+ virtual void removeSelectedAttachment();
+ virtual void saveAttachmentContent();
+ virtual void replaceAttachmentContent(bool deriveNameAndMimeType);
protected:
void setupUi();
void handleSegmentInfo(kax_analyzer_data_c const &data);
void handleTracks(kax_analyzer_data_c const &data);
+ void handleAttachments();
void populateTree();
void resetData();
void doModifications();
void expandCollapseAll(bool expand);
void reportValidationFailure(bool isCritical, QModelIndex const &pageIdx);
+
+ PageBase *currentlySelectedPage() const;
+
+ KaxAttachedPtr createAttachmentFromFile(QString const &fileName);
};
}}}
diff --git a/src/mkvtoolnix-gui/header_editor/top_level_page.cpp b/src/mkvtoolnix-gui/header_editor/top_level_page.cpp
index ebf089c74..566a617cb 100644
--- a/src/mkvtoolnix-gui/header_editor/top_level_page.cpp
+++ b/src/mkvtoolnix-gui/header_editor/top_level_page.cpp
@@ -21,4 +21,15 @@ TopLevelPage::init() {
m_parent.appendPage(this);
}
+QString
+TopLevelPage::internalIdentifier()
+ const {
+ return m_internalIdentifier;
+}
+
+void
+TopLevelPage::setInternalIdentifier(QString const &identifier) {
+ m_internalIdentifier = identifier;
+}
+
}}}
diff --git a/src/mkvtoolnix-gui/header_editor/top_level_page.h b/src/mkvtoolnix-gui/header_editor/top_level_page.h
index a469542a2..aa1fdd443 100644
--- a/src/mkvtoolnix-gui/header_editor/top_level_page.h
+++ b/src/mkvtoolnix-gui/header_editor/top_level_page.h
@@ -9,11 +9,15 @@ namespace mtx { namespace gui { namespace HeaderEditor {
class TopLevelPage: public EmptyPage {
Q_OBJECT;
+ QString m_internalIdentifier;
+
public:
TopLevelPage(Tab &parent, translatable_string_c const &title, bool customLayout = false);
virtual ~TopLevelPage();
virtual void init();
+ virtual QString internalIdentifier() const;
+ virtual void setInternalIdentifier(QString const &identifier);
};
}}}
diff --git a/src/mkvtoolnix-gui/header_editor/track_type_page.cpp b/src/mkvtoolnix-gui/header_editor/track_type_page.cpp
index 381326e4d..009a992c9 100644
--- a/src/mkvtoolnix-gui/header_editor/track_type_page.cpp
+++ b/src/mkvtoolnix-gui/header_editor/track_type_page.cpp
@@ -135,4 +135,10 @@ TrackTypePage::summarizeProperties() {
m_properties = properties.join(Q(", "));
}
+QString
+TrackTypePage::internalIdentifier()
+ const {
+ return Q("track %1").arg(m_trackIdxMkvmerge);
+}
+
}}}
diff --git a/src/mkvtoolnix-gui/header_editor/track_type_page.h b/src/mkvtoolnix-gui/header_editor/track_type_page.h
index 10fc8c419..aa9d949a3 100644
--- a/src/mkvtoolnix-gui/header_editor/track_type_page.h
+++ b/src/mkvtoolnix-gui/header_editor/track_type_page.h
@@ -29,6 +29,8 @@ public:
TrackTypePage(Tab &parent, EbmlMaster &master, uint64_t trackIdxMkvmerge);
virtual ~TrackTypePage();
+ virtual QString internalIdentifier() const override;
+
protected:
virtual void setItems(QList const &items) const override;
virtual void summarizeProperties();
diff --git a/src/mkvtoolnix-gui/mkvtoolnix-gui.pro b/src/mkvtoolnix-gui/mkvtoolnix-gui.pro
index f50ce8dfc..f74230fd3 100644
--- a/src/mkvtoolnix-gui/mkvtoolnix-gui.pro
+++ b/src/mkvtoolnix-gui/mkvtoolnix-gui.pro
@@ -40,7 +40,9 @@ FORMS += \
forms/watch_jobs/tab.ui \
forms/watch_jobs/tool.ui \
forms/merge/executable_location_dialog.ui \
- forms/main_window/prefs_run_program_widget.ui
+ forms/main_window/prefs_run_program_widget.ui \
+ forms/header_editor/attachments_page.ui \
+ forms/header_editor/attached_file_page.ui
RESOURCES += \
qt_resources.qrc
diff --git a/src/mkvtoolnix-gui/util/model.cpp b/src/mkvtoolnix-gui/util/model.cpp
index 26dfbbab4..21c038d8e 100644
--- a/src/mkvtoolnix-gui/util/model.cpp
+++ b/src/mkvtoolnix-gui/util/model.cpp
@@ -128,4 +128,20 @@ walkTree(QAbstractItemModel &model,
walkTree(model, model.index(row, 0, idx), worker);
}
+QModelIndex
+findIndex(QAbstractItemModel const &model,
+ std::function const &predicate,
+ QModelIndex const &idx) {
+ if (idx.isValid() && predicate(idx))
+ return idx;
+
+ for (auto row = 0, numRows = model.rowCount(idx); row < numRows; ++row) {
+ auto result = findIndex(model, predicate, model.index(row, 0, idx));
+ if (result.isValid())
+ return result;
+ }
+
+ return {};
+}
+
}}}
diff --git a/src/mkvtoolnix-gui/util/model.h b/src/mkvtoolnix-gui/util/model.h
index 1e9623034..9437f9d02 100644
--- a/src/mkvtoolnix-gui/util/model.h
+++ b/src/mkvtoolnix-gui/util/model.h
@@ -36,6 +36,7 @@ void withSelectedIndexes(QAbstractItemView *view, std::function const &worker);
+QModelIndex findIndex(QAbstractItemModel const &model, std::function const &predicate, QModelIndex const &idx = QModelIndex{});
}}}