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
+
+ 1
+
+
+ FocusList
+ QListWidget
+
+
+
+
+
+
+
+
+ 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();