瀏覽代碼

frontend: Split Qt UI dialogs into single files per C++ class

PatTheMav 10 月之前
父節點
當前提交
085c6245b0

+ 2 - 554
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 <qt-wrappers.hpp>
-#include <QLineEdit>
-#include <QHBoxLayout>
-#include <QUuid>
-
-#include <json11.hpp>
-
-#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 <QPushButton>
 
 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<QTableView *>(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<BrowserDock *>(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<QTableView *>(parent());
-
-	DelButton *del = reinterpret_cast<DelButton *>(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<QListView *>(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<ExtraBrowsersDelegate *>(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<QLineEdit *>(editor);
-	text->blockSignals(true);
-	text->setText(index.data().toString());
-	text->blockSignals(false);
-}
-
-bool ExtraBrowsersDelegate::eventFilter(QObject *object, QEvent *event)
-{
-	QLineEdit *edit = qobject_cast<QLineEdit *>(object);
-	if (!edit)
-		return false;
-
-	if (LineEditCanceled(event)) {
-		RevertText(edit);
-	}
-	if (LineEditChanged(event)) {
-		UpdateText(edit);
-
-		if (event->type() == QEvent::KeyPress) {
-			QKeyEvent *keyEvent = static_cast<QKeyEvent *>(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<EditWidget *>(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<EditWidget *>(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<QDockWidget>(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);
-	}
-}

+ 3 - 552
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 <qt-wrappers.hpp>
 #include <QLineEdit>
-#include <QHBoxLayout>
-#include <QUuid>
+#include <QModelIndex>
 
-#include <json11.hpp>
-
-#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<QTableView *>(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<BrowserDock *>(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<QTableView *>(parent());
-
-	DelButton *del = reinterpret_cast<DelButton *>(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<QListView *>(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<ExtraBrowsersDelegate *>(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<QLineEdit *>(editor);
-	text->blockSignals(true);
-	text->setText(index.data().toString());
-	text->blockSignals(false);
-}
-
-bool ExtraBrowsersDelegate::eventFilter(QObject *object, QEvent *event)
-{
-	QLineEdit *edit = qobject_cast<QLineEdit *>(object);
-	if (!edit)
-		return false;
-
-	if (LineEditCanceled(event)) {
-		RevertText(edit);
-	}
-	if (LineEditChanged(event)) {
-		UpdateText(edit);
-
-		if (event->type() == QEvent::KeyPress) {
-			QKeyEvent *keyEvent = static_cast<QKeyEvent *>(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<EditWidget *>(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<EditWidget *>(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<QDockWidget>(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);
-	}
-}

+ 10 - 8
frontend/dialogs/OBSBasicInteraction.cpp

@@ -15,22 +15,24 @@
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
 
-#include "obs-app.hpp"
-#include "moc_window-basic-interaction.cpp"
-#include "window-basic-main.hpp"
-#include "display-helpers.hpp"
+#include "OBSBasicInteraction.hpp"
+
+#include <dialogs/OBSBasicProperties.hpp>
+#include <utility/OBSEventFilter.hpp>
+#include <utility/display-helpers.hpp>
+#include <widgets/OBSBasic.hpp>
 
 #include <qt-wrappers.hpp>
-#include <QKeyEvent>
-#include <QCloseEvent>
-#include <QScreen>
-#include <QWindow>
+
+#include <QInputEvent>
 
 #ifdef _WIN32
 #define WIN32_LEAN_AND_MEAN 1
 #include <Windows.h>
 #endif
 
+#include "moc_OBSBasicInteraction.cpp"
+
 using namespace std;
 
 OBSBasicInteraction::OBSBasicInteraction(QWidget *parent, OBSSource source_)

+ 3 - 21
frontend/dialogs/OBSBasicInteraction.hpp

@@ -17,17 +17,13 @@
 
 #pragma once
 
-#include <QDialog>
-#include <memory>
-#include <functional>
+#include "ui_OBSBasicInteraction.h"
 
 #include <obs.hpp>
-#include <properties-view.hpp>
-
-class OBSBasic;
 
-#include "ui_OBSBasicInteraction.h"
+#include <QDialog>
 
+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<bool(QObject *, QEvent *)> 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;
-};

+ 0 - 7
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<typename T> static inline T GetOBSRef(QListWidgetItem *item)
-{
-	return item->data(static_cast<int>(QtDataRole::OBSRef)).value<T>();
-}
-
 OBSBasicSourceSelect::OBSBasicSourceSelect(OBSBasic *parent, const char *id_, undo_stack &undo_s)
 	: QDialog(parent),
 	  ui(new Ui::OBSBasicSourceSelect),

+ 0 - 5
frontend/dialogs/OBSBasicTransform.cpp

@@ -351,11 +351,6 @@ void OBSBasicTransform::OnCropChanged()
 	ignoreTransformSignal = false;
 }
 
-template<typename T> static T GetOBSRef(QListWidgetItem *item)
-{
-	return item->data(static_cast<int>(QtDataRole::OBSRef)).value<T>();
-}
-
 void OBSBasicTransform::OnSceneChanged(QListWidgetItem *current, QListWidgetItem *)
 {
 	if (!current)

+ 3 - 529
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 <qt-wrappers.hpp>
-#include <QLineEdit>
-#include <QHBoxLayout>
-#include <QUuid>
-
-#include <json11.hpp>
-
+#include "OBSExtraBrowsers.hpp"
 #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<QTableView *>(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<BrowserDock *>(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<QTableView *>(parent());
-
-	DelButton *del = reinterpret_cast<DelButton *>(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<QListView *>(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<ExtraBrowsersDelegate *>(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<QLineEdit *>(editor);
-	text->blockSignals(true);
-	text->setText(index.data().toString());
-	text->blockSignals(false);
-}
-
-bool ExtraBrowsersDelegate::eventFilter(QObject *object, QEvent *event)
-{
-	QLineEdit *edit = qobject_cast<QLineEdit *>(object);
-	if (!edit)
-		return false;
-
-	if (LineEditCanceled(event)) {
-		RevertText(edit);
-	}
-	if (LineEditChanged(event)) {
-		UpdateText(edit);
-
-		if (event->type() == QEvent::KeyPress) {
-			QKeyEvent *keyEvent = static_cast<QKeyEvent *>(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<EditWidget *>(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<EditWidget *>(edit_);
-	int row = edit->index.row();
-	int col = edit->index.column();
-	bool newItem = (row == model->items.size());
-
-	QString text = edit->text().trimmed();
+#include <utility/ExtraBrowsersDelegate.hpp>
 
-	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<QDockWidget>(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);
-	}
-}

+ 2 - 67
frontend/dialogs/OBSExtraBrowsers.hpp

@@ -1,15 +1,10 @@
 #pragma once
 
+#include <utility/ExtraBrowsersModel.hpp>
+
 #include <QDialog>
-#include <QScopedPointer>
-#include <QAbstractTableModel>
-#include <QStyledItemDelegate>
-#include <memory>
 
 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<Item> items;
-	QVector<int> 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;
-};

+ 8 - 396
frontend/dialogs/OBSMissingFiles.cpp

@@ -15,409 +15,21 @@
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
 
-#include "moc_window-missing-files.cpp"
-#include "window-basic-main.hpp"
+#include "OBSMissingFiles.hpp"
 
-#include "obs-app.hpp"
+#include <OBSApp.hpp>
+#include <utility/MissingFilesModel.hpp>
+#include <utility/MissingFilesPathItemDelegate.hpp>
 
-#include <QLineEdit>
-#include <QToolButton>
 #include <QFileDialog>
 
-#include <qt-wrappers.hpp>
-
-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<MissingFilesPathItemDelegate *>(this)->handleBrowse(container);
-	};
-
-	auto clearCallback = [this, container]() {
-		const_cast<MissingFilesPathItemDelegate *>(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<QLineEdit *>();
-	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<QLineEdit *>();
-		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<QLineEdit *>();
-
-	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<QLineEdit *>()->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<OBSBasic *>(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<MissingFileEntry> 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),

+ 5 - 64
frontend/dialogs/OBSMissingFiles.hpp

@@ -17,15 +17,14 @@
 
 #pragma once
 
-#include <QPointer>
-#include <QStyledItemDelegate>
-#include "obs-app.hpp"
 #include "ui_OBSMissingFiles.h"
 
-class MissingFilesModel;
+#include <obs.hpp>
+
+#include <QDialog>
+#include <QPointer>
 
-enum MissingFilesState { Missing, Found, Replaced, Cleared };
-Q_DECLARE_METATYPE(MissingFilesState);
+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<MissingFileEntry> files;
-
-	void fileCheckLoop(QList<MissingFileEntry> 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);
-};

+ 9 - 623
frontend/dialogs/OBSRemux.cpp

@@ -15,589 +15,21 @@
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
 
-#include "moc_window-remux.cpp"
+#include "OBSRemux.hpp"
 
-#include "obs-app.hpp"
+#include <utility/RemuxEntryPathItemDelegate.hpp>
+#include <utility/RemuxQueueModel.hpp>
+#include <utility/RemuxWorker.hpp>
+#include <widgets/OBSBasic.hpp>
+
+#include <qt-wrappers.hpp>
 
-#include <QCloseEvent>
 #include <QDirIterator>
-#include <QItemDelegate>
-#include <QLineEdit>
-#include <QMessageBox>
+#include <QDropEvent>
 #include <QMimeData>
-#include <QPainter>
 #include <QPushButton>
-#include <QStandardItemModel>
-#include <QStyledItemDelegate>
-#include <QToolButton>
-#include <QTimer>
-#include <qt-wrappers.hpp>
-
-#include "window-basic-main.hpp"
-
-#include <memory>
-#include <cmath>
-
-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<RemuxEntryState>();
-	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<RemuxEntryPathItemDelegate *>(this)->handleBrowse(container);
-		};
-
-		auto clearCallback = [this, container]() {
-			const_cast<RemuxEntryPathItemDelegate *>(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<QLineEdit *>();
-	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<QLineEdit *>();
-		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<RemuxEntryState>();
-
-	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<QLineEdit *>();
-
-	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<QLineEdit *>(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<RemuxWorker *>(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);
-}

+ 2 - 103
frontend/dialogs/OBSRemux.hpp

@@ -17,23 +17,14 @@
 
 #pragma once
 
-#include <QFileInfo>
-#include <QMutex>
-#include <QPointer>
-#include <QThread>
-#include <QStyledItemDelegate>
-#include <memory>
 #include "ui_OBSRemux.h"
 
-#include <media-io/media-remux.h>
-#include <util/threading.h>
+#include <QPointer>
+#include <QThread>
 
 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<RemuxQueueEntry> 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();
-};

+ 6 - 436
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 <qt-wrappers.hpp>
-#include <QLineEdit>
-#include <QHBoxLayout>
-#include <QUuid>
-
-#include <json11.hpp>
-
-#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();
-}
+#include <components/EditWidget.hpp>
 
-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<QTableView *>(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<BrowserDock *>(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<QTableView *>(parent());
-
-	DelButton *del = reinterpret_cast<DelButton *>(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<QListView *>(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);
-}
+#include <qt-wrappers.hpp>
 
-void ExtraBrowsersModel::Init()
-{
-	for (int i = 0; i < items.count(); i++)
-		AddDeleteButton(i);
-}
+#include <QKeyEvent>
 
-/* ------------------------------------------------------------------------- */
+#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<QDockWidget>(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);
-	}
-}

+ 0 - 64
frontend/utility/ExtraBrowsersDelegate.hpp

@@ -1,73 +1,9 @@
 #pragma once
 
-#include <QDialog>
-#include <QScopedPointer>
-#include <QAbstractTableModel>
 #include <QStyledItemDelegate>
-#include <memory>
 
-class Ui_OBSExtraBrowsers;
 class ExtraBrowsersModel;
 
-class QCefWidget;
-
-class OBSExtraBrowsers : public QDialog {
-	Q_OBJECT
-
-	std::unique_ptr<Ui_OBSExtraBrowsers> 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<Item> items;
-	QVector<int> deleted;
-
-	QString newTitle;
-	QString newURL;
-
-public slots:
-	void Init();
-};
-
 class ExtraBrowsersDelegate : public QStyledItemDelegate {
 	Q_OBJECT
 

+ 7 - 315
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 <qt-wrappers.hpp>
-#include <QLineEdit>
-#include <QHBoxLayout>
-#include <QUuid>
-
-#include <json11.hpp>
-
-#include "ui_OBSExtraBrowsers.h"
-
-using namespace json11;
+#include <components/DelButton.hpp>
+#include <docks/BrowserDock.hpp>
+#include <widgets/OBSBasic.hpp>
 
-#define OBJ_NAME_SUFFIX "_extraBrowser"
-
-enum class Column : int {
-	Title,
-	Url,
-	Delete,
+#include <qt-wrappers.hpp>
 
-	Count,
-};
+#include <QTableView>
 
-/* ------------------------------------------------------------------------- */
+#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<QTableView *>(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<ExtraBrowsersDelegate *>(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<QLineEdit *>(editor);
-	text->blockSignals(true);
-	text->setText(index.data().toString());
-	text->blockSignals(false);
-}
-
-bool ExtraBrowsersDelegate::eventFilter(QObject *object, QEvent *event)
-{
-	QLineEdit *edit = qobject_cast<QLineEdit *>(object);
-	if (!edit)
-		return false;
-
-	if (LineEditCanceled(event)) {
-		RevertText(edit);
-	}
-	if (LineEditChanged(event)) {
-		UpdateText(edit);
-
-		if (event->type() == QEvent::KeyPress) {
-			QKeyEvent *keyEvent = static_cast<QKeyEvent *>(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<EditWidget *>(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<EditWidget *>(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<QDockWidget>(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);
-	}
-}

+ 8 - 41
frontend/utility/ExtraBrowsersModel.hpp

@@ -1,32 +1,18 @@
 #pragma once
 
-#include <QDialog>
-#include <QScopedPointer>
 #include <QAbstractTableModel>
-#include <QStyledItemDelegate>
-#include <memory>
+#include <QUuid>
 
-class Ui_OBSExtraBrowsers;
-class ExtraBrowsersModel;
+enum class Column : int {
+	Title,
+	Url,
+	Delete,
 
-class QCefWidget;
-
-class OBSExtraBrowsers : public QDialog {
-	Q_OBJECT
-
-	std::unique_ptr<Ui_OBSExtraBrowsers> 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;
-};

+ 8 - 280
frontend/utility/MissingFilesModel.cpp

@@ -15,170 +15,20 @@
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
 
-#include "moc_window-missing-files.cpp"
-#include "window-basic-main.hpp"
+#include "MissingFilesModel.hpp"
 
-#include "obs-app.hpp"
+#include <widgets/OBSBasic.hpp>
 
-#include <QLineEdit>
-#include <QToolButton>
-#include <QFileDialog>
+#include <QFileInfo>
+#include <QMessageBox>
 
-#include <qt-wrappers.hpp>
-
-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<MissingFilesPathItemDelegate *>(this)->handleBrowse(container);
-	};
-
-	auto clearCallback = [this, container]() {
-		const_cast<MissingFilesPathItemDelegate *>(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<QLineEdit *>();
-	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<QLineEdit *>();
-		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<QLineEdit *>();
-
-	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<QLineEdit *>()->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;
-}

+ 3 - 55
frontend/utility/MissingFilesModel.hpp

@@ -17,41 +17,12 @@
 
 #pragma once
 
-#include <QPointer>
-#include <QStyledItemDelegate>
-#include "obs-app.hpp"
-#include "ui_OBSMissingFiles.h"
-
-class MissingFilesModel;
+#include <QAbstractTableModel>
+#include <QIcon>
 
 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<MissingFilesModel> filesModel;
-	std::unique_ptr<Ui::OBSMissingFiles> 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();
-};
+Q_DECLARE_METATYPE(MissingFilesState);
 
 class MissingFilesModel : public QAbstractTableModel {
 	Q_OBJECT
@@ -87,26 +58,3 @@ private:
 
 	void fileCheckLoop(QList<MissingFileEntry> 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);
-};

+ 5 - 383
frontend/utility/MissingFilesPathItemDelegate.cpp

@@ -15,32 +15,19 @@
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
 
-#include "moc_window-missing-files.cpp"
-#include "window-basic-main.hpp"
+#include "MissingFilesPathItemDelegate.hpp"
 
-#include "obs-app.hpp"
+#include <OBSApp.hpp>
 
+#include <QFileDialog>
+#include <QHBoxLayout>
 #include <QLineEdit>
 #include <QToolButton>
-#include <QFileDialog>
-
-#include <qt-wrappers.hpp>
-
-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<OBSBasic *>(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<MissingFileEntry> 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;
-}

+ 0 - 69
frontend/utility/MissingFilesPathItemDelegate.hpp

@@ -17,76 +17,7 @@
 
 #pragma once
 
-#include <QPointer>
 #include <QStyledItemDelegate>
-#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<MissingFilesModel> filesModel;
-	std::unique_ptr<Ui::OBSMissingFiles> 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<MissingFileEntry> files;
-
-	void fileCheckLoop(QList<MissingFileEntry> files, QString path, bool skipPrompt);
-};
 
 class MissingFilesPathItemDelegate : public QStyledItemDelegate {
 	Q_OBJECT

+ 2 - 49
frontend/utility/OBSEventFilter.hpp

@@ -17,55 +17,8 @@
 
 #pragma once
 
-#include <QDialog>
-#include <memory>
-#include <functional>
-
-#include <obs.hpp>
-#include <properties-view.hpp>
-
-class OBSBasic;
-
-#include "ui_OBSBasicInteraction.h"
-
-class OBSEventFilter;
-
-class OBSBasicInteraction : public QDialog {
-	Q_OBJECT
-
-private:
-	OBSBasic *main;
-
-	std::unique_ptr<Ui::OBSBasicInteraction> ui;
-	OBSSource source;
-	OBSSignal removedSignal;
-	OBSSignal renamedSignal;
-	std::unique_ptr<OBSEventFilter> 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 <QEvent>
+#include <QObject>
 
 typedef std::function<bool(QObject *, QEvent *)> EventFilterFunc;
 

+ 7 - 720
frontend/utility/RemuxEntryPathItemDelegate.cpp

@@ -15,44 +15,18 @@
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
 
-#include "moc_window-remux.cpp"
+#include "RemuxEntryPathItemDelegate.hpp"
+#include "RemuxQueueModel.hpp"
 
-#include "obs-app.hpp"
+#include <OBSApp.hpp>
 
-#include <QCloseEvent>
-#include <QDirIterator>
-#include <QItemDelegate>
-#include <QLineEdit>
-#include <QMessageBox>
-#include <QMimeData>
-#include <QPainter>
-#include <QPushButton>
-#include <QStandardItemModel>
-#include <QStyledItemDelegate>
-#include <QToolButton>
-#include <QTimer>
 #include <qt-wrappers.hpp>
 
-#include "window-basic-main.hpp"
-
-#include <memory>
-#include <cmath>
-
-using namespace std;
-
-enum RemuxEntryColumn {
-	State,
-	InputPath,
-	OutputPath,
-
-	Count
-};
-
-enum RemuxEntryRole { EntryStateRole = Qt::UserRole, NewPathsToProcessRole };
+#include <QHBoxLayout>
+#include <QLineEdit>
+#include <QToolButton>
 
-/**********************************************************
-  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<RemuxWorker *>(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);
-}

+ 0 - 127
frontend/utility/RemuxEntryPathItemDelegate.hpp

@@ -17,134 +17,7 @@
 
 #pragma once
 
-#include <QFileInfo>
-#include <QMutex>
-#include <QPointer>
-#include <QThread>
 #include <QStyledItemDelegate>
-#include <memory>
-#include "ui_OBSRemux.h"
-
-#include <media-io/media-remux.h>
-#include <util/threading.h>
-
-class RemuxQueueModel;
-class RemuxWorker;
-
-enum RemuxEntryState { Empty, Ready, Pending, InProgress, Complete, InvalidPath, Error };
-Q_DECLARE_METATYPE(RemuxEntryState);
-
-class OBSRemux : public QDialog {
-	Q_OBJECT
-
-	QPointer<RemuxQueueModel> queueModel;
-	QThread remuxer;
-	QPointer<RemuxWorker> worker;
-
-	std::unique_ptr<Ui::OBSRemux> 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<struct media_remux_job>;
-
-	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<RemuxQueueEntry> 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

+ 5 - 549
frontend/utility/RemuxQueueModel.cpp

@@ -15,230 +15,14 @@
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
 
-#include "moc_window-remux.cpp"
+#include "RemuxQueueModel.hpp"
 
-#include "obs-app.hpp"
+#include <OBSApp.hpp>
 
-#include <QCloseEvent>
-#include <QDirIterator>
-#include <QItemDelegate>
-#include <QLineEdit>
-#include <QMessageBox>
-#include <QMimeData>
-#include <QPainter>
-#include <QPushButton>
-#include <QStandardItemModel>
-#include <QStyledItemDelegate>
-#include <QToolButton>
-#include <QTimer>
-#include <qt-wrappers.hpp>
+#include <QDir>
+#include <QStyle>
 
-#include "window-basic-main.hpp"
-
-#include <memory>
-#include <cmath>
-
-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<RemuxEntryState>();
-	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<RemuxEntryPathItemDelegate *>(this)->handleBrowse(container);
-		};
-
-		auto clearCallback = [this, container]() {
-			const_cast<RemuxEntryPathItemDelegate *>(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<QLineEdit *>();
-	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<QLineEdit *>();
-		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<RemuxEntryState>();
-
-	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<QLineEdit *>();
-
-	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<QLineEdit *>(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<RemuxWorker *>(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);
-}

+ 10 - 105
frontend/utility/RemuxQueueModel.hpp

@@ -17,69 +17,23 @@
 
 #pragma once
 
-#include <QFileInfo>
-#include <QMutex>
-#include <QPointer>
-#include <QThread>
-#include <QStyledItemDelegate>
-#include <memory>
-#include "ui_OBSRemux.h"
-
-#include <media-io/media-remux.h>
-#include <util/threading.h>
-
-class RemuxQueueModel;
-class RemuxWorker;
+#include <QAbstractTableModel>
+#include <QFileInfoList>
 
 enum RemuxEntryState { Empty, Ready, Pending, InProgress, Complete, InvalidPath, Error };
-Q_DECLARE_METATYPE(RemuxEntryState);
-
-class OBSRemux : public QDialog {
-	Q_OBJECT
-
-	QPointer<RemuxQueueModel> queueModel;
-	QThread remuxer;
-	QPointer<RemuxWorker> worker;
 
-	std::unique_ptr<Ui::OBSRemux> 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<struct media_remux_job>;
-
-	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);
+Q_DECLARE_METATYPE(RemuxEntryState);
 
-public slots:
-	void updateProgress(float percent);
-	void remuxFinished(bool success);
-	void beginRemux();
-	bool stopRemux();
-	void clearFinished();
-	void clearAll();
+enum RemuxEntryColumn {
+	State,
+	InputPath,
+	OutputPath,
 
-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();
-};

+ 2 - 864
frontend/utility/RemuxWorker.cpp

@@ -15,873 +15,11 @@
     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
 
-#include "moc_window-remux.cpp"
+#include "RemuxWorker.hpp"
 
-#include "obs-app.hpp"
-
-#include <QCloseEvent>
-#include <QDirIterator>
-#include <QItemDelegate>
-#include <QLineEdit>
-#include <QMessageBox>
-#include <QMimeData>
-#include <QPainter>
-#include <QPushButton>
-#include <QStandardItemModel>
-#include <QStyledItemDelegate>
-#include <QToolButton>
-#include <QTimer>
+#include <media-io/media-remux.h>
 #include <qt-wrappers.hpp>
 
-#include "window-basic-main.hpp"
-
-#include <memory>
-#include <cmath>
-
-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<RemuxEntryState>();
-	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<RemuxEntryPathItemDelegate *>(this)->handleBrowse(container);
-		};
-
-		auto clearCallback = [this, container]() {
-			const_cast<RemuxEntryPathItemDelegate *>(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<QLineEdit *>();
-	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<QLineEdit *>();
-		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<RemuxEntryState>();
-
-	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<QLineEdit *>();
-
-	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<QLineEdit *>(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)

+ 1 - 130
frontend/utility/RemuxWorker.hpp

@@ -17,111 +17,8 @@
 
 #pragma once
 
-#include <QFileInfo>
 #include <QMutex>
-#include <QPointer>
-#include <QThread>
-#include <QStyledItemDelegate>
-#include <memory>
-#include "ui_OBSRemux.h"
-
-#include <media-io/media-remux.h>
-#include <util/threading.h>
-
-class RemuxQueueModel;
-class RemuxWorker;
-
-enum RemuxEntryState { Empty, Ready, Pending, InProgress, Complete, InvalidPath, Error };
-Q_DECLARE_METATYPE(RemuxEntryState);
-
-class OBSRemux : public QDialog {
-	Q_OBJECT
-
-	QPointer<RemuxQueueModel> queueModel;
-	QThread remuxer;
-	QPointer<RemuxWorker> worker;
-
-	std::unique_ptr<Ui::OBSRemux> 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<struct media_remux_job>;
-
-	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<RemuxQueueEntry> queue;
-	bool isProcessing;
-
-	static QVariant getIcon(RemuxEntryState state);
-
-	void checkInputPath(int row);
-};
+#include <QObject>
 
 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();
-};