From ceba24c7e9b2cc80dd54d6d825428721eaa41331 Mon Sep 17 00:00:00 2001 From: jp9000 Date: Wed, 25 Feb 2015 21:23:57 -0800 Subject: [PATCH] UI: Add user interface for filters --- obs/CMakeLists.txt | 3 + obs/data/locale/en-US.ini | 10 + obs/forms/OBSBasicFilters.ui | 466 +++++++++++++++++++++++ obs/window-basic-filters.cpp | 708 +++++++++++++++++++++++++++++++++++ obs/window-basic-filters.hpp | 114 ++++++ obs/window-basic-main.cpp | 23 ++ obs/window-basic-main.hpp | 5 + 7 files changed, 1329 insertions(+) create mode 100644 obs/forms/OBSBasicFilters.ui create mode 100644 obs/window-basic-filters.cpp create mode 100644 obs/window-basic-filters.hpp diff --git a/obs/CMakeLists.txt b/obs/CMakeLists.txt index fd0eacee7..e48c36d02 100644 --- a/obs/CMakeLists.txt +++ b/obs/CMakeLists.txt @@ -90,6 +90,7 @@ set(obs_SOURCES ${obs_PLATFORM_SOURCES} obs-app.cpp window-basic-main.cpp + window-basic-filters.cpp window-basic-settings.cpp window-basic-interaction.cpp window-basic-properties.cpp @@ -120,6 +121,7 @@ set(obs_HEADERS platform.hpp window-main.hpp window-basic-main.hpp + window-basic-filters.hpp window-basic-settings.hpp window-basic-interaction.hpp window-basic-properties.hpp @@ -154,6 +156,7 @@ set(obs_UI forms/OBSLogReply.ui forms/OBSBasic.ui forms/OBSBasicTransform.ui + forms/OBSBasicFilters.ui forms/OBSBasicSettings.ui forms/OBSBasicSourceSelect.ui forms/OBSBasicInteraction.ui diff --git a/obs/data/locale/en-US.ini b/obs/data/locale/en-US.ini index d51ab072b..64e407219 100644 --- a/obs/data/locale/en-US.ini +++ b/obs/data/locale/en-US.ini @@ -17,6 +17,7 @@ No="No" Add="Add" Remove="Remove" Rename="Rename" +Filters="Filters" Properties="Properties" MoveUp="Move Up" MoveDown="Move Down" @@ -127,6 +128,15 @@ Basic.InteractionWindow="Interacting with '%1'" Basic.StatusBar.Reconnecting="Disconnected, reconnecting (attempt %1)" Basic.StatusBar.ReconnectSuccessful="Reconnection successful" +# filters window +Basic.Filters="Filters" +Basic.Filters.AsyncFilters="Audio/Video Filters" +Basic.Filters.AudioFilters="Audio Filters" +Basic.Filters.EffectFilters="Effect Filters" +Basic.Filters.Title="Filters for '%1'" +Basic.Filters.AddFilter.Title="Filter name" +Basic.Filters.AddFilter.Text="Please specify the name of the filter" + # transform window Basic.TransformWindow="Scene Item Transform" Basic.TransformWindow.Position="Position" diff --git a/obs/forms/OBSBasicFilters.ui b/obs/forms/OBSBasicFilters.ui new file mode 100644 index 000000000..c53fe6750 --- /dev/null +++ b/obs/forms/OBSBasicFilters.ui @@ -0,0 +1,466 @@ + + + OBSBasicFilters + + + + 0 + 0 + 861 + 726 + + + + Basic.Filters + + + + + + QLayout::SetMinimumSize + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Basic.Filters.AsyncFilters + + + + + + + + 0 + 0 + + + + Qt::CustomContextMenu + + + + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 22 + 22 + + + + + + + + :/res/images/add.png:/res/images/add.png + + + false + + + true + + + addIconSmall + + + + + + + + 22 + 22 + + + + + + + + :/res/images/list_remove.png:/res/images/list_remove.png + + + false + + + true + + + removeIconSmall + + + + + + + + 22 + 22 + + + + + + + + :/res/images/up.png:/res/images/up.png + + + false + + + true + + + upArrowIconSmall + + + + + + + + 22 + 22 + + + + + + + + :/res/images/down.png:/res/images/down.png + + + false + + + true + + + downArrowIconSmall + + + + + + + + + + + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Basic.Filters.EffectFilters + + + + + + + + 0 + 0 + + + + Qt::CustomContextMenu + + + + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 22 + 22 + + + + + + + + :/res/images/add.png:/res/images/add.png + + + false + + + true + + + addIconSmall + + + + + + + + 22 + 22 + + + + + + + + :/res/images/list_remove.png:/res/images/list_remove.png + + + false + + + true + + + removeIconSmall + + + + + + + + 22 + 22 + + + + + + + + :/res/images/up.png:/res/images/up.png + + + false + + + true + + + upArrowIconSmall + + + + + + + + 22 + 22 + + + + + + + + :/res/images/down.png:/res/images/down.png + + + false + + + true + + + downArrowIconSmall + + + + + + + + + + + + + + + + + + + + 0 + 0 + + + + + 24 + 24 + + + + + + + + + + 4 + + + QLayout::SetMaximumSize + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + Close + + + false + + + + + + + + + + + + + + OBSQTDisplay + QWidget +
qt-display.hpp
+ 1 +
+ + FocusList + QListWidget +
focus-list.hpp
+
+
+ + + + + + close + clicked() + OBSBasicFilters + accept() + + + 811 + 701 + + + 722 + 727 + + + + +
diff --git a/obs/window-basic-filters.cpp b/obs/window-basic-filters.cpp new file mode 100644 index 000000000..12a952681 --- /dev/null +++ b/obs/window-basic-filters.cpp @@ -0,0 +1,708 @@ +/****************************************************************************** + Copyright (C) 2015 by Hugh Bailey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#include "window-namedialog.hpp" +#include "window-basic-filters.hpp" +#include "display-helpers.hpp" +#include "qt-wrappers.hpp" +#include "visibility-item-widget.hpp" +#include "obs-app.hpp" + +#include +#include +#include +#include +#include + +using namespace std; + +Q_DECLARE_METATYPE(OBSSource); + +OBSBasicFilters::OBSBasicFilters(QWidget *parent, OBSSource source_) + : QDialog (parent), + ui (new Ui::OBSBasicFilters), + source (source_), + addSignal (obs_source_get_signal_handler(source), + "filter_add", + OBSBasicFilters::OBSSourceFilterAdded, + this), + removeSignal (obs_source_get_signal_handler(source), + "filter_remove", + OBSBasicFilters::OBSSourceFilterRemoved, + this), + reorderSignal (obs_source_get_signal_handler(source), + "reorder_filters", + OBSBasicFilters::OBSSourceReordered, + this), + removeSourceSignal (obs_source_get_signal_handler(source), + "remove", + OBSBasicFilters::SourceRemoved, this), + renameSourceSignal (obs_source_get_signal_handler(source), + "rename", + OBSBasicFilters::SourceRenamed, this) +{ + ui->setupUi(this); + UpdateFilters(); + + ui->asyncFilters->setItemDelegate( + new VisibilityItemDelegate(ui->asyncFilters)); + ui->effectFilters->setItemDelegate( + new VisibilityItemDelegate(ui->effectFilters)); + + const char *name = obs_source_get_name(source); + setWindowTitle(QTStr("Basic.Filters.Title").arg(QT_UTF8(name))); + + connect(ui->preview, SIGNAL(DisplayResized()), + this, SLOT(OnPreviewResized())); + + connect(ui->asyncFilters->itemDelegate(), + SIGNAL(closeEditor(QWidget*, + QAbstractItemDelegate::EndEditHint)), + this, + SLOT(AsyncFilterNameEdited(QWidget*, + QAbstractItemDelegate::EndEditHint))); + + connect(ui->effectFilters->itemDelegate(), + SIGNAL(closeEditor(QWidget*, + QAbstractItemDelegate::EndEditHint)), + this, + SLOT(EffectFilterNameEdited(QWidget*, + QAbstractItemDelegate::EndEditHint))); + + uint32_t flags = obs_source_get_output_flags(source); + bool audio = (flags & OBS_SOURCE_AUDIO) != 0; + bool audioOnly = (flags & OBS_SOURCE_VIDEO) == 0; + bool async = (flags & OBS_SOURCE_ASYNC) != 0; + + if (!async && !audio) { + ui->asyncWidget->setVisible(false); + ui->separatorLine->setVisible(false); + } + if (audioOnly) { + ui->effectWidget->setVisible(false); + ui->separatorLine->setVisible(false); + } + + if (audioOnly || (audio && !async)) + ui->asyncLabel->setText(QTStr("Basic.Filters.AudioFilters")); +} + +OBSBasicFilters::~OBSBasicFilters() +{ + ui->asyncFilters->clear(); + ui->effectFilters->clear(); + QApplication::sendPostedEvents(this); +} + +void OBSBasicFilters::Init() +{ + gs_init_data init_data = {}; + + show(); + + QSize previewSize = GetPixelSize(ui->preview); + init_data.cx = uint32_t(previewSize.width()); + init_data.cy = uint32_t(previewSize.height()); + init_data.format = GS_RGBA; + QTToGSWindow(ui->preview->winId(), init_data.window); + + display = obs_display_create(&init_data); + + if (display) + obs_display_add_draw_callback(display, + OBSBasicFilters::DrawPreview, this); +} + +inline OBSSource OBSBasicFilters::GetFilter(int row, bool async) +{ + if (row == -1) + return OBSSource(); + + QListWidget *list = async ? ui->asyncFilters : ui->effectFilters; + QListWidgetItem *item = list->item(row); + if (!item) + return OBSSource(); + + QVariant v = item->data(Qt::UserRole); + return v.value(); +} + +void OBSBasicFilters::UpdatePropertiesView(int row, bool async) +{ + delete view; + view = nullptr; + + OBSSource filter = GetFilter(row, async); + if (!filter) + return; + + obs_data_t *settings = obs_source_get_settings(filter); + + view = new OBSPropertiesView(settings, filter, + (PropertiesReloadCallback)obs_source_properties, + (PropertiesUpdateCallback)obs_source_update); + + obs_data_release(settings); + + view->setMaximumHeight(250); + view->setMinimumHeight(150); + ui->rightLayout->addWidget(view); + view->show(); +} + +void OBSBasicFilters::AddFilter(OBSSource filter) +{ + uint32_t flags = obs_source_get_output_flags(filter); + bool async = (flags & OBS_SOURCE_ASYNC) != 0; + QListWidget *list = async ? ui->asyncFilters : ui->effectFilters; + + QListWidgetItem *item = new QListWidgetItem(); + Qt::ItemFlags itemFlags = item->flags(); + + item->setFlags(itemFlags | Qt::ItemIsEditable); + item->setData(Qt::UserRole, QVariant::fromValue(filter)); + + list->addItem(item); + list->setCurrentItem(item); + SetupVisibilityItem(list, item, filter); +} + +void OBSBasicFilters::RemoveFilter(OBSSource filter) +{ + uint32_t flags = obs_source_get_output_flags(filter); + bool async = (flags & OBS_SOURCE_ASYNC) != 0; + QListWidget *list = async ? ui->asyncFilters : ui->effectFilters; + + for (int i = 0; i < list->count(); i++) { + QListWidgetItem *item = list->item(i); + QVariant v = item->data(Qt::UserRole); + OBSSource curFilter = v.value(); + + if (filter == curFilter) { + delete item; + break; + } + } +} + +struct FilterOrderInfo { + int asyncIdx = 0; + int effectIdx = 0; + OBSBasicFilters *window; + + inline FilterOrderInfo(OBSBasicFilters *window_) : window(window_) {} +}; + +void OBSBasicFilters::ReorderFilter(QListWidget *list, + obs_source_t *filter, size_t idx) +{ + int count = list->count(); + + for (int i = 0; i < count; i++) { + QListWidgetItem *listItem = list->item(i); + QVariant v = listItem->data(Qt::UserRole); + OBSSource filterItem = v.value(); + + if (filterItem == filter) { + if ((int)idx != i) { + bool sel = (list->currentRow() == i); + + listItem = list->takeItem(i); + if (listItem) { + list->insertItem((int)idx, listItem); + SetupVisibilityItem(list, + listItem, filterItem); + + if (sel) + list->setCurrentRow((int)idx); + } + } + + break; + } + } +} + +void OBSBasicFilters::ReorderFilters() +{ + FilterOrderInfo info(this); + + obs_source_enum_filters(source, + [] (obs_source_t*, obs_source_t *filter, void *p) + { + FilterOrderInfo *info = + reinterpret_cast(p); + uint32_t flags; + bool async; + + flags = obs_source_get_output_flags(filter); + async = (flags & OBS_SOURCE_ASYNC) != 0; + + if (async) { + info->window->ReorderFilter( + info->window->ui->asyncFilters, + filter, info->asyncIdx++); + } else { + info->window->ReorderFilter( + info->window->ui->effectFilters, + filter, info->effectIdx++); + } + }, &info); +} + +void OBSBasicFilters::UpdateFilters() +{ + if (!source) + return; + + ui->effectFilters->clear(); + ui->asyncFilters->clear(); + + obs_source_enum_filters(source, + [] (obs_source_t*, obs_source_t *filter, void *p) + { + OBSBasicFilters *window = + reinterpret_cast(p); + + window->AddFilter(filter); + }, this); +} + +static bool filter_compatible(bool async, uint32_t sourceFlags, + uint32_t filterFlags) +{ + bool filterVideo = (filterFlags & OBS_SOURCE_VIDEO) != 0; + bool filterAsync = (filterFlags & OBS_SOURCE_ASYNC) != 0; + bool filterAudio = (filterFlags & OBS_SOURCE_AUDIO) != 0; + bool audio = (sourceFlags & OBS_SOURCE_AUDIO) != 0; + bool audioOnly = (sourceFlags & OBS_SOURCE_VIDEO) == 0; + bool asyncSource = (sourceFlags & OBS_SOURCE_ASYNC) != 0; + + if (async && ((audioOnly && filterVideo) || (!audio && !asyncSource))) + return false; + + return (async && (filterAudio || filterAsync)) || + (!async && !filterAudio && !filterAsync); +} + +QMenu *OBSBasicFilters::CreateAddFilterPopupMenu(bool async) +{ + uint32_t sourceFlags = obs_source_get_output_flags(source); + const char *type; + bool foundValues = false; + size_t idx = 0; + + QMenu *popup = new QMenu(QTStr("Add"), this); + while (obs_enum_filter_types(idx++, &type)) { + const char *name = obs_source_get_display_name( + OBS_SOURCE_TYPE_FILTER, type); + uint32_t filterFlags = obs_get_source_output_flags( + OBS_SOURCE_TYPE_FILTER, type); + + if (!filter_compatible(async, sourceFlags, filterFlags)) + continue; + + QAction *popupItem = new QAction(QT_UTF8(name), this); + popupItem->setData(QT_UTF8(type)); + connect(popupItem, SIGNAL(triggered(bool)), + this, SLOT(AddFilterFromAction())); + popup->addAction(popupItem); + + foundValues = true; + } + + if (!foundValues) { + delete popup; + popup = nullptr; + } + + return popup; +} + +void OBSBasicFilters::AddNewFilter(const char *id) +{ + if (id && *id) { + obs_source_t *existing_filter; + string name = obs_source_get_display_name( + OBS_SOURCE_TYPE_FILTER, id); + + bool success = NameDialog::AskForName(this, + QTStr("Basic.Filters.AddFilter.Title"), + QTStr("Basic.FIlters.AddFilter.Text"), name, + QT_UTF8(name.c_str())); + if (!success) + return; + + if (name.empty()) { + QMessageBox::information(this, + QTStr("NoNameEntered.Title"), + QTStr("NoNameEntered.Text")); + AddNewFilter(id); + return; + } + + existing_filter = obs_source_get_filter_by_name(source, + name.c_str()); + if (existing_filter) { + QMessageBox::information(this, + QTStr("NameExists.Title"), + QTStr("NameExists.Text")); + obs_source_release(existing_filter); + AddNewFilter(id); + return; + } + + obs_source_t *filter = obs_source_create(OBS_SOURCE_TYPE_FILTER, + id, name.c_str(), nullptr); + if (filter) { + obs_source_filter_add(source, filter); + obs_source_release(filter); + } + } +} + +void OBSBasicFilters::AddFilterFromAction() +{ + QAction *action = qobject_cast(sender()); + if (!action) + return; + + AddNewFilter(QT_TO_UTF8(action->data().toString())); +} + +void OBSBasicFilters::OnPreviewResized() +{ + if (resizeTimer) + killTimer(resizeTimer); + resizeTimer = startTimer(100); +} + +void OBSBasicFilters::closeEvent(QCloseEvent *event) +{ + QDialog::closeEvent(event); + if (!event->isAccepted()) + return; + + // remove draw callback and release display in case our drawable + // surfaces go away before the destructor gets called + obs_display_remove_draw_callback(display, + OBSBasicFilters::DrawPreview, this); + display = nullptr; +} + +void OBSBasicFilters::timerEvent(QTimerEvent *event) +{ + if (event->timerId() == resizeTimer) { + killTimer(resizeTimer); + resizeTimer = 0; + + QSize size = GetPixelSize(ui->preview); + obs_display_resize(display, size.width(), size.height()); + } +} + +/* OBS Signals */ + +void OBSBasicFilters::OBSSourceFilterAdded(void *param, calldata_t *data) +{ + OBSBasicFilters *window = reinterpret_cast(param); + obs_source_t *filter = (obs_source_t*)calldata_ptr(data, "filter"); + + QMetaObject::invokeMethod(window, "AddFilter", + Q_ARG(OBSSource, OBSSource(filter))); +} + +void OBSBasicFilters::OBSSourceFilterRemoved(void *param, calldata_t *data) +{ + OBSBasicFilters *window = reinterpret_cast(param); + obs_source_t *filter = (obs_source_t*)calldata_ptr(data, "filter"); + + QMetaObject::invokeMethod(window, "RemoveFilter", + Q_ARG(OBSSource, OBSSource(filter))); +} + +void OBSBasicFilters::OBSSourceReordered(void *param, calldata_t *data) +{ + QMetaObject::invokeMethod(reinterpret_cast(param), + "ReorderFilters"); + + UNUSED_PARAMETER(data); +} + +void OBSBasicFilters::SourceRemoved(void *data, calldata_t *params) +{ + UNUSED_PARAMETER(params); + + QMetaObject::invokeMethod(static_cast(data), + "close"); +} + +void OBSBasicFilters::SourceRenamed(void *data, calldata_t *params) +{ + const char *name = calldata_string(params, "new_name"); + QString title = QTStr("Basic.Filters.Title").arg(QT_UTF8(name)); + + QMetaObject::invokeMethod(static_cast(data), + "setWindowTitle", Q_ARG(QString, title)); +} + +void OBSBasicFilters::DrawPreview(void *data, uint32_t cx, uint32_t cy) +{ + OBSBasicFilters *window = static_cast(data); + + if (!window->source) + return; + + uint32_t sourceCX = max(obs_source_get_width(window->source), 1u); + uint32_t sourceCY = max(obs_source_get_height(window->source), 1u); + + int x, y; + int newCX, newCY; + float scale; + + GetScaleAndCenterPos(sourceCX, sourceCY, cx, cy, x, y, scale); + + newCX = int(scale * float(sourceCX)); + newCY = int(scale * float(sourceCY)); + + gs_viewport_push(); + gs_projection_push(); + gs_ortho(0.0f, float(sourceCX), 0.0f, float(sourceCY), -100.0f, 100.0f); + gs_set_viewport(x, y, newCX, newCY); + + obs_source_video_render(window->source); + + gs_projection_pop(); + gs_viewport_pop(); +} + +/* Qt Slots */ + +static bool QueryRemove(QWidget *parent, obs_source_t *source) +{ + const char *name = obs_source_get_name(source); + + QString text = QTStr("ConfirmRemove.Text"); + text.replace("$1", QT_UTF8(name)); + + QMessageBox remove_source(parent); + remove_source.setText(text); + QAbstractButton *Yes = remove_source.addButton(QTStr("Yes"), + QMessageBox::YesRole); + remove_source.addButton(QTStr("No"), QMessageBox::NoRole); + remove_source.setIcon(QMessageBox::Question); + remove_source.setWindowTitle(QTStr("ConfirmRemove.Title")); + remove_source.exec(); + + return Yes == remove_source.clickedButton(); +} + +void OBSBasicFilters::on_addAsyncFilter_clicked() +{ + QPointer popup = CreateAddFilterPopupMenu(true); + if (popup) + popup->exec(QCursor::pos()); +} + +void OBSBasicFilters::on_removeAsyncFilter_clicked() +{ + OBSSource filter = GetFilter(ui->asyncFilters->currentRow(), true); + if (filter) { + if (QueryRemove(this, filter)) + obs_source_filter_remove(source, filter); + } +} + +void OBSBasicFilters::on_moveAsyncFilterUp_clicked() +{ + OBSSource filter = GetFilter(ui->asyncFilters->currentRow(), true); + if (filter) + obs_source_filter_set_order(source, filter, OBS_ORDER_MOVE_UP); +} + +void OBSBasicFilters::on_moveAsyncFilterDown_clicked() +{ + OBSSource filter = GetFilter(ui->asyncFilters->currentRow(), true); + if (filter) + obs_source_filter_set_order(source, filter, + OBS_ORDER_MOVE_DOWN); +} + +void OBSBasicFilters::on_asyncFilters_GotFocus() +{ + UpdatePropertiesView(ui->asyncFilters->currentRow(), true); +} + +void OBSBasicFilters::on_asyncFilters_currentRowChanged(int row) +{ + UpdatePropertiesView(row, true); +} + +void OBSBasicFilters::on_addEffectFilter_clicked() +{ + QPointer popup = CreateAddFilterPopupMenu(false); + if (popup) + popup->exec(QCursor::pos()); +} + +void OBSBasicFilters::on_removeEffectFilter_clicked() +{ + OBSSource filter = GetFilter(ui->effectFilters->currentRow(), false); + if (filter) { + if (QueryRemove(this, filter)) + obs_source_filter_remove(source, filter); + } +} + +void OBSBasicFilters::on_moveEffectFilterUp_clicked() +{ + OBSSource filter = GetFilter(ui->effectFilters->currentRow(), false); + if (filter) + obs_source_filter_set_order(source, filter, OBS_ORDER_MOVE_UP); +} + +void OBSBasicFilters::on_moveEffectFilterDown_clicked() +{ + OBSSource filter = GetFilter(ui->effectFilters->currentRow(), false); + if (filter) + obs_source_filter_set_order(source, filter, + OBS_ORDER_MOVE_DOWN); +} + +void OBSBasicFilters::on_effectFilters_GotFocus() +{ + UpdatePropertiesView(ui->effectFilters->currentRow(), false); +} + +void OBSBasicFilters::on_effectFilters_currentRowChanged(int row) +{ + UpdatePropertiesView(row, false); +} + +void OBSBasicFilters::CustomContextMenu(const QPoint &pos, bool async) +{ + QListWidget *list = async ? ui->asyncFilters : ui->effectFilters; + QListWidgetItem *item = list->itemAt(pos); + + QMenu popup(window()); + + QPointer addMenu = CreateAddFilterPopupMenu(async); + if (addMenu) + popup.addMenu(addMenu); + + if (item) { + const char *renameSlot = async ? + SLOT(RenameAsyncFilter()) : SLOT(RenameEffectFilter()); + const char *removeSlot = async ? + SLOT(on_removeAsyncFilter_clicked()) : + SLOT(on_removeEffectFilter_clicked()); + + popup.addSeparator(); + popup.addAction(QTStr("Rename"), this, renameSlot); + popup.addAction(QTStr("Remove"), this, removeSlot); + } + + popup.exec(QCursor::pos()); +} + +void OBSBasicFilters::EditItem(QListWidgetItem *item, bool async) +{ + Qt::ItemFlags flags = item->flags(); + OBSSource filter = item->data(Qt::UserRole).value(); + const char *name = obs_source_get_name(filter); + QListWidget *list = async ? ui->asyncFilters : ui->effectFilters; + + item->setText(QT_UTF8(name)); + item->setFlags(flags | Qt::ItemIsEditable); + list->removeItemWidget(item); + list->editItem(item); + item->setFlags(flags); +} + +void OBSBasicFilters::on_asyncFilters_customContextMenuRequested( + const QPoint &pos) +{ + CustomContextMenu(pos, true); +} + +void OBSBasicFilters::on_effectFilters_customContextMenuRequested( + const QPoint &pos) +{ + CustomContextMenu(pos, false); +} + +void OBSBasicFilters::RenameAsyncFilter() +{ + EditItem(ui->asyncFilters->currentItem(), true); +} + +void OBSBasicFilters::RenameEffectFilter() +{ + EditItem(ui->effectFilters->currentItem(), false); +} + +void OBSBasicFilters::FilterNameEdited(QWidget *editor, QListWidget *list) +{ + QListWidgetItem *listItem = list->currentItem(); + OBSSource filter = listItem->data(Qt::UserRole).value(); + QLineEdit *edit = qobject_cast(editor); + string name = QT_TO_UTF8(edit->text().trimmed()); + + const char *prevName = obs_source_get_name(filter); + bool sameName = (name == prevName); + obs_source_t *foundFilter = nullptr; + + if (!sameName) + foundFilter = obs_source_get_filter_by_name(source, + name.c_str()); + + if (foundFilter || name.empty() || sameName) { + listItem->setText(QT_UTF8(prevName)); + + if (foundFilter) { + QMessageBox::information(window(), + QTStr("NameExists.Title"), + QTStr("NameExists.Text")); + obs_source_release(foundFilter); + + } else if (name.empty()) { + QMessageBox::information(window(), + QTStr("NoNameEntered.Title"), + QTStr("NoNameEntered.Text")); + } + } else { + listItem->setText(QT_UTF8(name.c_str())); + obs_source_set_name(filter, name.c_str()); + } + + listItem->setText(QString()); + SetupVisibilityItem(list, listItem, filter); +} + +void OBSBasicFilters::AsyncFilterNameEdited(QWidget *editor, + QAbstractItemDelegate::EndEditHint endHint) +{ + FilterNameEdited(editor, ui->asyncFilters); + UNUSED_PARAMETER(endHint); +} + +void OBSBasicFilters::EffectFilterNameEdited(QWidget *editor, + QAbstractItemDelegate::EndEditHint endHint) +{ + FilterNameEdited(editor, ui->effectFilters); + UNUSED_PARAMETER(endHint); +} diff --git a/obs/window-basic-filters.hpp b/obs/window-basic-filters.hpp new file mode 100644 index 000000000..24a3d8929 --- /dev/null +++ b/obs/window-basic-filters.hpp @@ -0,0 +1,114 @@ +/****************************************************************************** + Copyright (C) 2015 by Hugh Bailey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#pragma once + +#include +#include +#include +#include + +#include "properties-view.hpp" + +class OBSBasic; +class QMenu; + +#include "ui_OBSBasicFilters.h" + +class OBSBasicFilters : public QDialog { + Q_OBJECT + +private: + OBSBasic *main; + + std::unique_ptr ui; + int resizeTimer = 0; + OBSSource source; + OBSPropertiesView *view = nullptr; + + OBSDisplay display; + OBSSignal addSignal; + OBSSignal removeSignal; + OBSSignal reorderSignal; + + OBSSignal removeSourceSignal; + OBSSignal renameSourceSignal; + + inline OBSSource GetFilter(int row, bool async); + + void UpdateFilters(); + void UpdatePropertiesView(int row, bool async); + + static void OBSSourceFilterAdded(void *param, calldata_t *data); + static void OBSSourceFilterRemoved(void *param, calldata_t *data); + static void OBSSourceReordered(void *param, calldata_t *data); + static void SourceRemoved(void *param, calldata_t *data); + static void SourceRenamed(void *param, calldata_t *data); + static void DrawPreview(void *data, uint32_t cx, uint32_t cy); + + QMenu *CreateAddFilterPopupMenu(bool async); + + void AddNewFilter(const char *id); + void ReorderFilter(QListWidget *list, obs_source_t *filter, size_t idx); + + void CustomContextMenu(const QPoint &pos, bool async); + void EditItem(QListWidgetItem *item, bool async); + + void FilterNameEdited(QWidget *editor, QListWidget *list); + +private slots: + void AddFilter(OBSSource filter); + void RemoveFilter(OBSSource filter); + void ReorderFilters(); + void RenameAsyncFilter(); + void RenameEffectFilter(); + + void AddFilterFromAction(); + + void OnPreviewResized(); + + void on_addAsyncFilter_clicked(); + void on_removeAsyncFilter_clicked(); + void on_moveAsyncFilterUp_clicked(); + void on_moveAsyncFilterDown_clicked(); + void on_asyncFilters_currentRowChanged(int row); + void on_asyncFilters_customContextMenuRequested(const QPoint &pos); + void on_asyncFilters_GotFocus(); + + void on_addEffectFilter_clicked(); + void on_removeEffectFilter_clicked(); + void on_moveEffectFilterUp_clicked(); + void on_moveEffectFilterDown_clicked(); + void on_effectFilters_currentRowChanged(int row); + void on_effectFilters_customContextMenuRequested(const QPoint &pos); + void on_effectFilters_GotFocus(); + + void AsyncFilterNameEdited(QWidget *editor, + QAbstractItemDelegate::EndEditHint endHint); + void EffectFilterNameEdited(QWidget *editor, + QAbstractItemDelegate::EndEditHint endHint); + +public: + OBSBasicFilters(QWidget *parent, OBSSource source_); + ~OBSBasicFilters(); + + void Init(); + +protected: + virtual void closeEvent(QCloseEvent *event) override; + virtual void timerEvent(QTimerEvent *event) override; +}; diff --git a/obs/window-basic-main.cpp b/obs/window-basic-main.cpp index d010d0615..0b077fdd7 100644 --- a/obs/window-basic-main.cpp +++ b/obs/window-basic-main.cpp @@ -677,6 +677,9 @@ OBSBasic::~OBSBasic() if (properties) delete properties; + if (filters) + delete filters; + if (transformWindow) delete transformWindow; @@ -794,6 +797,16 @@ void OBSBasic::CreatePropertiesWindow(obs_source_t *source) properties->setAttribute(Qt::WA_DeleteOnClose, true); } +void OBSBasic::CreateFiltersWindow(obs_source_t *source) +{ + if (filters) + filters->close(); + + filters = new OBSBasicFilters(this, source); + filters->Init(); + filters->setAttribute(Qt::WA_DeleteOnClose, true); +} + /* Qt callbacks for invokeMethod */ void OBSBasic::AddScene(OBSSource source) @@ -1957,6 +1970,8 @@ void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos) action->setEnabled(obs_source_get_output_flags(source) & OBS_SOURCE_INTERACTION); + popup.addAction(QTStr("Filters"), this, + SLOT(OpenFilters())); popup.addAction(QTStr("Properties"), this, SLOT(on_actionSourceProperties_triggered())); } @@ -2305,6 +2320,14 @@ void OBSBasic::SceneItemNameEdited(QWidget *editor, UNUSED_PARAMETER(endHint); } +void OBSBasic::OpenFilters() +{ + OBSSceneItem item = GetCurrentSceneItem(); + OBSSource source = obs_sceneitem_get_source(item); + + CreateFiltersWindow(source); +} + void OBSBasic::StreamingStart() { ui->streamButton->setText(QTStr("Basic.Main.StopStreaming")); diff --git a/obs/window-basic-main.hpp b/obs/window-basic-main.hpp index da43b89b6..7ef219bbf 100644 --- a/obs/window-basic-main.hpp +++ b/obs/window-basic-main.hpp @@ -29,6 +29,7 @@ #include "window-basic-properties.hpp" #include "window-basic-transform.hpp" #include "window-basic-adv-audio.hpp" +#include "window-basic-filters.hpp" #include #include @@ -67,6 +68,7 @@ private: QPointer properties; QPointer transformWindow; QPointer advAudioWindow; + QPointer filters; QNetworkAccessManager networkManager; @@ -133,6 +135,7 @@ private: void CreateInteractionWindow(obs_source_t *source); void CreatePropertiesWindow(obs_source_t *source); + void CreateFiltersWindow(obs_source_t *source); public slots: void StreamingStart(); @@ -297,6 +300,8 @@ private slots: void SceneItemNameEdited(QWidget *editor, QAbstractItemDelegate::EndEditHint endHint); + void OpenFilters(); + public: explicit OBSBasic(QWidget *parent = 0); virtual ~OBSBasic();