diff --git a/UI/properties-view.cpp b/UI/properties-view.cpp index 35d433388..eba6b180a 100644 --- a/UI/properties-view.cpp +++ b/UI/properties-view.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include "double-slider.hpp" #include "slider-ignorewheel.hpp" #include "spinbox-ignorewheel.hpp" @@ -31,6 +32,9 @@ #include #include +#include +#include +#include #include using namespace std; @@ -171,13 +175,14 @@ void OBSPropertiesView::GetScrollPos(int &h, int &v) OBSPropertiesView::OBSPropertiesView(OBSData settings_, void *obj_, PropertiesReloadCallback reloadCallback, PropertiesUpdateCallback callback_, - int minSize_) + PropertiesVisualUpdateCb cb_, int minSize_) : VScrollArea(nullptr), properties(nullptr, obs_properties_destroy), settings(settings_), obj(obj_), reloadCallback(reloadCallback), callback(callback_), + cb(cb_), minSize(minSize_) { setFrameShape(QFrame::NoFrame); @@ -1885,6 +1890,12 @@ void WidgetInfo::ControlChanged() const char *setting = obs_property_name(property); obs_property_type type = obs_property_get_type(property); + if (!recently_updated) { + old_settings_cache = obs_data_create(); + obs_data_apply(old_settings_cache, view->settings); + obs_data_release(old_settings_cache); + } + switch (type) { case OBS_PROPERTY_INVALID: return; @@ -1933,8 +1944,32 @@ void WidgetInfo::ControlChanged() break; } - if (view->callback && !view->deferUpdate) - view->callback(view->obj, view->settings); + if (!recently_updated) { + recently_updated = true; + update_timer = new QTimer; + connect(update_timer, &QTimer::timeout, + [this, &ru = recently_updated]() { + if (view->callback && !view->deferUpdate) { + view->callback(view->obj, + old_settings_cache, + view->settings); + } + + ru = false; + }); + connect(update_timer, &QTimer::timeout, &QTimer::deleteLater); + update_timer->setSingleShot(true); + } + + if (update_timer) { + update_timer->stop(); + update_timer->start(500); + } else { + blog(LOG_DEBUG, "No update timer or no callback!"); + } + + if (view->cb && !view->deferUpdate) + view->cb(view->obj, view->settings); view->SignalChanged(); diff --git a/UI/properties-view.hpp b/UI/properties-view.hpp index 143e37100..ea51f7cc9 100644 --- a/UI/properties-view.hpp +++ b/UI/properties-view.hpp @@ -1,7 +1,10 @@ #pragma once #include "vertical-scroll-area.hpp" +#include #include +#include +#include #include #include @@ -10,7 +13,9 @@ class OBSPropertiesView; class QLabel; typedef obs_properties_t *(*PropertiesReloadCallback)(void *obj); -typedef void (*PropertiesUpdateCallback)(void *obj, obs_data_t *settings); +typedef void (*PropertiesUpdateCallback)(void *obj, obs_data_t *old_settings, + obs_data_t *new_settings); +typedef void (*PropertiesVisualUpdateCb)(void *obj, obs_data_t *settings); /* ------------------------------------------------------------------------- */ @@ -23,6 +28,9 @@ private: OBSPropertiesView *view; obs_property_t *property; QWidget *widget; + QPointer update_timer; + bool recently_updated = false; + OBSData old_settings_cache; void BoolChanged(const char *setting); void IntChanged(const char *setting); @@ -47,6 +55,15 @@ public: { } + ~WidgetInfo() + { + if (update_timer) { + update_timer->stop(); + update_timer->deleteLater(); + obs_data_release(old_settings_cache); + } + } + public slots: void ControlChanged(); @@ -83,6 +100,7 @@ private: std::string type; PropertiesReloadCallback reloadCallback; PropertiesUpdateCallback callback = nullptr; + PropertiesVisualUpdateCb cb = nullptr; int minSize; std::vector> children; std::string lastFocused; @@ -135,13 +153,15 @@ signals: public: OBSPropertiesView(OBSData settings, void *obj, PropertiesReloadCallback reloadCallback, - PropertiesUpdateCallback callback, int minSize = 0); + PropertiesUpdateCallback callback, + PropertiesVisualUpdateCb cb = nullptr, + int minSize = 0); OBSPropertiesView(OBSData settings, const char *type, PropertiesReloadCallback reloadCallback, int minSize = 0); inline obs_data_t *GetSettings() const { return settings; } - inline void UpdateSettings() { callback(obj, settings); } + inline void UpdateSettings() { callback(obj, nullptr, settings); } inline bool DeferUpdate() const { return deferUpdate; } }; diff --git a/UI/window-basic-filters.cpp b/UI/window-basic-filters.cpp index 52c2c2876..a4a55d054 100644 --- a/UI/window-basic-filters.cpp +++ b/UI/window-basic-filters.cpp @@ -15,6 +15,7 @@ along with this program. If not, see . ******************************************************************************/ +#include "properties-view.hpp" #include "window-namedialog.hpp" #include "window-basic-main.hpp" #include "window-basic-filters.hpp" @@ -23,9 +24,13 @@ #include "visibility-item-widget.hpp" #include "item-widget-helpers.hpp" #include "obs-app.hpp" +#include "undo-stack-obs.hpp" #include #include +#include +#include +#include #include #include #include @@ -202,10 +207,82 @@ void OBSBasicFilters::UpdatePropertiesView(int row, bool async) obs_data_t *settings = obs_source_get_settings(filter); + auto filter_change = [](void *vp, obs_data_t *nd_old_settings, + obs_data_t *new_settings) { + obs_source_t *source = reinterpret_cast(vp); + obs_source_t *parent = obs_filter_get_parent(source); + const char *source_name = obs_source_get_name(source); + OBSBasic *main = OBSBasic::Get(); + + obs_data_t *redo_wrapper = obs_data_create(); + obs_data_set_string(redo_wrapper, "name", source_name); + obs_data_set_string(redo_wrapper, "settings", + obs_data_get_json(new_settings)); + obs_data_set_string(redo_wrapper, "parent", + obs_source_get_name(parent)); + + obs_data_t *filter_settings = obs_source_get_settings(source); + obs_data_t *old_settings = + obs_data_get_defaults(filter_settings); + obs_data_apply(old_settings, nd_old_settings); + + obs_data_t *undo_wrapper = obs_data_create(); + obs_data_set_string(undo_wrapper, "name", source_name); + obs_data_set_string(undo_wrapper, "settings", + obs_data_get_json(old_settings)); + obs_data_set_string(undo_wrapper, "parent", + obs_source_get_name(parent)); + + auto undo_redo = [](const std::string &data) { + obs_data_t *dat = + obs_data_create_from_json(data.c_str()); + obs_source_t *parent_source = obs_get_source_by_name( + obs_data_get_string(dat, "parent")); + const char *filter_name = + obs_data_get_string(dat, "name"); + obs_source_t *filter = obs_source_get_filter_by_name( + parent_source, filter_name); + obs_data_t *settings = obs_data_create_from_json( + obs_data_get_string(dat, "settings")); + + obs_source_update(filter, settings); + obs_source_update_properties(filter); + + obs_data_release(dat); + obs_data_release(settings); + obs_source_release(filter); + obs_source_release(parent_source); + }; + std::string name = std::string(obs_source_get_name(source)); + std::string undo_data = obs_data_get_json(undo_wrapper); + std::string redo_data = obs_data_get_json(redo_wrapper); + main->undo_s.add_action(QTStr("Undo.Filters").arg(name.c_str()), + undo_redo, undo_redo, undo_data, + redo_data, NULL); + + obs_data_release(redo_wrapper); + obs_data_release(undo_wrapper); + obs_data_release(old_settings); + obs_data_release(filter_settings); + + obs_source_update(source, new_settings); + + main->undo_s.enable_undo_redo(); + }; + + auto disabled_undo = [](void *vp, obs_data_t *settings) { + OBSBasic *main = + reinterpret_cast(App()->GetMainWindow()); + main->undo_s.disable_undo_redo(); + obs_source_t *source = reinterpret_cast(vp); + obs_source_update(source, settings); + }; + view = new OBSPropertiesView( settings, filter, (PropertiesReloadCallback)obs_source_properties, - (PropertiesUpdateCallback)obs_source_update); + (PropertiesUpdateCallback)filter_change, + (PropertiesVisualUpdateCb)disabled_undo); updatePropertiesSignal.Connect(obs_source_get_signal_handler(filter), "update_properties", @@ -240,6 +317,7 @@ void OBSBasicFilters::AddFilter(OBSSource filter, bool focus) list->addItem(item); if (focus) list->setCurrentItem(item); + SetupVisibilityItem(list, item, filter); } @@ -486,6 +564,68 @@ void OBSBasicFilters::AddNewFilter(const char *id) return; } + obs_data_t *wrapper = obs_data_create(); + obs_data_set_string(wrapper, "sname", + obs_source_get_name(source)); + obs_data_set_string(wrapper, "fname", name.c_str()); + std::string scene_name = obs_source_get_name( + reinterpret_cast(App()->GetMainWindow()) + ->GetCurrentSceneSource()); + auto undo = [scene_name](const std::string &data) { + obs_source_t *ssource = + obs_get_source_by_name(scene_name.c_str()); + reinterpret_cast(App()->GetMainWindow()) + ->SetCurrentScene(ssource); + obs_source_release(ssource); + + obs_data_t *dat = + obs_data_create_from_json(data.c_str()); + obs_source_t *source = obs_get_source_by_name( + obs_data_get_string(dat, "sname")); + obs_source_t *filter = obs_source_get_filter_by_name( + source, obs_data_get_string(dat, "fname")); + obs_source_filter_remove(source, filter); + + obs_data_release(dat); + obs_source_release(source); + obs_source_release(filter); + }; + + obs_data_t *rwrapper = obs_data_create(); + obs_data_set_string(rwrapper, "sname", + obs_source_get_name(source)); + auto redo = [scene_name, id = std::string(id), + name](const std::string &data) { + obs_source_t *ssource = + obs_get_source_by_name(scene_name.c_str()); + reinterpret_cast(App()->GetMainWindow()) + ->SetCurrentScene(ssource); + obs_source_release(ssource); + + obs_data_t *dat = + obs_data_create_from_json(data.c_str()); + obs_source_t *source = obs_get_source_by_name( + obs_data_get_string(dat, "sname")); + + obs_source_t *filter = obs_source_create( + id.c_str(), name.c_str(), nullptr, nullptr); + if (filter) { + obs_source_filter_add(source, filter); + obs_source_release(filter); + } + + obs_data_release(dat); + obs_source_release(source); + }; + + std::string undo_data(obs_data_get_json(wrapper)); + std::string redo_data(obs_data_get_json(rwrapper)); + main->undo_s.add_action(QTStr("Undo.Add").arg(name.c_str()), + undo, redo, undo_data, redo_data, NULL); + + obs_data_release(wrapper); + obs_data_release(rwrapper); + obs_source_t *filter = obs_source_create(id, name.c_str(), nullptr, nullptr); if (filter) { @@ -674,8 +814,75 @@ void OBSBasicFilters::on_removeEffectFilter_clicked() { OBSSource filter = GetFilter(ui->effectFilters->currentRow(), false); if (filter) { - if (QueryRemove(this, filter)) + if (QueryRemove(this, filter)) { + obs_data_t *wrapper = obs_save_source(filter); + std::string parent_name(obs_source_get_name(source)); + obs_data_set_string(wrapper, "undo_name", + parent_name.c_str()); + + std::string scene_name = obs_source_get_name( + reinterpret_cast( + App()->GetMainWindow()) + ->GetCurrentSceneSource()); + auto undo = [scene_name](const std::string &data) { + obs_source_t *ssource = obs_get_source_by_name( + scene_name.c_str()); + reinterpret_cast( + App()->GetMainWindow()) + ->SetCurrentScene(ssource); + obs_source_release(ssource); + + obs_data_t *dat = + obs_data_create_from_json(data.c_str()); + obs_source_t *source = obs_get_source_by_name( + obs_data_get_string(dat, "undo_name")); + obs_source_t *filter = obs_load_source(dat); + obs_source_filter_add(source, filter); + + obs_data_release(dat); + obs_source_release(source); + obs_source_release(filter); + }; + + obs_data_t *rwrapper = obs_data_create(); + obs_data_set_string(rwrapper, "fname", + obs_source_get_name(filter)); + obs_data_set_string(rwrapper, "sname", + parent_name.c_str()); + auto redo = [scene_name](const std::string &data) { + obs_source_t *ssource = obs_get_source_by_name( + scene_name.c_str()); + reinterpret_cast( + App()->GetMainWindow()) + ->SetCurrentScene(ssource); + obs_source_release(ssource); + + obs_data_t *dat = + obs_data_create_from_json(data.c_str()); + obs_source_t *source = obs_get_source_by_name( + obs_data_get_string(dat, "sname")); + obs_source_t *filter = + obs_source_get_filter_by_name( + source, obs_data_get_string( + dat, "fname")); + obs_source_filter_remove(source, filter); + + obs_data_release(dat); + obs_source_release(filter); + obs_source_release(source); + }; + + std::string undo_data(obs_data_get_json(wrapper)); + std::string redo_data(obs_data_get_json(rwrapper)); + main->undo_s.add_action( + QTStr("Undo.Delete") + .arg(obs_source_get_name(filter)), + undo, redo, undo_data, redo_data, NULL); obs_source_filter_remove(source, filter); + + obs_data_release(wrapper); + obs_data_release(rwrapper); + } } } @@ -918,6 +1125,48 @@ void OBSBasicFilters::FilterNameEdited(QWidget *editor, QListWidget *list) listItem->setText(QT_UTF8(name.c_str())); obs_source_set_name(filter, name.c_str()); + + std::string scene_name = obs_source_get_name( + reinterpret_cast(App()->GetMainWindow()) + ->GetCurrentSceneSource()); + auto undo = [scene_name, prev = std::string(prevName), + name](const std::string &data) { + obs_source_t *ssource = + obs_get_source_by_name(scene_name.c_str()); + reinterpret_cast(App()->GetMainWindow()) + ->SetCurrentScene(ssource); + obs_source_release(ssource); + + obs_source_t *source = + obs_get_source_by_name(data.c_str()); + obs_source_t *filter = obs_source_get_filter_by_name( + source, name.c_str()); + obs_source_set_name(filter, prev.c_str()); + obs_source_release(source); + obs_source_release(filter); + }; + + auto redo = [scene_name, prev = std::string(prevName), + name](const std::string &data) { + obs_source_t *ssource = + obs_get_source_by_name(scene_name.c_str()); + reinterpret_cast(App()->GetMainWindow()) + ->SetCurrentScene(ssource); + obs_source_release(ssource); + + obs_source_t *source = + obs_get_source_by_name(data.c_str()); + obs_source_t *filter = obs_source_get_filter_by_name( + source, prev.c_str()); + obs_source_set_name(filter, name.c_str()); + obs_source_release(source); + obs_source_release(filter); + }; + + std::string undo_data(sourceName); + std::string redo_data(sourceName); + main->undo_s.add_action(QTStr("Undo.Rename").arg(name.c_str()), + undo, redo, undo_data, redo_data, NULL); } listItem->setText(QString()); diff --git a/UI/window-basic-properties.cpp b/UI/window-basic-properties.cpp index 4f149667d..4e9e2cde0 100644 --- a/UI/window-basic-properties.cpp +++ b/UI/window-basic-properties.cpp @@ -26,6 +26,10 @@ #include #include #include +#include +#include +#include +#include using namespace std; @@ -74,14 +78,28 @@ OBSBasicProperties::OBSBasicProperties(QWidget *parent, OBSSource source_) /* The OBSData constructor increments the reference once */ obs_data_release(oldSettings); - OBSData settings = obs_source_get_settings(source); + OBSData nd_settings = obs_source_get_settings(source); + OBSData settings = obs_data_get_defaults(nd_settings); + obs_data_apply(settings, nd_settings); obs_data_apply(oldSettings, settings); obs_data_release(settings); + obs_data_release(nd_settings); + + auto handle_memory = [](void *vp, obs_data_t *old_settings, + obs_data_t *new_settings) { + obs_source_t *source = reinterpret_cast(vp); + + obs_source_update(source, new_settings); + + UNUSED_PARAMETER(old_settings); + UNUSED_PARAMETER(vp); + }; view = new OBSPropertiesView( - settings, source, + nd_settings, source, (PropertiesReloadCallback)obs_source_properties, - (PropertiesUpdateCallback)obs_source_update); + (PropertiesUpdateCallback)handle_memory, + (PropertiesVisualUpdateCb)obs_source_update); view->setMinimumHeight(150); preview->setMinimumSize(20, 150); @@ -341,6 +359,51 @@ void OBSBasicProperties::on_buttonBox_clicked(QAbstractButton *button) QDialogButtonBox::ButtonRole val = buttonBox->buttonRole(button); if (val == QDialogButtonBox::AcceptRole) { + + std::string scene_name = + obs_source_get_name(main->GetCurrentSceneSource()); + + auto undo_redo = [scene_name](const std::string &data) { + obs_data_t *settings = + obs_data_create_from_json(data.c_str()); + obs_source_t *source = obs_get_source_by_name( + obs_data_get_string(settings, "undo_sname")); + obs_source_update(source, settings); + + obs_source_update_properties(source); + + obs_source_t *scene_source = + obs_get_source_by_name(scene_name.c_str()); + + OBSBasic::Get()->SetCurrentScene(source); + + obs_source_release(scene_source); + + obs_data_release(settings); + obs_source_release(source); + }; + + obs_data_t *new_settings = obs_data_create(); + obs_data_t *curr_settings = obs_source_get_settings(source); + obs_data_apply(new_settings, curr_settings); + obs_data_set_string(new_settings, "undo_sname", + obs_source_get_name(source)); + obs_data_set_string(oldSettings, "undo_sname", + obs_source_get_name(source)); + + std::string undo_data(obs_data_get_json(oldSettings)); + std::string redo_data(obs_data_get_json(new_settings)); + + if (undo_data.compare(redo_data) != 0) + main->undo_s.add_action( + QTStr("Undo.Properties") + .arg(obs_source_get_name(source)), + undo_redo, undo_redo, undo_data, redo_data, + NULL); + + obs_data_release(new_settings); + obs_data_release(curr_settings); + acceptClicked = true; close();