From 085c6245b0c36e4fa03527d36b4676ced5deef3e Mon Sep 17 00:00:00 2001 From: PatTheMav Date: Wed, 4 Dec 2024 18:36:57 +0100 Subject: [PATCH] frontend: Split Qt UI dialogs into single files per C++ class --- frontend/components/DelButton.hpp | 556 +---------- frontend/components/EditWidget.hpp | 555 +---------- frontend/dialogs/OBSBasicInteraction.cpp | 18 +- frontend/dialogs/OBSBasicInteraction.hpp | 28 +- frontend/dialogs/OBSBasicSourceSelect.cpp | 7 - frontend/dialogs/OBSBasicTransform.cpp | 5 - frontend/dialogs/OBSExtraBrowsers.cpp | 532 +---------- frontend/dialogs/OBSExtraBrowsers.hpp | 69 +- frontend/dialogs/OBSMissingFiles.cpp | 404 +------- frontend/dialogs/OBSMissingFiles.hpp | 69 +- frontend/dialogs/OBSRemux.cpp | 634 +------------ frontend/dialogs/OBSRemux.hpp | 105 +-- frontend/utility/ExtraBrowsersDelegate.cpp | 442 +-------- frontend/utility/ExtraBrowsersDelegate.hpp | 64 -- frontend/utility/ExtraBrowsersModel.cpp | 322 +------ frontend/utility/ExtraBrowsersModel.hpp | 49 +- frontend/utility/MissingFilesModel.cpp | 288 +----- frontend/utility/MissingFilesModel.hpp | 58 +- .../utility/MissingFilesPathItemDelegate.cpp | 388 +------- .../utility/MissingFilesPathItemDelegate.hpp | 69 -- frontend/utility/OBSEventFilter.hpp | 51 +- .../utility/RemuxEntryPathItemDelegate.cpp | 727 +-------------- .../utility/RemuxEntryPathItemDelegate.hpp | 127 --- frontend/utility/RemuxQueueModel.cpp | 554 +---------- frontend/utility/RemuxQueueModel.hpp | 115 +-- frontend/utility/RemuxWorker.cpp | 866 +----------------- frontend/utility/RemuxWorker.hpp | 131 +-- 27 files changed, 114 insertions(+), 7119 deletions(-) diff --git a/frontend/components/DelButton.hpp b/frontend/components/DelButton.hpp index 8f4232e1d..dab84e8b7 100644 --- a/frontend/components/DelButton.hpp +++ b/frontend/components/DelButton.hpp @@ -1,110 +1,6 @@ -#include "moc_window-extra-browsers.cpp" -#include "window-dock-browser.hpp" -#include "window-basic-main.hpp" +#pragma once -#include -#include -#include -#include - -#include - -#include "ui_OBSExtraBrowsers.h" - -using namespace json11; - -#define OBJ_NAME_SUFFIX "_extraBrowser" - -enum class Column : int { - Title, - Url, - Delete, - - Count, -}; - -/* ------------------------------------------------------------------------- */ - -void ExtraBrowsersModel::Reset() -{ - items.clear(); - - OBSBasic *main = OBSBasic::Get(); - - for (int i = 0; i < main->extraBrowserDocks.size(); i++) { - Item item; - item.prevIdx = i; - item.title = main->extraBrowserDockNames[i]; - item.url = main->extraBrowserDockTargets[i]; - items.push_back(item); - } -} - -int ExtraBrowsersModel::rowCount(const QModelIndex &) const -{ - int count = items.size() + 1; - return count; -} - -int ExtraBrowsersModel::columnCount(const QModelIndex &) const -{ - return (int)Column::Count; -} - -QVariant ExtraBrowsersModel::data(const QModelIndex &index, int role) const -{ - int column = index.column(); - int idx = index.row(); - int count = items.size(); - bool validRole = role == Qt::DisplayRole || role == Qt::AccessibleTextRole; - - if (!validRole) - return QVariant(); - - if (idx >= 0 && idx < count) { - switch (column) { - case (int)Column::Title: - return items[idx].title; - case (int)Column::Url: - return items[idx].url; - } - } else if (idx == count) { - switch (column) { - case (int)Column::Title: - return newTitle; - case (int)Column::Url: - return newURL; - } - } - - return QVariant(); -} - -QVariant ExtraBrowsersModel::headerData(int section, Qt::Orientation orientation, int role) const -{ - bool validRole = role == Qt::DisplayRole || role == Qt::AccessibleTextRole; - - if (validRole && orientation == Qt::Orientation::Horizontal) { - switch (section) { - case (int)Column::Title: - return QTStr("ExtraBrowsers.DockName"); - case (int)Column::Url: - return QStringLiteral("URL"); - } - } - - return QVariant(); -} - -Qt::ItemFlags ExtraBrowsersModel::flags(const QModelIndex &index) const -{ - Qt::ItemFlags flags = QAbstractTableModel::flags(index); - - if (index.column() != (int)Column::Delete) - flags |= Qt::ItemIsEditable; - - return flags; -} +#include class DelButton : public QPushButton { public: @@ -112,451 +8,3 @@ public: QPersistentModelIndex index; }; - -class EditWidget : public QLineEdit { -public: - inline EditWidget(QWidget *parent, QModelIndex index_) : QLineEdit(parent), index(index_) {} - - QPersistentModelIndex index; -}; - -void ExtraBrowsersModel::AddDeleteButton(int idx) -{ - QTableView *widget = reinterpret_cast(parent()); - - QModelIndex index = createIndex(idx, (int)Column::Delete, nullptr); - - QPushButton *del = new DelButton(index); - del->setProperty("class", "icon-trash"); - del->setObjectName("extraPanelDelete"); - del->setMinimumSize(QSize(20, 20)); - connect(del, &QPushButton::clicked, this, &ExtraBrowsersModel::DeleteItem); - - widget->setIndexWidget(index, del); - widget->setRowHeight(idx, 20); - widget->setColumnWidth(idx, 20); -} - -void ExtraBrowsersModel::CheckToAdd() -{ - if (newTitle.isEmpty() || newURL.isEmpty()) - return; - - int idx = items.size() + 1; - beginInsertRows(QModelIndex(), idx, idx); - - Item item; - item.prevIdx = -1; - item.title = newTitle; - item.url = newURL; - items.push_back(item); - - newTitle = ""; - newURL = ""; - - endInsertRows(); - - AddDeleteButton(idx - 1); -} - -void ExtraBrowsersModel::UpdateItem(Item &item) -{ - int idx = item.prevIdx; - - OBSBasic *main = OBSBasic::Get(); - BrowserDock *dock = reinterpret_cast(main->extraBrowserDocks[idx].get()); - dock->setWindowTitle(item.title); - dock->setObjectName(item.title + OBJ_NAME_SUFFIX); - - if (main->extraBrowserDockNames[idx] != item.title) { - main->extraBrowserDockNames[idx] = item.title; - dock->toggleViewAction()->setText(item.title); - dock->setTitle(item.title); - } - - if (main->extraBrowserDockTargets[idx] != item.url) { - dock->cefWidget->setURL(QT_TO_UTF8(item.url)); - main->extraBrowserDockTargets[idx] = item.url; - } -} - -void ExtraBrowsersModel::DeleteItem() -{ - QTableView *widget = reinterpret_cast(parent()); - - DelButton *del = reinterpret_cast(sender()); - int row = del->index.row(); - - /* there's some sort of internal bug in Qt and deleting certain index - * widgets or "editors" that can cause a crash inside Qt if the widget - * is not manually removed, at least on 5.7 */ - widget->setIndexWidget(del->index, nullptr); - del->deleteLater(); - - /* --------- */ - - beginRemoveRows(QModelIndex(), row, row); - - int prevIdx = items[row].prevIdx; - items.removeAt(row); - - if (prevIdx != -1) { - int i = 0; - for (; i < deleted.size() && deleted[i] < prevIdx; i++) - ; - deleted.insert(i, prevIdx); - } - - endRemoveRows(); -} - -void ExtraBrowsersModel::Apply() -{ - OBSBasic *main = OBSBasic::Get(); - - for (Item &item : items) { - if (item.prevIdx != -1) { - UpdateItem(item); - } else { - QString uuid = QUuid::createUuid().toString(); - uuid.replace(QRegularExpression("[{}-]"), ""); - main->AddExtraBrowserDock(item.title, item.url, uuid, true); - } - } - - for (int i = deleted.size() - 1; i >= 0; i--) { - int idx = deleted[i]; - main->extraBrowserDockTargets.removeAt(idx); - main->extraBrowserDockNames.removeAt(idx); - main->extraBrowserDocks.removeAt(idx); - } - - if (main->extraBrowserDocks.empty()) - main->extraBrowserMenuDocksSeparator.clear(); - - deleted.clear(); - - Reset(); -} - -void ExtraBrowsersModel::TabSelection(bool forward) -{ - QListView *widget = reinterpret_cast(parent()); - QItemSelectionModel *selModel = widget->selectionModel(); - - QModelIndex sel = selModel->currentIndex(); - int row = sel.row(); - int col = sel.column(); - - switch (sel.column()) { - case (int)Column::Title: - if (!forward) { - if (row == 0) { - return; - } - - row -= 1; - } - - col += 1; - break; - - case (int)Column::Url: - if (forward) { - if (row == items.size()) { - return; - } - - row += 1; - } - - col -= 1; - } - - sel = createIndex(row, col, nullptr); - selModel->setCurrentIndex(sel, QItemSelectionModel::Clear); -} - -void ExtraBrowsersModel::Init() -{ - for (int i = 0; i < items.count(); i++) - AddDeleteButton(i); -} - -/* ------------------------------------------------------------------------- */ - -QWidget *ExtraBrowsersDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, - const QModelIndex &index) const -{ - QLineEdit *text = new EditWidget(parent, index); - text->installEventFilter(const_cast(this)); - text->setSizePolicy(QSizePolicy(QSizePolicy::Policy::Expanding, QSizePolicy::Policy::Expanding, - QSizePolicy::ControlType::LineEdit)); - return text; -} - -void ExtraBrowsersDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const -{ - QLineEdit *text = reinterpret_cast(editor); - text->blockSignals(true); - text->setText(index.data().toString()); - text->blockSignals(false); -} - -bool ExtraBrowsersDelegate::eventFilter(QObject *object, QEvent *event) -{ - QLineEdit *edit = qobject_cast(object); - if (!edit) - return false; - - if (LineEditCanceled(event)) { - RevertText(edit); - } - if (LineEditChanged(event)) { - UpdateText(edit); - - if (event->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast(event); - if (keyEvent->key() == Qt::Key_Tab) { - model->TabSelection(true); - } else if (keyEvent->key() == Qt::Key_Backtab) { - model->TabSelection(false); - } - } - return true; - } - - return false; -} - -bool ExtraBrowsersDelegate::ValidName(const QString &name) const -{ - for (auto &item : model->items) { - if (name.compare(item.title, Qt::CaseInsensitive) == 0) { - return false; - } - } - return true; -} - -void ExtraBrowsersDelegate::RevertText(QLineEdit *edit_) -{ - EditWidget *edit = reinterpret_cast(edit_); - int row = edit->index.row(); - int col = edit->index.column(); - bool newItem = (row == model->items.size()); - - QString oldText; - if (col == (int)Column::Title) { - oldText = newItem ? model->newTitle : model->items[row].title; - } else { - oldText = newItem ? model->newURL : model->items[row].url; - } - - edit->setText(oldText); -} - -bool ExtraBrowsersDelegate::UpdateText(QLineEdit *edit_) -{ - EditWidget *edit = reinterpret_cast(edit_); - int row = edit->index.row(); - int col = edit->index.column(); - bool newItem = (row == model->items.size()); - - QString text = edit->text().trimmed(); - - if (!newItem && text.isEmpty()) { - return false; - } - - if (col == (int)Column::Title) { - QString oldText = newItem ? model->newTitle : model->items[row].title; - bool same = oldText.compare(text, Qt::CaseInsensitive) == 0; - - if (!same && !ValidName(text)) { - edit->setText(oldText); - return false; - } - } - - if (!newItem) { - /* if edited existing item, update it*/ - switch (col) { - case (int)Column::Title: - model->items[row].title = text; - break; - case (int)Column::Url: - model->items[row].url = text; - break; - } - } else { - /* if both new values filled out, create new one */ - switch (col) { - case (int)Column::Title: - model->newTitle = text; - break; - case (int)Column::Url: - model->newURL = text; - break; - } - - model->CheckToAdd(); - } - - emit commitData(edit); - return true; -} - -/* ------------------------------------------------------------------------- */ - -OBSExtraBrowsers::OBSExtraBrowsers(QWidget *parent) : QDialog(parent), ui(new Ui::OBSExtraBrowsers) -{ - ui->setupUi(this); - - setAttribute(Qt::WA_DeleteOnClose, true); - setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); - - model = new ExtraBrowsersModel(ui->table); - - ui->table->setModel(model); - ui->table->setItemDelegateForColumn((int)Column::Title, new ExtraBrowsersDelegate(model)); - ui->table->setItemDelegateForColumn((int)Column::Url, new ExtraBrowsersDelegate(model)); - ui->table->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeMode::Stretch); - ui->table->horizontalHeader()->setSectionResizeMode((int)Column::Delete, QHeaderView::ResizeMode::Fixed); - ui->table->setEditTriggers(QAbstractItemView::EditTrigger::CurrentChanged); -} - -OBSExtraBrowsers::~OBSExtraBrowsers() {} - -void OBSExtraBrowsers::closeEvent(QCloseEvent *event) -{ - QDialog::closeEvent(event); - model->Apply(); -} - -void OBSExtraBrowsers::on_apply_clicked() -{ - model->Apply(); -} - -/* ------------------------------------------------------------------------- */ - -void OBSBasic::ClearExtraBrowserDocks() -{ - extraBrowserDockTargets.clear(); - extraBrowserDockNames.clear(); - extraBrowserDocks.clear(); -} - -void OBSBasic::LoadExtraBrowserDocks() -{ - const char *jsonStr = config_get_string(App()->GetUserConfig(), "BasicWindow", "ExtraBrowserDocks"); - - std::string err; - Json json = Json::parse(jsonStr, err); - if (!err.empty()) - return; - - Json::array array = json.array_items(); - if (!array.empty()) - extraBrowserMenuDocksSeparator = ui->menuDocks->addSeparator(); - - for (Json &item : array) { - std::string title = item["title"].string_value(); - std::string url = item["url"].string_value(); - std::string uuid = item["uuid"].string_value(); - - AddExtraBrowserDock(title.c_str(), url.c_str(), uuid.c_str(), false); - } -} - -void OBSBasic::SaveExtraBrowserDocks() -{ - Json::array array; - for (int i = 0; i < extraBrowserDocks.size(); i++) { - QDockWidget *dock = extraBrowserDocks[i].get(); - QString title = extraBrowserDockNames[i]; - QString url = extraBrowserDockTargets[i]; - QString uuid = dock->property("uuid").toString(); - Json::object obj{ - {"title", QT_TO_UTF8(title)}, - {"url", QT_TO_UTF8(url)}, - {"uuid", QT_TO_UTF8(uuid)}, - }; - array.push_back(obj); - } - - std::string output = Json(array).dump(); - config_set_string(App()->GetUserConfig(), "BasicWindow", "ExtraBrowserDocks", output.c_str()); -} - -void OBSBasic::ManageExtraBrowserDocks() -{ - if (!extraBrowsers.isNull()) { - extraBrowsers->show(); - extraBrowsers->raise(); - return; - } - - extraBrowsers = new OBSExtraBrowsers(this); - extraBrowsers->show(); -} - -void OBSBasic::AddExtraBrowserDock(const QString &title, const QString &url, const QString &uuid, bool firstCreate) -{ - static int panel_version = -1; - if (panel_version == -1) { - panel_version = obs_browser_qcef_version(); - } - - BrowserDock *dock = new BrowserDock(title); - QString bId(uuid.isEmpty() ? QUuid::createUuid().toString() : uuid); - bId.replace(QRegularExpression("[{}-]"), ""); - dock->setProperty("uuid", bId); - dock->setObjectName(title + OBJ_NAME_SUFFIX); - dock->resize(460, 600); - dock->setMinimumSize(80, 80); - dock->setWindowTitle(title); - dock->setAllowedAreas(Qt::AllDockWidgetAreas); - - QCefWidget *browser = cef->create_widget(dock, QT_TO_UTF8(url), nullptr); - if (browser && panel_version >= 1) - browser->allowAllPopups(true); - - dock->SetWidget(browser); - - /* Add support for Twitch Dashboard panels */ - if (url.contains("twitch.tv/popout") && url.contains("dashboard/live")) { - QRegularExpression re("twitch.tv\\/popout\\/([^/]+)\\/"); - QRegularExpressionMatch match = re.match(url); - QString username = match.captured(1); - if (username.length() > 0) { - std::string script; - script = "Object.defineProperty(document, 'referrer', { get: () => '"; - script += "https://twitch.tv/"; - script += QT_TO_UTF8(username); - script += "/dashboard/live"; - script += "'});"; - browser->setStartupScript(script); - } - } - - AddDockWidget(dock, Qt::RightDockWidgetArea, true); - extraBrowserDocks.push_back(std::shared_ptr(dock)); - extraBrowserDockNames.push_back(title); - extraBrowserDockTargets.push_back(url); - - if (firstCreate) { - dock->setFloating(true); - - QPoint curPos = pos(); - QSize wSizeD2 = size() / 2; - QSize dSizeD2 = dock->size() / 2; - - curPos.setX(curPos.x() + wSizeD2.width() - dSizeD2.width()); - curPos.setY(curPos.y() + wSizeD2.height() - dSizeD2.height()); - - dock->move(curPos); - dock->setVisible(true); - } -} diff --git a/frontend/components/EditWidget.hpp b/frontend/components/EditWidget.hpp index 8f4232e1d..dbc431228 100644 --- a/frontend/components/EditWidget.hpp +++ b/frontend/components/EditWidget.hpp @@ -1,117 +1,9 @@ -#include "moc_window-extra-browsers.cpp" -#include "window-dock-browser.hpp" -#include "window-basic-main.hpp" +#pragma once -#include #include -#include -#include +#include -#include - -#include "ui_OBSExtraBrowsers.h" - -using namespace json11; - -#define OBJ_NAME_SUFFIX "_extraBrowser" - -enum class Column : int { - Title, - Url, - Delete, - - Count, -}; - -/* ------------------------------------------------------------------------- */ - -void ExtraBrowsersModel::Reset() -{ - items.clear(); - - OBSBasic *main = OBSBasic::Get(); - - for (int i = 0; i < main->extraBrowserDocks.size(); i++) { - Item item; - item.prevIdx = i; - item.title = main->extraBrowserDockNames[i]; - item.url = main->extraBrowserDockTargets[i]; - items.push_back(item); - } -} - -int ExtraBrowsersModel::rowCount(const QModelIndex &) const -{ - int count = items.size() + 1; - return count; -} - -int ExtraBrowsersModel::columnCount(const QModelIndex &) const -{ - return (int)Column::Count; -} - -QVariant ExtraBrowsersModel::data(const QModelIndex &index, int role) const -{ - int column = index.column(); - int idx = index.row(); - int count = items.size(); - bool validRole = role == Qt::DisplayRole || role == Qt::AccessibleTextRole; - - if (!validRole) - return QVariant(); - - if (idx >= 0 && idx < count) { - switch (column) { - case (int)Column::Title: - return items[idx].title; - case (int)Column::Url: - return items[idx].url; - } - } else if (idx == count) { - switch (column) { - case (int)Column::Title: - return newTitle; - case (int)Column::Url: - return newURL; - } - } - - return QVariant(); -} - -QVariant ExtraBrowsersModel::headerData(int section, Qt::Orientation orientation, int role) const -{ - bool validRole = role == Qt::DisplayRole || role == Qt::AccessibleTextRole; - - if (validRole && orientation == Qt::Orientation::Horizontal) { - switch (section) { - case (int)Column::Title: - return QTStr("ExtraBrowsers.DockName"); - case (int)Column::Url: - return QStringLiteral("URL"); - } - } - - return QVariant(); -} - -Qt::ItemFlags ExtraBrowsersModel::flags(const QModelIndex &index) const -{ - Qt::ItemFlags flags = QAbstractTableModel::flags(index); - - if (index.column() != (int)Column::Delete) - flags |= Qt::ItemIsEditable; - - return flags; -} - -class DelButton : public QPushButton { -public: - inline DelButton(QModelIndex index_) : QPushButton(), index(index_) {} - - QPersistentModelIndex index; -}; +class QPersistentModelIndex; class EditWidget : public QLineEdit { public: @@ -119,444 +11,3 @@ public: QPersistentModelIndex index; }; - -void ExtraBrowsersModel::AddDeleteButton(int idx) -{ - QTableView *widget = reinterpret_cast(parent()); - - QModelIndex index = createIndex(idx, (int)Column::Delete, nullptr); - - QPushButton *del = new DelButton(index); - del->setProperty("class", "icon-trash"); - del->setObjectName("extraPanelDelete"); - del->setMinimumSize(QSize(20, 20)); - connect(del, &QPushButton::clicked, this, &ExtraBrowsersModel::DeleteItem); - - widget->setIndexWidget(index, del); - widget->setRowHeight(idx, 20); - widget->setColumnWidth(idx, 20); -} - -void ExtraBrowsersModel::CheckToAdd() -{ - if (newTitle.isEmpty() || newURL.isEmpty()) - return; - - int idx = items.size() + 1; - beginInsertRows(QModelIndex(), idx, idx); - - Item item; - item.prevIdx = -1; - item.title = newTitle; - item.url = newURL; - items.push_back(item); - - newTitle = ""; - newURL = ""; - - endInsertRows(); - - AddDeleteButton(idx - 1); -} - -void ExtraBrowsersModel::UpdateItem(Item &item) -{ - int idx = item.prevIdx; - - OBSBasic *main = OBSBasic::Get(); - BrowserDock *dock = reinterpret_cast(main->extraBrowserDocks[idx].get()); - dock->setWindowTitle(item.title); - dock->setObjectName(item.title + OBJ_NAME_SUFFIX); - - if (main->extraBrowserDockNames[idx] != item.title) { - main->extraBrowserDockNames[idx] = item.title; - dock->toggleViewAction()->setText(item.title); - dock->setTitle(item.title); - } - - if (main->extraBrowserDockTargets[idx] != item.url) { - dock->cefWidget->setURL(QT_TO_UTF8(item.url)); - main->extraBrowserDockTargets[idx] = item.url; - } -} - -void ExtraBrowsersModel::DeleteItem() -{ - QTableView *widget = reinterpret_cast(parent()); - - DelButton *del = reinterpret_cast(sender()); - int row = del->index.row(); - - /* there's some sort of internal bug in Qt and deleting certain index - * widgets or "editors" that can cause a crash inside Qt if the widget - * is not manually removed, at least on 5.7 */ - widget->setIndexWidget(del->index, nullptr); - del->deleteLater(); - - /* --------- */ - - beginRemoveRows(QModelIndex(), row, row); - - int prevIdx = items[row].prevIdx; - items.removeAt(row); - - if (prevIdx != -1) { - int i = 0; - for (; i < deleted.size() && deleted[i] < prevIdx; i++) - ; - deleted.insert(i, prevIdx); - } - - endRemoveRows(); -} - -void ExtraBrowsersModel::Apply() -{ - OBSBasic *main = OBSBasic::Get(); - - for (Item &item : items) { - if (item.prevIdx != -1) { - UpdateItem(item); - } else { - QString uuid = QUuid::createUuid().toString(); - uuid.replace(QRegularExpression("[{}-]"), ""); - main->AddExtraBrowserDock(item.title, item.url, uuid, true); - } - } - - for (int i = deleted.size() - 1; i >= 0; i--) { - int idx = deleted[i]; - main->extraBrowserDockTargets.removeAt(idx); - main->extraBrowserDockNames.removeAt(idx); - main->extraBrowserDocks.removeAt(idx); - } - - if (main->extraBrowserDocks.empty()) - main->extraBrowserMenuDocksSeparator.clear(); - - deleted.clear(); - - Reset(); -} - -void ExtraBrowsersModel::TabSelection(bool forward) -{ - QListView *widget = reinterpret_cast(parent()); - QItemSelectionModel *selModel = widget->selectionModel(); - - QModelIndex sel = selModel->currentIndex(); - int row = sel.row(); - int col = sel.column(); - - switch (sel.column()) { - case (int)Column::Title: - if (!forward) { - if (row == 0) { - return; - } - - row -= 1; - } - - col += 1; - break; - - case (int)Column::Url: - if (forward) { - if (row == items.size()) { - return; - } - - row += 1; - } - - col -= 1; - } - - sel = createIndex(row, col, nullptr); - selModel->setCurrentIndex(sel, QItemSelectionModel::Clear); -} - -void ExtraBrowsersModel::Init() -{ - for (int i = 0; i < items.count(); i++) - AddDeleteButton(i); -} - -/* ------------------------------------------------------------------------- */ - -QWidget *ExtraBrowsersDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, - const QModelIndex &index) const -{ - QLineEdit *text = new EditWidget(parent, index); - text->installEventFilter(const_cast(this)); - text->setSizePolicy(QSizePolicy(QSizePolicy::Policy::Expanding, QSizePolicy::Policy::Expanding, - QSizePolicy::ControlType::LineEdit)); - return text; -} - -void ExtraBrowsersDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const -{ - QLineEdit *text = reinterpret_cast(editor); - text->blockSignals(true); - text->setText(index.data().toString()); - text->blockSignals(false); -} - -bool ExtraBrowsersDelegate::eventFilter(QObject *object, QEvent *event) -{ - QLineEdit *edit = qobject_cast(object); - if (!edit) - return false; - - if (LineEditCanceled(event)) { - RevertText(edit); - } - if (LineEditChanged(event)) { - UpdateText(edit); - - if (event->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast(event); - if (keyEvent->key() == Qt::Key_Tab) { - model->TabSelection(true); - } else if (keyEvent->key() == Qt::Key_Backtab) { - model->TabSelection(false); - } - } - return true; - } - - return false; -} - -bool ExtraBrowsersDelegate::ValidName(const QString &name) const -{ - for (auto &item : model->items) { - if (name.compare(item.title, Qt::CaseInsensitive) == 0) { - return false; - } - } - return true; -} - -void ExtraBrowsersDelegate::RevertText(QLineEdit *edit_) -{ - EditWidget *edit = reinterpret_cast(edit_); - int row = edit->index.row(); - int col = edit->index.column(); - bool newItem = (row == model->items.size()); - - QString oldText; - if (col == (int)Column::Title) { - oldText = newItem ? model->newTitle : model->items[row].title; - } else { - oldText = newItem ? model->newURL : model->items[row].url; - } - - edit->setText(oldText); -} - -bool ExtraBrowsersDelegate::UpdateText(QLineEdit *edit_) -{ - EditWidget *edit = reinterpret_cast(edit_); - int row = edit->index.row(); - int col = edit->index.column(); - bool newItem = (row == model->items.size()); - - QString text = edit->text().trimmed(); - - if (!newItem && text.isEmpty()) { - return false; - } - - if (col == (int)Column::Title) { - QString oldText = newItem ? model->newTitle : model->items[row].title; - bool same = oldText.compare(text, Qt::CaseInsensitive) == 0; - - if (!same && !ValidName(text)) { - edit->setText(oldText); - return false; - } - } - - if (!newItem) { - /* if edited existing item, update it*/ - switch (col) { - case (int)Column::Title: - model->items[row].title = text; - break; - case (int)Column::Url: - model->items[row].url = text; - break; - } - } else { - /* if both new values filled out, create new one */ - switch (col) { - case (int)Column::Title: - model->newTitle = text; - break; - case (int)Column::Url: - model->newURL = text; - break; - } - - model->CheckToAdd(); - } - - emit commitData(edit); - return true; -} - -/* ------------------------------------------------------------------------- */ - -OBSExtraBrowsers::OBSExtraBrowsers(QWidget *parent) : QDialog(parent), ui(new Ui::OBSExtraBrowsers) -{ - ui->setupUi(this); - - setAttribute(Qt::WA_DeleteOnClose, true); - setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); - - model = new ExtraBrowsersModel(ui->table); - - ui->table->setModel(model); - ui->table->setItemDelegateForColumn((int)Column::Title, new ExtraBrowsersDelegate(model)); - ui->table->setItemDelegateForColumn((int)Column::Url, new ExtraBrowsersDelegate(model)); - ui->table->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeMode::Stretch); - ui->table->horizontalHeader()->setSectionResizeMode((int)Column::Delete, QHeaderView::ResizeMode::Fixed); - ui->table->setEditTriggers(QAbstractItemView::EditTrigger::CurrentChanged); -} - -OBSExtraBrowsers::~OBSExtraBrowsers() {} - -void OBSExtraBrowsers::closeEvent(QCloseEvent *event) -{ - QDialog::closeEvent(event); - model->Apply(); -} - -void OBSExtraBrowsers::on_apply_clicked() -{ - model->Apply(); -} - -/* ------------------------------------------------------------------------- */ - -void OBSBasic::ClearExtraBrowserDocks() -{ - extraBrowserDockTargets.clear(); - extraBrowserDockNames.clear(); - extraBrowserDocks.clear(); -} - -void OBSBasic::LoadExtraBrowserDocks() -{ - const char *jsonStr = config_get_string(App()->GetUserConfig(), "BasicWindow", "ExtraBrowserDocks"); - - std::string err; - Json json = Json::parse(jsonStr, err); - if (!err.empty()) - return; - - Json::array array = json.array_items(); - if (!array.empty()) - extraBrowserMenuDocksSeparator = ui->menuDocks->addSeparator(); - - for (Json &item : array) { - std::string title = item["title"].string_value(); - std::string url = item["url"].string_value(); - std::string uuid = item["uuid"].string_value(); - - AddExtraBrowserDock(title.c_str(), url.c_str(), uuid.c_str(), false); - } -} - -void OBSBasic::SaveExtraBrowserDocks() -{ - Json::array array; - for (int i = 0; i < extraBrowserDocks.size(); i++) { - QDockWidget *dock = extraBrowserDocks[i].get(); - QString title = extraBrowserDockNames[i]; - QString url = extraBrowserDockTargets[i]; - QString uuid = dock->property("uuid").toString(); - Json::object obj{ - {"title", QT_TO_UTF8(title)}, - {"url", QT_TO_UTF8(url)}, - {"uuid", QT_TO_UTF8(uuid)}, - }; - array.push_back(obj); - } - - std::string output = Json(array).dump(); - config_set_string(App()->GetUserConfig(), "BasicWindow", "ExtraBrowserDocks", output.c_str()); -} - -void OBSBasic::ManageExtraBrowserDocks() -{ - if (!extraBrowsers.isNull()) { - extraBrowsers->show(); - extraBrowsers->raise(); - return; - } - - extraBrowsers = new OBSExtraBrowsers(this); - extraBrowsers->show(); -} - -void OBSBasic::AddExtraBrowserDock(const QString &title, const QString &url, const QString &uuid, bool firstCreate) -{ - static int panel_version = -1; - if (panel_version == -1) { - panel_version = obs_browser_qcef_version(); - } - - BrowserDock *dock = new BrowserDock(title); - QString bId(uuid.isEmpty() ? QUuid::createUuid().toString() : uuid); - bId.replace(QRegularExpression("[{}-]"), ""); - dock->setProperty("uuid", bId); - dock->setObjectName(title + OBJ_NAME_SUFFIX); - dock->resize(460, 600); - dock->setMinimumSize(80, 80); - dock->setWindowTitle(title); - dock->setAllowedAreas(Qt::AllDockWidgetAreas); - - QCefWidget *browser = cef->create_widget(dock, QT_TO_UTF8(url), nullptr); - if (browser && panel_version >= 1) - browser->allowAllPopups(true); - - dock->SetWidget(browser); - - /* Add support for Twitch Dashboard panels */ - if (url.contains("twitch.tv/popout") && url.contains("dashboard/live")) { - QRegularExpression re("twitch.tv\\/popout\\/([^/]+)\\/"); - QRegularExpressionMatch match = re.match(url); - QString username = match.captured(1); - if (username.length() > 0) { - std::string script; - script = "Object.defineProperty(document, 'referrer', { get: () => '"; - script += "https://twitch.tv/"; - script += QT_TO_UTF8(username); - script += "/dashboard/live"; - script += "'});"; - browser->setStartupScript(script); - } - } - - AddDockWidget(dock, Qt::RightDockWidgetArea, true); - extraBrowserDocks.push_back(std::shared_ptr(dock)); - extraBrowserDockNames.push_back(title); - extraBrowserDockTargets.push_back(url); - - if (firstCreate) { - dock->setFloating(true); - - QPoint curPos = pos(); - QSize wSizeD2 = size() / 2; - QSize dSizeD2 = dock->size() / 2; - - curPos.setX(curPos.x() + wSizeD2.width() - dSizeD2.width()); - curPos.setY(curPos.y() + wSizeD2.height() - dSizeD2.height()); - - dock->move(curPos); - dock->setVisible(true); - } -} diff --git a/frontend/dialogs/OBSBasicInteraction.cpp b/frontend/dialogs/OBSBasicInteraction.cpp index 93d572c5b..e4c2b705e 100644 --- a/frontend/dialogs/OBSBasicInteraction.cpp +++ b/frontend/dialogs/OBSBasicInteraction.cpp @@ -15,22 +15,24 @@ along with this program. If not, see . ******************************************************************************/ -#include "obs-app.hpp" -#include "moc_window-basic-interaction.cpp" -#include "window-basic-main.hpp" -#include "display-helpers.hpp" +#include "OBSBasicInteraction.hpp" + +#include +#include +#include +#include #include -#include -#include -#include -#include + +#include #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN 1 #include #endif +#include "moc_OBSBasicInteraction.cpp" + using namespace std; OBSBasicInteraction::OBSBasicInteraction(QWidget *parent, OBSSource source_) diff --git a/frontend/dialogs/OBSBasicInteraction.hpp b/frontend/dialogs/OBSBasicInteraction.hpp index 6bfaaab4a..01e288b6b 100644 --- a/frontend/dialogs/OBSBasicInteraction.hpp +++ b/frontend/dialogs/OBSBasicInteraction.hpp @@ -17,17 +17,13 @@ #pragma once -#include -#include -#include - -#include -#include - -class OBSBasic; - #include "ui_OBSBasicInteraction.h" +#include + +#include + +class OBSBasic; class OBSEventFilter; class OBSBasicInteraction : public QDialog { @@ -66,17 +62,3 @@ protected: virtual void closeEvent(QCloseEvent *event) override; virtual bool nativeEvent(const QByteArray &eventType, void *message, qintptr *result) override; }; - -typedef std::function EventFilterFunc; - -class OBSEventFilter : public QObject { - Q_OBJECT -public: - OBSEventFilter(EventFilterFunc filter_) : filter(filter_) {} - -protected: - bool eventFilter(QObject *obj, QEvent *event) { return filter(obj, event); } - -public: - EventFilterFunc filter; -}; diff --git a/frontend/dialogs/OBSBasicSourceSelect.cpp b/frontend/dialogs/OBSBasicSourceSelect.cpp index 84e9ad44e..7f7cc3a3c 100644 --- a/frontend/dialogs/OBSBasicSourceSelect.cpp +++ b/frontend/dialogs/OBSBasicSourceSelect.cpp @@ -332,13 +332,6 @@ static inline const char *GetSourceDisplayName(const char *id) return obs_source_get_display_name(v_id); } -Q_DECLARE_METATYPE(OBSScene); - -template static inline T GetOBSRef(QListWidgetItem *item) -{ - return item->data(static_cast(QtDataRole::OBSRef)).value(); -} - OBSBasicSourceSelect::OBSBasicSourceSelect(OBSBasic *parent, const char *id_, undo_stack &undo_s) : QDialog(parent), ui(new Ui::OBSBasicSourceSelect), diff --git a/frontend/dialogs/OBSBasicTransform.cpp b/frontend/dialogs/OBSBasicTransform.cpp index 173a68dc6..7889f84bc 100644 --- a/frontend/dialogs/OBSBasicTransform.cpp +++ b/frontend/dialogs/OBSBasicTransform.cpp @@ -351,11 +351,6 @@ void OBSBasicTransform::OnCropChanged() ignoreTransformSignal = false; } -template static T GetOBSRef(QListWidgetItem *item) -{ - return item->data(static_cast(QtDataRole::OBSRef)).value(); -} - void OBSBasicTransform::OnSceneChanged(QListWidgetItem *current, QListWidgetItem *) { if (!current) diff --git a/frontend/dialogs/OBSExtraBrowsers.cpp b/frontend/dialogs/OBSExtraBrowsers.cpp index 8f4232e1d..ab5789e79 100644 --- a/frontend/dialogs/OBSExtraBrowsers.cpp +++ b/frontend/dialogs/OBSExtraBrowsers.cpp @@ -1,413 +1,9 @@ -#include "moc_window-extra-browsers.cpp" -#include "window-dock-browser.hpp" -#include "window-basic-main.hpp" - -#include -#include -#include -#include - -#include - +#include "OBSExtraBrowsers.hpp" #include "ui_OBSExtraBrowsers.h" -using namespace json11; +#include -#define OBJ_NAME_SUFFIX "_extraBrowser" - -enum class Column : int { - Title, - Url, - Delete, - - Count, -}; - -/* ------------------------------------------------------------------------- */ - -void ExtraBrowsersModel::Reset() -{ - items.clear(); - - OBSBasic *main = OBSBasic::Get(); - - for (int i = 0; i < main->extraBrowserDocks.size(); i++) { - Item item; - item.prevIdx = i; - item.title = main->extraBrowserDockNames[i]; - item.url = main->extraBrowserDockTargets[i]; - items.push_back(item); - } -} - -int ExtraBrowsersModel::rowCount(const QModelIndex &) const -{ - int count = items.size() + 1; - return count; -} - -int ExtraBrowsersModel::columnCount(const QModelIndex &) const -{ - return (int)Column::Count; -} - -QVariant ExtraBrowsersModel::data(const QModelIndex &index, int role) const -{ - int column = index.column(); - int idx = index.row(); - int count = items.size(); - bool validRole = role == Qt::DisplayRole || role == Qt::AccessibleTextRole; - - if (!validRole) - return QVariant(); - - if (idx >= 0 && idx < count) { - switch (column) { - case (int)Column::Title: - return items[idx].title; - case (int)Column::Url: - return items[idx].url; - } - } else if (idx == count) { - switch (column) { - case (int)Column::Title: - return newTitle; - case (int)Column::Url: - return newURL; - } - } - - return QVariant(); -} - -QVariant ExtraBrowsersModel::headerData(int section, Qt::Orientation orientation, int role) const -{ - bool validRole = role == Qt::DisplayRole || role == Qt::AccessibleTextRole; - - if (validRole && orientation == Qt::Orientation::Horizontal) { - switch (section) { - case (int)Column::Title: - return QTStr("ExtraBrowsers.DockName"); - case (int)Column::Url: - return QStringLiteral("URL"); - } - } - - return QVariant(); -} - -Qt::ItemFlags ExtraBrowsersModel::flags(const QModelIndex &index) const -{ - Qt::ItemFlags flags = QAbstractTableModel::flags(index); - - if (index.column() != (int)Column::Delete) - flags |= Qt::ItemIsEditable; - - return flags; -} - -class DelButton : public QPushButton { -public: - inline DelButton(QModelIndex index_) : QPushButton(), index(index_) {} - - QPersistentModelIndex index; -}; - -class EditWidget : public QLineEdit { -public: - inline EditWidget(QWidget *parent, QModelIndex index_) : QLineEdit(parent), index(index_) {} - - QPersistentModelIndex index; -}; - -void ExtraBrowsersModel::AddDeleteButton(int idx) -{ - QTableView *widget = reinterpret_cast(parent()); - - QModelIndex index = createIndex(idx, (int)Column::Delete, nullptr); - - QPushButton *del = new DelButton(index); - del->setProperty("class", "icon-trash"); - del->setObjectName("extraPanelDelete"); - del->setMinimumSize(QSize(20, 20)); - connect(del, &QPushButton::clicked, this, &ExtraBrowsersModel::DeleteItem); - - widget->setIndexWidget(index, del); - widget->setRowHeight(idx, 20); - widget->setColumnWidth(idx, 20); -} - -void ExtraBrowsersModel::CheckToAdd() -{ - if (newTitle.isEmpty() || newURL.isEmpty()) - return; - - int idx = items.size() + 1; - beginInsertRows(QModelIndex(), idx, idx); - - Item item; - item.prevIdx = -1; - item.title = newTitle; - item.url = newURL; - items.push_back(item); - - newTitle = ""; - newURL = ""; - - endInsertRows(); - - AddDeleteButton(idx - 1); -} - -void ExtraBrowsersModel::UpdateItem(Item &item) -{ - int idx = item.prevIdx; - - OBSBasic *main = OBSBasic::Get(); - BrowserDock *dock = reinterpret_cast(main->extraBrowserDocks[idx].get()); - dock->setWindowTitle(item.title); - dock->setObjectName(item.title + OBJ_NAME_SUFFIX); - - if (main->extraBrowserDockNames[idx] != item.title) { - main->extraBrowserDockNames[idx] = item.title; - dock->toggleViewAction()->setText(item.title); - dock->setTitle(item.title); - } - - if (main->extraBrowserDockTargets[idx] != item.url) { - dock->cefWidget->setURL(QT_TO_UTF8(item.url)); - main->extraBrowserDockTargets[idx] = item.url; - } -} - -void ExtraBrowsersModel::DeleteItem() -{ - QTableView *widget = reinterpret_cast(parent()); - - DelButton *del = reinterpret_cast(sender()); - int row = del->index.row(); - - /* there's some sort of internal bug in Qt and deleting certain index - * widgets or "editors" that can cause a crash inside Qt if the widget - * is not manually removed, at least on 5.7 */ - widget->setIndexWidget(del->index, nullptr); - del->deleteLater(); - - /* --------- */ - - beginRemoveRows(QModelIndex(), row, row); - - int prevIdx = items[row].prevIdx; - items.removeAt(row); - - if (prevIdx != -1) { - int i = 0; - for (; i < deleted.size() && deleted[i] < prevIdx; i++) - ; - deleted.insert(i, prevIdx); - } - - endRemoveRows(); -} - -void ExtraBrowsersModel::Apply() -{ - OBSBasic *main = OBSBasic::Get(); - - for (Item &item : items) { - if (item.prevIdx != -1) { - UpdateItem(item); - } else { - QString uuid = QUuid::createUuid().toString(); - uuid.replace(QRegularExpression("[{}-]"), ""); - main->AddExtraBrowserDock(item.title, item.url, uuid, true); - } - } - - for (int i = deleted.size() - 1; i >= 0; i--) { - int idx = deleted[i]; - main->extraBrowserDockTargets.removeAt(idx); - main->extraBrowserDockNames.removeAt(idx); - main->extraBrowserDocks.removeAt(idx); - } - - if (main->extraBrowserDocks.empty()) - main->extraBrowserMenuDocksSeparator.clear(); - - deleted.clear(); - - Reset(); -} - -void ExtraBrowsersModel::TabSelection(bool forward) -{ - QListView *widget = reinterpret_cast(parent()); - QItemSelectionModel *selModel = widget->selectionModel(); - - QModelIndex sel = selModel->currentIndex(); - int row = sel.row(); - int col = sel.column(); - - switch (sel.column()) { - case (int)Column::Title: - if (!forward) { - if (row == 0) { - return; - } - - row -= 1; - } - - col += 1; - break; - - case (int)Column::Url: - if (forward) { - if (row == items.size()) { - return; - } - - row += 1; - } - - col -= 1; - } - - sel = createIndex(row, col, nullptr); - selModel->setCurrentIndex(sel, QItemSelectionModel::Clear); -} - -void ExtraBrowsersModel::Init() -{ - for (int i = 0; i < items.count(); i++) - AddDeleteButton(i); -} - -/* ------------------------------------------------------------------------- */ - -QWidget *ExtraBrowsersDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, - const QModelIndex &index) const -{ - QLineEdit *text = new EditWidget(parent, index); - text->installEventFilter(const_cast(this)); - text->setSizePolicy(QSizePolicy(QSizePolicy::Policy::Expanding, QSizePolicy::Policy::Expanding, - QSizePolicy::ControlType::LineEdit)); - return text; -} - -void ExtraBrowsersDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const -{ - QLineEdit *text = reinterpret_cast(editor); - text->blockSignals(true); - text->setText(index.data().toString()); - text->blockSignals(false); -} - -bool ExtraBrowsersDelegate::eventFilter(QObject *object, QEvent *event) -{ - QLineEdit *edit = qobject_cast(object); - if (!edit) - return false; - - if (LineEditCanceled(event)) { - RevertText(edit); - } - if (LineEditChanged(event)) { - UpdateText(edit); - - if (event->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast(event); - if (keyEvent->key() == Qt::Key_Tab) { - model->TabSelection(true); - } else if (keyEvent->key() == Qt::Key_Backtab) { - model->TabSelection(false); - } - } - return true; - } - - return false; -} - -bool ExtraBrowsersDelegate::ValidName(const QString &name) const -{ - for (auto &item : model->items) { - if (name.compare(item.title, Qt::CaseInsensitive) == 0) { - return false; - } - } - return true; -} - -void ExtraBrowsersDelegate::RevertText(QLineEdit *edit_) -{ - EditWidget *edit = reinterpret_cast(edit_); - int row = edit->index.row(); - int col = edit->index.column(); - bool newItem = (row == model->items.size()); - - QString oldText; - if (col == (int)Column::Title) { - oldText = newItem ? model->newTitle : model->items[row].title; - } else { - oldText = newItem ? model->newURL : model->items[row].url; - } - - edit->setText(oldText); -} - -bool ExtraBrowsersDelegate::UpdateText(QLineEdit *edit_) -{ - EditWidget *edit = reinterpret_cast(edit_); - int row = edit->index.row(); - int col = edit->index.column(); - bool newItem = (row == model->items.size()); - - QString text = edit->text().trimmed(); - - if (!newItem && text.isEmpty()) { - return false; - } - - if (col == (int)Column::Title) { - QString oldText = newItem ? model->newTitle : model->items[row].title; - bool same = oldText.compare(text, Qt::CaseInsensitive) == 0; - - if (!same && !ValidName(text)) { - edit->setText(oldText); - return false; - } - } - - if (!newItem) { - /* if edited existing item, update it*/ - switch (col) { - case (int)Column::Title: - model->items[row].title = text; - break; - case (int)Column::Url: - model->items[row].url = text; - break; - } - } else { - /* if both new values filled out, create new one */ - switch (col) { - case (int)Column::Title: - model->newTitle = text; - break; - case (int)Column::Url: - model->newURL = text; - break; - } - - model->CheckToAdd(); - } - - emit commitData(edit); - return true; -} - -/* ------------------------------------------------------------------------- */ +#include "moc_OBSExtraBrowsers.cpp" OBSExtraBrowsers::OBSExtraBrowsers(QWidget *parent) : QDialog(parent), ui(new Ui::OBSExtraBrowsers) { @@ -438,125 +34,3 @@ void OBSExtraBrowsers::on_apply_clicked() { model->Apply(); } - -/* ------------------------------------------------------------------------- */ - -void OBSBasic::ClearExtraBrowserDocks() -{ - extraBrowserDockTargets.clear(); - extraBrowserDockNames.clear(); - extraBrowserDocks.clear(); -} - -void OBSBasic::LoadExtraBrowserDocks() -{ - const char *jsonStr = config_get_string(App()->GetUserConfig(), "BasicWindow", "ExtraBrowserDocks"); - - std::string err; - Json json = Json::parse(jsonStr, err); - if (!err.empty()) - return; - - Json::array array = json.array_items(); - if (!array.empty()) - extraBrowserMenuDocksSeparator = ui->menuDocks->addSeparator(); - - for (Json &item : array) { - std::string title = item["title"].string_value(); - std::string url = item["url"].string_value(); - std::string uuid = item["uuid"].string_value(); - - AddExtraBrowserDock(title.c_str(), url.c_str(), uuid.c_str(), false); - } -} - -void OBSBasic::SaveExtraBrowserDocks() -{ - Json::array array; - for (int i = 0; i < extraBrowserDocks.size(); i++) { - QDockWidget *dock = extraBrowserDocks[i].get(); - QString title = extraBrowserDockNames[i]; - QString url = extraBrowserDockTargets[i]; - QString uuid = dock->property("uuid").toString(); - Json::object obj{ - {"title", QT_TO_UTF8(title)}, - {"url", QT_TO_UTF8(url)}, - {"uuid", QT_TO_UTF8(uuid)}, - }; - array.push_back(obj); - } - - std::string output = Json(array).dump(); - config_set_string(App()->GetUserConfig(), "BasicWindow", "ExtraBrowserDocks", output.c_str()); -} - -void OBSBasic::ManageExtraBrowserDocks() -{ - if (!extraBrowsers.isNull()) { - extraBrowsers->show(); - extraBrowsers->raise(); - return; - } - - extraBrowsers = new OBSExtraBrowsers(this); - extraBrowsers->show(); -} - -void OBSBasic::AddExtraBrowserDock(const QString &title, const QString &url, const QString &uuid, bool firstCreate) -{ - static int panel_version = -1; - if (panel_version == -1) { - panel_version = obs_browser_qcef_version(); - } - - BrowserDock *dock = new BrowserDock(title); - QString bId(uuid.isEmpty() ? QUuid::createUuid().toString() : uuid); - bId.replace(QRegularExpression("[{}-]"), ""); - dock->setProperty("uuid", bId); - dock->setObjectName(title + OBJ_NAME_SUFFIX); - dock->resize(460, 600); - dock->setMinimumSize(80, 80); - dock->setWindowTitle(title); - dock->setAllowedAreas(Qt::AllDockWidgetAreas); - - QCefWidget *browser = cef->create_widget(dock, QT_TO_UTF8(url), nullptr); - if (browser && panel_version >= 1) - browser->allowAllPopups(true); - - dock->SetWidget(browser); - - /* Add support for Twitch Dashboard panels */ - if (url.contains("twitch.tv/popout") && url.contains("dashboard/live")) { - QRegularExpression re("twitch.tv\\/popout\\/([^/]+)\\/"); - QRegularExpressionMatch match = re.match(url); - QString username = match.captured(1); - if (username.length() > 0) { - std::string script; - script = "Object.defineProperty(document, 'referrer', { get: () => '"; - script += "https://twitch.tv/"; - script += QT_TO_UTF8(username); - script += "/dashboard/live"; - script += "'});"; - browser->setStartupScript(script); - } - } - - AddDockWidget(dock, Qt::RightDockWidgetArea, true); - extraBrowserDocks.push_back(std::shared_ptr(dock)); - extraBrowserDockNames.push_back(title); - extraBrowserDockTargets.push_back(url); - - if (firstCreate) { - dock->setFloating(true); - - QPoint curPos = pos(); - QSize wSizeD2 = size() / 2; - QSize dSizeD2 = dock->size() / 2; - - curPos.setX(curPos.x() + wSizeD2.width() - dSizeD2.width()); - curPos.setY(curPos.y() + wSizeD2.height() - dSizeD2.height()); - - dock->move(curPos); - dock->setVisible(true); - } -} diff --git a/frontend/dialogs/OBSExtraBrowsers.hpp b/frontend/dialogs/OBSExtraBrowsers.hpp index 690d784dd..c71a12cae 100644 --- a/frontend/dialogs/OBSExtraBrowsers.hpp +++ b/frontend/dialogs/OBSExtraBrowsers.hpp @@ -1,15 +1,10 @@ #pragma once +#include + #include -#include -#include -#include -#include class Ui_OBSExtraBrowsers; -class ExtraBrowsersModel; - -class QCefWidget; class OBSExtraBrowsers : public QDialog { Q_OBJECT @@ -26,63 +21,3 @@ public: public slots: void on_apply_clicked(); }; - -class ExtraBrowsersModel : public QAbstractTableModel { - Q_OBJECT - -public: - inline ExtraBrowsersModel(QObject *parent = nullptr) : QAbstractTableModel(parent) - { - Reset(); - QMetaObject::invokeMethod(this, "Init", Qt::QueuedConnection); - } - - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - int columnCount(const QModelIndex &parent = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role) const override; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - Qt::ItemFlags flags(const QModelIndex &index) const override; - - struct Item { - int prevIdx; - QString title; - QString url; - }; - - void TabSelection(bool forward); - - void AddDeleteButton(int idx); - void Reset(); - void CheckToAdd(); - void UpdateItem(Item &item); - void DeleteItem(); - void Apply(); - - QVector items; - QVector deleted; - - QString newTitle; - QString newURL; - -public slots: - void Init(); -}; - -class ExtraBrowsersDelegate : public QStyledItemDelegate { - Q_OBJECT - -public: - inline ExtraBrowsersDelegate(ExtraBrowsersModel *model_) : QStyledItemDelegate(nullptr), model(model_) {} - - QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, - const QModelIndex &index) const override; - - void setEditorData(QWidget *editor, const QModelIndex &index) const override; - - bool eventFilter(QObject *object, QEvent *event) override; - void RevertText(QLineEdit *edit); - bool UpdateText(QLineEdit *edit); - bool ValidName(const QString &text) const; - - ExtraBrowsersModel *model; -}; diff --git a/frontend/dialogs/OBSMissingFiles.cpp b/frontend/dialogs/OBSMissingFiles.cpp index 57e641a74..ca5d7a101 100644 --- a/frontend/dialogs/OBSMissingFiles.cpp +++ b/frontend/dialogs/OBSMissingFiles.cpp @@ -15,409 +15,21 @@ along with this program. If not, see . ******************************************************************************/ -#include "moc_window-missing-files.cpp" -#include "window-basic-main.hpp" +#include "OBSMissingFiles.hpp" -#include "obs-app.hpp" +#include +#include +#include -#include -#include #include -#include - -enum MissingFilesColumn { - Source, - OriginalPath, - NewPath, - State, - - Count -}; +#include "moc_OBSMissingFiles.cpp" +// TODO: Fix redefinition error of due to clash with enums defined in importer code. enum MissingFilesRole { EntryStateRole = Qt::UserRole, NewPathsToProcessRole }; -/********************************************************** - Delegate - Presents cells in the grid. -**********************************************************/ - -MissingFilesPathItemDelegate::MissingFilesPathItemDelegate(bool isOutput, const QString &defaultPath) - : QStyledItemDelegate(), - isOutput(isOutput), - defaultPath(defaultPath) -{ -} - -QWidget *MissingFilesPathItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem & /* option */, - const QModelIndex &) const -{ - QSizePolicy buttonSizePolicy(QSizePolicy::Policy::Minimum, QSizePolicy::Policy::Expanding, - QSizePolicy::ControlType::PushButton); - - QWidget *container = new QWidget(parent); - - auto browseCallback = [this, container]() { - const_cast(this)->handleBrowse(container); - }; - - auto clearCallback = [this, container]() { - const_cast(this)->handleClear(container); - }; - - QHBoxLayout *layout = new QHBoxLayout(); - layout->setContentsMargins(0, 0, 0, 0); - layout->setSpacing(0); - - QLineEdit *text = new QLineEdit(); - text->setObjectName(QStringLiteral("text")); - text->setSizePolicy(QSizePolicy(QSizePolicy::Policy::Expanding, QSizePolicy::Policy::Expanding, - QSizePolicy::ControlType::LineEdit)); - layout->addWidget(text); - - QToolButton *browseButton = new QToolButton(); - browseButton->setText("..."); - browseButton->setSizePolicy(buttonSizePolicy); - layout->addWidget(browseButton); - - container->connect(browseButton, &QToolButton::clicked, browseCallback); - - // The "clear" button is not shown in input cells - if (isOutput) { - QToolButton *clearButton = new QToolButton(); - clearButton->setText("X"); - clearButton->setSizePolicy(buttonSizePolicy); - layout->addWidget(clearButton); - - container->connect(clearButton, &QToolButton::clicked, clearCallback); - } - - container->setLayout(layout); - container->setFocusProxy(text); - - return container; -} - -void MissingFilesPathItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const -{ - QLineEdit *text = editor->findChild(); - text->setText(index.data().toString()); - - editor->setProperty(PATH_LIST_PROP, QVariant()); -} - -void MissingFilesPathItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, - const QModelIndex &index) const -{ - // We use the PATH_LIST_PROP property to pass a list of - // path strings from the editor widget into the model's - // NewPathsToProcessRole. This is only used when paths - // are selected through the "browse" or "delete" buttons - // in the editor. If the user enters new text in the - // text box, we simply pass that text on to the model - // as normal text data in the default role. - QVariant pathListProp = editor->property(PATH_LIST_PROP); - if (pathListProp.isValid()) { - QStringList list = editor->property(PATH_LIST_PROP).toStringList(); - if (isOutput) { - model->setData(index, list); - } else - model->setData(index, list, MissingFilesRole::NewPathsToProcessRole); - } else { - QLineEdit *lineEdit = editor->findChild(); - model->setData(index, lineEdit->text(), 0); - } -} - -void MissingFilesPathItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, - const QModelIndex &index) const -{ - QStyleOptionViewItem localOption = option; - initStyleOption(&localOption, index); - - QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &localOption, painter); -} - -void MissingFilesPathItemDelegate::handleBrowse(QWidget *container) -{ - - QLineEdit *text = container->findChild(); - - QString currentPath = text->text(); - if (currentPath.isEmpty() || currentPath.compare(QTStr("MissingFiles.Clear")) == 0) - currentPath = defaultPath; - - bool isSet = false; - if (isOutput) { - QString newPath = - QFileDialog::getOpenFileName(container, QTStr("MissingFiles.SelectFile"), currentPath, nullptr); - -#ifdef __APPLE__ - // TODO: Revisit when QTBUG-42661 is fixed - container->window()->raise(); -#endif - - if (!newPath.isEmpty()) { - container->setProperty(PATH_LIST_PROP, QStringList() << newPath); - isSet = true; - } - } - - if (isSet) - emit commitData(container); -} - -void MissingFilesPathItemDelegate::handleClear(QWidget *container) -{ - // An empty string list will indicate that the entry is being - // blanked and should be deleted. - container->setProperty(PATH_LIST_PROP, QStringList() << QTStr("MissingFiles.Clear")); - container->findChild()->clearFocus(); - ((QWidget *)container->parent())->setFocus(); - emit commitData(container); -} - -/** - Model -**/ - -MissingFilesModel::MissingFilesModel(QObject *parent) : QAbstractTableModel(parent) -{ - QStyle *style = QApplication::style(); - - warningIcon = style->standardIcon(QStyle::SP_MessageBoxWarning); -} - -int MissingFilesModel::rowCount(const QModelIndex &) const -{ - return files.length(); -} - -int MissingFilesModel::columnCount(const QModelIndex &) const -{ - return MissingFilesColumn::Count; -} - -int MissingFilesModel::found() const -{ - int res = 0; - - for (int i = 0; i < files.length(); i++) { - if (files[i].state != Missing && files[i].state != Cleared) - res++; - } - - return res; -} - -QVariant MissingFilesModel::data(const QModelIndex &index, int role) const -{ - QVariant result = QVariant(); - - if (index.row() >= files.length()) { - return QVariant(); - } else if (role == Qt::DisplayRole) { - QFileInfo fi(files[index.row()].originalPath); - - switch (index.column()) { - case MissingFilesColumn::Source: - result = files[index.row()].source; - break; - case MissingFilesColumn::OriginalPath: - result = fi.fileName(); - break; - case MissingFilesColumn::NewPath: - result = files[index.row()].newPath; - break; - case MissingFilesColumn::State: - switch (files[index.row()].state) { - case MissingFilesState::Missing: - result = QTStr("MissingFiles.Missing"); - break; - - case MissingFilesState::Replaced: - result = QTStr("MissingFiles.Replaced"); - break; - - case MissingFilesState::Found: - result = QTStr("MissingFiles.Found"); - break; - - case MissingFilesState::Cleared: - result = QTStr("MissingFiles.Cleared"); - break; - } - break; - } - } else if (role == Qt::DecorationRole && index.column() == MissingFilesColumn::Source) { - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - OBSSourceAutoRelease source = obs_get_source_by_name(files[index.row()].source.toStdString().c_str()); - - if (source) { - result = main->GetSourceIcon(obs_source_get_id(source)); - } - } else if (role == Qt::FontRole && index.column() == MissingFilesColumn::State) { - QFont font = QFont(); - font.setBold(true); - - result = font; - } else if (role == Qt::ToolTipRole && index.column() == MissingFilesColumn::State) { - switch (files[index.row()].state) { - case MissingFilesState::Missing: - result = QTStr("MissingFiles.Missing"); - break; - - case MissingFilesState::Replaced: - result = QTStr("MissingFiles.Replaced"); - break; - - case MissingFilesState::Found: - result = QTStr("MissingFiles.Found"); - break; - - case MissingFilesState::Cleared: - result = QTStr("MissingFiles.Cleared"); - break; - - default: - break; - } - } else if (role == Qt::ToolTipRole) { - switch (index.column()) { - case MissingFilesColumn::OriginalPath: - result = files[index.row()].originalPath; - break; - case MissingFilesColumn::NewPath: - result = files[index.row()].newPath; - break; - default: - break; - } - } - - return result; -} - -Qt::ItemFlags MissingFilesModel::flags(const QModelIndex &index) const -{ - Qt::ItemFlags flags = QAbstractTableModel::flags(index); - - if (index.column() == MissingFilesColumn::OriginalPath) { - flags &= ~Qt::ItemIsEditable; - } else if (index.column() == MissingFilesColumn::NewPath && index.row() != files.length()) { - flags |= Qt::ItemIsEditable; - } - - return flags; -} - -void MissingFilesModel::fileCheckLoop(QList files, QString path, bool skipPrompt) -{ - loop = false; - QUrl url = QUrl().fromLocalFile(path); - QString dir = url.toDisplayString(QUrl::RemoveScheme | QUrl::RemoveFilename | QUrl::PreferLocalFile); - - bool prompted = skipPrompt; - - for (int i = 0; i < files.length(); i++) { - if (files[i].state != MissingFilesState::Missing) - continue; - - QUrl origFile = QUrl().fromLocalFile(files[i].originalPath); - QString filename = origFile.fileName(); - QString testFile = dir + filename; - - if (os_file_exists(testFile.toStdString().c_str())) { - if (!prompted) { - QMessageBox::StandardButton button = - QMessageBox::question(nullptr, QTStr("MissingFiles.AutoSearch"), - QTStr("MissingFiles.AutoSearchText")); - - if (button == QMessageBox::No) - break; - - prompted = true; - } - QModelIndex in = index(i, MissingFilesColumn::NewPath); - setData(in, testFile, 0); - } - } - loop = true; -} - -bool MissingFilesModel::setData(const QModelIndex &index, const QVariant &value, int role) -{ - bool success = false; - - if (role == MissingFilesRole::NewPathsToProcessRole) { - QStringList list = value.toStringList(); - - int row = index.row() + 1; - beginInsertRows(QModelIndex(), row, row); - - MissingFileEntry entry; - entry.originalPath = list[0].replace("\\", "/"); - entry.source = list[1]; - - files.insert(row, entry); - row++; - - endInsertRows(); - - success = true; - } else { - QString path = value.toString(); - if (index.column() == MissingFilesColumn::NewPath) { - files[index.row()].newPath = value.toString(); - QString fileName = QUrl(path).fileName(); - QString origFileName = QUrl(files[index.row()].originalPath).fileName(); - - if (path.isEmpty()) { - files[index.row()].state = MissingFilesState::Missing; - } else if (path.compare(QTStr("MissingFiles.Clear")) == 0) { - files[index.row()].state = MissingFilesState::Cleared; - } else if (fileName.compare(origFileName) == 0) { - files[index.row()].state = MissingFilesState::Found; - - if (loop) - fileCheckLoop(files, path, false); - } else { - files[index.row()].state = MissingFilesState::Replaced; - - if (loop) - fileCheckLoop(files, path, false); - } - - emit dataChanged(index, index); - success = true; - } - } - - return success; -} - -QVariant MissingFilesModel::headerData(int section, Qt::Orientation orientation, int role) const -{ - QVariant result = QVariant(); - - if (role == Qt::DisplayRole && orientation == Qt::Orientation::Horizontal) { - switch (section) { - case MissingFilesColumn::State: - result = QTStr("MissingFiles.State"); - break; - case MissingFilesColumn::Source: - result = QTStr("Basic.Main.Source"); - break; - case MissingFilesColumn::OriginalPath: - result = QTStr("MissingFiles.MissingFile"); - break; - case MissingFilesColumn::NewPath: - result = QTStr("MissingFiles.NewFile"); - break; - } - } - - return result; -} +// TODO: Fix redefinition error of due to clash with enums defined in importer code. +enum MissingFilesColumn { Source, OriginalPath, NewPath, State, Count }; OBSMissingFiles::OBSMissingFiles(obs_missing_files_t *files, QWidget *parent) : QDialog(parent), diff --git a/frontend/dialogs/OBSMissingFiles.hpp b/frontend/dialogs/OBSMissingFiles.hpp index 2b24e0f8e..2d5bb41e6 100644 --- a/frontend/dialogs/OBSMissingFiles.hpp +++ b/frontend/dialogs/OBSMissingFiles.hpp @@ -17,15 +17,14 @@ #pragma once -#include -#include -#include "obs-app.hpp" #include "ui_OBSMissingFiles.h" -class MissingFilesModel; +#include -enum MissingFilesState { Missing, Found, Replaced, Cleared }; -Q_DECLARE_METATYPE(MissingFilesState); +#include +#include + +class MissingFilesModel; class OBSMissingFiles : public QDialog { Q_OBJECT @@ -52,61 +51,3 @@ private: public slots: void dataChanged(); }; - -class MissingFilesModel : public QAbstractTableModel { - Q_OBJECT - - friend class OBSMissingFiles; - -public: - explicit MissingFilesModel(QObject *parent = 0); - - int rowCount(const QModelIndex &parent = QModelIndex()) const; - int columnCount(const QModelIndex &parent = QModelIndex()) const; - int found() const; - QVariant data(const QModelIndex &index, int role) const; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; - Qt::ItemFlags flags(const QModelIndex &index) const; - bool setData(const QModelIndex &index, const QVariant &value, int role); - - bool loop = true; - - QIcon warningIcon; - -private: - struct MissingFileEntry { - MissingFilesState state = MissingFilesState::Missing; - - QString source; - - QString originalPath; - QString newPath; - }; - - QList files; - - void fileCheckLoop(QList files, QString path, bool skipPrompt); -}; - -class MissingFilesPathItemDelegate : public QStyledItemDelegate { - Q_OBJECT - -public: - MissingFilesPathItemDelegate(bool isOutput, const QString &defaultPath); - - virtual QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem & /* option */, - const QModelIndex &index) const override; - - virtual void setEditorData(QWidget *editor, const QModelIndex &index) const override; - virtual void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override; - virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, - const QModelIndex &index) const override; - -private: - bool isOutput; - QString defaultPath; - const char *PATH_LIST_PROP = "pathList"; - - void handleBrowse(QWidget *container); - void handleClear(QWidget *container); -}; diff --git a/frontend/dialogs/OBSRemux.cpp b/frontend/dialogs/OBSRemux.cpp index d72ebbd9c..84b28372e 100644 --- a/frontend/dialogs/OBSRemux.cpp +++ b/frontend/dialogs/OBSRemux.cpp @@ -15,589 +15,21 @@ along with this program. If not, see . ******************************************************************************/ -#include "moc_window-remux.cpp" +#include "OBSRemux.hpp" -#include "obs-app.hpp" +#include +#include +#include +#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include "window-basic-main.hpp" +#include +#include +#include +#include -#include -#include - -using namespace std; - -enum RemuxEntryColumn { - State, - InputPath, - OutputPath, - - Count -}; - -enum RemuxEntryRole { EntryStateRole = Qt::UserRole, NewPathsToProcessRole }; - -/********************************************************** - Delegate - Presents cells in the grid. -**********************************************************/ - -RemuxEntryPathItemDelegate::RemuxEntryPathItemDelegate(bool isOutput, const QString &defaultPath) - : QStyledItemDelegate(), - isOutput(isOutput), - defaultPath(defaultPath) -{ -} - -QWidget *RemuxEntryPathItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem & /* option */, - const QModelIndex &index) const -{ - RemuxEntryState state = index.model() - ->index(index.row(), RemuxEntryColumn::State) - .data(RemuxEntryRole::EntryStateRole) - .value(); - if (state == RemuxEntryState::Pending || state == RemuxEntryState::InProgress) { - // Never allow modification of rows that are - // in progress. - return Q_NULLPTR; - } else if (isOutput && state != RemuxEntryState::Ready) { - // Do not allow modification of output rows - // that aren't associated with a valid input. - return Q_NULLPTR; - } else if (!isOutput && state == RemuxEntryState::Complete) { - // Don't allow modification of rows that are - // already complete. - return Q_NULLPTR; - } else { - QSizePolicy buttonSizePolicy(QSizePolicy::Policy::Minimum, QSizePolicy::Policy::Expanding, - QSizePolicy::ControlType::PushButton); - - QWidget *container = new QWidget(parent); - - auto browseCallback = [this, container]() { - const_cast(this)->handleBrowse(container); - }; - - auto clearCallback = [this, container]() { - const_cast(this)->handleClear(container); - }; - - QHBoxLayout *layout = new QHBoxLayout(); - layout->setContentsMargins(0, 0, 0, 0); - layout->setSpacing(0); - - QLineEdit *text = new QLineEdit(); - text->setObjectName(QStringLiteral("text")); - text->setSizePolicy(QSizePolicy(QSizePolicy::Policy::Expanding, QSizePolicy::Policy::Expanding, - QSizePolicy::ControlType::LineEdit)); - layout->addWidget(text); - - QObject::connect(text, &QLineEdit::editingFinished, this, &RemuxEntryPathItemDelegate::updateText); - - QToolButton *browseButton = new QToolButton(); - browseButton->setText("..."); - browseButton->setSizePolicy(buttonSizePolicy); - layout->addWidget(browseButton); - - container->connect(browseButton, &QToolButton::clicked, browseCallback); - - // The "clear" button is not shown in output cells - // or the insertion point's input cell. - if (!isOutput && state != RemuxEntryState::Empty) { - QToolButton *clearButton = new QToolButton(); - clearButton->setText("X"); - clearButton->setSizePolicy(buttonSizePolicy); - layout->addWidget(clearButton); - - container->connect(clearButton, &QToolButton::clicked, clearCallback); - } - - container->setLayout(layout); - container->setFocusProxy(text); - return container; - } -} - -void RemuxEntryPathItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const -{ - QLineEdit *text = editor->findChild(); - text->setText(index.data().toString()); - editor->setProperty(PATH_LIST_PROP, QVariant()); -} - -void RemuxEntryPathItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, - const QModelIndex &index) const -{ - // We use the PATH_LIST_PROP property to pass a list of - // path strings from the editor widget into the model's - // NewPathsToProcessRole. This is only used when paths - // are selected through the "browse" or "delete" buttons - // in the editor. If the user enters new text in the - // text box, we simply pass that text on to the model - // as normal text data in the default role. - QVariant pathListProp = editor->property(PATH_LIST_PROP); - if (pathListProp.isValid()) { - QStringList list = editor->property(PATH_LIST_PROP).toStringList(); - if (isOutput) { - if (list.size() > 0) - model->setData(index, list); - } else - model->setData(index, list, RemuxEntryRole::NewPathsToProcessRole); - } else { - QLineEdit *lineEdit = editor->findChild(); - model->setData(index, lineEdit->text()); - } -} - -void RemuxEntryPathItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, - const QModelIndex &index) const -{ - RemuxEntryState state = index.model() - ->index(index.row(), RemuxEntryColumn::State) - .data(RemuxEntryRole::EntryStateRole) - .value(); - - QStyleOptionViewItem localOption = option; - initStyleOption(&localOption, index); - - if (isOutput) { - if (state != Ready) { - QColor background = - localOption.palette.color(QPalette::ColorGroup::Disabled, QPalette::ColorRole::Window); - - localOption.backgroundBrush = QBrush(background); - } - } - - QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &localOption, painter); -} - -void RemuxEntryPathItemDelegate::handleBrowse(QWidget *container) -{ - QString ExtensionPattern = "(*.mp4 *.flv *.mov *.mkv *.ts *.m3u8)"; - - QLineEdit *text = container->findChild(); - - QString currentPath = text->text(); - if (currentPath.isEmpty()) - currentPath = defaultPath; - - bool isSet = false; - if (isOutput) { - QString newPath = SaveFile(container, QTStr("Remux.SelectTarget"), currentPath, ExtensionPattern); - - if (!newPath.isEmpty()) { - container->setProperty(PATH_LIST_PROP, QStringList() << newPath); - isSet = true; - } - } else { - QStringList paths = OpenFiles(container, QTStr("Remux.SelectRecording"), currentPath, - QTStr("Remux.OBSRecording") + QString(" ") + ExtensionPattern); - - if (!paths.empty()) { - container->setProperty(PATH_LIST_PROP, paths); - isSet = true; - } -#ifdef __APPLE__ - // TODO: Revisit when QTBUG-42661 is fixed - container->window()->raise(); -#endif - } - - if (isSet) - emit commitData(container); -} - -void RemuxEntryPathItemDelegate::handleClear(QWidget *container) -{ - // An empty string list will indicate that the entry is being - // blanked and should be deleted. - container->setProperty(PATH_LIST_PROP, QStringList()); - - emit commitData(container); -} - -void RemuxEntryPathItemDelegate::updateText() -{ - QLineEdit *lineEdit = dynamic_cast(sender()); - QWidget *editor = lineEdit->parentWidget(); - emit commitData(editor); -} - -/********************************************************** - Model - Manages the queue's data -**********************************************************/ - -int RemuxQueueModel::rowCount(const QModelIndex &) const -{ - return queue.length() + (isProcessing ? 0 : 1); -} - -int RemuxQueueModel::columnCount(const QModelIndex &) const -{ - return RemuxEntryColumn::Count; -} - -QVariant RemuxQueueModel::data(const QModelIndex &index, int role) const -{ - QVariant result = QVariant(); - - if (index.row() >= queue.length()) { - return QVariant(); - } else if (role == Qt::DisplayRole) { - switch (index.column()) { - case RemuxEntryColumn::InputPath: - result = queue[index.row()].sourcePath; - break; - case RemuxEntryColumn::OutputPath: - result = queue[index.row()].targetPath; - break; - } - } else if (role == Qt::DecorationRole && index.column() == RemuxEntryColumn::State) { - result = getIcon(queue[index.row()].state); - } else if (role == RemuxEntryRole::EntryStateRole) { - result = queue[index.row()].state; - } - - return result; -} - -QVariant RemuxQueueModel::headerData(int section, Qt::Orientation orientation, int role) const -{ - QVariant result = QVariant(); - - if (role == Qt::DisplayRole && orientation == Qt::Orientation::Horizontal) { - switch (section) { - case RemuxEntryColumn::State: - result = QString(); - break; - case RemuxEntryColumn::InputPath: - result = QTStr("Remux.SourceFile"); - break; - case RemuxEntryColumn::OutputPath: - result = QTStr("Remux.TargetFile"); - break; - } - } - - return result; -} - -Qt::ItemFlags RemuxQueueModel::flags(const QModelIndex &index) const -{ - Qt::ItemFlags flags = QAbstractTableModel::flags(index); - - if (index.column() == RemuxEntryColumn::InputPath) { - flags |= Qt::ItemIsEditable; - } else if (index.column() == RemuxEntryColumn::OutputPath && index.row() != queue.length()) { - flags |= Qt::ItemIsEditable; - } - - return flags; -} - -bool RemuxQueueModel::setData(const QModelIndex &index, const QVariant &value, int role) -{ - bool success = false; - - if (role == RemuxEntryRole::NewPathsToProcessRole) { - QStringList pathList = value.toStringList(); - - if (pathList.size() == 0) { - if (index.row() < queue.size()) { - beginRemoveRows(QModelIndex(), index.row(), index.row()); - queue.removeAt(index.row()); - endRemoveRows(); - } - } else { - if (pathList.size() >= 1 && index.row() < queue.length()) { - queue[index.row()].sourcePath = pathList[0]; - checkInputPath(index.row()); - - pathList.removeAt(0); - - success = true; - } - - if (pathList.size() > 0) { - int row = index.row(); - int lastRow = row + pathList.size() - 1; - beginInsertRows(QModelIndex(), row, lastRow); - - for (QString path : pathList) { - RemuxQueueEntry entry; - entry.sourcePath = path; - entry.state = RemuxEntryState::Empty; - - queue.insert(row, entry); - row++; - } - endInsertRows(); - - for (row = index.row(); row <= lastRow; row++) { - checkInputPath(row); - } - - success = true; - } - } - } else if (index.row() == queue.length()) { - QString path = value.toString(); - - if (!path.isEmpty()) { - RemuxQueueEntry entry; - entry.sourcePath = path; - entry.state = RemuxEntryState::Empty; - - beginInsertRows(QModelIndex(), queue.length() + 1, queue.length() + 1); - queue.append(entry); - endInsertRows(); - - checkInputPath(index.row()); - success = true; - } - } else { - QString path = value.toString(); - - if (path.isEmpty()) { - if (index.column() == RemuxEntryColumn::InputPath) { - beginRemoveRows(QModelIndex(), index.row(), index.row()); - queue.removeAt(index.row()); - endRemoveRows(); - } - } else { - switch (index.column()) { - case RemuxEntryColumn::InputPath: - queue[index.row()].sourcePath = value.toString(); - checkInputPath(index.row()); - success = true; - break; - case RemuxEntryColumn::OutputPath: - queue[index.row()].targetPath = value.toString(); - emit dataChanged(index, index); - success = true; - break; - } - } - } - - return success; -} - -QVariant RemuxQueueModel::getIcon(RemuxEntryState state) -{ - QVariant icon; - QStyle *style = QApplication::style(); - - switch (state) { - case RemuxEntryState::Complete: - icon = style->standardIcon(QStyle::SP_DialogApplyButton); - break; - - case RemuxEntryState::InProgress: - icon = style->standardIcon(QStyle::SP_ArrowRight); - break; - - case RemuxEntryState::Error: - icon = style->standardIcon(QStyle::SP_DialogCancelButton); - break; - - case RemuxEntryState::InvalidPath: - icon = style->standardIcon(QStyle::SP_MessageBoxWarning); - break; - - default: - break; - } - - return icon; -} - -void RemuxQueueModel::checkInputPath(int row) -{ - RemuxQueueEntry &entry = queue[row]; - - if (entry.sourcePath.isEmpty()) { - entry.state = RemuxEntryState::Empty; - } else { - entry.sourcePath = QDir::toNativeSeparators(entry.sourcePath); - QFileInfo fileInfo(entry.sourcePath); - if (fileInfo.exists()) - entry.state = RemuxEntryState::Ready; - else - entry.state = RemuxEntryState::InvalidPath; - - QString newExt = ".mp4"; - QString suffix = fileInfo.suffix(); - - if (suffix.contains("mov", Qt::CaseInsensitive) || suffix.contains("mp4", Qt::CaseInsensitive)) { - newExt = ".remuxed." + suffix; - } - - if (entry.state == RemuxEntryState::Ready) - entry.targetPath = QDir::toNativeSeparators(fileInfo.path() + QDir::separator() + - fileInfo.completeBaseName() + newExt); - } - - if (entry.state == RemuxEntryState::Ready && isProcessing) - entry.state = RemuxEntryState::Pending; - - emit dataChanged(index(row, 0), index(row, RemuxEntryColumn::Count)); -} - -QFileInfoList RemuxQueueModel::checkForOverwrites() const -{ - QFileInfoList list; - - for (const RemuxQueueEntry &entry : queue) { - if (entry.state == RemuxEntryState::Ready) { - QFileInfo fileInfo(entry.targetPath); - if (fileInfo.exists()) { - list.append(fileInfo); - } - } - } - - return list; -} - -bool RemuxQueueModel::checkForErrors() const -{ - bool hasErrors = false; - - for (const RemuxQueueEntry &entry : queue) { - if (entry.state == RemuxEntryState::Error) { - hasErrors = true; - break; - } - } - - return hasErrors; -} - -void RemuxQueueModel::clearAll() -{ - beginRemoveRows(QModelIndex(), 0, queue.size() - 1); - queue.clear(); - endRemoveRows(); -} - -void RemuxQueueModel::clearFinished() -{ - int index = 0; - - for (index = 0; index < queue.size(); index++) { - const RemuxQueueEntry &entry = queue[index]; - if (entry.state == RemuxEntryState::Complete) { - beginRemoveRows(QModelIndex(), index, index); - queue.removeAt(index); - endRemoveRows(); - index--; - } - } -} - -bool RemuxQueueModel::canClearFinished() const -{ - bool canClearFinished = false; - for (const RemuxQueueEntry &entry : queue) - if (entry.state == RemuxEntryState::Complete) { - canClearFinished = true; - break; - } - - return canClearFinished; -} - -void RemuxQueueModel::beginProcessing() -{ - for (RemuxQueueEntry &entry : queue) - if (entry.state == RemuxEntryState::Ready) - entry.state = RemuxEntryState::Pending; - - // Signal that the insertion point no longer exists. - beginRemoveRows(QModelIndex(), queue.length(), queue.length()); - endRemoveRows(); - - isProcessing = true; - - emit dataChanged(index(0, RemuxEntryColumn::State), index(queue.length(), RemuxEntryColumn::State)); -} - -void RemuxQueueModel::endProcessing() -{ - for (RemuxQueueEntry &entry : queue) { - if (entry.state == RemuxEntryState::Pending) { - entry.state = RemuxEntryState::Ready; - } - } - - // Signal that the insertion point exists again. - isProcessing = false; - if (!autoRemux) { - beginInsertRows(QModelIndex(), queue.length(), queue.length()); - endInsertRows(); - } - - emit dataChanged(index(0, RemuxEntryColumn::State), index(queue.length(), RemuxEntryColumn::State)); -} - -bool RemuxQueueModel::beginNextEntry(QString &inputPath, QString &outputPath) -{ - bool anyStarted = false; - - for (int row = 0; row < queue.length(); row++) { - RemuxQueueEntry &entry = queue[row]; - if (entry.state == RemuxEntryState::Pending) { - entry.state = RemuxEntryState::InProgress; - - inputPath = entry.sourcePath; - outputPath = entry.targetPath; - - QModelIndex index = this->index(row, RemuxEntryColumn::State); - emit dataChanged(index, index); - - anyStarted = true; - break; - } - } - - return anyStarted; -} - -void RemuxQueueModel::finishEntry(bool success) -{ - for (int row = 0; row < queue.length(); row++) { - RemuxQueueEntry &entry = queue[row]; - if (entry.state == RemuxEntryState::InProgress) { - if (success) - entry.state = RemuxEntryState::Complete; - else - entry.state = RemuxEntryState::Error; - - QModelIndex index = this->index(row, RemuxEntryColumn::State); - emit dataChanged(index, index); - - break; - } - } -} - -/********************************************************** - The actual remux window implementation -**********************************************************/ +#include "moc_OBSRemux.cpp" OBSRemux::OBSRemux(const char *path, QWidget *parent, bool autoRemux_) : QDialog(parent), @@ -876,49 +308,3 @@ void OBSRemux::clearAll() { queueModel->clearAll(); } - -/********************************************************** - Worker thread - Executes the libobs remux operation as a - background process. -**********************************************************/ - -void RemuxWorker::UpdateProgress(float percent) -{ - if (abs(lastProgress - percent) < 0.1f) - return; - - emit updateProgress(percent); - lastProgress = percent; -} - -void RemuxWorker::remux(const QString &source, const QString &target) -{ - isWorking = true; - - auto callback = [](void *data, float percent) { - RemuxWorker *rw = static_cast(data); - - QMutexLocker lock(&rw->updateMutex); - - rw->UpdateProgress(percent); - - return rw->isWorking; - }; - - bool stopped = false; - bool success = false; - - media_remux_job_t mr_job = nullptr; - if (media_remux_job_create(&mr_job, QT_TO_UTF8(source), QT_TO_UTF8(target))) { - - success = media_remux_job_process(mr_job, callback, this); - - media_remux_job_destroy(mr_job); - - stopped = !isWorking; - } - - isWorking = false; - - emit remuxFinished(!stopped && success); -} diff --git a/frontend/dialogs/OBSRemux.hpp b/frontend/dialogs/OBSRemux.hpp index 20a13a780..87d68587e 100644 --- a/frontend/dialogs/OBSRemux.hpp +++ b/frontend/dialogs/OBSRemux.hpp @@ -17,23 +17,14 @@ #pragma once -#include -#include -#include -#include -#include -#include #include "ui_OBSRemux.h" -#include -#include +#include +#include class RemuxQueueModel; class RemuxWorker; -enum RemuxEntryState { Empty, Ready, Pending, InProgress, Complete, InvalidPath, Error }; -Q_DECLARE_METATYPE(RemuxEntryState); - class OBSRemux : public QDialog { Q_OBJECT @@ -79,95 +70,3 @@ public slots: signals: void remux(const QString &source, const QString &target); }; - -class RemuxQueueModel : public QAbstractTableModel { - Q_OBJECT - - friend class OBSRemux; - -public: - RemuxQueueModel(QObject *parent = 0) : QAbstractTableModel(parent), isProcessing(false) {} - - int rowCount(const QModelIndex &parent = QModelIndex()) const; - int columnCount(const QModelIndex &parent = QModelIndex()) const; - QVariant data(const QModelIndex &index, int role) const; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; - Qt::ItemFlags flags(const QModelIndex &index) const; - bool setData(const QModelIndex &index, const QVariant &value, int role); - - QFileInfoList checkForOverwrites() const; - bool checkForErrors() const; - void beginProcessing(); - void endProcessing(); - bool beginNextEntry(QString &inputPath, QString &outputPath); - void finishEntry(bool success); - bool canClearFinished() const; - void clearFinished(); - void clearAll(); - - bool autoRemux = false; - -private: - struct RemuxQueueEntry { - RemuxEntryState state; - - QString sourcePath; - QString targetPath; - }; - - QList queue; - bool isProcessing; - - static QVariant getIcon(RemuxEntryState state); - - void checkInputPath(int row); -}; - -class RemuxWorker : public QObject { - Q_OBJECT - - QMutex updateMutex; - - bool isWorking; - - float lastProgress; - void UpdateProgress(float percent); - - explicit RemuxWorker() : isWorking(false) {} - virtual ~RemuxWorker(){}; - -private slots: - void remux(const QString &source, const QString &target); - -signals: - void updateProgress(float percent); - void remuxFinished(bool success); - - friend class OBSRemux; -}; - -class RemuxEntryPathItemDelegate : public QStyledItemDelegate { - Q_OBJECT - -public: - RemuxEntryPathItemDelegate(bool isOutput, const QString &defaultPath); - - virtual QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem & /* option */, - const QModelIndex &index) const override; - - virtual void setEditorData(QWidget *editor, const QModelIndex &index) const override; - virtual void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override; - virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, - const QModelIndex &index) const override; - -private: - bool isOutput; - QString defaultPath; - const char *PATH_LIST_PROP = "pathList"; - - void handleBrowse(QWidget *container); - void handleClear(QWidget *container); - -private slots: - void updateText(); -}; diff --git a/frontend/utility/ExtraBrowsersDelegate.cpp b/frontend/utility/ExtraBrowsersDelegate.cpp index 8f4232e1d..e984797fb 100644 --- a/frontend/utility/ExtraBrowsersDelegate.cpp +++ b/frontend/utility/ExtraBrowsersDelegate.cpp @@ -1,289 +1,13 @@ -#include "moc_window-extra-browsers.cpp" -#include "window-dock-browser.hpp" -#include "window-basic-main.hpp" +#include "ExtraBrowsersDelegate.hpp" +#include "ExtraBrowsersModel.hpp" + +#include #include -#include -#include -#include -#include +#include -#include "ui_OBSExtraBrowsers.h" - -using namespace json11; - -#define OBJ_NAME_SUFFIX "_extraBrowser" - -enum class Column : int { - Title, - Url, - Delete, - - Count, -}; - -/* ------------------------------------------------------------------------- */ - -void ExtraBrowsersModel::Reset() -{ - items.clear(); - - OBSBasic *main = OBSBasic::Get(); - - for (int i = 0; i < main->extraBrowserDocks.size(); i++) { - Item item; - item.prevIdx = i; - item.title = main->extraBrowserDockNames[i]; - item.url = main->extraBrowserDockTargets[i]; - items.push_back(item); - } -} - -int ExtraBrowsersModel::rowCount(const QModelIndex &) const -{ - int count = items.size() + 1; - return count; -} - -int ExtraBrowsersModel::columnCount(const QModelIndex &) const -{ - return (int)Column::Count; -} - -QVariant ExtraBrowsersModel::data(const QModelIndex &index, int role) const -{ - int column = index.column(); - int idx = index.row(); - int count = items.size(); - bool validRole = role == Qt::DisplayRole || role == Qt::AccessibleTextRole; - - if (!validRole) - return QVariant(); - - if (idx >= 0 && idx < count) { - switch (column) { - case (int)Column::Title: - return items[idx].title; - case (int)Column::Url: - return items[idx].url; - } - } else if (idx == count) { - switch (column) { - case (int)Column::Title: - return newTitle; - case (int)Column::Url: - return newURL; - } - } - - return QVariant(); -} - -QVariant ExtraBrowsersModel::headerData(int section, Qt::Orientation orientation, int role) const -{ - bool validRole = role == Qt::DisplayRole || role == Qt::AccessibleTextRole; - - if (validRole && orientation == Qt::Orientation::Horizontal) { - switch (section) { - case (int)Column::Title: - return QTStr("ExtraBrowsers.DockName"); - case (int)Column::Url: - return QStringLiteral("URL"); - } - } - - return QVariant(); -} - -Qt::ItemFlags ExtraBrowsersModel::flags(const QModelIndex &index) const -{ - Qt::ItemFlags flags = QAbstractTableModel::flags(index); - - if (index.column() != (int)Column::Delete) - flags |= Qt::ItemIsEditable; - - return flags; -} - -class DelButton : public QPushButton { -public: - inline DelButton(QModelIndex index_) : QPushButton(), index(index_) {} - - QPersistentModelIndex index; -}; - -class EditWidget : public QLineEdit { -public: - inline EditWidget(QWidget *parent, QModelIndex index_) : QLineEdit(parent), index(index_) {} - - QPersistentModelIndex index; -}; - -void ExtraBrowsersModel::AddDeleteButton(int idx) -{ - QTableView *widget = reinterpret_cast(parent()); - - QModelIndex index = createIndex(idx, (int)Column::Delete, nullptr); - - QPushButton *del = new DelButton(index); - del->setProperty("class", "icon-trash"); - del->setObjectName("extraPanelDelete"); - del->setMinimumSize(QSize(20, 20)); - connect(del, &QPushButton::clicked, this, &ExtraBrowsersModel::DeleteItem); - - widget->setIndexWidget(index, del); - widget->setRowHeight(idx, 20); - widget->setColumnWidth(idx, 20); -} - -void ExtraBrowsersModel::CheckToAdd() -{ - if (newTitle.isEmpty() || newURL.isEmpty()) - return; - - int idx = items.size() + 1; - beginInsertRows(QModelIndex(), idx, idx); - - Item item; - item.prevIdx = -1; - item.title = newTitle; - item.url = newURL; - items.push_back(item); - - newTitle = ""; - newURL = ""; - - endInsertRows(); - - AddDeleteButton(idx - 1); -} - -void ExtraBrowsersModel::UpdateItem(Item &item) -{ - int idx = item.prevIdx; - - OBSBasic *main = OBSBasic::Get(); - BrowserDock *dock = reinterpret_cast(main->extraBrowserDocks[idx].get()); - dock->setWindowTitle(item.title); - dock->setObjectName(item.title + OBJ_NAME_SUFFIX); - - if (main->extraBrowserDockNames[idx] != item.title) { - main->extraBrowserDockNames[idx] = item.title; - dock->toggleViewAction()->setText(item.title); - dock->setTitle(item.title); - } - - if (main->extraBrowserDockTargets[idx] != item.url) { - dock->cefWidget->setURL(QT_TO_UTF8(item.url)); - main->extraBrowserDockTargets[idx] = item.url; - } -} - -void ExtraBrowsersModel::DeleteItem() -{ - QTableView *widget = reinterpret_cast(parent()); - - DelButton *del = reinterpret_cast(sender()); - int row = del->index.row(); - - /* there's some sort of internal bug in Qt and deleting certain index - * widgets or "editors" that can cause a crash inside Qt if the widget - * is not manually removed, at least on 5.7 */ - widget->setIndexWidget(del->index, nullptr); - del->deleteLater(); - - /* --------- */ - - beginRemoveRows(QModelIndex(), row, row); - - int prevIdx = items[row].prevIdx; - items.removeAt(row); - - if (prevIdx != -1) { - int i = 0; - for (; i < deleted.size() && deleted[i] < prevIdx; i++) - ; - deleted.insert(i, prevIdx); - } - - endRemoveRows(); -} - -void ExtraBrowsersModel::Apply() -{ - OBSBasic *main = OBSBasic::Get(); - - for (Item &item : items) { - if (item.prevIdx != -1) { - UpdateItem(item); - } else { - QString uuid = QUuid::createUuid().toString(); - uuid.replace(QRegularExpression("[{}-]"), ""); - main->AddExtraBrowserDock(item.title, item.url, uuid, true); - } - } - - for (int i = deleted.size() - 1; i >= 0; i--) { - int idx = deleted[i]; - main->extraBrowserDockTargets.removeAt(idx); - main->extraBrowserDockNames.removeAt(idx); - main->extraBrowserDocks.removeAt(idx); - } - - if (main->extraBrowserDocks.empty()) - main->extraBrowserMenuDocksSeparator.clear(); - - deleted.clear(); - - Reset(); -} - -void ExtraBrowsersModel::TabSelection(bool forward) -{ - QListView *widget = reinterpret_cast(parent()); - QItemSelectionModel *selModel = widget->selectionModel(); - - QModelIndex sel = selModel->currentIndex(); - int row = sel.row(); - int col = sel.column(); - - switch (sel.column()) { - case (int)Column::Title: - if (!forward) { - if (row == 0) { - return; - } - - row -= 1; - } - - col += 1; - break; - - case (int)Column::Url: - if (forward) { - if (row == items.size()) { - return; - } - - row += 1; - } - - col -= 1; - } - - sel = createIndex(row, col, nullptr); - selModel->setCurrentIndex(sel, QItemSelectionModel::Clear); -} - -void ExtraBrowsersModel::Init() -{ - for (int i = 0; i < items.count(); i++) - AddDeleteButton(i); -} - -/* ------------------------------------------------------------------------- */ +#include "moc_ExtraBrowsersDelegate.cpp" QWidget *ExtraBrowsersDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &index) const @@ -406,157 +130,3 @@ bool ExtraBrowsersDelegate::UpdateText(QLineEdit *edit_) emit commitData(edit); return true; } - -/* ------------------------------------------------------------------------- */ - -OBSExtraBrowsers::OBSExtraBrowsers(QWidget *parent) : QDialog(parent), ui(new Ui::OBSExtraBrowsers) -{ - ui->setupUi(this); - - setAttribute(Qt::WA_DeleteOnClose, true); - setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); - - model = new ExtraBrowsersModel(ui->table); - - ui->table->setModel(model); - ui->table->setItemDelegateForColumn((int)Column::Title, new ExtraBrowsersDelegate(model)); - ui->table->setItemDelegateForColumn((int)Column::Url, new ExtraBrowsersDelegate(model)); - ui->table->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeMode::Stretch); - ui->table->horizontalHeader()->setSectionResizeMode((int)Column::Delete, QHeaderView::ResizeMode::Fixed); - ui->table->setEditTriggers(QAbstractItemView::EditTrigger::CurrentChanged); -} - -OBSExtraBrowsers::~OBSExtraBrowsers() {} - -void OBSExtraBrowsers::closeEvent(QCloseEvent *event) -{ - QDialog::closeEvent(event); - model->Apply(); -} - -void OBSExtraBrowsers::on_apply_clicked() -{ - model->Apply(); -} - -/* ------------------------------------------------------------------------- */ - -void OBSBasic::ClearExtraBrowserDocks() -{ - extraBrowserDockTargets.clear(); - extraBrowserDockNames.clear(); - extraBrowserDocks.clear(); -} - -void OBSBasic::LoadExtraBrowserDocks() -{ - const char *jsonStr = config_get_string(App()->GetUserConfig(), "BasicWindow", "ExtraBrowserDocks"); - - std::string err; - Json json = Json::parse(jsonStr, err); - if (!err.empty()) - return; - - Json::array array = json.array_items(); - if (!array.empty()) - extraBrowserMenuDocksSeparator = ui->menuDocks->addSeparator(); - - for (Json &item : array) { - std::string title = item["title"].string_value(); - std::string url = item["url"].string_value(); - std::string uuid = item["uuid"].string_value(); - - AddExtraBrowserDock(title.c_str(), url.c_str(), uuid.c_str(), false); - } -} - -void OBSBasic::SaveExtraBrowserDocks() -{ - Json::array array; - for (int i = 0; i < extraBrowserDocks.size(); i++) { - QDockWidget *dock = extraBrowserDocks[i].get(); - QString title = extraBrowserDockNames[i]; - QString url = extraBrowserDockTargets[i]; - QString uuid = dock->property("uuid").toString(); - Json::object obj{ - {"title", QT_TO_UTF8(title)}, - {"url", QT_TO_UTF8(url)}, - {"uuid", QT_TO_UTF8(uuid)}, - }; - array.push_back(obj); - } - - std::string output = Json(array).dump(); - config_set_string(App()->GetUserConfig(), "BasicWindow", "ExtraBrowserDocks", output.c_str()); -} - -void OBSBasic::ManageExtraBrowserDocks() -{ - if (!extraBrowsers.isNull()) { - extraBrowsers->show(); - extraBrowsers->raise(); - return; - } - - extraBrowsers = new OBSExtraBrowsers(this); - extraBrowsers->show(); -} - -void OBSBasic::AddExtraBrowserDock(const QString &title, const QString &url, const QString &uuid, bool firstCreate) -{ - static int panel_version = -1; - if (panel_version == -1) { - panel_version = obs_browser_qcef_version(); - } - - BrowserDock *dock = new BrowserDock(title); - QString bId(uuid.isEmpty() ? QUuid::createUuid().toString() : uuid); - bId.replace(QRegularExpression("[{}-]"), ""); - dock->setProperty("uuid", bId); - dock->setObjectName(title + OBJ_NAME_SUFFIX); - dock->resize(460, 600); - dock->setMinimumSize(80, 80); - dock->setWindowTitle(title); - dock->setAllowedAreas(Qt::AllDockWidgetAreas); - - QCefWidget *browser = cef->create_widget(dock, QT_TO_UTF8(url), nullptr); - if (browser && panel_version >= 1) - browser->allowAllPopups(true); - - dock->SetWidget(browser); - - /* Add support for Twitch Dashboard panels */ - if (url.contains("twitch.tv/popout") && url.contains("dashboard/live")) { - QRegularExpression re("twitch.tv\\/popout\\/([^/]+)\\/"); - QRegularExpressionMatch match = re.match(url); - QString username = match.captured(1); - if (username.length() > 0) { - std::string script; - script = "Object.defineProperty(document, 'referrer', { get: () => '"; - script += "https://twitch.tv/"; - script += QT_TO_UTF8(username); - script += "/dashboard/live"; - script += "'});"; - browser->setStartupScript(script); - } - } - - AddDockWidget(dock, Qt::RightDockWidgetArea, true); - extraBrowserDocks.push_back(std::shared_ptr(dock)); - extraBrowserDockNames.push_back(title); - extraBrowserDockTargets.push_back(url); - - if (firstCreate) { - dock->setFloating(true); - - QPoint curPos = pos(); - QSize wSizeD2 = size() / 2; - QSize dSizeD2 = dock->size() / 2; - - curPos.setX(curPos.x() + wSizeD2.width() - dSizeD2.width()); - curPos.setY(curPos.y() + wSizeD2.height() - dSizeD2.height()); - - dock->move(curPos); - dock->setVisible(true); - } -} diff --git a/frontend/utility/ExtraBrowsersDelegate.hpp b/frontend/utility/ExtraBrowsersDelegate.hpp index 690d784dd..6abafc153 100644 --- a/frontend/utility/ExtraBrowsersDelegate.hpp +++ b/frontend/utility/ExtraBrowsersDelegate.hpp @@ -1,73 +1,9 @@ #pragma once -#include -#include -#include #include -#include -class Ui_OBSExtraBrowsers; class ExtraBrowsersModel; -class QCefWidget; - -class OBSExtraBrowsers : public QDialog { - Q_OBJECT - - std::unique_ptr ui; - ExtraBrowsersModel *model; - -public: - OBSExtraBrowsers(QWidget *parent); - ~OBSExtraBrowsers(); - - void closeEvent(QCloseEvent *event) override; - -public slots: - void on_apply_clicked(); -}; - -class ExtraBrowsersModel : public QAbstractTableModel { - Q_OBJECT - -public: - inline ExtraBrowsersModel(QObject *parent = nullptr) : QAbstractTableModel(parent) - { - Reset(); - QMetaObject::invokeMethod(this, "Init", Qt::QueuedConnection); - } - - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - int columnCount(const QModelIndex &parent = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role) const override; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - Qt::ItemFlags flags(const QModelIndex &index) const override; - - struct Item { - int prevIdx; - QString title; - QString url; - }; - - void TabSelection(bool forward); - - void AddDeleteButton(int idx); - void Reset(); - void CheckToAdd(); - void UpdateItem(Item &item); - void DeleteItem(); - void Apply(); - - QVector items; - QVector deleted; - - QString newTitle; - QString newURL; - -public slots: - void Init(); -}; - class ExtraBrowsersDelegate : public QStyledItemDelegate { Q_OBJECT diff --git a/frontend/utility/ExtraBrowsersModel.cpp b/frontend/utility/ExtraBrowsersModel.cpp index 8f4232e1d..5409c3c19 100644 --- a/frontend/utility/ExtraBrowsersModel.cpp +++ b/frontend/utility/ExtraBrowsersModel.cpp @@ -1,29 +1,14 @@ -#include "moc_window-extra-browsers.cpp" -#include "window-dock-browser.hpp" -#include "window-basic-main.hpp" +#include "ExtraBrowsersModel.hpp" + +#include +#include +#include #include -#include -#include -#include -#include +#include -#include "ui_OBSExtraBrowsers.h" - -using namespace json11; - -#define OBJ_NAME_SUFFIX "_extraBrowser" - -enum class Column : int { - Title, - Url, - Delete, - - Count, -}; - -/* ------------------------------------------------------------------------- */ +#include "moc_ExtraBrowsersModel.cpp" void ExtraBrowsersModel::Reset() { @@ -105,21 +90,6 @@ Qt::ItemFlags ExtraBrowsersModel::flags(const QModelIndex &index) const return flags; } - -class DelButton : public QPushButton { -public: - inline DelButton(QModelIndex index_) : QPushButton(), index(index_) {} - - QPersistentModelIndex index; -}; - -class EditWidget : public QLineEdit { -public: - inline EditWidget(QWidget *parent, QModelIndex index_) : QLineEdit(parent), index(index_) {} - - QPersistentModelIndex index; -}; - void ExtraBrowsersModel::AddDeleteButton(int idx) { QTableView *widget = reinterpret_cast(parent()); @@ -282,281 +252,3 @@ void ExtraBrowsersModel::Init() for (int i = 0; i < items.count(); i++) AddDeleteButton(i); } - -/* ------------------------------------------------------------------------- */ - -QWidget *ExtraBrowsersDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, - const QModelIndex &index) const -{ - QLineEdit *text = new EditWidget(parent, index); - text->installEventFilter(const_cast(this)); - text->setSizePolicy(QSizePolicy(QSizePolicy::Policy::Expanding, QSizePolicy::Policy::Expanding, - QSizePolicy::ControlType::LineEdit)); - return text; -} - -void ExtraBrowsersDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const -{ - QLineEdit *text = reinterpret_cast(editor); - text->blockSignals(true); - text->setText(index.data().toString()); - text->blockSignals(false); -} - -bool ExtraBrowsersDelegate::eventFilter(QObject *object, QEvent *event) -{ - QLineEdit *edit = qobject_cast(object); - if (!edit) - return false; - - if (LineEditCanceled(event)) { - RevertText(edit); - } - if (LineEditChanged(event)) { - UpdateText(edit); - - if (event->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast(event); - if (keyEvent->key() == Qt::Key_Tab) { - model->TabSelection(true); - } else if (keyEvent->key() == Qt::Key_Backtab) { - model->TabSelection(false); - } - } - return true; - } - - return false; -} - -bool ExtraBrowsersDelegate::ValidName(const QString &name) const -{ - for (auto &item : model->items) { - if (name.compare(item.title, Qt::CaseInsensitive) == 0) { - return false; - } - } - return true; -} - -void ExtraBrowsersDelegate::RevertText(QLineEdit *edit_) -{ - EditWidget *edit = reinterpret_cast(edit_); - int row = edit->index.row(); - int col = edit->index.column(); - bool newItem = (row == model->items.size()); - - QString oldText; - if (col == (int)Column::Title) { - oldText = newItem ? model->newTitle : model->items[row].title; - } else { - oldText = newItem ? model->newURL : model->items[row].url; - } - - edit->setText(oldText); -} - -bool ExtraBrowsersDelegate::UpdateText(QLineEdit *edit_) -{ - EditWidget *edit = reinterpret_cast(edit_); - int row = edit->index.row(); - int col = edit->index.column(); - bool newItem = (row == model->items.size()); - - QString text = edit->text().trimmed(); - - if (!newItem && text.isEmpty()) { - return false; - } - - if (col == (int)Column::Title) { - QString oldText = newItem ? model->newTitle : model->items[row].title; - bool same = oldText.compare(text, Qt::CaseInsensitive) == 0; - - if (!same && !ValidName(text)) { - edit->setText(oldText); - return false; - } - } - - if (!newItem) { - /* if edited existing item, update it*/ - switch (col) { - case (int)Column::Title: - model->items[row].title = text; - break; - case (int)Column::Url: - model->items[row].url = text; - break; - } - } else { - /* if both new values filled out, create new one */ - switch (col) { - case (int)Column::Title: - model->newTitle = text; - break; - case (int)Column::Url: - model->newURL = text; - break; - } - - model->CheckToAdd(); - } - - emit commitData(edit); - return true; -} - -/* ------------------------------------------------------------------------- */ - -OBSExtraBrowsers::OBSExtraBrowsers(QWidget *parent) : QDialog(parent), ui(new Ui::OBSExtraBrowsers) -{ - ui->setupUi(this); - - setAttribute(Qt::WA_DeleteOnClose, true); - setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); - - model = new ExtraBrowsersModel(ui->table); - - ui->table->setModel(model); - ui->table->setItemDelegateForColumn((int)Column::Title, new ExtraBrowsersDelegate(model)); - ui->table->setItemDelegateForColumn((int)Column::Url, new ExtraBrowsersDelegate(model)); - ui->table->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeMode::Stretch); - ui->table->horizontalHeader()->setSectionResizeMode((int)Column::Delete, QHeaderView::ResizeMode::Fixed); - ui->table->setEditTriggers(QAbstractItemView::EditTrigger::CurrentChanged); -} - -OBSExtraBrowsers::~OBSExtraBrowsers() {} - -void OBSExtraBrowsers::closeEvent(QCloseEvent *event) -{ - QDialog::closeEvent(event); - model->Apply(); -} - -void OBSExtraBrowsers::on_apply_clicked() -{ - model->Apply(); -} - -/* ------------------------------------------------------------------------- */ - -void OBSBasic::ClearExtraBrowserDocks() -{ - extraBrowserDockTargets.clear(); - extraBrowserDockNames.clear(); - extraBrowserDocks.clear(); -} - -void OBSBasic::LoadExtraBrowserDocks() -{ - const char *jsonStr = config_get_string(App()->GetUserConfig(), "BasicWindow", "ExtraBrowserDocks"); - - std::string err; - Json json = Json::parse(jsonStr, err); - if (!err.empty()) - return; - - Json::array array = json.array_items(); - if (!array.empty()) - extraBrowserMenuDocksSeparator = ui->menuDocks->addSeparator(); - - for (Json &item : array) { - std::string title = item["title"].string_value(); - std::string url = item["url"].string_value(); - std::string uuid = item["uuid"].string_value(); - - AddExtraBrowserDock(title.c_str(), url.c_str(), uuid.c_str(), false); - } -} - -void OBSBasic::SaveExtraBrowserDocks() -{ - Json::array array; - for (int i = 0; i < extraBrowserDocks.size(); i++) { - QDockWidget *dock = extraBrowserDocks[i].get(); - QString title = extraBrowserDockNames[i]; - QString url = extraBrowserDockTargets[i]; - QString uuid = dock->property("uuid").toString(); - Json::object obj{ - {"title", QT_TO_UTF8(title)}, - {"url", QT_TO_UTF8(url)}, - {"uuid", QT_TO_UTF8(uuid)}, - }; - array.push_back(obj); - } - - std::string output = Json(array).dump(); - config_set_string(App()->GetUserConfig(), "BasicWindow", "ExtraBrowserDocks", output.c_str()); -} - -void OBSBasic::ManageExtraBrowserDocks() -{ - if (!extraBrowsers.isNull()) { - extraBrowsers->show(); - extraBrowsers->raise(); - return; - } - - extraBrowsers = new OBSExtraBrowsers(this); - extraBrowsers->show(); -} - -void OBSBasic::AddExtraBrowserDock(const QString &title, const QString &url, const QString &uuid, bool firstCreate) -{ - static int panel_version = -1; - if (panel_version == -1) { - panel_version = obs_browser_qcef_version(); - } - - BrowserDock *dock = new BrowserDock(title); - QString bId(uuid.isEmpty() ? QUuid::createUuid().toString() : uuid); - bId.replace(QRegularExpression("[{}-]"), ""); - dock->setProperty("uuid", bId); - dock->setObjectName(title + OBJ_NAME_SUFFIX); - dock->resize(460, 600); - dock->setMinimumSize(80, 80); - dock->setWindowTitle(title); - dock->setAllowedAreas(Qt::AllDockWidgetAreas); - - QCefWidget *browser = cef->create_widget(dock, QT_TO_UTF8(url), nullptr); - if (browser && panel_version >= 1) - browser->allowAllPopups(true); - - dock->SetWidget(browser); - - /* Add support for Twitch Dashboard panels */ - if (url.contains("twitch.tv/popout") && url.contains("dashboard/live")) { - QRegularExpression re("twitch.tv\\/popout\\/([^/]+)\\/"); - QRegularExpressionMatch match = re.match(url); - QString username = match.captured(1); - if (username.length() > 0) { - std::string script; - script = "Object.defineProperty(document, 'referrer', { get: () => '"; - script += "https://twitch.tv/"; - script += QT_TO_UTF8(username); - script += "/dashboard/live"; - script += "'});"; - browser->setStartupScript(script); - } - } - - AddDockWidget(dock, Qt::RightDockWidgetArea, true); - extraBrowserDocks.push_back(std::shared_ptr(dock)); - extraBrowserDockNames.push_back(title); - extraBrowserDockTargets.push_back(url); - - if (firstCreate) { - dock->setFloating(true); - - QPoint curPos = pos(); - QSize wSizeD2 = size() / 2; - QSize dSizeD2 = dock->size() / 2; - - curPos.setX(curPos.x() + wSizeD2.width() - dSizeD2.width()); - curPos.setY(curPos.y() + wSizeD2.height() - dSizeD2.height()); - - dock->move(curPos); - dock->setVisible(true); - } -} diff --git a/frontend/utility/ExtraBrowsersModel.hpp b/frontend/utility/ExtraBrowsersModel.hpp index 690d784dd..2424875e6 100644 --- a/frontend/utility/ExtraBrowsersModel.hpp +++ b/frontend/utility/ExtraBrowsersModel.hpp @@ -1,32 +1,18 @@ #pragma once -#include -#include #include -#include -#include +#include -class Ui_OBSExtraBrowsers; -class ExtraBrowsersModel; +enum class Column : int { + Title, + Url, + Delete, -class QCefWidget; - -class OBSExtraBrowsers : public QDialog { - Q_OBJECT - - std::unique_ptr ui; - ExtraBrowsersModel *model; - -public: - OBSExtraBrowsers(QWidget *parent); - ~OBSExtraBrowsers(); - - void closeEvent(QCloseEvent *event) override; - -public slots: - void on_apply_clicked(); + Count, }; +#define OBJ_NAME_SUFFIX "_extraBrowser" + class ExtraBrowsersModel : public QAbstractTableModel { Q_OBJECT @@ -67,22 +53,3 @@ public: public slots: void Init(); }; - -class ExtraBrowsersDelegate : public QStyledItemDelegate { - Q_OBJECT - -public: - inline ExtraBrowsersDelegate(ExtraBrowsersModel *model_) : QStyledItemDelegate(nullptr), model(model_) {} - - QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, - const QModelIndex &index) const override; - - void setEditorData(QWidget *editor, const QModelIndex &index) const override; - - bool eventFilter(QObject *object, QEvent *event) override; - void RevertText(QLineEdit *edit); - bool UpdateText(QLineEdit *edit); - bool ValidName(const QString &text) const; - - ExtraBrowsersModel *model; -}; diff --git a/frontend/utility/MissingFilesModel.cpp b/frontend/utility/MissingFilesModel.cpp index 57e641a74..bda79680a 100644 --- a/frontend/utility/MissingFilesModel.cpp +++ b/frontend/utility/MissingFilesModel.cpp @@ -15,170 +15,20 @@ along with this program. If not, see . ******************************************************************************/ -#include "moc_window-missing-files.cpp" -#include "window-basic-main.hpp" +#include "MissingFilesModel.hpp" -#include "obs-app.hpp" +#include -#include -#include -#include +#include +#include -#include - -enum MissingFilesColumn { - Source, - OriginalPath, - NewPath, - State, - - Count -}; +#include "moc_MissingFilesModel.cpp" +// TODO: Fix redefinition error of due to clash with enums defined in importer code. enum MissingFilesRole { EntryStateRole = Qt::UserRole, NewPathsToProcessRole }; -/********************************************************** - Delegate - Presents cells in the grid. -**********************************************************/ - -MissingFilesPathItemDelegate::MissingFilesPathItemDelegate(bool isOutput, const QString &defaultPath) - : QStyledItemDelegate(), - isOutput(isOutput), - defaultPath(defaultPath) -{ -} - -QWidget *MissingFilesPathItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem & /* option */, - const QModelIndex &) const -{ - QSizePolicy buttonSizePolicy(QSizePolicy::Policy::Minimum, QSizePolicy::Policy::Expanding, - QSizePolicy::ControlType::PushButton); - - QWidget *container = new QWidget(parent); - - auto browseCallback = [this, container]() { - const_cast(this)->handleBrowse(container); - }; - - auto clearCallback = [this, container]() { - const_cast(this)->handleClear(container); - }; - - QHBoxLayout *layout = new QHBoxLayout(); - layout->setContentsMargins(0, 0, 0, 0); - layout->setSpacing(0); - - QLineEdit *text = new QLineEdit(); - text->setObjectName(QStringLiteral("text")); - text->setSizePolicy(QSizePolicy(QSizePolicy::Policy::Expanding, QSizePolicy::Policy::Expanding, - QSizePolicy::ControlType::LineEdit)); - layout->addWidget(text); - - QToolButton *browseButton = new QToolButton(); - browseButton->setText("..."); - browseButton->setSizePolicy(buttonSizePolicy); - layout->addWidget(browseButton); - - container->connect(browseButton, &QToolButton::clicked, browseCallback); - - // The "clear" button is not shown in input cells - if (isOutput) { - QToolButton *clearButton = new QToolButton(); - clearButton->setText("X"); - clearButton->setSizePolicy(buttonSizePolicy); - layout->addWidget(clearButton); - - container->connect(clearButton, &QToolButton::clicked, clearCallback); - } - - container->setLayout(layout); - container->setFocusProxy(text); - - return container; -} - -void MissingFilesPathItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const -{ - QLineEdit *text = editor->findChild(); - text->setText(index.data().toString()); - - editor->setProperty(PATH_LIST_PROP, QVariant()); -} - -void MissingFilesPathItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, - const QModelIndex &index) const -{ - // We use the PATH_LIST_PROP property to pass a list of - // path strings from the editor widget into the model's - // NewPathsToProcessRole. This is only used when paths - // are selected through the "browse" or "delete" buttons - // in the editor. If the user enters new text in the - // text box, we simply pass that text on to the model - // as normal text data in the default role. - QVariant pathListProp = editor->property(PATH_LIST_PROP); - if (pathListProp.isValid()) { - QStringList list = editor->property(PATH_LIST_PROP).toStringList(); - if (isOutput) { - model->setData(index, list); - } else - model->setData(index, list, MissingFilesRole::NewPathsToProcessRole); - } else { - QLineEdit *lineEdit = editor->findChild(); - model->setData(index, lineEdit->text(), 0); - } -} - -void MissingFilesPathItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, - const QModelIndex &index) const -{ - QStyleOptionViewItem localOption = option; - initStyleOption(&localOption, index); - - QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &localOption, painter); -} - -void MissingFilesPathItemDelegate::handleBrowse(QWidget *container) -{ - - QLineEdit *text = container->findChild(); - - QString currentPath = text->text(); - if (currentPath.isEmpty() || currentPath.compare(QTStr("MissingFiles.Clear")) == 0) - currentPath = defaultPath; - - bool isSet = false; - if (isOutput) { - QString newPath = - QFileDialog::getOpenFileName(container, QTStr("MissingFiles.SelectFile"), currentPath, nullptr); - -#ifdef __APPLE__ - // TODO: Revisit when QTBUG-42661 is fixed - container->window()->raise(); -#endif - - if (!newPath.isEmpty()) { - container->setProperty(PATH_LIST_PROP, QStringList() << newPath); - isSet = true; - } - } - - if (isSet) - emit commitData(container); -} - -void MissingFilesPathItemDelegate::handleClear(QWidget *container) -{ - // An empty string list will indicate that the entry is being - // blanked and should be deleted. - container->setProperty(PATH_LIST_PROP, QStringList() << QTStr("MissingFiles.Clear")); - container->findChild()->clearFocus(); - ((QWidget *)container->parent())->setFocus(); - emit commitData(container); -} - -/** - Model -**/ +// TODO: Fix redefinition error of due to clash with enums defined in importer code. +enum MissingFilesColumn { Source, OriginalPath, NewPath, State, Count }; MissingFilesModel::MissingFilesModel(QObject *parent) : QAbstractTableModel(parent) { @@ -418,125 +268,3 @@ QVariant MissingFilesModel::headerData(int section, Qt::Orientation orientation, return result; } - -OBSMissingFiles::OBSMissingFiles(obs_missing_files_t *files, QWidget *parent) - : QDialog(parent), - filesModel(new MissingFilesModel), - ui(new Ui::OBSMissingFiles) -{ - setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); - - ui->setupUi(this); - - ui->tableView->setModel(filesModel); - ui->tableView->setItemDelegateForColumn(MissingFilesColumn::OriginalPath, - new MissingFilesPathItemDelegate(false, "")); - ui->tableView->setItemDelegateForColumn(MissingFilesColumn::NewPath, - new MissingFilesPathItemDelegate(true, "")); - ui->tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeMode::Stretch); - ui->tableView->horizontalHeader()->setSectionResizeMode(MissingFilesColumn::Source, - QHeaderView::ResizeMode::ResizeToContents); - ui->tableView->horizontalHeader()->setMaximumSectionSize(width() / 3); - ui->tableView->horizontalHeader()->setSectionResizeMode(MissingFilesColumn::State, - QHeaderView::ResizeMode::ResizeToContents); - ui->tableView->setEditTriggers(QAbstractItemView::EditTrigger::CurrentChanged); - - ui->warningIcon->setPixmap(filesModel->warningIcon.pixmap(QSize(32, 32))); - - for (size_t i = 0; i < obs_missing_files_count(files); i++) { - obs_missing_file_t *f = obs_missing_files_get_file(files, (int)i); - - const char *oldPath = obs_missing_file_get_path(f); - const char *name = obs_missing_file_get_source_name(f); - - addMissingFile(oldPath, name); - } - - QString found = QTStr("MissingFiles.NumFound").arg("0", QString::number(obs_missing_files_count(files))); - - ui->found->setText(found); - - fileStore = files; - - connect(ui->doneButton, &QPushButton::clicked, this, &OBSMissingFiles::saveFiles); - connect(ui->browseButton, &QPushButton::clicked, this, &OBSMissingFiles::browseFolders); - connect(ui->cancelButton, &QPushButton::clicked, this, &OBSMissingFiles::close); - connect(filesModel, &MissingFilesModel::dataChanged, this, &OBSMissingFiles::dataChanged); - - QModelIndex index = filesModel->createIndex(0, 1); - QMetaObject::invokeMethod(ui->tableView, "setCurrentIndex", Qt::QueuedConnection, - Q_ARG(const QModelIndex &, index)); -} - -OBSMissingFiles::~OBSMissingFiles() -{ - obs_missing_files_destroy(fileStore); -} - -void OBSMissingFiles::addMissingFile(const char *originalPath, const char *sourceName) -{ - QStringList list; - - list.append(originalPath); - list.append(sourceName); - - QModelIndex insertIndex = filesModel->index(filesModel->rowCount() - 1, MissingFilesColumn::Source); - - filesModel->setData(insertIndex, list, MissingFilesRole::NewPathsToProcessRole); -} - -void OBSMissingFiles::saveFiles() -{ - for (int i = 0; i < filesModel->files.length(); i++) { - MissingFilesState state = filesModel->files[i].state; - if (state != MissingFilesState::Missing) { - obs_missing_file_t *f = obs_missing_files_get_file(fileStore, i); - - QString path = filesModel->files[i].newPath; - - if (state == MissingFilesState::Cleared) { - obs_missing_file_issue_callback(f, ""); - } else { - char *p = bstrdup(path.toStdString().c_str()); - obs_missing_file_issue_callback(f, p); - bfree(p); - } - } - } - - QDialog::accept(); -} - -void OBSMissingFiles::browseFolders() -{ - QString dir = QFileDialog::getExistingDirectory(this, QTStr("MissingFiles.SelectDir"), "", - QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); - - if (dir != "") { - dir += "/"; - filesModel->fileCheckLoop(filesModel->files, dir, true); - } -} - -void OBSMissingFiles::dataChanged() -{ - QString found = - QTStr("MissingFiles.NumFound") - .arg(QString::number(filesModel->found()), QString::number(obs_missing_files_count(fileStore))); - - ui->found->setText(found); - - ui->tableView->resizeColumnToContents(MissingFilesColumn::State); - ui->tableView->resizeColumnToContents(MissingFilesColumn::Source); -} - -QIcon OBSMissingFiles::GetWarningIcon() -{ - return filesModel->warningIcon; -} - -void OBSMissingFiles::SetWarningIcon(const QIcon &icon) -{ - ui->warningIcon->setPixmap(icon.pixmap(QSize(32, 32))); - filesModel->warningIcon = icon; -} diff --git a/frontend/utility/MissingFilesModel.hpp b/frontend/utility/MissingFilesModel.hpp index 2b24e0f8e..4b7057e84 100644 --- a/frontend/utility/MissingFilesModel.hpp +++ b/frontend/utility/MissingFilesModel.hpp @@ -17,42 +17,13 @@ #pragma once -#include -#include -#include "obs-app.hpp" -#include "ui_OBSMissingFiles.h" - -class MissingFilesModel; +#include +#include enum MissingFilesState { Missing, Found, Replaced, Cleared }; + Q_DECLARE_METATYPE(MissingFilesState); -class OBSMissingFiles : public QDialog { - Q_OBJECT - Q_PROPERTY(QIcon warningIcon READ GetWarningIcon WRITE SetWarningIcon DESIGNABLE true) - - QPointer filesModel; - std::unique_ptr ui; - -public: - explicit OBSMissingFiles(obs_missing_files_t *files, QWidget *parent = nullptr); - virtual ~OBSMissingFiles() override; - - void addMissingFile(const char *originalPath, const char *sourceName); - - QIcon GetWarningIcon(); - void SetWarningIcon(const QIcon &icon); - -private: - void saveFiles(); - void browseFolders(); - - obs_missing_files_t *fileStore; - -public slots: - void dataChanged(); -}; - class MissingFilesModel : public QAbstractTableModel { Q_OBJECT @@ -87,26 +58,3 @@ private: void fileCheckLoop(QList files, QString path, bool skipPrompt); }; - -class MissingFilesPathItemDelegate : public QStyledItemDelegate { - Q_OBJECT - -public: - MissingFilesPathItemDelegate(bool isOutput, const QString &defaultPath); - - virtual QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem & /* option */, - const QModelIndex &index) const override; - - virtual void setEditorData(QWidget *editor, const QModelIndex &index) const override; - virtual void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override; - virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, - const QModelIndex &index) const override; - -private: - bool isOutput; - QString defaultPath; - const char *PATH_LIST_PROP = "pathList"; - - void handleBrowse(QWidget *container); - void handleClear(QWidget *container); -}; diff --git a/frontend/utility/MissingFilesPathItemDelegate.cpp b/frontend/utility/MissingFilesPathItemDelegate.cpp index 57e641a74..f1e722511 100644 --- a/frontend/utility/MissingFilesPathItemDelegate.cpp +++ b/frontend/utility/MissingFilesPathItemDelegate.cpp @@ -15,32 +15,19 @@ along with this program. If not, see . ******************************************************************************/ -#include "moc_window-missing-files.cpp" -#include "window-basic-main.hpp" +#include "MissingFilesPathItemDelegate.hpp" -#include "obs-app.hpp" +#include +#include +#include #include #include -#include -#include - -enum MissingFilesColumn { - Source, - OriginalPath, - NewPath, - State, - - Count -}; +#include "moc_MissingFilesPathItemDelegate.cpp" enum MissingFilesRole { EntryStateRole = Qt::UserRole, NewPathsToProcessRole }; -/********************************************************** - Delegate - Presents cells in the grid. -**********************************************************/ - MissingFilesPathItemDelegate::MissingFilesPathItemDelegate(bool isOutput, const QString &defaultPath) : QStyledItemDelegate(), isOutput(isOutput), @@ -175,368 +162,3 @@ void MissingFilesPathItemDelegate::handleClear(QWidget *container) ((QWidget *)container->parent())->setFocus(); emit commitData(container); } - -/** - Model -**/ - -MissingFilesModel::MissingFilesModel(QObject *parent) : QAbstractTableModel(parent) -{ - QStyle *style = QApplication::style(); - - warningIcon = style->standardIcon(QStyle::SP_MessageBoxWarning); -} - -int MissingFilesModel::rowCount(const QModelIndex &) const -{ - return files.length(); -} - -int MissingFilesModel::columnCount(const QModelIndex &) const -{ - return MissingFilesColumn::Count; -} - -int MissingFilesModel::found() const -{ - int res = 0; - - for (int i = 0; i < files.length(); i++) { - if (files[i].state != Missing && files[i].state != Cleared) - res++; - } - - return res; -} - -QVariant MissingFilesModel::data(const QModelIndex &index, int role) const -{ - QVariant result = QVariant(); - - if (index.row() >= files.length()) { - return QVariant(); - } else if (role == Qt::DisplayRole) { - QFileInfo fi(files[index.row()].originalPath); - - switch (index.column()) { - case MissingFilesColumn::Source: - result = files[index.row()].source; - break; - case MissingFilesColumn::OriginalPath: - result = fi.fileName(); - break; - case MissingFilesColumn::NewPath: - result = files[index.row()].newPath; - break; - case MissingFilesColumn::State: - switch (files[index.row()].state) { - case MissingFilesState::Missing: - result = QTStr("MissingFiles.Missing"); - break; - - case MissingFilesState::Replaced: - result = QTStr("MissingFiles.Replaced"); - break; - - case MissingFilesState::Found: - result = QTStr("MissingFiles.Found"); - break; - - case MissingFilesState::Cleared: - result = QTStr("MissingFiles.Cleared"); - break; - } - break; - } - } else if (role == Qt::DecorationRole && index.column() == MissingFilesColumn::Source) { - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - OBSSourceAutoRelease source = obs_get_source_by_name(files[index.row()].source.toStdString().c_str()); - - if (source) { - result = main->GetSourceIcon(obs_source_get_id(source)); - } - } else if (role == Qt::FontRole && index.column() == MissingFilesColumn::State) { - QFont font = QFont(); - font.setBold(true); - - result = font; - } else if (role == Qt::ToolTipRole && index.column() == MissingFilesColumn::State) { - switch (files[index.row()].state) { - case MissingFilesState::Missing: - result = QTStr("MissingFiles.Missing"); - break; - - case MissingFilesState::Replaced: - result = QTStr("MissingFiles.Replaced"); - break; - - case MissingFilesState::Found: - result = QTStr("MissingFiles.Found"); - break; - - case MissingFilesState::Cleared: - result = QTStr("MissingFiles.Cleared"); - break; - - default: - break; - } - } else if (role == Qt::ToolTipRole) { - switch (index.column()) { - case MissingFilesColumn::OriginalPath: - result = files[index.row()].originalPath; - break; - case MissingFilesColumn::NewPath: - result = files[index.row()].newPath; - break; - default: - break; - } - } - - return result; -} - -Qt::ItemFlags MissingFilesModel::flags(const QModelIndex &index) const -{ - Qt::ItemFlags flags = QAbstractTableModel::flags(index); - - if (index.column() == MissingFilesColumn::OriginalPath) { - flags &= ~Qt::ItemIsEditable; - } else if (index.column() == MissingFilesColumn::NewPath && index.row() != files.length()) { - flags |= Qt::ItemIsEditable; - } - - return flags; -} - -void MissingFilesModel::fileCheckLoop(QList files, QString path, bool skipPrompt) -{ - loop = false; - QUrl url = QUrl().fromLocalFile(path); - QString dir = url.toDisplayString(QUrl::RemoveScheme | QUrl::RemoveFilename | QUrl::PreferLocalFile); - - bool prompted = skipPrompt; - - for (int i = 0; i < files.length(); i++) { - if (files[i].state != MissingFilesState::Missing) - continue; - - QUrl origFile = QUrl().fromLocalFile(files[i].originalPath); - QString filename = origFile.fileName(); - QString testFile = dir + filename; - - if (os_file_exists(testFile.toStdString().c_str())) { - if (!prompted) { - QMessageBox::StandardButton button = - QMessageBox::question(nullptr, QTStr("MissingFiles.AutoSearch"), - QTStr("MissingFiles.AutoSearchText")); - - if (button == QMessageBox::No) - break; - - prompted = true; - } - QModelIndex in = index(i, MissingFilesColumn::NewPath); - setData(in, testFile, 0); - } - } - loop = true; -} - -bool MissingFilesModel::setData(const QModelIndex &index, const QVariant &value, int role) -{ - bool success = false; - - if (role == MissingFilesRole::NewPathsToProcessRole) { - QStringList list = value.toStringList(); - - int row = index.row() + 1; - beginInsertRows(QModelIndex(), row, row); - - MissingFileEntry entry; - entry.originalPath = list[0].replace("\\", "/"); - entry.source = list[1]; - - files.insert(row, entry); - row++; - - endInsertRows(); - - success = true; - } else { - QString path = value.toString(); - if (index.column() == MissingFilesColumn::NewPath) { - files[index.row()].newPath = value.toString(); - QString fileName = QUrl(path).fileName(); - QString origFileName = QUrl(files[index.row()].originalPath).fileName(); - - if (path.isEmpty()) { - files[index.row()].state = MissingFilesState::Missing; - } else if (path.compare(QTStr("MissingFiles.Clear")) == 0) { - files[index.row()].state = MissingFilesState::Cleared; - } else if (fileName.compare(origFileName) == 0) { - files[index.row()].state = MissingFilesState::Found; - - if (loop) - fileCheckLoop(files, path, false); - } else { - files[index.row()].state = MissingFilesState::Replaced; - - if (loop) - fileCheckLoop(files, path, false); - } - - emit dataChanged(index, index); - success = true; - } - } - - return success; -} - -QVariant MissingFilesModel::headerData(int section, Qt::Orientation orientation, int role) const -{ - QVariant result = QVariant(); - - if (role == Qt::DisplayRole && orientation == Qt::Orientation::Horizontal) { - switch (section) { - case MissingFilesColumn::State: - result = QTStr("MissingFiles.State"); - break; - case MissingFilesColumn::Source: - result = QTStr("Basic.Main.Source"); - break; - case MissingFilesColumn::OriginalPath: - result = QTStr("MissingFiles.MissingFile"); - break; - case MissingFilesColumn::NewPath: - result = QTStr("MissingFiles.NewFile"); - break; - } - } - - return result; -} - -OBSMissingFiles::OBSMissingFiles(obs_missing_files_t *files, QWidget *parent) - : QDialog(parent), - filesModel(new MissingFilesModel), - ui(new Ui::OBSMissingFiles) -{ - setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); - - ui->setupUi(this); - - ui->tableView->setModel(filesModel); - ui->tableView->setItemDelegateForColumn(MissingFilesColumn::OriginalPath, - new MissingFilesPathItemDelegate(false, "")); - ui->tableView->setItemDelegateForColumn(MissingFilesColumn::NewPath, - new MissingFilesPathItemDelegate(true, "")); - ui->tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeMode::Stretch); - ui->tableView->horizontalHeader()->setSectionResizeMode(MissingFilesColumn::Source, - QHeaderView::ResizeMode::ResizeToContents); - ui->tableView->horizontalHeader()->setMaximumSectionSize(width() / 3); - ui->tableView->horizontalHeader()->setSectionResizeMode(MissingFilesColumn::State, - QHeaderView::ResizeMode::ResizeToContents); - ui->tableView->setEditTriggers(QAbstractItemView::EditTrigger::CurrentChanged); - - ui->warningIcon->setPixmap(filesModel->warningIcon.pixmap(QSize(32, 32))); - - for (size_t i = 0; i < obs_missing_files_count(files); i++) { - obs_missing_file_t *f = obs_missing_files_get_file(files, (int)i); - - const char *oldPath = obs_missing_file_get_path(f); - const char *name = obs_missing_file_get_source_name(f); - - addMissingFile(oldPath, name); - } - - QString found = QTStr("MissingFiles.NumFound").arg("0", QString::number(obs_missing_files_count(files))); - - ui->found->setText(found); - - fileStore = files; - - connect(ui->doneButton, &QPushButton::clicked, this, &OBSMissingFiles::saveFiles); - connect(ui->browseButton, &QPushButton::clicked, this, &OBSMissingFiles::browseFolders); - connect(ui->cancelButton, &QPushButton::clicked, this, &OBSMissingFiles::close); - connect(filesModel, &MissingFilesModel::dataChanged, this, &OBSMissingFiles::dataChanged); - - QModelIndex index = filesModel->createIndex(0, 1); - QMetaObject::invokeMethod(ui->tableView, "setCurrentIndex", Qt::QueuedConnection, - Q_ARG(const QModelIndex &, index)); -} - -OBSMissingFiles::~OBSMissingFiles() -{ - obs_missing_files_destroy(fileStore); -} - -void OBSMissingFiles::addMissingFile(const char *originalPath, const char *sourceName) -{ - QStringList list; - - list.append(originalPath); - list.append(sourceName); - - QModelIndex insertIndex = filesModel->index(filesModel->rowCount() - 1, MissingFilesColumn::Source); - - filesModel->setData(insertIndex, list, MissingFilesRole::NewPathsToProcessRole); -} - -void OBSMissingFiles::saveFiles() -{ - for (int i = 0; i < filesModel->files.length(); i++) { - MissingFilesState state = filesModel->files[i].state; - if (state != MissingFilesState::Missing) { - obs_missing_file_t *f = obs_missing_files_get_file(fileStore, i); - - QString path = filesModel->files[i].newPath; - - if (state == MissingFilesState::Cleared) { - obs_missing_file_issue_callback(f, ""); - } else { - char *p = bstrdup(path.toStdString().c_str()); - obs_missing_file_issue_callback(f, p); - bfree(p); - } - } - } - - QDialog::accept(); -} - -void OBSMissingFiles::browseFolders() -{ - QString dir = QFileDialog::getExistingDirectory(this, QTStr("MissingFiles.SelectDir"), "", - QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); - - if (dir != "") { - dir += "/"; - filesModel->fileCheckLoop(filesModel->files, dir, true); - } -} - -void OBSMissingFiles::dataChanged() -{ - QString found = - QTStr("MissingFiles.NumFound") - .arg(QString::number(filesModel->found()), QString::number(obs_missing_files_count(fileStore))); - - ui->found->setText(found); - - ui->tableView->resizeColumnToContents(MissingFilesColumn::State); - ui->tableView->resizeColumnToContents(MissingFilesColumn::Source); -} - -QIcon OBSMissingFiles::GetWarningIcon() -{ - return filesModel->warningIcon; -} - -void OBSMissingFiles::SetWarningIcon(const QIcon &icon) -{ - ui->warningIcon->setPixmap(icon.pixmap(QSize(32, 32))); - filesModel->warningIcon = icon; -} diff --git a/frontend/utility/MissingFilesPathItemDelegate.hpp b/frontend/utility/MissingFilesPathItemDelegate.hpp index 2b24e0f8e..8b9606226 100644 --- a/frontend/utility/MissingFilesPathItemDelegate.hpp +++ b/frontend/utility/MissingFilesPathItemDelegate.hpp @@ -17,76 +17,7 @@ #pragma once -#include #include -#include "obs-app.hpp" -#include "ui_OBSMissingFiles.h" - -class MissingFilesModel; - -enum MissingFilesState { Missing, Found, Replaced, Cleared }; -Q_DECLARE_METATYPE(MissingFilesState); - -class OBSMissingFiles : public QDialog { - Q_OBJECT - Q_PROPERTY(QIcon warningIcon READ GetWarningIcon WRITE SetWarningIcon DESIGNABLE true) - - QPointer filesModel; - std::unique_ptr ui; - -public: - explicit OBSMissingFiles(obs_missing_files_t *files, QWidget *parent = nullptr); - virtual ~OBSMissingFiles() override; - - void addMissingFile(const char *originalPath, const char *sourceName); - - QIcon GetWarningIcon(); - void SetWarningIcon(const QIcon &icon); - -private: - void saveFiles(); - void browseFolders(); - - obs_missing_files_t *fileStore; - -public slots: - void dataChanged(); -}; - -class MissingFilesModel : public QAbstractTableModel { - Q_OBJECT - - friend class OBSMissingFiles; - -public: - explicit MissingFilesModel(QObject *parent = 0); - - int rowCount(const QModelIndex &parent = QModelIndex()) const; - int columnCount(const QModelIndex &parent = QModelIndex()) const; - int found() const; - QVariant data(const QModelIndex &index, int role) const; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; - Qt::ItemFlags flags(const QModelIndex &index) const; - bool setData(const QModelIndex &index, const QVariant &value, int role); - - bool loop = true; - - QIcon warningIcon; - -private: - struct MissingFileEntry { - MissingFilesState state = MissingFilesState::Missing; - - QString source; - - QString originalPath; - QString newPath; - }; - - QList files; - - void fileCheckLoop(QList files, QString path, bool skipPrompt); -}; class MissingFilesPathItemDelegate : public QStyledItemDelegate { Q_OBJECT diff --git a/frontend/utility/OBSEventFilter.hpp b/frontend/utility/OBSEventFilter.hpp index 6bfaaab4a..fe83ea674 100644 --- a/frontend/utility/OBSEventFilter.hpp +++ b/frontend/utility/OBSEventFilter.hpp @@ -17,55 +17,8 @@ #pragma once -#include -#include -#include - -#include -#include - -class OBSBasic; - -#include "ui_OBSBasicInteraction.h" - -class OBSEventFilter; - -class OBSBasicInteraction : public QDialog { - Q_OBJECT - -private: - OBSBasic *main; - - std::unique_ptr ui; - OBSSource source; - OBSSignal removedSignal; - OBSSignal renamedSignal; - std::unique_ptr eventFilter; - - static void SourceRemoved(void *data, calldata_t *params); - static void SourceRenamed(void *data, calldata_t *params); - static void DrawPreview(void *data, uint32_t cx, uint32_t cy); - - bool GetSourceRelativeXY(int mouseX, int mouseY, int &x, int &y); - - bool HandleMouseClickEvent(QMouseEvent *event); - bool HandleMouseMoveEvent(QMouseEvent *event); - bool HandleMouseWheelEvent(QWheelEvent *event); - bool HandleFocusEvent(QFocusEvent *event); - bool HandleKeyEvent(QKeyEvent *event); - - OBSEventFilter *BuildEventFilter(); - -public: - OBSBasicInteraction(QWidget *parent, OBSSource source_); - ~OBSBasicInteraction(); - - void Init(); - -protected: - virtual void closeEvent(QCloseEvent *event) override; - virtual bool nativeEvent(const QByteArray &eventType, void *message, qintptr *result) override; -}; +#include +#include typedef std::function EventFilterFunc; diff --git a/frontend/utility/RemuxEntryPathItemDelegate.cpp b/frontend/utility/RemuxEntryPathItemDelegate.cpp index d72ebbd9c..0349d1fab 100644 --- a/frontend/utility/RemuxEntryPathItemDelegate.cpp +++ b/frontend/utility/RemuxEntryPathItemDelegate.cpp @@ -15,44 +15,18 @@ along with this program. If not, see . ******************************************************************************/ -#include "moc_window-remux.cpp" +#include "RemuxEntryPathItemDelegate.hpp" +#include "RemuxQueueModel.hpp" -#include "obs-app.hpp" +#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include "window-basic-main.hpp" +#include +#include +#include -#include -#include - -using namespace std; - -enum RemuxEntryColumn { - State, - InputPath, - OutputPath, - - Count -}; - -enum RemuxEntryRole { EntryStateRole = Qt::UserRole, NewPathsToProcessRole }; - -/********************************************************** - Delegate - Presents cells in the grid. -**********************************************************/ +#include "moc_RemuxEntryPathItemDelegate.cpp" RemuxEntryPathItemDelegate::RemuxEntryPathItemDelegate(bool isOutput, const QString &defaultPath) : QStyledItemDelegate(), @@ -235,690 +209,3 @@ void RemuxEntryPathItemDelegate::updateText() QWidget *editor = lineEdit->parentWidget(); emit commitData(editor); } - -/********************************************************** - Model - Manages the queue's data -**********************************************************/ - -int RemuxQueueModel::rowCount(const QModelIndex &) const -{ - return queue.length() + (isProcessing ? 0 : 1); -} - -int RemuxQueueModel::columnCount(const QModelIndex &) const -{ - return RemuxEntryColumn::Count; -} - -QVariant RemuxQueueModel::data(const QModelIndex &index, int role) const -{ - QVariant result = QVariant(); - - if (index.row() >= queue.length()) { - return QVariant(); - } else if (role == Qt::DisplayRole) { - switch (index.column()) { - case RemuxEntryColumn::InputPath: - result = queue[index.row()].sourcePath; - break; - case RemuxEntryColumn::OutputPath: - result = queue[index.row()].targetPath; - break; - } - } else if (role == Qt::DecorationRole && index.column() == RemuxEntryColumn::State) { - result = getIcon(queue[index.row()].state); - } else if (role == RemuxEntryRole::EntryStateRole) { - result = queue[index.row()].state; - } - - return result; -} - -QVariant RemuxQueueModel::headerData(int section, Qt::Orientation orientation, int role) const -{ - QVariant result = QVariant(); - - if (role == Qt::DisplayRole && orientation == Qt::Orientation::Horizontal) { - switch (section) { - case RemuxEntryColumn::State: - result = QString(); - break; - case RemuxEntryColumn::InputPath: - result = QTStr("Remux.SourceFile"); - break; - case RemuxEntryColumn::OutputPath: - result = QTStr("Remux.TargetFile"); - break; - } - } - - return result; -} - -Qt::ItemFlags RemuxQueueModel::flags(const QModelIndex &index) const -{ - Qt::ItemFlags flags = QAbstractTableModel::flags(index); - - if (index.column() == RemuxEntryColumn::InputPath) { - flags |= Qt::ItemIsEditable; - } else if (index.column() == RemuxEntryColumn::OutputPath && index.row() != queue.length()) { - flags |= Qt::ItemIsEditable; - } - - return flags; -} - -bool RemuxQueueModel::setData(const QModelIndex &index, const QVariant &value, int role) -{ - bool success = false; - - if (role == RemuxEntryRole::NewPathsToProcessRole) { - QStringList pathList = value.toStringList(); - - if (pathList.size() == 0) { - if (index.row() < queue.size()) { - beginRemoveRows(QModelIndex(), index.row(), index.row()); - queue.removeAt(index.row()); - endRemoveRows(); - } - } else { - if (pathList.size() >= 1 && index.row() < queue.length()) { - queue[index.row()].sourcePath = pathList[0]; - checkInputPath(index.row()); - - pathList.removeAt(0); - - success = true; - } - - if (pathList.size() > 0) { - int row = index.row(); - int lastRow = row + pathList.size() - 1; - beginInsertRows(QModelIndex(), row, lastRow); - - for (QString path : pathList) { - RemuxQueueEntry entry; - entry.sourcePath = path; - entry.state = RemuxEntryState::Empty; - - queue.insert(row, entry); - row++; - } - endInsertRows(); - - for (row = index.row(); row <= lastRow; row++) { - checkInputPath(row); - } - - success = true; - } - } - } else if (index.row() == queue.length()) { - QString path = value.toString(); - - if (!path.isEmpty()) { - RemuxQueueEntry entry; - entry.sourcePath = path; - entry.state = RemuxEntryState::Empty; - - beginInsertRows(QModelIndex(), queue.length() + 1, queue.length() + 1); - queue.append(entry); - endInsertRows(); - - checkInputPath(index.row()); - success = true; - } - } else { - QString path = value.toString(); - - if (path.isEmpty()) { - if (index.column() == RemuxEntryColumn::InputPath) { - beginRemoveRows(QModelIndex(), index.row(), index.row()); - queue.removeAt(index.row()); - endRemoveRows(); - } - } else { - switch (index.column()) { - case RemuxEntryColumn::InputPath: - queue[index.row()].sourcePath = value.toString(); - checkInputPath(index.row()); - success = true; - break; - case RemuxEntryColumn::OutputPath: - queue[index.row()].targetPath = value.toString(); - emit dataChanged(index, index); - success = true; - break; - } - } - } - - return success; -} - -QVariant RemuxQueueModel::getIcon(RemuxEntryState state) -{ - QVariant icon; - QStyle *style = QApplication::style(); - - switch (state) { - case RemuxEntryState::Complete: - icon = style->standardIcon(QStyle::SP_DialogApplyButton); - break; - - case RemuxEntryState::InProgress: - icon = style->standardIcon(QStyle::SP_ArrowRight); - break; - - case RemuxEntryState::Error: - icon = style->standardIcon(QStyle::SP_DialogCancelButton); - break; - - case RemuxEntryState::InvalidPath: - icon = style->standardIcon(QStyle::SP_MessageBoxWarning); - break; - - default: - break; - } - - return icon; -} - -void RemuxQueueModel::checkInputPath(int row) -{ - RemuxQueueEntry &entry = queue[row]; - - if (entry.sourcePath.isEmpty()) { - entry.state = RemuxEntryState::Empty; - } else { - entry.sourcePath = QDir::toNativeSeparators(entry.sourcePath); - QFileInfo fileInfo(entry.sourcePath); - if (fileInfo.exists()) - entry.state = RemuxEntryState::Ready; - else - entry.state = RemuxEntryState::InvalidPath; - - QString newExt = ".mp4"; - QString suffix = fileInfo.suffix(); - - if (suffix.contains("mov", Qt::CaseInsensitive) || suffix.contains("mp4", Qt::CaseInsensitive)) { - newExt = ".remuxed." + suffix; - } - - if (entry.state == RemuxEntryState::Ready) - entry.targetPath = QDir::toNativeSeparators(fileInfo.path() + QDir::separator() + - fileInfo.completeBaseName() + newExt); - } - - if (entry.state == RemuxEntryState::Ready && isProcessing) - entry.state = RemuxEntryState::Pending; - - emit dataChanged(index(row, 0), index(row, RemuxEntryColumn::Count)); -} - -QFileInfoList RemuxQueueModel::checkForOverwrites() const -{ - QFileInfoList list; - - for (const RemuxQueueEntry &entry : queue) { - if (entry.state == RemuxEntryState::Ready) { - QFileInfo fileInfo(entry.targetPath); - if (fileInfo.exists()) { - list.append(fileInfo); - } - } - } - - return list; -} - -bool RemuxQueueModel::checkForErrors() const -{ - bool hasErrors = false; - - for (const RemuxQueueEntry &entry : queue) { - if (entry.state == RemuxEntryState::Error) { - hasErrors = true; - break; - } - } - - return hasErrors; -} - -void RemuxQueueModel::clearAll() -{ - beginRemoveRows(QModelIndex(), 0, queue.size() - 1); - queue.clear(); - endRemoveRows(); -} - -void RemuxQueueModel::clearFinished() -{ - int index = 0; - - for (index = 0; index < queue.size(); index++) { - const RemuxQueueEntry &entry = queue[index]; - if (entry.state == RemuxEntryState::Complete) { - beginRemoveRows(QModelIndex(), index, index); - queue.removeAt(index); - endRemoveRows(); - index--; - } - } -} - -bool RemuxQueueModel::canClearFinished() const -{ - bool canClearFinished = false; - for (const RemuxQueueEntry &entry : queue) - if (entry.state == RemuxEntryState::Complete) { - canClearFinished = true; - break; - } - - return canClearFinished; -} - -void RemuxQueueModel::beginProcessing() -{ - for (RemuxQueueEntry &entry : queue) - if (entry.state == RemuxEntryState::Ready) - entry.state = RemuxEntryState::Pending; - - // Signal that the insertion point no longer exists. - beginRemoveRows(QModelIndex(), queue.length(), queue.length()); - endRemoveRows(); - - isProcessing = true; - - emit dataChanged(index(0, RemuxEntryColumn::State), index(queue.length(), RemuxEntryColumn::State)); -} - -void RemuxQueueModel::endProcessing() -{ - for (RemuxQueueEntry &entry : queue) { - if (entry.state == RemuxEntryState::Pending) { - entry.state = RemuxEntryState::Ready; - } - } - - // Signal that the insertion point exists again. - isProcessing = false; - if (!autoRemux) { - beginInsertRows(QModelIndex(), queue.length(), queue.length()); - endInsertRows(); - } - - emit dataChanged(index(0, RemuxEntryColumn::State), index(queue.length(), RemuxEntryColumn::State)); -} - -bool RemuxQueueModel::beginNextEntry(QString &inputPath, QString &outputPath) -{ - bool anyStarted = false; - - for (int row = 0; row < queue.length(); row++) { - RemuxQueueEntry &entry = queue[row]; - if (entry.state == RemuxEntryState::Pending) { - entry.state = RemuxEntryState::InProgress; - - inputPath = entry.sourcePath; - outputPath = entry.targetPath; - - QModelIndex index = this->index(row, RemuxEntryColumn::State); - emit dataChanged(index, index); - - anyStarted = true; - break; - } - } - - return anyStarted; -} - -void RemuxQueueModel::finishEntry(bool success) -{ - for (int row = 0; row < queue.length(); row++) { - RemuxQueueEntry &entry = queue[row]; - if (entry.state == RemuxEntryState::InProgress) { - if (success) - entry.state = RemuxEntryState::Complete; - else - entry.state = RemuxEntryState::Error; - - QModelIndex index = this->index(row, RemuxEntryColumn::State); - emit dataChanged(index, index); - - break; - } - } -} - -/********************************************************** - The actual remux window implementation -**********************************************************/ - -OBSRemux::OBSRemux(const char *path, QWidget *parent, bool autoRemux_) - : QDialog(parent), - queueModel(new RemuxQueueModel), - worker(new RemuxWorker()), - ui(new Ui::OBSRemux), - recPath(path), - autoRemux(autoRemux_) -{ - setAcceptDrops(true); - - setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); - - ui->setupUi(this); - - ui->progressBar->setVisible(false); - ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); - ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)->setEnabled(false); - - if (autoRemux) { - resize(280, 40); - ui->tableView->hide(); - ui->buttonBox->hide(); - ui->label->hide(); - } - - ui->progressBar->setMinimum(0); - ui->progressBar->setMaximum(1000); - ui->progressBar->setValue(0); - - ui->tableView->setModel(queueModel); - ui->tableView->setItemDelegateForColumn(RemuxEntryColumn::InputPath, - new RemuxEntryPathItemDelegate(false, recPath)); - ui->tableView->setItemDelegateForColumn(RemuxEntryColumn::OutputPath, - new RemuxEntryPathItemDelegate(true, recPath)); - ui->tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeMode::Stretch); - ui->tableView->horizontalHeader()->setSectionResizeMode(RemuxEntryColumn::State, - QHeaderView::ResizeMode::Fixed); - ui->tableView->setEditTriggers(QAbstractItemView::EditTrigger::CurrentChanged); - ui->tableView->setTextElideMode(Qt::ElideMiddle); - ui->tableView->setWordWrap(false); - - installEventFilter(CreateShortcutFilter()); - - ui->buttonBox->button(QDialogButtonBox::Ok)->setText(QTStr("Remux.Remux")); - ui->buttonBox->button(QDialogButtonBox::Reset)->setText(QTStr("Remux.ClearFinished")); - ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)->setText(QTStr("Remux.ClearAll")); - ui->buttonBox->button(QDialogButtonBox::Reset)->setDisabled(true); - - connect(ui->buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked, this, &OBSRemux::beginRemux); - connect(ui->buttonBox->button(QDialogButtonBox::Reset), &QPushButton::clicked, this, &OBSRemux::clearFinished); - connect(ui->buttonBox->button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, this, - &OBSRemux::clearAll); - connect(ui->buttonBox->button(QDialogButtonBox::Close), &QPushButton::clicked, this, &OBSRemux::close); - - worker->moveToThread(&remuxer); - remuxer.start(); - - connect(worker.data(), &RemuxWorker::updateProgress, this, &OBSRemux::updateProgress); - connect(&remuxer, &QThread::finished, worker.data(), &QObject::deleteLater); - connect(worker.data(), &RemuxWorker::remuxFinished, this, &OBSRemux::remuxFinished); - connect(this, &OBSRemux::remux, worker.data(), &RemuxWorker::remux); - - connect(queueModel.data(), &RemuxQueueModel::rowsInserted, this, &OBSRemux::rowCountChanged); - connect(queueModel.data(), &RemuxQueueModel::rowsRemoved, this, &OBSRemux::rowCountChanged); - - QModelIndex index = queueModel->createIndex(0, 1); - QMetaObject::invokeMethod(ui->tableView, "setCurrentIndex", Qt::QueuedConnection, - Q_ARG(const QModelIndex &, index)); -} - -bool OBSRemux::stopRemux() -{ - if (!worker->isWorking) - return true; - - // By locking the worker thread's mutex, we ensure that its - // update poll will be blocked as long as we're in here with - // the popup open. - QMutexLocker lock(&worker->updateMutex); - - bool exit = false; - - if (QMessageBox::critical(nullptr, QTStr("Remux.ExitUnfinishedTitle"), QTStr("Remux.ExitUnfinished"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes) { - exit = true; - } - - if (exit) { - // Inform the worker it should no longer be - // working. It will interrupt accordingly in - // its next update callback. - worker->isWorking = false; - } - - return exit; -} - -OBSRemux::~OBSRemux() -{ - stopRemux(); - remuxer.quit(); - remuxer.wait(); -} - -void OBSRemux::rowCountChanged(const QModelIndex &, int, int) -{ - // See if there are still any rows ready to remux. Change - // the state of the "go" button accordingly. - // There must be more than one row, since there will always be - // at least one row for the empty insertion point. - if (queueModel->rowCount() > 1) { - ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); - ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)->setEnabled(true); - ui->buttonBox->button(QDialogButtonBox::Reset)->setEnabled(queueModel->canClearFinished()); - } else { - ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); - ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)->setEnabled(false); - ui->buttonBox->button(QDialogButtonBox::Reset)->setEnabled(false); - } -} - -void OBSRemux::dropEvent(QDropEvent *ev) -{ - QStringList urlList; - - for (QUrl url : ev->mimeData()->urls()) { - QFileInfo fileInfo(url.toLocalFile()); - - if (fileInfo.isDir()) { - QStringList directoryFilter; - directoryFilter << "*.flv" - << "*.mp4" - << "*.mov" - << "*.mkv" - << "*.ts" - << "*.m3u8"; - - QDirIterator dirIter(fileInfo.absoluteFilePath(), directoryFilter, QDir::Files, - QDirIterator::Subdirectories); - - while (dirIter.hasNext()) { - urlList.append(dirIter.next()); - } - } else { - urlList.append(fileInfo.canonicalFilePath()); - } - } - - if (urlList.empty()) { - QMessageBox::information(nullptr, QTStr("Remux.NoFilesAddedTitle"), QTStr("Remux.NoFilesAdded"), - QMessageBox::Ok); - } else if (!autoRemux) { - QModelIndex insertIndex = queueModel->index(queueModel->rowCount() - 1, RemuxEntryColumn::InputPath); - queueModel->setData(insertIndex, urlList, RemuxEntryRole::NewPathsToProcessRole); - } -} - -void OBSRemux::dragEnterEvent(QDragEnterEvent *ev) -{ - if (ev->mimeData()->hasUrls() && !worker->isWorking) - ev->accept(); -} - -void OBSRemux::beginRemux() -{ - if (worker->isWorking) { - stopRemux(); - return; - } - - bool proceedWithRemux = true; - QFileInfoList overwriteFiles = queueModel->checkForOverwrites(); - - if (!overwriteFiles.empty()) { - QString message = QTStr("Remux.FileExists"); - message += "\n\n"; - - for (QFileInfo fileInfo : overwriteFiles) - message += fileInfo.canonicalFilePath() + "\n"; - - if (OBSMessageBox::question(this, QTStr("Remux.FileExistsTitle"), message) != QMessageBox::Yes) - proceedWithRemux = false; - } - - if (!proceedWithRemux) - return; - - // Set all jobs to "pending" first. - queueModel->beginProcessing(); - - ui->progressBar->setVisible(true); - ui->buttonBox->button(QDialogButtonBox::Ok)->setText(QTStr("Remux.Stop")); - setAcceptDrops(false); - - remuxNextEntry(); -} - -void OBSRemux::AutoRemux(QString inFile, QString outFile) -{ - if (inFile != "" && outFile != "" && autoRemux) { - ui->progressBar->setVisible(true); - emit remux(inFile, outFile); - autoRemuxFile = outFile; - } -} - -void OBSRemux::remuxNextEntry() -{ - worker->lastProgress = 0.f; - - QString inputPath, outputPath; - if (queueModel->beginNextEntry(inputPath, outputPath)) { - emit remux(inputPath, outputPath); - } else { - queueModel->autoRemux = autoRemux; - queueModel->endProcessing(); - - if (!autoRemux) { - OBSMessageBox::information(this, QTStr("Remux.FinishedTitle"), - queueModel->checkForErrors() ? QTStr("Remux.FinishedError") - : QTStr("Remux.Finished")); - } - - ui->progressBar->setVisible(autoRemux); - ui->buttonBox->button(QDialogButtonBox::Ok)->setText(QTStr("Remux.Remux")); - ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)->setEnabled(true); - ui->buttonBox->button(QDialogButtonBox::Reset)->setEnabled(queueModel->canClearFinished()); - setAcceptDrops(true); - } -} - -void OBSRemux::closeEvent(QCloseEvent *event) -{ - if (!stopRemux()) - event->ignore(); - else - QDialog::closeEvent(event); -} - -void OBSRemux::reject() -{ - if (!stopRemux()) - return; - - QDialog::reject(); -} - -void OBSRemux::updateProgress(float percent) -{ - ui->progressBar->setValue(percent * 10); -} - -void OBSRemux::remuxFinished(bool success) -{ - ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); - - queueModel->finishEntry(success); - - if (autoRemux && autoRemuxFile != "") { - QTimer::singleShot(3000, this, &OBSRemux::close); - - OBSBasic *main = OBSBasic::Get(); - main->ShowStatusBarMessage(QTStr("Basic.StatusBar.AutoRemuxedTo").arg(autoRemuxFile)); - } - - remuxNextEntry(); -} - -void OBSRemux::clearFinished() -{ - queueModel->clearFinished(); -} - -void OBSRemux::clearAll() -{ - queueModel->clearAll(); -} - -/********************************************************** - Worker thread - Executes the libobs remux operation as a - background process. -**********************************************************/ - -void RemuxWorker::UpdateProgress(float percent) -{ - if (abs(lastProgress - percent) < 0.1f) - return; - - emit updateProgress(percent); - lastProgress = percent; -} - -void RemuxWorker::remux(const QString &source, const QString &target) -{ - isWorking = true; - - auto callback = [](void *data, float percent) { - RemuxWorker *rw = static_cast(data); - - QMutexLocker lock(&rw->updateMutex); - - rw->UpdateProgress(percent); - - return rw->isWorking; - }; - - bool stopped = false; - bool success = false; - - media_remux_job_t mr_job = nullptr; - if (media_remux_job_create(&mr_job, QT_TO_UTF8(source), QT_TO_UTF8(target))) { - - success = media_remux_job_process(mr_job, callback, this); - - media_remux_job_destroy(mr_job); - - stopped = !isWorking; - } - - isWorking = false; - - emit remuxFinished(!stopped && success); -} diff --git a/frontend/utility/RemuxEntryPathItemDelegate.hpp b/frontend/utility/RemuxEntryPathItemDelegate.hpp index 20a13a780..dcbd7086f 100644 --- a/frontend/utility/RemuxEntryPathItemDelegate.hpp +++ b/frontend/utility/RemuxEntryPathItemDelegate.hpp @@ -17,134 +17,7 @@ #pragma once -#include -#include -#include -#include #include -#include -#include "ui_OBSRemux.h" - -#include -#include - -class RemuxQueueModel; -class RemuxWorker; - -enum RemuxEntryState { Empty, Ready, Pending, InProgress, Complete, InvalidPath, Error }; -Q_DECLARE_METATYPE(RemuxEntryState); - -class OBSRemux : public QDialog { - Q_OBJECT - - QPointer queueModel; - QThread remuxer; - QPointer worker; - - std::unique_ptr ui; - - const char *recPath; - - virtual void closeEvent(QCloseEvent *event) override; - virtual void reject() override; - - bool autoRemux; - QString autoRemuxFile; - -public: - explicit OBSRemux(const char *recPath, QWidget *parent = nullptr, bool autoRemux = false); - virtual ~OBSRemux() override; - - using job_t = std::shared_ptr; - - void AutoRemux(QString inFile, QString outFile); - -protected: - virtual void dropEvent(QDropEvent *ev) override; - virtual void dragEnterEvent(QDragEnterEvent *ev) override; - - void remuxNextEntry(); - -private slots: - void rowCountChanged(const QModelIndex &parent, int first, int last); - -public slots: - void updateProgress(float percent); - void remuxFinished(bool success); - void beginRemux(); - bool stopRemux(); - void clearFinished(); - void clearAll(); - -signals: - void remux(const QString &source, const QString &target); -}; - -class RemuxQueueModel : public QAbstractTableModel { - Q_OBJECT - - friend class OBSRemux; - -public: - RemuxQueueModel(QObject *parent = 0) : QAbstractTableModel(parent), isProcessing(false) {} - - int rowCount(const QModelIndex &parent = QModelIndex()) const; - int columnCount(const QModelIndex &parent = QModelIndex()) const; - QVariant data(const QModelIndex &index, int role) const; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; - Qt::ItemFlags flags(const QModelIndex &index) const; - bool setData(const QModelIndex &index, const QVariant &value, int role); - - QFileInfoList checkForOverwrites() const; - bool checkForErrors() const; - void beginProcessing(); - void endProcessing(); - bool beginNextEntry(QString &inputPath, QString &outputPath); - void finishEntry(bool success); - bool canClearFinished() const; - void clearFinished(); - void clearAll(); - - bool autoRemux = false; - -private: - struct RemuxQueueEntry { - RemuxEntryState state; - - QString sourcePath; - QString targetPath; - }; - - QList queue; - bool isProcessing; - - static QVariant getIcon(RemuxEntryState state); - - void checkInputPath(int row); -}; - -class RemuxWorker : public QObject { - Q_OBJECT - - QMutex updateMutex; - - bool isWorking; - - float lastProgress; - void UpdateProgress(float percent); - - explicit RemuxWorker() : isWorking(false) {} - virtual ~RemuxWorker(){}; - -private slots: - void remux(const QString &source, const QString &target); - -signals: - void updateProgress(float percent); - void remuxFinished(bool success); - - friend class OBSRemux; -}; class RemuxEntryPathItemDelegate : public QStyledItemDelegate { Q_OBJECT diff --git a/frontend/utility/RemuxQueueModel.cpp b/frontend/utility/RemuxQueueModel.cpp index d72ebbd9c..cae17b138 100644 --- a/frontend/utility/RemuxQueueModel.cpp +++ b/frontend/utility/RemuxQueueModel.cpp @@ -15,230 +15,14 @@ along with this program. If not, see . ******************************************************************************/ -#include "moc_window-remux.cpp" +#include "RemuxQueueModel.hpp" -#include "obs-app.hpp" +#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include -#include "window-basic-main.hpp" - -#include -#include - -using namespace std; - -enum RemuxEntryColumn { - State, - InputPath, - OutputPath, - - Count -}; - -enum RemuxEntryRole { EntryStateRole = Qt::UserRole, NewPathsToProcessRole }; - -/********************************************************** - Delegate - Presents cells in the grid. -**********************************************************/ - -RemuxEntryPathItemDelegate::RemuxEntryPathItemDelegate(bool isOutput, const QString &defaultPath) - : QStyledItemDelegate(), - isOutput(isOutput), - defaultPath(defaultPath) -{ -} - -QWidget *RemuxEntryPathItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem & /* option */, - const QModelIndex &index) const -{ - RemuxEntryState state = index.model() - ->index(index.row(), RemuxEntryColumn::State) - .data(RemuxEntryRole::EntryStateRole) - .value(); - if (state == RemuxEntryState::Pending || state == RemuxEntryState::InProgress) { - // Never allow modification of rows that are - // in progress. - return Q_NULLPTR; - } else if (isOutput && state != RemuxEntryState::Ready) { - // Do not allow modification of output rows - // that aren't associated with a valid input. - return Q_NULLPTR; - } else if (!isOutput && state == RemuxEntryState::Complete) { - // Don't allow modification of rows that are - // already complete. - return Q_NULLPTR; - } else { - QSizePolicy buttonSizePolicy(QSizePolicy::Policy::Minimum, QSizePolicy::Policy::Expanding, - QSizePolicy::ControlType::PushButton); - - QWidget *container = new QWidget(parent); - - auto browseCallback = [this, container]() { - const_cast(this)->handleBrowse(container); - }; - - auto clearCallback = [this, container]() { - const_cast(this)->handleClear(container); - }; - - QHBoxLayout *layout = new QHBoxLayout(); - layout->setContentsMargins(0, 0, 0, 0); - layout->setSpacing(0); - - QLineEdit *text = new QLineEdit(); - text->setObjectName(QStringLiteral("text")); - text->setSizePolicy(QSizePolicy(QSizePolicy::Policy::Expanding, QSizePolicy::Policy::Expanding, - QSizePolicy::ControlType::LineEdit)); - layout->addWidget(text); - - QObject::connect(text, &QLineEdit::editingFinished, this, &RemuxEntryPathItemDelegate::updateText); - - QToolButton *browseButton = new QToolButton(); - browseButton->setText("..."); - browseButton->setSizePolicy(buttonSizePolicy); - layout->addWidget(browseButton); - - container->connect(browseButton, &QToolButton::clicked, browseCallback); - - // The "clear" button is not shown in output cells - // or the insertion point's input cell. - if (!isOutput && state != RemuxEntryState::Empty) { - QToolButton *clearButton = new QToolButton(); - clearButton->setText("X"); - clearButton->setSizePolicy(buttonSizePolicy); - layout->addWidget(clearButton); - - container->connect(clearButton, &QToolButton::clicked, clearCallback); - } - - container->setLayout(layout); - container->setFocusProxy(text); - return container; - } -} - -void RemuxEntryPathItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const -{ - QLineEdit *text = editor->findChild(); - text->setText(index.data().toString()); - editor->setProperty(PATH_LIST_PROP, QVariant()); -} - -void RemuxEntryPathItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, - const QModelIndex &index) const -{ - // We use the PATH_LIST_PROP property to pass a list of - // path strings from the editor widget into the model's - // NewPathsToProcessRole. This is only used when paths - // are selected through the "browse" or "delete" buttons - // in the editor. If the user enters new text in the - // text box, we simply pass that text on to the model - // as normal text data in the default role. - QVariant pathListProp = editor->property(PATH_LIST_PROP); - if (pathListProp.isValid()) { - QStringList list = editor->property(PATH_LIST_PROP).toStringList(); - if (isOutput) { - if (list.size() > 0) - model->setData(index, list); - } else - model->setData(index, list, RemuxEntryRole::NewPathsToProcessRole); - } else { - QLineEdit *lineEdit = editor->findChild(); - model->setData(index, lineEdit->text()); - } -} - -void RemuxEntryPathItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, - const QModelIndex &index) const -{ - RemuxEntryState state = index.model() - ->index(index.row(), RemuxEntryColumn::State) - .data(RemuxEntryRole::EntryStateRole) - .value(); - - QStyleOptionViewItem localOption = option; - initStyleOption(&localOption, index); - - if (isOutput) { - if (state != Ready) { - QColor background = - localOption.palette.color(QPalette::ColorGroup::Disabled, QPalette::ColorRole::Window); - - localOption.backgroundBrush = QBrush(background); - } - } - - QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &localOption, painter); -} - -void RemuxEntryPathItemDelegate::handleBrowse(QWidget *container) -{ - QString ExtensionPattern = "(*.mp4 *.flv *.mov *.mkv *.ts *.m3u8)"; - - QLineEdit *text = container->findChild(); - - QString currentPath = text->text(); - if (currentPath.isEmpty()) - currentPath = defaultPath; - - bool isSet = false; - if (isOutput) { - QString newPath = SaveFile(container, QTStr("Remux.SelectTarget"), currentPath, ExtensionPattern); - - if (!newPath.isEmpty()) { - container->setProperty(PATH_LIST_PROP, QStringList() << newPath); - isSet = true; - } - } else { - QStringList paths = OpenFiles(container, QTStr("Remux.SelectRecording"), currentPath, - QTStr("Remux.OBSRecording") + QString(" ") + ExtensionPattern); - - if (!paths.empty()) { - container->setProperty(PATH_LIST_PROP, paths); - isSet = true; - } -#ifdef __APPLE__ - // TODO: Revisit when QTBUG-42661 is fixed - container->window()->raise(); -#endif - } - - if (isSet) - emit commitData(container); -} - -void RemuxEntryPathItemDelegate::handleClear(QWidget *container) -{ - // An empty string list will indicate that the entry is being - // blanked and should be deleted. - container->setProperty(PATH_LIST_PROP, QStringList()); - - emit commitData(container); -} - -void RemuxEntryPathItemDelegate::updateText() -{ - QLineEdit *lineEdit = dynamic_cast(sender()); - QWidget *editor = lineEdit->parentWidget(); - emit commitData(editor); -} - -/********************************************************** - Model - Manages the queue's data -**********************************************************/ +#include "moc_RemuxQueueModel.cpp" int RemuxQueueModel::rowCount(const QModelIndex &) const { @@ -594,331 +378,3 @@ void RemuxQueueModel::finishEntry(bool success) } } } - -/********************************************************** - The actual remux window implementation -**********************************************************/ - -OBSRemux::OBSRemux(const char *path, QWidget *parent, bool autoRemux_) - : QDialog(parent), - queueModel(new RemuxQueueModel), - worker(new RemuxWorker()), - ui(new Ui::OBSRemux), - recPath(path), - autoRemux(autoRemux_) -{ - setAcceptDrops(true); - - setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); - - ui->setupUi(this); - - ui->progressBar->setVisible(false); - ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); - ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)->setEnabled(false); - - if (autoRemux) { - resize(280, 40); - ui->tableView->hide(); - ui->buttonBox->hide(); - ui->label->hide(); - } - - ui->progressBar->setMinimum(0); - ui->progressBar->setMaximum(1000); - ui->progressBar->setValue(0); - - ui->tableView->setModel(queueModel); - ui->tableView->setItemDelegateForColumn(RemuxEntryColumn::InputPath, - new RemuxEntryPathItemDelegate(false, recPath)); - ui->tableView->setItemDelegateForColumn(RemuxEntryColumn::OutputPath, - new RemuxEntryPathItemDelegate(true, recPath)); - ui->tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeMode::Stretch); - ui->tableView->horizontalHeader()->setSectionResizeMode(RemuxEntryColumn::State, - QHeaderView::ResizeMode::Fixed); - ui->tableView->setEditTriggers(QAbstractItemView::EditTrigger::CurrentChanged); - ui->tableView->setTextElideMode(Qt::ElideMiddle); - ui->tableView->setWordWrap(false); - - installEventFilter(CreateShortcutFilter()); - - ui->buttonBox->button(QDialogButtonBox::Ok)->setText(QTStr("Remux.Remux")); - ui->buttonBox->button(QDialogButtonBox::Reset)->setText(QTStr("Remux.ClearFinished")); - ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)->setText(QTStr("Remux.ClearAll")); - ui->buttonBox->button(QDialogButtonBox::Reset)->setDisabled(true); - - connect(ui->buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked, this, &OBSRemux::beginRemux); - connect(ui->buttonBox->button(QDialogButtonBox::Reset), &QPushButton::clicked, this, &OBSRemux::clearFinished); - connect(ui->buttonBox->button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, this, - &OBSRemux::clearAll); - connect(ui->buttonBox->button(QDialogButtonBox::Close), &QPushButton::clicked, this, &OBSRemux::close); - - worker->moveToThread(&remuxer); - remuxer.start(); - - connect(worker.data(), &RemuxWorker::updateProgress, this, &OBSRemux::updateProgress); - connect(&remuxer, &QThread::finished, worker.data(), &QObject::deleteLater); - connect(worker.data(), &RemuxWorker::remuxFinished, this, &OBSRemux::remuxFinished); - connect(this, &OBSRemux::remux, worker.data(), &RemuxWorker::remux); - - connect(queueModel.data(), &RemuxQueueModel::rowsInserted, this, &OBSRemux::rowCountChanged); - connect(queueModel.data(), &RemuxQueueModel::rowsRemoved, this, &OBSRemux::rowCountChanged); - - QModelIndex index = queueModel->createIndex(0, 1); - QMetaObject::invokeMethod(ui->tableView, "setCurrentIndex", Qt::QueuedConnection, - Q_ARG(const QModelIndex &, index)); -} - -bool OBSRemux::stopRemux() -{ - if (!worker->isWorking) - return true; - - // By locking the worker thread's mutex, we ensure that its - // update poll will be blocked as long as we're in here with - // the popup open. - QMutexLocker lock(&worker->updateMutex); - - bool exit = false; - - if (QMessageBox::critical(nullptr, QTStr("Remux.ExitUnfinishedTitle"), QTStr("Remux.ExitUnfinished"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes) { - exit = true; - } - - if (exit) { - // Inform the worker it should no longer be - // working. It will interrupt accordingly in - // its next update callback. - worker->isWorking = false; - } - - return exit; -} - -OBSRemux::~OBSRemux() -{ - stopRemux(); - remuxer.quit(); - remuxer.wait(); -} - -void OBSRemux::rowCountChanged(const QModelIndex &, int, int) -{ - // See if there are still any rows ready to remux. Change - // the state of the "go" button accordingly. - // There must be more than one row, since there will always be - // at least one row for the empty insertion point. - if (queueModel->rowCount() > 1) { - ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); - ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)->setEnabled(true); - ui->buttonBox->button(QDialogButtonBox::Reset)->setEnabled(queueModel->canClearFinished()); - } else { - ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); - ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)->setEnabled(false); - ui->buttonBox->button(QDialogButtonBox::Reset)->setEnabled(false); - } -} - -void OBSRemux::dropEvent(QDropEvent *ev) -{ - QStringList urlList; - - for (QUrl url : ev->mimeData()->urls()) { - QFileInfo fileInfo(url.toLocalFile()); - - if (fileInfo.isDir()) { - QStringList directoryFilter; - directoryFilter << "*.flv" - << "*.mp4" - << "*.mov" - << "*.mkv" - << "*.ts" - << "*.m3u8"; - - QDirIterator dirIter(fileInfo.absoluteFilePath(), directoryFilter, QDir::Files, - QDirIterator::Subdirectories); - - while (dirIter.hasNext()) { - urlList.append(dirIter.next()); - } - } else { - urlList.append(fileInfo.canonicalFilePath()); - } - } - - if (urlList.empty()) { - QMessageBox::information(nullptr, QTStr("Remux.NoFilesAddedTitle"), QTStr("Remux.NoFilesAdded"), - QMessageBox::Ok); - } else if (!autoRemux) { - QModelIndex insertIndex = queueModel->index(queueModel->rowCount() - 1, RemuxEntryColumn::InputPath); - queueModel->setData(insertIndex, urlList, RemuxEntryRole::NewPathsToProcessRole); - } -} - -void OBSRemux::dragEnterEvent(QDragEnterEvent *ev) -{ - if (ev->mimeData()->hasUrls() && !worker->isWorking) - ev->accept(); -} - -void OBSRemux::beginRemux() -{ - if (worker->isWorking) { - stopRemux(); - return; - } - - bool proceedWithRemux = true; - QFileInfoList overwriteFiles = queueModel->checkForOverwrites(); - - if (!overwriteFiles.empty()) { - QString message = QTStr("Remux.FileExists"); - message += "\n\n"; - - for (QFileInfo fileInfo : overwriteFiles) - message += fileInfo.canonicalFilePath() + "\n"; - - if (OBSMessageBox::question(this, QTStr("Remux.FileExistsTitle"), message) != QMessageBox::Yes) - proceedWithRemux = false; - } - - if (!proceedWithRemux) - return; - - // Set all jobs to "pending" first. - queueModel->beginProcessing(); - - ui->progressBar->setVisible(true); - ui->buttonBox->button(QDialogButtonBox::Ok)->setText(QTStr("Remux.Stop")); - setAcceptDrops(false); - - remuxNextEntry(); -} - -void OBSRemux::AutoRemux(QString inFile, QString outFile) -{ - if (inFile != "" && outFile != "" && autoRemux) { - ui->progressBar->setVisible(true); - emit remux(inFile, outFile); - autoRemuxFile = outFile; - } -} - -void OBSRemux::remuxNextEntry() -{ - worker->lastProgress = 0.f; - - QString inputPath, outputPath; - if (queueModel->beginNextEntry(inputPath, outputPath)) { - emit remux(inputPath, outputPath); - } else { - queueModel->autoRemux = autoRemux; - queueModel->endProcessing(); - - if (!autoRemux) { - OBSMessageBox::information(this, QTStr("Remux.FinishedTitle"), - queueModel->checkForErrors() ? QTStr("Remux.FinishedError") - : QTStr("Remux.Finished")); - } - - ui->progressBar->setVisible(autoRemux); - ui->buttonBox->button(QDialogButtonBox::Ok)->setText(QTStr("Remux.Remux")); - ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)->setEnabled(true); - ui->buttonBox->button(QDialogButtonBox::Reset)->setEnabled(queueModel->canClearFinished()); - setAcceptDrops(true); - } -} - -void OBSRemux::closeEvent(QCloseEvent *event) -{ - if (!stopRemux()) - event->ignore(); - else - QDialog::closeEvent(event); -} - -void OBSRemux::reject() -{ - if (!stopRemux()) - return; - - QDialog::reject(); -} - -void OBSRemux::updateProgress(float percent) -{ - ui->progressBar->setValue(percent * 10); -} - -void OBSRemux::remuxFinished(bool success) -{ - ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); - - queueModel->finishEntry(success); - - if (autoRemux && autoRemuxFile != "") { - QTimer::singleShot(3000, this, &OBSRemux::close); - - OBSBasic *main = OBSBasic::Get(); - main->ShowStatusBarMessage(QTStr("Basic.StatusBar.AutoRemuxedTo").arg(autoRemuxFile)); - } - - remuxNextEntry(); -} - -void OBSRemux::clearFinished() -{ - queueModel->clearFinished(); -} - -void OBSRemux::clearAll() -{ - queueModel->clearAll(); -} - -/********************************************************** - Worker thread - Executes the libobs remux operation as a - background process. -**********************************************************/ - -void RemuxWorker::UpdateProgress(float percent) -{ - if (abs(lastProgress - percent) < 0.1f) - return; - - emit updateProgress(percent); - lastProgress = percent; -} - -void RemuxWorker::remux(const QString &source, const QString &target) -{ - isWorking = true; - - auto callback = [](void *data, float percent) { - RemuxWorker *rw = static_cast(data); - - QMutexLocker lock(&rw->updateMutex); - - rw->UpdateProgress(percent); - - return rw->isWorking; - }; - - bool stopped = false; - bool success = false; - - media_remux_job_t mr_job = nullptr; - if (media_remux_job_create(&mr_job, QT_TO_UTF8(source), QT_TO_UTF8(target))) { - - success = media_remux_job_process(mr_job, callback, this); - - media_remux_job_destroy(mr_job); - - stopped = !isWorking; - } - - isWorking = false; - - emit remuxFinished(!stopped && success); -} diff --git a/frontend/utility/RemuxQueueModel.hpp b/frontend/utility/RemuxQueueModel.hpp index 20a13a780..954b5fd5e 100644 --- a/frontend/utility/RemuxQueueModel.hpp +++ b/frontend/utility/RemuxQueueModel.hpp @@ -17,69 +17,23 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include "ui_OBSRemux.h" - -#include -#include - -class RemuxQueueModel; -class RemuxWorker; +#include +#include enum RemuxEntryState { Empty, Ready, Pending, InProgress, Complete, InvalidPath, Error }; + Q_DECLARE_METATYPE(RemuxEntryState); -class OBSRemux : public QDialog { - Q_OBJECT +enum RemuxEntryColumn { + State, + InputPath, + OutputPath, - QPointer queueModel; - QThread remuxer; - QPointer worker; - - std::unique_ptr ui; - - const char *recPath; - - virtual void closeEvent(QCloseEvent *event) override; - virtual void reject() override; - - bool autoRemux; - QString autoRemuxFile; - -public: - explicit OBSRemux(const char *recPath, QWidget *parent = nullptr, bool autoRemux = false); - virtual ~OBSRemux() override; - - using job_t = std::shared_ptr; - - void AutoRemux(QString inFile, QString outFile); - -protected: - virtual void dropEvent(QDropEvent *ev) override; - virtual void dragEnterEvent(QDragEnterEvent *ev) override; - - void remuxNextEntry(); - -private slots: - void rowCountChanged(const QModelIndex &parent, int first, int last); - -public slots: - void updateProgress(float percent); - void remuxFinished(bool success); - void beginRemux(); - bool stopRemux(); - void clearFinished(); - void clearAll(); - -signals: - void remux(const QString &source, const QString &target); + Count }; +enum RemuxEntryRole { EntryStateRole = Qt::UserRole, NewPathsToProcessRole }; + class RemuxQueueModel : public QAbstractTableModel { Q_OBJECT @@ -122,52 +76,3 @@ private: void checkInputPath(int row); }; - -class RemuxWorker : public QObject { - Q_OBJECT - - QMutex updateMutex; - - bool isWorking; - - float lastProgress; - void UpdateProgress(float percent); - - explicit RemuxWorker() : isWorking(false) {} - virtual ~RemuxWorker(){}; - -private slots: - void remux(const QString &source, const QString &target); - -signals: - void updateProgress(float percent); - void remuxFinished(bool success); - - friend class OBSRemux; -}; - -class RemuxEntryPathItemDelegate : public QStyledItemDelegate { - Q_OBJECT - -public: - RemuxEntryPathItemDelegate(bool isOutput, const QString &defaultPath); - - virtual QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem & /* option */, - const QModelIndex &index) const override; - - virtual void setEditorData(QWidget *editor, const QModelIndex &index) const override; - virtual void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override; - virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, - const QModelIndex &index) const override; - -private: - bool isOutput; - QString defaultPath; - const char *PATH_LIST_PROP = "pathList"; - - void handleBrowse(QWidget *container); - void handleClear(QWidget *container); - -private slots: - void updateText(); -}; diff --git a/frontend/utility/RemuxWorker.cpp b/frontend/utility/RemuxWorker.cpp index d72ebbd9c..e9d54093b 100644 --- a/frontend/utility/RemuxWorker.cpp +++ b/frontend/utility/RemuxWorker.cpp @@ -15,873 +15,11 @@ along with this program. If not, see . ******************************************************************************/ -#include "moc_window-remux.cpp" +#include "RemuxWorker.hpp" -#include "obs-app.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include #include -#include "window-basic-main.hpp" - -#include -#include - -using namespace std; - -enum RemuxEntryColumn { - State, - InputPath, - OutputPath, - - Count -}; - -enum RemuxEntryRole { EntryStateRole = Qt::UserRole, NewPathsToProcessRole }; - -/********************************************************** - Delegate - Presents cells in the grid. -**********************************************************/ - -RemuxEntryPathItemDelegate::RemuxEntryPathItemDelegate(bool isOutput, const QString &defaultPath) - : QStyledItemDelegate(), - isOutput(isOutput), - defaultPath(defaultPath) -{ -} - -QWidget *RemuxEntryPathItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem & /* option */, - const QModelIndex &index) const -{ - RemuxEntryState state = index.model() - ->index(index.row(), RemuxEntryColumn::State) - .data(RemuxEntryRole::EntryStateRole) - .value(); - if (state == RemuxEntryState::Pending || state == RemuxEntryState::InProgress) { - // Never allow modification of rows that are - // in progress. - return Q_NULLPTR; - } else if (isOutput && state != RemuxEntryState::Ready) { - // Do not allow modification of output rows - // that aren't associated with a valid input. - return Q_NULLPTR; - } else if (!isOutput && state == RemuxEntryState::Complete) { - // Don't allow modification of rows that are - // already complete. - return Q_NULLPTR; - } else { - QSizePolicy buttonSizePolicy(QSizePolicy::Policy::Minimum, QSizePolicy::Policy::Expanding, - QSizePolicy::ControlType::PushButton); - - QWidget *container = new QWidget(parent); - - auto browseCallback = [this, container]() { - const_cast(this)->handleBrowse(container); - }; - - auto clearCallback = [this, container]() { - const_cast(this)->handleClear(container); - }; - - QHBoxLayout *layout = new QHBoxLayout(); - layout->setContentsMargins(0, 0, 0, 0); - layout->setSpacing(0); - - QLineEdit *text = new QLineEdit(); - text->setObjectName(QStringLiteral("text")); - text->setSizePolicy(QSizePolicy(QSizePolicy::Policy::Expanding, QSizePolicy::Policy::Expanding, - QSizePolicy::ControlType::LineEdit)); - layout->addWidget(text); - - QObject::connect(text, &QLineEdit::editingFinished, this, &RemuxEntryPathItemDelegate::updateText); - - QToolButton *browseButton = new QToolButton(); - browseButton->setText("..."); - browseButton->setSizePolicy(buttonSizePolicy); - layout->addWidget(browseButton); - - container->connect(browseButton, &QToolButton::clicked, browseCallback); - - // The "clear" button is not shown in output cells - // or the insertion point's input cell. - if (!isOutput && state != RemuxEntryState::Empty) { - QToolButton *clearButton = new QToolButton(); - clearButton->setText("X"); - clearButton->setSizePolicy(buttonSizePolicy); - layout->addWidget(clearButton); - - container->connect(clearButton, &QToolButton::clicked, clearCallback); - } - - container->setLayout(layout); - container->setFocusProxy(text); - return container; - } -} - -void RemuxEntryPathItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const -{ - QLineEdit *text = editor->findChild(); - text->setText(index.data().toString()); - editor->setProperty(PATH_LIST_PROP, QVariant()); -} - -void RemuxEntryPathItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, - const QModelIndex &index) const -{ - // We use the PATH_LIST_PROP property to pass a list of - // path strings from the editor widget into the model's - // NewPathsToProcessRole. This is only used when paths - // are selected through the "browse" or "delete" buttons - // in the editor. If the user enters new text in the - // text box, we simply pass that text on to the model - // as normal text data in the default role. - QVariant pathListProp = editor->property(PATH_LIST_PROP); - if (pathListProp.isValid()) { - QStringList list = editor->property(PATH_LIST_PROP).toStringList(); - if (isOutput) { - if (list.size() > 0) - model->setData(index, list); - } else - model->setData(index, list, RemuxEntryRole::NewPathsToProcessRole); - } else { - QLineEdit *lineEdit = editor->findChild(); - model->setData(index, lineEdit->text()); - } -} - -void RemuxEntryPathItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, - const QModelIndex &index) const -{ - RemuxEntryState state = index.model() - ->index(index.row(), RemuxEntryColumn::State) - .data(RemuxEntryRole::EntryStateRole) - .value(); - - QStyleOptionViewItem localOption = option; - initStyleOption(&localOption, index); - - if (isOutput) { - if (state != Ready) { - QColor background = - localOption.palette.color(QPalette::ColorGroup::Disabled, QPalette::ColorRole::Window); - - localOption.backgroundBrush = QBrush(background); - } - } - - QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &localOption, painter); -} - -void RemuxEntryPathItemDelegate::handleBrowse(QWidget *container) -{ - QString ExtensionPattern = "(*.mp4 *.flv *.mov *.mkv *.ts *.m3u8)"; - - QLineEdit *text = container->findChild(); - - QString currentPath = text->text(); - if (currentPath.isEmpty()) - currentPath = defaultPath; - - bool isSet = false; - if (isOutput) { - QString newPath = SaveFile(container, QTStr("Remux.SelectTarget"), currentPath, ExtensionPattern); - - if (!newPath.isEmpty()) { - container->setProperty(PATH_LIST_PROP, QStringList() << newPath); - isSet = true; - } - } else { - QStringList paths = OpenFiles(container, QTStr("Remux.SelectRecording"), currentPath, - QTStr("Remux.OBSRecording") + QString(" ") + ExtensionPattern); - - if (!paths.empty()) { - container->setProperty(PATH_LIST_PROP, paths); - isSet = true; - } -#ifdef __APPLE__ - // TODO: Revisit when QTBUG-42661 is fixed - container->window()->raise(); -#endif - } - - if (isSet) - emit commitData(container); -} - -void RemuxEntryPathItemDelegate::handleClear(QWidget *container) -{ - // An empty string list will indicate that the entry is being - // blanked and should be deleted. - container->setProperty(PATH_LIST_PROP, QStringList()); - - emit commitData(container); -} - -void RemuxEntryPathItemDelegate::updateText() -{ - QLineEdit *lineEdit = dynamic_cast(sender()); - QWidget *editor = lineEdit->parentWidget(); - emit commitData(editor); -} - -/********************************************************** - Model - Manages the queue's data -**********************************************************/ - -int RemuxQueueModel::rowCount(const QModelIndex &) const -{ - return queue.length() + (isProcessing ? 0 : 1); -} - -int RemuxQueueModel::columnCount(const QModelIndex &) const -{ - return RemuxEntryColumn::Count; -} - -QVariant RemuxQueueModel::data(const QModelIndex &index, int role) const -{ - QVariant result = QVariant(); - - if (index.row() >= queue.length()) { - return QVariant(); - } else if (role == Qt::DisplayRole) { - switch (index.column()) { - case RemuxEntryColumn::InputPath: - result = queue[index.row()].sourcePath; - break; - case RemuxEntryColumn::OutputPath: - result = queue[index.row()].targetPath; - break; - } - } else if (role == Qt::DecorationRole && index.column() == RemuxEntryColumn::State) { - result = getIcon(queue[index.row()].state); - } else if (role == RemuxEntryRole::EntryStateRole) { - result = queue[index.row()].state; - } - - return result; -} - -QVariant RemuxQueueModel::headerData(int section, Qt::Orientation orientation, int role) const -{ - QVariant result = QVariant(); - - if (role == Qt::DisplayRole && orientation == Qt::Orientation::Horizontal) { - switch (section) { - case RemuxEntryColumn::State: - result = QString(); - break; - case RemuxEntryColumn::InputPath: - result = QTStr("Remux.SourceFile"); - break; - case RemuxEntryColumn::OutputPath: - result = QTStr("Remux.TargetFile"); - break; - } - } - - return result; -} - -Qt::ItemFlags RemuxQueueModel::flags(const QModelIndex &index) const -{ - Qt::ItemFlags flags = QAbstractTableModel::flags(index); - - if (index.column() == RemuxEntryColumn::InputPath) { - flags |= Qt::ItemIsEditable; - } else if (index.column() == RemuxEntryColumn::OutputPath && index.row() != queue.length()) { - flags |= Qt::ItemIsEditable; - } - - return flags; -} - -bool RemuxQueueModel::setData(const QModelIndex &index, const QVariant &value, int role) -{ - bool success = false; - - if (role == RemuxEntryRole::NewPathsToProcessRole) { - QStringList pathList = value.toStringList(); - - if (pathList.size() == 0) { - if (index.row() < queue.size()) { - beginRemoveRows(QModelIndex(), index.row(), index.row()); - queue.removeAt(index.row()); - endRemoveRows(); - } - } else { - if (pathList.size() >= 1 && index.row() < queue.length()) { - queue[index.row()].sourcePath = pathList[0]; - checkInputPath(index.row()); - - pathList.removeAt(0); - - success = true; - } - - if (pathList.size() > 0) { - int row = index.row(); - int lastRow = row + pathList.size() - 1; - beginInsertRows(QModelIndex(), row, lastRow); - - for (QString path : pathList) { - RemuxQueueEntry entry; - entry.sourcePath = path; - entry.state = RemuxEntryState::Empty; - - queue.insert(row, entry); - row++; - } - endInsertRows(); - - for (row = index.row(); row <= lastRow; row++) { - checkInputPath(row); - } - - success = true; - } - } - } else if (index.row() == queue.length()) { - QString path = value.toString(); - - if (!path.isEmpty()) { - RemuxQueueEntry entry; - entry.sourcePath = path; - entry.state = RemuxEntryState::Empty; - - beginInsertRows(QModelIndex(), queue.length() + 1, queue.length() + 1); - queue.append(entry); - endInsertRows(); - - checkInputPath(index.row()); - success = true; - } - } else { - QString path = value.toString(); - - if (path.isEmpty()) { - if (index.column() == RemuxEntryColumn::InputPath) { - beginRemoveRows(QModelIndex(), index.row(), index.row()); - queue.removeAt(index.row()); - endRemoveRows(); - } - } else { - switch (index.column()) { - case RemuxEntryColumn::InputPath: - queue[index.row()].sourcePath = value.toString(); - checkInputPath(index.row()); - success = true; - break; - case RemuxEntryColumn::OutputPath: - queue[index.row()].targetPath = value.toString(); - emit dataChanged(index, index); - success = true; - break; - } - } - } - - return success; -} - -QVariant RemuxQueueModel::getIcon(RemuxEntryState state) -{ - QVariant icon; - QStyle *style = QApplication::style(); - - switch (state) { - case RemuxEntryState::Complete: - icon = style->standardIcon(QStyle::SP_DialogApplyButton); - break; - - case RemuxEntryState::InProgress: - icon = style->standardIcon(QStyle::SP_ArrowRight); - break; - - case RemuxEntryState::Error: - icon = style->standardIcon(QStyle::SP_DialogCancelButton); - break; - - case RemuxEntryState::InvalidPath: - icon = style->standardIcon(QStyle::SP_MessageBoxWarning); - break; - - default: - break; - } - - return icon; -} - -void RemuxQueueModel::checkInputPath(int row) -{ - RemuxQueueEntry &entry = queue[row]; - - if (entry.sourcePath.isEmpty()) { - entry.state = RemuxEntryState::Empty; - } else { - entry.sourcePath = QDir::toNativeSeparators(entry.sourcePath); - QFileInfo fileInfo(entry.sourcePath); - if (fileInfo.exists()) - entry.state = RemuxEntryState::Ready; - else - entry.state = RemuxEntryState::InvalidPath; - - QString newExt = ".mp4"; - QString suffix = fileInfo.suffix(); - - if (suffix.contains("mov", Qt::CaseInsensitive) || suffix.contains("mp4", Qt::CaseInsensitive)) { - newExt = ".remuxed." + suffix; - } - - if (entry.state == RemuxEntryState::Ready) - entry.targetPath = QDir::toNativeSeparators(fileInfo.path() + QDir::separator() + - fileInfo.completeBaseName() + newExt); - } - - if (entry.state == RemuxEntryState::Ready && isProcessing) - entry.state = RemuxEntryState::Pending; - - emit dataChanged(index(row, 0), index(row, RemuxEntryColumn::Count)); -} - -QFileInfoList RemuxQueueModel::checkForOverwrites() const -{ - QFileInfoList list; - - for (const RemuxQueueEntry &entry : queue) { - if (entry.state == RemuxEntryState::Ready) { - QFileInfo fileInfo(entry.targetPath); - if (fileInfo.exists()) { - list.append(fileInfo); - } - } - } - - return list; -} - -bool RemuxQueueModel::checkForErrors() const -{ - bool hasErrors = false; - - for (const RemuxQueueEntry &entry : queue) { - if (entry.state == RemuxEntryState::Error) { - hasErrors = true; - break; - } - } - - return hasErrors; -} - -void RemuxQueueModel::clearAll() -{ - beginRemoveRows(QModelIndex(), 0, queue.size() - 1); - queue.clear(); - endRemoveRows(); -} - -void RemuxQueueModel::clearFinished() -{ - int index = 0; - - for (index = 0; index < queue.size(); index++) { - const RemuxQueueEntry &entry = queue[index]; - if (entry.state == RemuxEntryState::Complete) { - beginRemoveRows(QModelIndex(), index, index); - queue.removeAt(index); - endRemoveRows(); - index--; - } - } -} - -bool RemuxQueueModel::canClearFinished() const -{ - bool canClearFinished = false; - for (const RemuxQueueEntry &entry : queue) - if (entry.state == RemuxEntryState::Complete) { - canClearFinished = true; - break; - } - - return canClearFinished; -} - -void RemuxQueueModel::beginProcessing() -{ - for (RemuxQueueEntry &entry : queue) - if (entry.state == RemuxEntryState::Ready) - entry.state = RemuxEntryState::Pending; - - // Signal that the insertion point no longer exists. - beginRemoveRows(QModelIndex(), queue.length(), queue.length()); - endRemoveRows(); - - isProcessing = true; - - emit dataChanged(index(0, RemuxEntryColumn::State), index(queue.length(), RemuxEntryColumn::State)); -} - -void RemuxQueueModel::endProcessing() -{ - for (RemuxQueueEntry &entry : queue) { - if (entry.state == RemuxEntryState::Pending) { - entry.state = RemuxEntryState::Ready; - } - } - - // Signal that the insertion point exists again. - isProcessing = false; - if (!autoRemux) { - beginInsertRows(QModelIndex(), queue.length(), queue.length()); - endInsertRows(); - } - - emit dataChanged(index(0, RemuxEntryColumn::State), index(queue.length(), RemuxEntryColumn::State)); -} - -bool RemuxQueueModel::beginNextEntry(QString &inputPath, QString &outputPath) -{ - bool anyStarted = false; - - for (int row = 0; row < queue.length(); row++) { - RemuxQueueEntry &entry = queue[row]; - if (entry.state == RemuxEntryState::Pending) { - entry.state = RemuxEntryState::InProgress; - - inputPath = entry.sourcePath; - outputPath = entry.targetPath; - - QModelIndex index = this->index(row, RemuxEntryColumn::State); - emit dataChanged(index, index); - - anyStarted = true; - break; - } - } - - return anyStarted; -} - -void RemuxQueueModel::finishEntry(bool success) -{ - for (int row = 0; row < queue.length(); row++) { - RemuxQueueEntry &entry = queue[row]; - if (entry.state == RemuxEntryState::InProgress) { - if (success) - entry.state = RemuxEntryState::Complete; - else - entry.state = RemuxEntryState::Error; - - QModelIndex index = this->index(row, RemuxEntryColumn::State); - emit dataChanged(index, index); - - break; - } - } -} - -/********************************************************** - The actual remux window implementation -**********************************************************/ - -OBSRemux::OBSRemux(const char *path, QWidget *parent, bool autoRemux_) - : QDialog(parent), - queueModel(new RemuxQueueModel), - worker(new RemuxWorker()), - ui(new Ui::OBSRemux), - recPath(path), - autoRemux(autoRemux_) -{ - setAcceptDrops(true); - - setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); - - ui->setupUi(this); - - ui->progressBar->setVisible(false); - ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); - ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)->setEnabled(false); - - if (autoRemux) { - resize(280, 40); - ui->tableView->hide(); - ui->buttonBox->hide(); - ui->label->hide(); - } - - ui->progressBar->setMinimum(0); - ui->progressBar->setMaximum(1000); - ui->progressBar->setValue(0); - - ui->tableView->setModel(queueModel); - ui->tableView->setItemDelegateForColumn(RemuxEntryColumn::InputPath, - new RemuxEntryPathItemDelegate(false, recPath)); - ui->tableView->setItemDelegateForColumn(RemuxEntryColumn::OutputPath, - new RemuxEntryPathItemDelegate(true, recPath)); - ui->tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeMode::Stretch); - ui->tableView->horizontalHeader()->setSectionResizeMode(RemuxEntryColumn::State, - QHeaderView::ResizeMode::Fixed); - ui->tableView->setEditTriggers(QAbstractItemView::EditTrigger::CurrentChanged); - ui->tableView->setTextElideMode(Qt::ElideMiddle); - ui->tableView->setWordWrap(false); - - installEventFilter(CreateShortcutFilter()); - - ui->buttonBox->button(QDialogButtonBox::Ok)->setText(QTStr("Remux.Remux")); - ui->buttonBox->button(QDialogButtonBox::Reset)->setText(QTStr("Remux.ClearFinished")); - ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)->setText(QTStr("Remux.ClearAll")); - ui->buttonBox->button(QDialogButtonBox::Reset)->setDisabled(true); - - connect(ui->buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked, this, &OBSRemux::beginRemux); - connect(ui->buttonBox->button(QDialogButtonBox::Reset), &QPushButton::clicked, this, &OBSRemux::clearFinished); - connect(ui->buttonBox->button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, this, - &OBSRemux::clearAll); - connect(ui->buttonBox->button(QDialogButtonBox::Close), &QPushButton::clicked, this, &OBSRemux::close); - - worker->moveToThread(&remuxer); - remuxer.start(); - - connect(worker.data(), &RemuxWorker::updateProgress, this, &OBSRemux::updateProgress); - connect(&remuxer, &QThread::finished, worker.data(), &QObject::deleteLater); - connect(worker.data(), &RemuxWorker::remuxFinished, this, &OBSRemux::remuxFinished); - connect(this, &OBSRemux::remux, worker.data(), &RemuxWorker::remux); - - connect(queueModel.data(), &RemuxQueueModel::rowsInserted, this, &OBSRemux::rowCountChanged); - connect(queueModel.data(), &RemuxQueueModel::rowsRemoved, this, &OBSRemux::rowCountChanged); - - QModelIndex index = queueModel->createIndex(0, 1); - QMetaObject::invokeMethod(ui->tableView, "setCurrentIndex", Qt::QueuedConnection, - Q_ARG(const QModelIndex &, index)); -} - -bool OBSRemux::stopRemux() -{ - if (!worker->isWorking) - return true; - - // By locking the worker thread's mutex, we ensure that its - // update poll will be blocked as long as we're in here with - // the popup open. - QMutexLocker lock(&worker->updateMutex); - - bool exit = false; - - if (QMessageBox::critical(nullptr, QTStr("Remux.ExitUnfinishedTitle"), QTStr("Remux.ExitUnfinished"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes) { - exit = true; - } - - if (exit) { - // Inform the worker it should no longer be - // working. It will interrupt accordingly in - // its next update callback. - worker->isWorking = false; - } - - return exit; -} - -OBSRemux::~OBSRemux() -{ - stopRemux(); - remuxer.quit(); - remuxer.wait(); -} - -void OBSRemux::rowCountChanged(const QModelIndex &, int, int) -{ - // See if there are still any rows ready to remux. Change - // the state of the "go" button accordingly. - // There must be more than one row, since there will always be - // at least one row for the empty insertion point. - if (queueModel->rowCount() > 1) { - ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); - ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)->setEnabled(true); - ui->buttonBox->button(QDialogButtonBox::Reset)->setEnabled(queueModel->canClearFinished()); - } else { - ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); - ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)->setEnabled(false); - ui->buttonBox->button(QDialogButtonBox::Reset)->setEnabled(false); - } -} - -void OBSRemux::dropEvent(QDropEvent *ev) -{ - QStringList urlList; - - for (QUrl url : ev->mimeData()->urls()) { - QFileInfo fileInfo(url.toLocalFile()); - - if (fileInfo.isDir()) { - QStringList directoryFilter; - directoryFilter << "*.flv" - << "*.mp4" - << "*.mov" - << "*.mkv" - << "*.ts" - << "*.m3u8"; - - QDirIterator dirIter(fileInfo.absoluteFilePath(), directoryFilter, QDir::Files, - QDirIterator::Subdirectories); - - while (dirIter.hasNext()) { - urlList.append(dirIter.next()); - } - } else { - urlList.append(fileInfo.canonicalFilePath()); - } - } - - if (urlList.empty()) { - QMessageBox::information(nullptr, QTStr("Remux.NoFilesAddedTitle"), QTStr("Remux.NoFilesAdded"), - QMessageBox::Ok); - } else if (!autoRemux) { - QModelIndex insertIndex = queueModel->index(queueModel->rowCount() - 1, RemuxEntryColumn::InputPath); - queueModel->setData(insertIndex, urlList, RemuxEntryRole::NewPathsToProcessRole); - } -} - -void OBSRemux::dragEnterEvent(QDragEnterEvent *ev) -{ - if (ev->mimeData()->hasUrls() && !worker->isWorking) - ev->accept(); -} - -void OBSRemux::beginRemux() -{ - if (worker->isWorking) { - stopRemux(); - return; - } - - bool proceedWithRemux = true; - QFileInfoList overwriteFiles = queueModel->checkForOverwrites(); - - if (!overwriteFiles.empty()) { - QString message = QTStr("Remux.FileExists"); - message += "\n\n"; - - for (QFileInfo fileInfo : overwriteFiles) - message += fileInfo.canonicalFilePath() + "\n"; - - if (OBSMessageBox::question(this, QTStr("Remux.FileExistsTitle"), message) != QMessageBox::Yes) - proceedWithRemux = false; - } - - if (!proceedWithRemux) - return; - - // Set all jobs to "pending" first. - queueModel->beginProcessing(); - - ui->progressBar->setVisible(true); - ui->buttonBox->button(QDialogButtonBox::Ok)->setText(QTStr("Remux.Stop")); - setAcceptDrops(false); - - remuxNextEntry(); -} - -void OBSRemux::AutoRemux(QString inFile, QString outFile) -{ - if (inFile != "" && outFile != "" && autoRemux) { - ui->progressBar->setVisible(true); - emit remux(inFile, outFile); - autoRemuxFile = outFile; - } -} - -void OBSRemux::remuxNextEntry() -{ - worker->lastProgress = 0.f; - - QString inputPath, outputPath; - if (queueModel->beginNextEntry(inputPath, outputPath)) { - emit remux(inputPath, outputPath); - } else { - queueModel->autoRemux = autoRemux; - queueModel->endProcessing(); - - if (!autoRemux) { - OBSMessageBox::information(this, QTStr("Remux.FinishedTitle"), - queueModel->checkForErrors() ? QTStr("Remux.FinishedError") - : QTStr("Remux.Finished")); - } - - ui->progressBar->setVisible(autoRemux); - ui->buttonBox->button(QDialogButtonBox::Ok)->setText(QTStr("Remux.Remux")); - ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)->setEnabled(true); - ui->buttonBox->button(QDialogButtonBox::Reset)->setEnabled(queueModel->canClearFinished()); - setAcceptDrops(true); - } -} - -void OBSRemux::closeEvent(QCloseEvent *event) -{ - if (!stopRemux()) - event->ignore(); - else - QDialog::closeEvent(event); -} - -void OBSRemux::reject() -{ - if (!stopRemux()) - return; - - QDialog::reject(); -} - -void OBSRemux::updateProgress(float percent) -{ - ui->progressBar->setValue(percent * 10); -} - -void OBSRemux::remuxFinished(bool success) -{ - ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); - - queueModel->finishEntry(success); - - if (autoRemux && autoRemuxFile != "") { - QTimer::singleShot(3000, this, &OBSRemux::close); - - OBSBasic *main = OBSBasic::Get(); - main->ShowStatusBarMessage(QTStr("Basic.StatusBar.AutoRemuxedTo").arg(autoRemuxFile)); - } - - remuxNextEntry(); -} - -void OBSRemux::clearFinished() -{ - queueModel->clearFinished(); -} - -void OBSRemux::clearAll() -{ - queueModel->clearAll(); -} - -/********************************************************** - Worker thread - Executes the libobs remux operation as a - background process. -**********************************************************/ - void RemuxWorker::UpdateProgress(float percent) { if (abs(lastProgress - percent) < 0.1f) diff --git a/frontend/utility/RemuxWorker.hpp b/frontend/utility/RemuxWorker.hpp index 20a13a780..9816e3501 100644 --- a/frontend/utility/RemuxWorker.hpp +++ b/frontend/utility/RemuxWorker.hpp @@ -17,111 +17,8 @@ #pragma once -#include #include -#include -#include -#include -#include -#include "ui_OBSRemux.h" - -#include -#include - -class RemuxQueueModel; -class RemuxWorker; - -enum RemuxEntryState { Empty, Ready, Pending, InProgress, Complete, InvalidPath, Error }; -Q_DECLARE_METATYPE(RemuxEntryState); - -class OBSRemux : public QDialog { - Q_OBJECT - - QPointer queueModel; - QThread remuxer; - QPointer worker; - - std::unique_ptr ui; - - const char *recPath; - - virtual void closeEvent(QCloseEvent *event) override; - virtual void reject() override; - - bool autoRemux; - QString autoRemuxFile; - -public: - explicit OBSRemux(const char *recPath, QWidget *parent = nullptr, bool autoRemux = false); - virtual ~OBSRemux() override; - - using job_t = std::shared_ptr; - - void AutoRemux(QString inFile, QString outFile); - -protected: - virtual void dropEvent(QDropEvent *ev) override; - virtual void dragEnterEvent(QDragEnterEvent *ev) override; - - void remuxNextEntry(); - -private slots: - void rowCountChanged(const QModelIndex &parent, int first, int last); - -public slots: - void updateProgress(float percent); - void remuxFinished(bool success); - void beginRemux(); - bool stopRemux(); - void clearFinished(); - void clearAll(); - -signals: - void remux(const QString &source, const QString &target); -}; - -class RemuxQueueModel : public QAbstractTableModel { - Q_OBJECT - - friend class OBSRemux; - -public: - RemuxQueueModel(QObject *parent = 0) : QAbstractTableModel(parent), isProcessing(false) {} - - int rowCount(const QModelIndex &parent = QModelIndex()) const; - int columnCount(const QModelIndex &parent = QModelIndex()) const; - QVariant data(const QModelIndex &index, int role) const; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; - Qt::ItemFlags flags(const QModelIndex &index) const; - bool setData(const QModelIndex &index, const QVariant &value, int role); - - QFileInfoList checkForOverwrites() const; - bool checkForErrors() const; - void beginProcessing(); - void endProcessing(); - bool beginNextEntry(QString &inputPath, QString &outputPath); - void finishEntry(bool success); - bool canClearFinished() const; - void clearFinished(); - void clearAll(); - - bool autoRemux = false; - -private: - struct RemuxQueueEntry { - RemuxEntryState state; - - QString sourcePath; - QString targetPath; - }; - - QList queue; - bool isProcessing; - - static QVariant getIcon(RemuxEntryState state); - - void checkInputPath(int row); -}; +#include class RemuxWorker : public QObject { Q_OBJECT @@ -145,29 +42,3 @@ signals: friend class OBSRemux; }; - -class RemuxEntryPathItemDelegate : public QStyledItemDelegate { - Q_OBJECT - -public: - RemuxEntryPathItemDelegate(bool isOutput, const QString &defaultPath); - - virtual QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem & /* option */, - const QModelIndex &index) const override; - - virtual void setEditorData(QWidget *editor, const QModelIndex &index) const override; - virtual void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override; - virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, - const QModelIndex &index) const override; - -private: - bool isOutput; - QString defaultPath; - const char *PATH_LIST_PROP = "pathList"; - - void handleBrowse(QWidget *container); - void handleClear(QWidget *container); - -private slots: - void updateText(); -};