Merge pull request #3426 from Programatic/undo_redo

UI: Implement Undo/Redo System
This commit is contained in:
Jim 2021-03-30 03:16:24 -07:00 committed by GitHub
commit 5d87f3c00b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 2061 additions and 61 deletions

View file

@ -260,7 +260,8 @@ set(obs_SOURCES
obs-proxy-style.cpp obs-proxy-style.cpp
locked-checkbox.cpp locked-checkbox.cpp
visibility-checkbox.cpp visibility-checkbox.cpp
media-slider.cpp) media-slider.cpp
undo-stack-obs.cpp)
set(obs_HEADERS set(obs_HEADERS
${obs_PLATFORM_HEADERS} ${obs_PLATFORM_HEADERS}
@ -331,7 +332,8 @@ set(obs_HEADERS
log-viewer.hpp log-viewer.hpp
obs-proxy-style.hpp obs-proxy-style.hpp
obs-proxy-style.hpp obs-proxy-style.hpp
media-slider.hpp) media-slider.hpp
undo-stack-obs.hpp)
set(obs_importers_HEADERS set(obs_importers_HEADERS
importers/importers.hpp) importers/importers.hpp)

View file

@ -1,3 +1,4 @@
#include "window-basic-main.hpp"
#include "context-bar-controls.hpp" #include "context-bar-controls.hpp"
#include "qt-wrappers.hpp" #include "qt-wrappers.hpp"
#include "obs-app.hpp" #include "obs-app.hpp"
@ -33,6 +34,63 @@ SourceToolbar::SourceToolbar(QWidget *parent, OBSSource source)
{ {
} }
void SourceToolbar::SaveOldProperties(obs_source_t *source)
{
if (oldData)
obs_data_release(oldData);
oldData = obs_data_create();
obs_data_t *oldSettings = obs_source_get_settings(source);
obs_data_apply(oldData, oldSettings);
obs_data_set_string(oldData, "undo_sname", obs_source_get_name(source));
obs_data_release(oldSettings);
obs_data_release(oldData);
}
void SourceToolbar::SetUndoProperties(obs_source_t *source)
{
OBSBasic *main = reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
std::string scene_name =
obs_source_get_name(main->GetCurrentSceneSource());
auto undo_redo = [scene_name,
main = std::move(main)](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_t *scene_source =
obs_get_source_by_name(scene_name.c_str());
main->SetCurrentScene(scene_source);
obs_source_release(scene_source);
obs_data_release(settings);
obs_source_release(source);
main->UpdateContextBar();
};
OBSData new_settings = obs_data_create();
OBSData 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));
std::string undo_data(obs_data_get_json(oldData));
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, nullptr);
obs_data_release(new_settings);
obs_data_release(curr_settings);
obs_data_release(oldData);
}
/* ========================================================================= */ /* ========================================================================= */
BrowserToolbar::BrowserToolbar(QWidget *parent, OBSSource source) BrowserToolbar::BrowserToolbar(QWidget *parent, OBSSource source)
@ -163,8 +221,10 @@ void ComboSelectToolbar::on_device_currentIndexChanged(int idx)
return; return;
} }
SaveOldProperties(source);
UpdateSourceComboToolbarValue(ui->device, source, idx, prop_name, UpdateSourceComboToolbarValue(ui->device, source, idx, prop_name,
is_int); is_int);
SetUndoProperties(source);
} }
AudioCaptureToolbar::AudioCaptureToolbar(QWidget *parent, OBSSource source) AudioCaptureToolbar::AudioCaptureToolbar(QWidget *parent, OBSSource source)
@ -370,10 +430,12 @@ void GameCaptureToolbar::on_mode_currentIndexChanged(int idx)
QString id = ui->mode->itemData(idx).toString(); QString id = ui->mode->itemData(idx).toString();
SaveOldProperties(source);
obs_data_t *settings = obs_data_create(); obs_data_t *settings = obs_data_create();
obs_data_set_string(settings, "capture_mode", QT_TO_UTF8(id)); obs_data_set_string(settings, "capture_mode", QT_TO_UTF8(id));
obs_source_update(source, settings); obs_source_update(source, settings);
obs_data_release(settings); obs_data_release(settings);
SetUndoProperties(source);
UpdateWindowVisibility(); UpdateWindowVisibility();
} }
@ -387,10 +449,12 @@ void GameCaptureToolbar::on_window_currentIndexChanged(int idx)
QString id = ui->window->itemData(idx).toString(); QString id = ui->window->itemData(idx).toString();
SaveOldProperties(source);
obs_data_t *settings = obs_data_create(); obs_data_t *settings = obs_data_create();
obs_data_set_string(settings, "window", QT_TO_UTF8(id)); obs_data_set_string(settings, "window", QT_TO_UTF8(id));
obs_source_update(source, settings); obs_source_update(source, settings);
obs_data_release(settings); obs_data_release(settings);
SetUndoProperties(source);
} }
/* ========================================================================= */ /* ========================================================================= */
@ -434,10 +498,12 @@ void ImageSourceToolbar::on_browse_clicked()
ui->path->setText(path); ui->path->setText(path);
SaveOldProperties(source);
obs_data_t *settings = obs_data_create(); obs_data_t *settings = obs_data_create();
obs_data_set_string(settings, "file", QT_TO_UTF8(path)); obs_data_set_string(settings, "file", QT_TO_UTF8(path));
obs_source_update(source, settings); obs_source_update(source, settings);
obs_data_release(settings); obs_data_release(settings);
SetUndoProperties(source);
} }
/* ========================================================================= */ /* ========================================================================= */
@ -518,10 +584,14 @@ void ColorSourceToolbar::on_choose_clicked()
color = newColor; color = newColor;
UpdateColor(); UpdateColor();
SaveOldProperties(source);
obs_data_t *settings = obs_data_create(); obs_data_t *settings = obs_data_create();
obs_data_set_int(settings, "color", color_to_int(color)); obs_data_set_int(settings, "color", color_to_int(color));
obs_source_update(source, settings); obs_source_update(source, settings);
obs_data_release(settings); obs_data_release(settings);
SetUndoProperties(source);
} }
/* ========================================================================= */ /* ========================================================================= */
@ -596,6 +666,8 @@ void TextSourceToolbar::on_selectFont_clicked()
flags |= font.strikeOut() ? OBS_FONT_STRIKEOUT : 0; flags |= font.strikeOut() ? OBS_FONT_STRIKEOUT : 0;
obs_data_set_int(font_obj, "flags", flags); obs_data_set_int(font_obj, "flags", flags);
SaveOldProperties(source);
obs_data_t *settings = obs_data_create(); obs_data_t *settings = obs_data_create();
obs_data_set_obj(settings, "font", font_obj); obs_data_set_obj(settings, "font", font_obj);
@ -603,6 +675,8 @@ void TextSourceToolbar::on_selectFont_clicked()
obs_source_update(source, settings); obs_source_update(source, settings);
obs_data_release(settings); obs_data_release(settings);
SetUndoProperties(source);
} }
void TextSourceToolbar::on_selectColor_clicked() void TextSourceToolbar::on_selectColor_clicked()
@ -628,6 +702,8 @@ void TextSourceToolbar::on_selectColor_clicked()
color = newColor; color = newColor;
SaveOldProperties(source);
obs_data_t *settings = obs_data_create(); obs_data_t *settings = obs_data_create();
if (!strncmp(obs_source_get_id(source), "text_ft2_source", 15)) { if (!strncmp(obs_source_get_id(source), "text_ft2_source", 15)) {
obs_data_set_int(settings, "color1", color_to_int(color)); obs_data_set_int(settings, "color1", color_to_int(color));
@ -637,6 +713,8 @@ void TextSourceToolbar::on_selectColor_clicked()
} }
obs_source_update(source, settings); obs_source_update(source, settings);
obs_data_release(settings); obs_data_release(settings);
SetUndoProperties(source);
} }
void TextSourceToolbar::on_text_textChanged() void TextSourceToolbar::on_text_textChanged()

View file

@ -22,6 +22,10 @@ protected:
std::unique_ptr<obs_properties_t, properties_delete_t>; std::unique_ptr<obs_properties_t, properties_delete_t>;
properties_t props; properties_t props;
OBSData oldData;
void SaveOldProperties(obs_source_t *source);
void SetUndoProperties(obs_source_t *source);
public: public:
SourceToolbar(QWidget *parent, OBSSource source); SourceToolbar(QWidget *parent, OBSSource source);

View file

@ -264,6 +264,34 @@ Basic.SceneTransitions="Scene Transitions"
Basic.TransitionDuration="Duration" Basic.TransitionDuration="Duration"
Basic.TogglePreviewProgramMode="Studio Mode" Basic.TogglePreviewProgramMode="Studio Mode"
# undo
Undo.Undo="Undo"
Undo.Redo="Redo"
Undo.Add="Add '%1'"
Undo.Delete="Delete '%1'"
Undo.Rename="Rename '%1'"
Undo.SceneCollection.Switch="Switch to '%1'"
Undo.Item.Undo="Undo %1"
Undo.Item.Redo="Redo %1"
Undo.Sources.Multi="Delete %1 Sources"
Undo.Filters="Filter Changes on '%1'"
Undo.Transform="Transform source(s) In '%1'"
Undo.Transform.Paste="Paste Transformation in '%1'"
Undo.Transform.Rotate="Rotation In '%1'"
Undo.Transform.Reset="Transform Reset In '%1'"
Undo.Transform.HFlip="Horizontal Flip In '%1'"
Undo.Transform.VFlip="Vertical Flip In '%1'"
Undo.Transform.FitToScren="Fit to Screen In '%1'"
Undo.Transform.StretchToScreen="Stretch to Screen in '%1'"
Undo.Transform.Center="Center to Screen in '%1'"
Undo.Transform.VCenter="Vertical Center to Screen in '%1'"
Undo.Transform.HCenter="Horizontal Center to Screen in '%1'"
Undo.Volume.Change="Volume Change on '%1'"
Undo.Audio="Audio Changes"
Undo.Properties="Property Change on '%1'"
Undo.Scene.Duplicate="Duplicate Scene '%1'"
# transition name dialog # transition name dialog
TransitionNameDlg.Text="Please enter the name of the transition" TransitionNameDlg.Text="Please enter the name of the transition"
TransitionNameDlg.Title="Transition Name" TransitionNameDlg.Title="Transition Name"

View file

@ -586,6 +586,9 @@
<string>Paste.Filters</string> <string>Paste.Filters</string>
</property> </property>
</action> </action>
<addaction name="actionMainUndo"/>
<addaction name="actionMainRedo"/>
<addaction name="separator"/>
<addaction name="actionCopySource"/> <addaction name="actionCopySource"/>
<addaction name="actionPasteRef"/> <addaction name="actionPasteRef"/>
<addaction name="actionPasteDup"/> <addaction name="actionPasteDup"/>
@ -599,6 +602,7 @@
<addaction name="actionLockPreview"/> <addaction name="actionLockPreview"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="actionAdvAudioProperties"/> <addaction name="actionAdvAudioProperties"/>
<addaction name="separator"/>
</widget> </widget>
<widget class="QMenu" name="profileMenu"> <widget class="QMenu" name="profileMenu">
<property name="title"> <property name="title">
@ -2034,6 +2038,22 @@
<string>Basic.MainMenu.View.ContextBar</string> <string>Basic.MainMenu.View.ContextBar</string>
</property> </property>
</action> </action>
<action name="actionMainUndo">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Undo</string>
</property>
</action>
<action name="actionMainRedo">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Redo</string>
</property>
</action>
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget> <customwidget>

View file

@ -20,6 +20,7 @@
#include <QStackedWidget> #include <QStackedWidget>
#include <QDir> #include <QDir>
#include <QGroupBox> #include <QGroupBox>
#include <QObject>
#include "double-slider.hpp" #include "double-slider.hpp"
#include "slider-ignorewheel.hpp" #include "slider-ignorewheel.hpp"
#include "spinbox-ignorewheel.hpp" #include "spinbox-ignorewheel.hpp"
@ -31,6 +32,9 @@
#include <cstdlib> #include <cstdlib>
#include <initializer_list> #include <initializer_list>
#include <obs-data.h>
#include <obs.h>
#include <qtimer.h>
#include <string> #include <string>
using namespace std; using namespace std;
@ -171,13 +175,14 @@ void OBSPropertiesView::GetScrollPos(int &h, int &v)
OBSPropertiesView::OBSPropertiesView(OBSData settings_, void *obj_, OBSPropertiesView::OBSPropertiesView(OBSData settings_, void *obj_,
PropertiesReloadCallback reloadCallback, PropertiesReloadCallback reloadCallback,
PropertiesUpdateCallback callback_, PropertiesUpdateCallback callback_,
int minSize_) PropertiesVisualUpdateCb cb_, int minSize_)
: VScrollArea(nullptr), : VScrollArea(nullptr),
properties(nullptr, obs_properties_destroy), properties(nullptr, obs_properties_destroy),
settings(settings_), settings(settings_),
obj(obj_), obj(obj_),
reloadCallback(reloadCallback), reloadCallback(reloadCallback),
callback(callback_), callback(callback_),
cb(cb_),
minSize(minSize_) minSize(minSize_)
{ {
setFrameShape(QFrame::NoFrame); setFrameShape(QFrame::NoFrame);
@ -1885,6 +1890,12 @@ void WidgetInfo::ControlChanged()
const char *setting = obs_property_name(property); const char *setting = obs_property_name(property);
obs_property_type type = obs_property_get_type(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) { switch (type) {
case OBS_PROPERTY_INVALID: case OBS_PROPERTY_INVALID:
return; return;
@ -1933,8 +1944,32 @@ void WidgetInfo::ControlChanged()
break; break;
} }
if (view->callback && !view->deferUpdate) if (!recently_updated) {
view->callback(view->obj, view->settings); 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(); view->SignalChanged();

View file

@ -1,7 +1,10 @@
#pragma once #pragma once
#include "vertical-scroll-area.hpp" #include "vertical-scroll-area.hpp"
#include <obs-data.h>
#include <obs.hpp> #include <obs.hpp>
#include <qtimer.h>
#include <QPointer>
#include <vector> #include <vector>
#include <memory> #include <memory>
@ -10,7 +13,9 @@ class OBSPropertiesView;
class QLabel; class QLabel;
typedef obs_properties_t *(*PropertiesReloadCallback)(void *obj); 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; OBSPropertiesView *view;
obs_property_t *property; obs_property_t *property;
QWidget *widget; QWidget *widget;
QPointer<QTimer> update_timer;
bool recently_updated = false;
OBSData old_settings_cache;
void BoolChanged(const char *setting); void BoolChanged(const char *setting);
void IntChanged(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: public slots:
void ControlChanged(); void ControlChanged();
@ -83,6 +100,7 @@ private:
std::string type; std::string type;
PropertiesReloadCallback reloadCallback; PropertiesReloadCallback reloadCallback;
PropertiesUpdateCallback callback = nullptr; PropertiesUpdateCallback callback = nullptr;
PropertiesVisualUpdateCb cb = nullptr;
int minSize; int minSize;
std::vector<std::unique_ptr<WidgetInfo>> children; std::vector<std::unique_ptr<WidgetInfo>> children;
std::string lastFocused; std::string lastFocused;
@ -135,13 +153,15 @@ signals:
public: public:
OBSPropertiesView(OBSData settings, void *obj, OBSPropertiesView(OBSData settings, void *obj,
PropertiesReloadCallback reloadCallback, PropertiesReloadCallback reloadCallback,
PropertiesUpdateCallback callback, int minSize = 0); PropertiesUpdateCallback callback,
PropertiesVisualUpdateCb cb = nullptr,
int minSize = 0);
OBSPropertiesView(OBSData settings, const char *type, OBSPropertiesView(OBSData settings, const char *type,
PropertiesReloadCallback reloadCallback, PropertiesReloadCallback reloadCallback,
int minSize = 0); int minSize = 0);
inline obs_data_t *GetSettings() const { return settings; } 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; } inline bool DeferUpdate() const { return deferUpdate; }
}; };

View file

@ -403,6 +403,34 @@ void SourceTreeItem::ExitEditMode(bool save)
/* rename */ /* rename */
SignalBlocker sourcesSignalBlocker(this); SignalBlocker sourcesSignalBlocker(this);
std::string prevName(obs_source_get_name(source));
std::string scene_name =
obs_source_get_name(main->GetCurrentSceneSource());
auto undo = [scene_name, prevName, main](const std::string &data) {
obs_source_t *source = obs_get_source_by_name(data.c_str());
obs_source_set_name(source, prevName.c_str());
obs_source_release(source);
obs_source_t *scene_source =
obs_get_source_by_name(scene_name.c_str());
main->SetCurrentScene(scene_source);
obs_source_release(scene_source);
};
auto redo = [scene_name, main, newName](const std::string &data) {
obs_source_t *source = obs_get_source_by_name(data.c_str());
obs_source_set_name(source, newName.c_str());
obs_source_release(source);
obs_source_t *scene_source =
obs_get_source_by_name(scene_name.c_str());
main->SetCurrentScene(scene_source);
obs_source_release(scene_source);
};
main->undo_s.add_action(QTStr("Undo.Rename").arg(newName.c_str()), undo,
redo, newName, prevName, NULL);
obs_source_set_name(source, newName.c_str()); obs_source_set_name(source, newName.c_str());
label->setText(QT_UTF8(newName.c_str())); label->setText(QT_UTF8(newName.c_str()));
} }

101
UI/undo-stack-obs.cpp Normal file
View file

@ -0,0 +1,101 @@
#include "undo-stack-obs.hpp"
#include <util/util.hpp>
undo_stack::undo_stack(ui_ptr ui) : ui(ui) {}
void undo_stack::release()
{
for (auto f : undo_items)
if (f.d)
f.d(true);
for (auto f : redo_items)
if (f.d)
f.d(false);
}
void undo_stack::add_action(const QString &name, undo_redo_cb undo,
undo_redo_cb redo, std::string undo_data,
std::string redo_data, func d)
{
undo_redo_t n = {name, undo_data, redo_data, undo, redo, d};
undo_items.push_front(n);
clear_redo();
ui->actionMainUndo->setText(QTStr("Undo.Item.Undo").arg(name));
ui->actionMainUndo->setEnabled(true);
ui->actionMainRedo->setText(QTStr("Undo.Redo"));
ui->actionMainRedo->setDisabled(true);
}
void undo_stack::undo()
{
if (undo_items.size() == 0 || disabled)
return;
undo_redo_t temp = undo_items.front();
temp.undo(temp.undo_data);
redo_items.push_front(temp);
undo_items.pop_front();
ui->actionMainRedo->setText(QTStr("Undo.Item.Redo").arg(temp.name));
ui->actionMainRedo->setEnabled(true);
if (undo_items.size() == 0) {
ui->actionMainUndo->setDisabled(true);
ui->actionMainUndo->setText(QTStr("Undo.Undo"));
} else {
ui->actionMainUndo->setText(
QTStr("Undo.Item.Undo").arg(undo_items.front().name));
}
}
void undo_stack::redo()
{
if (redo_items.size() == 0 || disabled)
return;
undo_redo_t temp = redo_items.front();
temp.redo(temp.redo_data);
undo_items.push_front(temp);
redo_items.pop_front();
ui->actionMainUndo->setText(QTStr("Undo.Item.Undo").arg(temp.name));
ui->actionMainUndo->setEnabled(true);
if (redo_items.size() == 0) {
ui->actionMainRedo->setDisabled(true);
ui->actionMainRedo->setText(QTStr("Undo.Redo"));
} else {
ui->actionMainRedo->setText(
QTStr("Undo.Item.Redo").arg(redo_items.front().name));
}
}
void undo_stack::enable_undo_redo()
{
disabled = false;
ui->actionMainUndo->setDisabled(false);
ui->actionMainRedo->setDisabled(false);
}
void undo_stack::disable_undo_redo()
{
disabled = true;
ui->actionMainUndo->setDisabled(true);
ui->actionMainRedo->setDisabled(true);
}
void undo_stack::clear_redo()
{
for (auto f : redo_items)
if (f.d)
f.d(false);
redo_items.clear();
}

46
UI/undo-stack-obs.hpp Normal file
View file

@ -0,0 +1,46 @@
#pragma once
#include <QString>
#include <deque>
#include <functional>
#include <string>
#include <memory>
#include "ui_OBSBasic.h"
typedef std::function<void(const std::string &data)> undo_redo_cb;
typedef std::function<void(bool is_undo)> func;
typedef std::unique_ptr<Ui::OBSBasic> &ui_ptr;
struct undo_redo_t {
QString name;
std::string undo_data;
std::string redo_data;
undo_redo_cb undo;
undo_redo_cb redo;
func d;
};
class undo_stack {
private:
ui_ptr ui;
std::deque<undo_redo_t> undo_items;
std::deque<undo_redo_t> redo_items;
bool disabled = false;
void clear_redo();
public:
undo_stack(ui_ptr ui);
void enable_undo_redo();
void disable_undo_redo();
void release();
void add_action(const QString &name, undo_redo_cb undo,
undo_redo_cb redo, std::string undo_data,
std::string redo_data, func d);
void undo();
void redo();
};

View file

@ -15,6 +15,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/ ******************************************************************************/
#include "properties-view.hpp"
#include "window-namedialog.hpp" #include "window-namedialog.hpp"
#include "window-basic-main.hpp" #include "window-basic-main.hpp"
#include "window-basic-filters.hpp" #include "window-basic-filters.hpp"
@ -23,9 +24,13 @@
#include "visibility-item-widget.hpp" #include "visibility-item-widget.hpp"
#include "item-widget-helpers.hpp" #include "item-widget-helpers.hpp"
#include "obs-app.hpp" #include "obs-app.hpp"
#include "undo-stack-obs.hpp"
#include <QMessageBox> #include <QMessageBox>
#include <QCloseEvent> #include <QCloseEvent>
#include <obs-data.h>
#include <obs.h>
#include <util/base.h>
#include <vector> #include <vector>
#include <string> #include <string>
#include <QMenu> #include <QMenu>
@ -202,10 +207,82 @@ void OBSBasicFilters::UpdatePropertiesView(int row, bool async)
obs_data_t *settings = obs_source_get_settings(filter); 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<obs_source_t *>(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<OBSBasic *>(App()->GetMainWindow());
main->undo_s.disable_undo_redo();
obs_source_t *source = reinterpret_cast<obs_source_t *>(vp);
obs_source_update(source, settings);
};
view = new OBSPropertiesView( view = new OBSPropertiesView(
settings, filter, settings, filter,
(PropertiesReloadCallback)obs_source_properties, (PropertiesReloadCallback)obs_source_properties,
(PropertiesUpdateCallback)obs_source_update); (PropertiesUpdateCallback)filter_change,
(PropertiesVisualUpdateCb)disabled_undo);
updatePropertiesSignal.Connect(obs_source_get_signal_handler(filter), updatePropertiesSignal.Connect(obs_source_get_signal_handler(filter),
"update_properties", "update_properties",
@ -240,6 +317,7 @@ void OBSBasicFilters::AddFilter(OBSSource filter, bool focus)
list->addItem(item); list->addItem(item);
if (focus) if (focus)
list->setCurrentItem(item); list->setCurrentItem(item);
SetupVisibilityItem(list, item, filter); SetupVisibilityItem(list, item, filter);
} }
@ -486,6 +564,68 @@ void OBSBasicFilters::AddNewFilter(const char *id)
return; 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<OBSBasic *>(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<OBSBasic *>(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<OBSBasic *>(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_t *filter =
obs_source_create(id, name.c_str(), nullptr, nullptr); obs_source_create(id, name.c_str(), nullptr, nullptr);
if (filter) { if (filter) {
@ -674,8 +814,75 @@ void OBSBasicFilters::on_removeEffectFilter_clicked()
{ {
OBSSource filter = GetFilter(ui->effectFilters->currentRow(), false); OBSSource filter = GetFilter(ui->effectFilters->currentRow(), false);
if (filter) { 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<OBSBasic *>(
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<OBSBasic *>(
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<OBSBasic *>(
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_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())); listItem->setText(QT_UTF8(name.c_str()));
obs_source_set_name(filter, name.c_str()); obs_source_set_name(filter, name.c_str());
std::string scene_name = obs_source_get_name(
reinterpret_cast<OBSBasic *>(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<OBSBasic *>(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<OBSBasic *>(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()); listItem->setText(QString());

View file

@ -151,17 +151,55 @@ bool OBSBasic::AddSceneCollection(bool create_new, const QString &qname)
} }
} }
auto new_collection = [this, create_new](const std::string &file,
const std::string &name) {
SaveProjectNow(); SaveProjectNow();
config_set_string(App()->GlobalConfig(), "Basic", "SceneCollection", config_set_string(App()->GlobalConfig(), "Basic",
name.c_str()); "SceneCollection", name.c_str());
config_set_string(App()->GlobalConfig(), "Basic", "SceneCollectionFile", config_set_string(App()->GlobalConfig(), "Basic",
file.c_str()); "SceneCollectionFile", file.c_str());
if (create_new) { if (create_new) {
CreateDefaultScene(false); CreateDefaultScene(false);
} }
SaveProjectNow(); SaveProjectNow();
RefreshSceneCollections(); RefreshSceneCollections();
};
new_collection(file, name);
auto undo = [this, oldName = name](const std::string &data) {
std::string newPath;
auto cb = [&](const char *name, const char *filePath) {
if (strcmp(oldName.c_str(), name) != 0) {
newPath = filePath;
return false;
}
return true;
};
EnumSceneCollections(cb);
char path[512];
int ret = GetConfigPath(path, 512, "obs-studio/basic/scenes/");
if (ret <= 0) {
blog(LOG_WARNING,
"Failed to get scene collection config path");
return;
}
std::string file = path + data + ".json";
os_unlink(file.c_str());
file += ".bak";
os_unlink(file.c_str());
Load(newPath.c_str());
RefreshSceneCollections();
};
auto redo = [new_collection, file, name](const std::string &) {
new_collection(file, name);
};
undo_s.add_action(QTStr("Undo.Add").arg(name.c_str()), undo, redo, file,
"", NULL);
blog(LOG_INFO, "Added scene collection '%s' (%s, %s.json)", blog(LOG_INFO, "Added scene collection '%s' (%s, %s.json)",
name.c_str(), create_new ? "clean" : "duplicate", file.c_str()); name.c_str(), create_new ? "clean" : "duplicate", file.c_str());
@ -245,11 +283,13 @@ void OBSBasic::on_actionRenameSceneCollection_triggered()
{ {
std::string name; std::string name;
std::string file; std::string file;
std::string oname;
std::string oldFile = config_get_string(App()->GlobalConfig(), "Basic", std::string oldFile = config_get_string(App()->GlobalConfig(), "Basic",
"SceneCollectionFile"); "SceneCollectionFile");
const char *oldName = config_get_string(App()->GlobalConfig(), "Basic", const char *oldName = config_get_string(App()->GlobalConfig(), "Basic",
"SceneCollection"); "SceneCollection");
oname = std::string(oldName);
bool success = GetSceneCollectionName(this, name, file, oldName); bool success = GetSceneCollectionName(this, name, file, oldName);
if (!success) if (!success)
@ -268,6 +308,59 @@ void OBSBasic::on_actionRenameSceneCollection_triggered()
return; return;
} }
auto undo = [name = oname, file = oldFile, of = file,
this](const std::string &) {
config_set_string(App()->GlobalConfig(), "Basic",
"SceneCollection", name.c_str());
config_set_string(App()->GlobalConfig(), "Basic",
"SceneCollectionFile", file.c_str());
SaveProjectNow();
char path[512];
int ret = GetConfigPath(path, 512, "obs-studio/basic/scenes/");
if (ret <= 0) {
blog(LOG_WARNING,
"Failed to get scene collection config path");
return;
}
std::string oldFile = of;
oldFile.insert(0, path);
oldFile += ".json";
os_unlink(oldFile.c_str());
oldFile += ".bak";
os_unlink(oldFile.c_str());
UpdateTitleBar();
RefreshSceneCollections();
};
auto redo = [of = oldFile, name, file, this](const std::string &) {
config_set_string(App()->GlobalConfig(), "Basic",
"SceneCollection", name.c_str());
config_set_string(App()->GlobalConfig(), "Basic",
"SceneCollectionFile", file.c_str());
SaveProjectNow();
char path[512];
int ret = GetConfigPath(path, 512, "obs-studio/basic/scenes/");
if (ret <= 0) {
blog(LOG_WARNING,
"Failed to get scene collection config path");
return;
}
std::string oldFile = of;
oldFile.insert(0, path);
oldFile += ".json";
os_unlink(oldFile.c_str());
oldFile += ".bak";
os_unlink(oldFile.c_str());
UpdateTitleBar();
RefreshSceneCollections();
};
oldFile.insert(0, path); oldFile.insert(0, path);
oldFile += ".json"; oldFile += ".json";
os_unlink(oldFile.c_str()); os_unlink(oldFile.c_str());
@ -282,6 +375,9 @@ void OBSBasic::on_actionRenameSceneCollection_triggered()
UpdateTitleBar(); UpdateTitleBar();
RefreshSceneCollections(); RefreshSceneCollections();
undo_s.add_action(QTStr("Undo.Rename").arg(name.c_str()), undo, redo,
"", "", NULL);
if (api) { if (api) {
api->on_event(OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED); api->on_event(OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED);
api->on_event(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED); api->on_event(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED);
@ -331,6 +427,32 @@ void OBSBasic::on_actionRemoveSceneCollection_triggered()
oldFile.insert(0, path); oldFile.insert(0, path);
oldFile += ".json"; oldFile += ".json";
obs_data_t *data =
obs_data_create_from_json_file_safe(oldFile.c_str(), "bak");
obs_data_set_string(data, "undo_filename", oldFile.c_str());
auto undo = [this](const std::string &data) {
obs_data_t *dat = obs_data_create_from_json(data.c_str());
LoadData(dat, obs_data_get_string(dat, "undo_filename"));
SaveProjectNow();
RefreshSceneCollections();
obs_data_release(dat);
};
auto redo = [this, of = oldFile, newPath](const std::string &) {
std::string oldFile = of;
os_unlink(oldFile.c_str());
oldFile += ".bak";
os_unlink(oldFile.c_str());
Load(newPath.c_str());
RefreshSceneCollections();
};
std::string undo_data = std::string(obs_data_get_json(data));
undo_s.add_action(QTStr("Undo.Delete").arg(oldName.c_str()), undo, redo,
undo_data, "", NULL);
obs_data_release(data);
os_unlink(oldFile.c_str()); os_unlink(oldFile.c_str());
oldFile += ".bak"; oldFile += ".bak";
os_unlink(oldFile.c_str()); os_unlink(oldFile.c_str());
@ -410,6 +532,9 @@ void OBSBasic::ChangeSceneCollection()
const char *oldName = config_get_string(App()->GlobalConfig(), "Basic", const char *oldName = config_get_string(App()->GlobalConfig(), "Basic",
"SceneCollection"); "SceneCollection");
std::string oldFile = std::string(config_get_string(
App()->GlobalConfig(), "Basic", "SceneCollectionFile"));
if (action->text().compare(QT_UTF8(oldName)) == 0) { if (action->text().compare(QT_UTF8(oldName)) == 0) {
action->setChecked(true); action->setChecked(true);
return; return;
@ -431,6 +556,29 @@ void OBSBasic::ChangeSceneCollection()
UpdateTitleBar(); UpdateTitleBar();
auto undo = [this, fn = std::string(oldFile)](const std::string &) {
string fileName = fn;
char path[512];
int ret = GetConfigPath(path, 512, "obs-studio/basic/scenes/");
if (ret <= 0) {
blog(LOG_WARNING,
"Failed to get scene collection config path");
return;
}
fileName.insert(0, path);
fileName += ".json";
Load(fileName.c_str());
RefreshSceneCollections();
};
auto redo = [this, fileName](const std::string &) {
Load(fileName.c_str());
RefreshSceneCollections();
};
undo_s.add_action(QTStr("Undo.SceneCollection.Switch").arg(newName),
undo, redo, "", "", NULL);
if (api) if (api)
api->on_event(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED); api->on_event(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED);
} }

View file

@ -17,7 +17,10 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/ ******************************************************************************/
#include <cstddef>
#include <ctime> #include <ctime>
#include <obs-data.h>
#include <obs.h>
#include <obs.hpp> #include <obs.hpp>
#include <QGuiApplication> #include <QGuiApplication>
#include <QMessageBox> #include <QMessageBox>
@ -58,6 +61,7 @@
#include "remote-text.hpp" #include "remote-text.hpp"
#include "ui-validation.hpp" #include "ui-validation.hpp"
#include "media-controls.hpp" #include "media-controls.hpp"
#include "undo-stack-obs.hpp"
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>
@ -203,7 +207,7 @@ extern void RegisterTwitchAuth();
extern void RegisterRestreamAuth(); extern void RegisterRestreamAuth();
OBSBasic::OBSBasic(QWidget *parent) OBSBasic::OBSBasic(QWidget *parent)
: OBSMainWindow(parent), ui(new Ui::OBSBasic) : OBSMainWindow(parent), undo_s(ui), ui(new Ui::OBSBasic)
{ {
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
qRegisterMetaTypeStreamOperators<SignalContainer<OBSScene>>( qRegisterMetaTypeStreamOperators<SignalContainer<OBSScene>>(
@ -366,6 +370,16 @@ OBSBasic::OBSBasic(QWidget *parent)
assignDockToggle(ui->controlsDock, ui->toggleControls); assignDockToggle(ui->controlsDock, ui->toggleControls);
assignDockToggle(statsDock, ui->toggleStats); assignDockToggle(statsDock, ui->toggleStats);
// Register shortcuts for Undo/Redo
ui->actionMainUndo->setShortcut(Qt::CTRL + Qt::Key_Z);
QList<QKeySequence> shrt;
shrt << QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Z)
<< QKeySequence(Qt::CTRL + Qt::Key_Y);
ui->actionMainRedo->setShortcuts(shrt);
ui->actionMainUndo->setShortcutContext(Qt::ApplicationShortcut);
ui->actionMainRedo->setShortcutContext(Qt::ApplicationShortcut);
//hide all docking panes //hide all docking panes
ui->toggleScenes->setChecked(false); ui->toggleScenes->setChecked(false);
ui->toggleSources->setChecked(false); ui->toggleSources->setChecked(false);
@ -922,6 +936,11 @@ void OBSBasic::Load(const char *file)
return; return;
} }
LoadData(data, file);
}
void OBSBasic::LoadData(obs_data_t *data, const char *file)
{
ClearSceneData(); ClearSceneData();
InitDefaultTransitions(); InitDefaultTransitions();
ClearContextBar(); ClearContextBar();
@ -3622,6 +3641,33 @@ void OBSBasic::DuplicateSelectedScene()
OBS_SCENE_DUP_REFS); OBS_SCENE_DUP_REFS);
source = obs_scene_get_source(scene); source = obs_scene_get_source(scene);
SetCurrentScene(source, true); SetCurrentScene(source, true);
auto undo = [](const std::string &data) {
obs_source_t *source =
obs_get_source_by_name(data.c_str());
obs_source_remove(source);
obs_source_release(source);
};
auto redo = [this, name](const std::string &data) {
obs_source_t *source =
obs_get_source_by_name(data.c_str());
obs_scene_t *scene = obs_scene_from_source(source);
obs_source_release(source);
scene = obs_scene_duplicate(scene, name.c_str(),
OBS_SCENE_DUP_REFS);
source = obs_scene_get_source(scene);
SetCurrentScene(source, true);
obs_scene_release(scene);
};
undo_s.add_action(
QTStr("Undo.Scene.Duplicate")
.arg(obs_source_get_name(source)),
undo, redo, obs_source_get_name(source),
obs_source_get_name(obs_scene_get_source(curScene)),
NULL);
obs_scene_release(scene); obs_scene_release(scene);
break; break;
@ -3631,15 +3677,107 @@ void OBSBasic::DuplicateSelectedScene()
void OBSBasic::RemoveSelectedScene() void OBSBasic::RemoveSelectedScene()
{ {
OBSScene scene = GetCurrentScene(); OBSScene scene = GetCurrentScene();
if (scene) {
obs_source_t *source = obs_scene_get_source(scene); obs_source_t *source = obs_scene_get_source(scene);
if (QueryRemoveSource(source)) {
OBSSource curProgramScene = OBSGetStrongRef(programScene);
if (source && QueryRemoveSource(source)) {
vector<std::string> item_ids;
obs_data_t *wrapper = obs_save_source(source);
obs_data_array_t *arr = obs_data_array_create();
struct wrap {
obs_data_array_t *arr;
vector<std::string> &items;
};
wrap passthrough = {arr, item_ids};
obs_scene_enum_items(
scene,
[](obs_scene_t *, obs_sceneitem_t *item,
void *vp_wrap) {
wrap *passthrough = (wrap *)vp_wrap;
passthrough->items.push_back(obs_source_get_name(
obs_sceneitem_get_source(item)));
obs_data_array_t *arr = passthrough->arr;
obs_sceneitem_save(item, arr);
obs_source_addref(
obs_sceneitem_get_source(item));
return true;
},
(void *)&passthrough);
obs_data_array_t *list_order = SaveSceneListOrder();
obs_data_set_array(wrapper, "arr", arr);
obs_data_set_array(wrapper, "list_order", list_order);
obs_data_set_string(wrapper, "name",
obs_source_get_name(source));
auto d = [item_ids](bool remove_ref) {
for (auto &item : item_ids) {
obs_source_t *source =
obs_get_source_by_name(item.c_str());
blog(LOG_INFO, "%s", item.c_str());
if (remove_ref) {
obs_source_release(source);
obs_source_release(source);
}
}
};
auto undo = [this, d](const std::string &data) {
obs_data_t *dat =
obs_data_create_from_json(data.c_str());
obs_source_release(obs_load_source(dat));
obs_data_array_t *arr = obs_data_get_array(dat, "arr");
obs_data_array_t *list_order =
obs_data_get_array(dat, "list_order");
const char *sname = obs_data_get_string(dat, "name");
obs_source_t *source = obs_get_source_by_name(sname);
obs_scene_t *scene = obs_scene_from_source(source);
obs_sceneitems_add(scene, arr);
LoadSceneListOrder(list_order);
SetCurrentScene(source);
obs_data_release(dat);
obs_data_array_release(arr);
obs_data_array_release(list_order);
obs_source_release(source);
d(true);
};
obs_data_t *rwrapper = obs_data_create();
obs_data_set_string(rwrapper, "name",
obs_source_get_name(source));
auto redo = [d](const std::string &data) {
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, "name"));
obs_source_remove(source); obs_source_remove(source);
obs_source_release(source);
obs_data_release(dat);
d(false);
};
std::string undo_data = obs_data_get_json(wrapper);
std::string redo_data = obs_data_get_json(wrapper);
undo_s.add_action(
QTStr("Undo.Delete").arg(obs_source_get_name(source)),
undo, redo, undo_data, redo_data, [d](bool undo) {
if (undo) {
d(true);
}
});
obs_source_remove(source);
obs_data_release(wrapper);
obs_data_release(rwrapper);
obs_data_array_release(arr);
obs_data_array_release(list_order);
if (api) if (api)
api->on_event( api->on_event(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED);
OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED);
}
} }
} }
@ -4330,6 +4468,7 @@ void OBSBasic::closeEvent(QCloseEvent *event)
/* Clear all scene data (dialogs, widgets, widget sub-items, scenes, /* Clear all scene data (dialogs, widgets, widget sub-items, scenes,
* sources, etc) so that all references are released before shutdown */ * sources, etc) so that all references are released before shutdown */
undo_s.release();
ClearSceneData(); ClearSceneData();
App()->quit(); App()->quit();
@ -4435,6 +4574,47 @@ void OBSBasic::on_action_Settings_triggered()
} }
} }
void save_audio_source(int channel, obs_data_t *save)
{
obs_source_t *source = obs_get_output_source(channel);
if (!source)
return;
obs_data_t *obj = obs_data_create();
obs_data_set_double(obj, "vol", obs_source_get_volume(source));
obs_data_set_double(obj, "balance",
obs_source_get_balance_value(source));
obs_data_set_double(obj, "mixers", obs_source_get_audio_mixers(source));
obs_data_set_double(obj, "sync", obs_source_get_sync_offset(source));
obs_data_set_double(obj, "flags", obs_source_get_flags(source));
obs_data_set_obj(save, std::to_string(channel).c_str(), obj);
obs_data_release(obj);
obs_source_release(source);
}
void load_audio_source(int channel, obs_data_t *data)
{
obs_source_t *source = obs_get_output_source(channel);
if (!source)
return;
obs_data_t *save =
obs_data_get_obj(data, std::to_string(channel).c_str());
obs_source_set_volume(source, obs_data_get_double(save, "vol"));
obs_source_set_balance_value(source,
obs_data_get_double(save, "balance"));
obs_source_set_audio_mixers(source,
obs_data_get_double(save, "mixers"));
obs_source_set_sync_offset(source, obs_data_get_double(save, "sync"));
obs_source_set_flags(source, obs_data_get_double(save, "flags"));
obs_data_release(save);
obs_source_release(source);
}
void OBSBasic::on_actionAdvAudioProperties_triggered() void OBSBasic::on_actionAdvAudioProperties_triggered()
{ {
if (advAudioWindow != nullptr) { if (advAudioWindow != nullptr) {
@ -4452,6 +4632,53 @@ void OBSBasic::on_actionAdvAudioProperties_triggered()
connect(advAudioWindow, SIGNAL(destroyed()), this, connect(advAudioWindow, SIGNAL(destroyed()), this,
SLOT(AdvAudioPropsDestroyed())); SLOT(AdvAudioPropsDestroyed()));
obs_data_t *wrapper = obs_data_create();
save_audio_source(1, wrapper);
save_audio_source(2, wrapper);
save_audio_source(3, wrapper);
save_audio_source(4, wrapper);
save_audio_source(5, wrapper);
save_audio_source(6, wrapper);
std::string undo_data(obs_data_get_json(wrapper));
connect(advAudioWindow, &QDialog::finished, [this, undo_data]() {
auto undo_redo = [](const std::string &data) {
obs_data_t *audio_data =
obs_data_create_from_json(data.c_str());
load_audio_source(1, audio_data);
load_audio_source(2, audio_data);
load_audio_source(3, audio_data);
load_audio_source(4, audio_data);
load_audio_source(5, audio_data);
load_audio_source(6, audio_data);
obs_data_release(audio_data);
};
obs_data_t *wrapper = obs_data_create();
save_audio_source(1, wrapper);
save_audio_source(2, wrapper);
save_audio_source(3, wrapper);
save_audio_source(4, wrapper);
save_audio_source(5, wrapper);
save_audio_source(6, wrapper);
std::string redo_data(obs_data_get_json(wrapper));
if (undo_data.compare(redo_data) != 0)
undo_s.add_action(QTStr("Undo.Audio"), undo_redo,
undo_redo, undo_data, redo_data,
NULL);
obs_data_release(wrapper);
});
obs_data_release(wrapper);
} }
void OBSBasic::AdvAudioPropsClicked() void OBSBasic::AdvAudioPropsClicked()
@ -4686,20 +4913,34 @@ void OBSBasic::on_actionAddScene_triggered()
return; return;
} }
auto undo_fn = [](const std::string &data) {
obs_source_t *t = obs_get_source_by_name(data.c_str());
if (t) {
obs_source_release(t);
obs_source_remove(t);
}
};
auto redo_fn = [this](const std::string &data) {
obs_scene_t *scene = obs_scene_create(data.c_str());
obs_source_t *source = obs_scene_get_source(scene);
SetCurrentScene(source);
obs_scene_release(scene);
};
undo_s.add_action(QTStr("Undo.Add").arg(QString(name.c_str())),
undo_fn, redo_fn, name, name, NULL);
obs_scene_t *scene = obs_scene_create(name.c_str()); obs_scene_t *scene = obs_scene_create(name.c_str());
source = obs_scene_get_source(scene); source = obs_scene_get_source(scene);
SetCurrentScene(source); SetCurrentScene(source);
RefreshSources(scene);
obs_scene_release(scene); obs_scene_release(scene);
} }
} }
void OBSBasic::on_actionRemoveScene_triggered() void OBSBasic::on_actionRemoveScene_triggered()
{ {
OBSScene scene = GetCurrentScene(); RemoveSelectedScene();
obs_source_t *source = obs_scene_get_source(scene);
if (source && QueryRemoveSource(source))
obs_source_remove(source);
} }
void OBSBasic::ChangeSceneIndex(bool relative, int offset, int invalidIdx) void OBSBasic::ChangeSceneIndex(bool relative, int offset, int invalidIdx)
@ -5114,12 +5355,13 @@ void OBSBasic::on_scenes_itemDoubleClicked(QListWidgetItem *witem)
void OBSBasic::AddSource(const char *id) void OBSBasic::AddSource(const char *id)
{ {
if (id && *id) { if (id && *id) {
OBSBasicSourceSelect sourceSelect(this, id); OBSBasicSourceSelect sourceSelect(this, id, undo_s);
sourceSelect.exec(); sourceSelect.exec();
if (sourceSelect.newSource && strcmp(id, "group") != 0) if (sourceSelect.newSource && strcmp(id, "group") != 0) {
CreatePropertiesWindow(sourceSelect.newSource); CreatePropertiesWindow(sourceSelect.newSource);
} }
} }
}
QMenu *OBSBasic::CreateAddSourcePopupMenu() QMenu *OBSBasic::CreateAddSourcePopupMenu()
{ {
@ -5258,9 +5500,11 @@ void OBSBasic::on_actionRemoveSource_triggered()
if (!items.size()) if (!items.size())
return; return;
auto removeMultiple = [this](size_t count) { bool confirmed = false;
if (items.size() > 1) {
QString text = QTStr("ConfirmRemove.TextMultiple") QString text = QTStr("ConfirmRemove.TextMultiple")
.arg(QString::number(count)); .arg(QString::number(items.size()));
QMessageBox remove_items(this); QMessageBox remove_items(this);
remove_items.setText(text); remove_items.setText(text);
@ -5272,22 +5516,140 @@ void OBSBasic::on_actionRemoveSource_triggered()
remove_items.setWindowTitle(QTStr("ConfirmRemove.Title")); remove_items.setWindowTitle(QTStr("ConfirmRemove.Title"));
remove_items.exec(); remove_items.exec();
return Yes == remove_items.clickedButton(); confirmed = remove_items.clickedButton();
}; } else {
if (items.size() == 1) {
OBSSceneItem &item = items[0]; OBSSceneItem &item = items[0];
obs_source_t *source = obs_sceneitem_get_source(item); obs_source_t *source = obs_sceneitem_get_source(item);
if (source && QueryRemoveSource(source)) if (source && QueryRemoveSource(source))
confirmed = true;
}
if (!confirmed)
return;
struct source_save {
std::string name;
std::string scene_name;
int pos;
bool in_group = false;
int64_t group_id;
};
vector<source_save> item_save;
obs_data_t *wrapper = obs_data_create();
obs_data_array_t *data = obs_data_array_create();
for (const auto &item : items) {
obs_sceneitem_save(item, data);
obs_source_t *source = obs_sceneitem_get_source(item);
obs_source_addref(source);
obs_source_set_hidden(source, true);
obs_sceneitem_t *grp =
obs_sceneitem_get_group(GetCurrentScene(), item);
obs_scene_t *scene = obs_sceneitem_get_scene(item);
source_save save = {
obs_source_get_name(source),
obs_source_get_name(obs_scene_get_source(scene)),
obs_sceneitem_get_order_position(item),
grp ? true : false, obs_sceneitem_get_id(grp)};
item_save.push_back(save);
}
obs_scene_t *scene = GetCurrentScene();
const char *name = obs_source_get_name(obs_scene_get_source(scene));
obs_data_set_array(wrapper, "data_array", data);
obs_data_set_string(wrapper, "name", name);
std::string undo_data(obs_data_get_json(wrapper));
auto undo_fn = [this, item_save](const std::string &data) {
obs_data_t *dat = obs_data_create_from_json(data.c_str());
obs_data_array_t *sources_data =
obs_data_get_array(dat, "data_array");
const char *name = obs_data_get_string(dat, "name");
obs_source_t *src = obs_get_source_by_name(name);
obs_scene_t *scene = obs_scene_from_source(src);
obs_sceneitems_add(scene, sources_data);
SetCurrentScene(scene);
for (const auto &save : item_save) {
obs_source_t *source =
obs_get_source_by_name(save.name.c_str());
obs_source_set_hidden(source, false);
if (save.in_group) {
obs_sceneitem_t *grp =
obs_scene_find_sceneitem_by_id(
scene, save.group_id);
obs_sceneitem_t *item =
obs_scene_sceneitem_from_source(scene,
source);
obs_sceneitem_group_add_item(grp, item);
obs_sceneitem_set_order_position(item,
save.pos);
obs_sceneitem_release(item);
}
obs_source_release(source);
obs_source_release(source);
}
obs_source_release(src);
obs_data_array_release(sources_data);
obs_data_release(dat);
};
auto redo_fn = [item_save](const std::string &) {
for (const auto &save : item_save) {
obs_source_t *source =
obs_get_source_by_name(save.name.c_str());
obs_source_t *scene_source =
obs_get_source_by_name(save.scene_name.c_str());
obs_scene_t *scene =
obs_scene_from_source(scene_source);
if (!scene)
scene = obs_group_from_source(scene_source);
obs_sceneitem_t *item =
obs_scene_sceneitem_from_source(scene, source);
obs_sceneitem_remove(item); obs_sceneitem_remove(item);
} else { obs_source_set_hidden(source, true);
if (removeMultiple(items.size())) {
obs_sceneitem_release(item);
obs_source_release(scene_source);
/* usually want to release source, but redo needs to add a reference to keep alive */
}
};
auto d = [item_save](bool is_undo) {
if (!is_undo)
return;
for (const auto &item : item_save) {
obs_source_t *source =
obs_get_source_by_name(item.name.c_str());
obs_source_release(source);
obs_source_release(source);
}
};
QString action_name;
if (items.size() > 1)
action_name = QTStr("Undo.Sources.Multi")
.arg(QString::number(items.size()));
else
action_name =
QTStr("Undo.Delete")
.arg(QString(obs_source_get_name(
obs_sceneitem_get_source(items[0]))));
undo_s.add_action(action_name, undo_fn, redo_fn, undo_data, "", d);
obs_data_array_release(data);
obs_data_release(wrapper);
for (auto &item : items) for (auto &item : items)
obs_sceneitem_remove(item); obs_sceneitem_remove(item);
} }
}
}
void OBSBasic::on_actionInteract_triggered() void OBSBasic::on_actionInteract_triggered()
{ {
@ -5524,6 +5886,27 @@ static void RenameListItem(OBSBasic *parent, QListWidget *listWidget,
obs_source_release(foundSource); obs_source_release(foundSource);
} else { } else {
auto undo = [prev = std::string(prevName)](
const std::string &data) {
obs_source_t *source =
obs_get_source_by_name(data.c_str());
obs_source_set_name(source, prev.c_str());
obs_source_release(source);
};
auto redo = [name](const std::string &data) {
obs_source_t *source =
obs_get_source_by_name(data.c_str());
obs_source_set_name(source, name.c_str());
obs_source_release(source);
};
std::string undo_data(name);
std::string redo_data(prevName);
parent->undo_s.add_action(
QTStr("Undo.Rename").arg(name.c_str()), undo, redo,
undo_data, redo_data, NULL);
listItem->setText(QT_UTF8(name.c_str())); listItem->setText(QT_UTF8(name.c_str()));
obs_source_set_name(source, name.c_str()); obs_source_set_name(source, name.c_str());
} }
@ -6739,8 +7122,23 @@ void OBSBasic::on_actionCopyTransform_triggered()
ui->actionPasteTransform->setEnabled(true); ui->actionPasteTransform->setEnabled(true);
} }
void undo_redo(const std::string &data)
{
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, "scene_name"));
reinterpret_cast<OBSBasic *>(App()->GetMainWindow())
->SetCurrentScene(source);
obs_source_release(source);
obs_data_release(dat);
obs_scene_load_transform_states(data.c_str());
}
void OBSBasic::on_actionPasteTransform_triggered() void OBSBasic::on_actionPasteTransform_triggered()
{ {
obs_data_t *wrapper =
obs_scene_save_transform_states(GetCurrentScene(), false);
auto func = [](obs_scene_t *scene, obs_sceneitem_t *item, void *param) { auto func = [](obs_scene_t *scene, obs_sceneitem_t *item, void *param) {
if (!obs_sceneitem_selected(item)) if (!obs_sceneitem_selected(item))
return true; return true;
@ -6756,6 +7154,19 @@ void OBSBasic::on_actionPasteTransform_triggered()
}; };
obs_scene_enum_items(GetCurrentScene(), func, nullptr); obs_scene_enum_items(GetCurrentScene(), func, nullptr);
obs_data_t *rwrapper =
obs_scene_save_transform_states(GetCurrentScene(), false);
std::string undo_data(obs_data_get_json(wrapper));
std::string redo_data(obs_data_get_json(rwrapper));
undo_s.add_action(
QTStr("Undo.Transform.Paste")
.arg(obs_source_get_name(GetCurrentSceneSource())),
undo_redo, undo_redo, undo_data, redo_data, NULL);
obs_data_release(wrapper);
obs_data_release(rwrapper);
} }
static bool reset_tr(obs_scene_t *scene, obs_sceneitem_t *item, void *param) static bool reset_tr(obs_scene_t *scene, obs_sceneitem_t *item, void *param)
@ -6791,6 +7202,22 @@ static bool reset_tr(obs_scene_t *scene, obs_sceneitem_t *item, void *param)
void OBSBasic::on_actionResetTransform_triggered() void OBSBasic::on_actionResetTransform_triggered()
{ {
obs_scene_t *scene = GetCurrentScene();
obs_data_t *wrapper = obs_scene_save_transform_states(scene, false);
obs_scene_enum_items(scene, reset_tr, nullptr);
obs_data_t *rwrapper = obs_scene_save_transform_states(scene, false);
std::string undo_data(obs_data_get_json(wrapper));
std::string redo_data(obs_data_get_json(rwrapper));
undo_s.add_action(
QTStr("Undo.Transform.Reset")
.arg(obs_source_get_name(obs_scene_get_source(scene))),
undo_redo, undo_redo, undo_data, redo_data, NULL);
obs_data_release(wrapper);
obs_data_release(rwrapper);
obs_scene_enum_items(GetCurrentScene(), reset_tr, nullptr); obs_scene_enum_items(GetCurrentScene(), reset_tr, nullptr);
} }
@ -6868,19 +7295,61 @@ static bool RotateSelectedSources(obs_scene_t *scene, obs_sceneitem_t *item,
void OBSBasic::on_actionRotate90CW_triggered() void OBSBasic::on_actionRotate90CW_triggered()
{ {
float f90CW = 90.0f; float f90CW = 90.0f;
obs_data_t *wrapper =
obs_scene_save_transform_states(GetCurrentScene(), false);
obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CW); obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CW);
obs_data_t *rwrapper =
obs_scene_save_transform_states(GetCurrentScene(), false);
std::string undo_data(obs_data_get_json(wrapper));
std::string redo_data(obs_data_get_json(rwrapper));
undo_s.add_action(QTStr("Undo.Transform.Rotate")
.arg(obs_source_get_name(obs_scene_get_source(
GetCurrentScene()))),
undo_redo, undo_redo, undo_data, redo_data, NULL);
obs_data_release(wrapper);
obs_data_release(rwrapper);
} }
void OBSBasic::on_actionRotate90CCW_triggered() void OBSBasic::on_actionRotate90CCW_triggered()
{ {
float f90CCW = -90.0f; float f90CCW = -90.0f;
obs_data_t *wrapper =
obs_scene_save_transform_states(GetCurrentScene(), false);
obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CCW); obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CCW);
obs_data_t *rwrapper =
obs_scene_save_transform_states(GetCurrentScene(), false);
std::string undo_data(obs_data_get_json(wrapper));
std::string redo_data(obs_data_get_json(rwrapper));
undo_s.add_action(QTStr("Undo.Transform.Rotate")
.arg(obs_source_get_name(obs_scene_get_source(
GetCurrentScene()))),
undo_redo, undo_redo, undo_data, redo_data, NULL);
obs_data_release(wrapper);
obs_data_release(rwrapper);
} }
void OBSBasic::on_actionRotate180_triggered() void OBSBasic::on_actionRotate180_triggered()
{ {
float f180 = 180.0f; float f180 = 180.0f;
obs_data_t *wrapper =
obs_scene_save_transform_states(GetCurrentScene(), false);
obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f180); obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f180);
obs_data_t *rwrapper =
obs_scene_save_transform_states(GetCurrentScene(), false);
std::string undo_data(obs_data_get_json(wrapper));
std::string redo_data(obs_data_get_json(rwrapper));
undo_s.add_action(QTStr("Undo.Transform.Rotate")
.arg(obs_source_get_name(obs_scene_get_source(
GetCurrentScene()))),
undo_redo, undo_redo, undo_data, redo_data, NULL);
obs_data_release(wrapper);
obs_data_release(rwrapper);
} }
static bool MultiplySelectedItemScale(obs_scene_t *scene, obs_sceneitem_t *item, static bool MultiplySelectedItemScale(obs_scene_t *scene, obs_sceneitem_t *item,
@ -6915,16 +7384,44 @@ void OBSBasic::on_actionFlipHorizontal_triggered()
{ {
vec2 scale; vec2 scale;
vec2_set(&scale, -1.0f, 1.0f); vec2_set(&scale, -1.0f, 1.0f);
obs_data_t *wrapper =
obs_scene_save_transform_states(GetCurrentScene(), false);
obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale,
&scale); &scale);
obs_data_t *rwrapper =
obs_scene_save_transform_states(GetCurrentScene(), false);
std::string undo_data(obs_data_get_json(wrapper));
std::string redo_data(obs_data_get_json(rwrapper));
undo_s.add_action(QTStr("Undo.Transform.HFlip")
.arg(obs_source_get_name(obs_scene_get_source(
GetCurrentScene()))),
undo_redo, undo_redo, undo_data, redo_data, NULL);
obs_data_release(wrapper);
obs_data_release(rwrapper);
} }
void OBSBasic::on_actionFlipVertical_triggered() void OBSBasic::on_actionFlipVertical_triggered()
{ {
vec2 scale; vec2 scale;
vec2_set(&scale, 1.0f, -1.0f); vec2_set(&scale, 1.0f, -1.0f);
obs_data_t *wrapper =
obs_scene_save_transform_states(GetCurrentScene(), false);
obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale,
&scale); &scale);
obs_data_t *rwrapper =
obs_scene_save_transform_states(GetCurrentScene(), false);
std::string undo_data(obs_data_get_json(wrapper));
std::string redo_data(obs_data_get_json(rwrapper));
undo_s.add_action(QTStr("Undo.Transform.VFlip")
.arg(obs_source_get_name(obs_scene_get_source(
GetCurrentScene()))),
undo_redo, undo_redo, undo_data, redo_data, NULL);
obs_data_release(wrapper);
obs_data_release(rwrapper);
} }
static bool CenterAlignSelectedItems(obs_scene_t *scene, obs_sceneitem_t *item, static bool CenterAlignSelectedItems(obs_scene_t *scene, obs_sceneitem_t *item,
@ -6964,15 +7461,43 @@ static bool CenterAlignSelectedItems(obs_scene_t *scene, obs_sceneitem_t *item,
void OBSBasic::on_actionFitToScreen_triggered() void OBSBasic::on_actionFitToScreen_triggered()
{ {
obs_bounds_type boundsType = OBS_BOUNDS_SCALE_INNER; obs_bounds_type boundsType = OBS_BOUNDS_SCALE_INNER;
obs_data_t *wrapper =
obs_scene_save_transform_states(GetCurrentScene(), false);
obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems,
&boundsType); &boundsType);
obs_data_t *rwrapper =
obs_scene_save_transform_states(GetCurrentScene(), false);
std::string undo_data(obs_data_get_json(wrapper));
std::string redo_data(obs_data_get_json(rwrapper));
undo_s.add_action(QTStr("Undo.Transform.FitToScreen")
.arg(obs_source_get_name(obs_scene_get_source(
GetCurrentScene()))),
undo_redo, undo_redo, undo_data, redo_data, NULL);
obs_data_release(wrapper);
obs_data_release(rwrapper);
} }
void OBSBasic::on_actionStretchToScreen_triggered() void OBSBasic::on_actionStretchToScreen_triggered()
{ {
obs_bounds_type boundsType = OBS_BOUNDS_STRETCH; obs_bounds_type boundsType = OBS_BOUNDS_STRETCH;
obs_data_t *wrapper =
obs_scene_save_transform_states(GetCurrentScene(), false);
obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems,
&boundsType); &boundsType);
obs_data_t *rwrapper =
obs_scene_save_transform_states(GetCurrentScene(), false);
std::string undo_data(obs_data_get_json(wrapper));
std::string redo_data(obs_data_get_json(rwrapper));
undo_s.add_action(QTStr("Undo.Transform.StretchToScreen")
.arg(obs_source_get_name(obs_scene_get_source(
GetCurrentScene()))),
undo_redo, undo_redo, undo_data, redo_data, NULL);
obs_data_release(wrapper);
obs_data_release(rwrapper);
} }
enum class CenterType { enum class CenterType {
@ -7033,19 +7558,61 @@ static bool center_to_scene(obs_scene_t *, obs_sceneitem_t *item, void *param)
void OBSBasic::on_actionCenterToScreen_triggered() void OBSBasic::on_actionCenterToScreen_triggered()
{ {
CenterType centerType = CenterType::Scene; CenterType centerType = CenterType::Scene;
obs_data_t *wrapper =
obs_scene_save_transform_states(GetCurrentScene(), false);
obs_scene_enum_items(GetCurrentScene(), center_to_scene, &centerType); obs_scene_enum_items(GetCurrentScene(), center_to_scene, &centerType);
obs_data_t *rwrapper =
obs_scene_save_transform_states(GetCurrentScene(), false);
std::string undo_data(obs_data_get_json(wrapper));
std::string redo_data(obs_data_get_json(rwrapper));
undo_s.add_action(QTStr("Undo.Transform.Center")
.arg(obs_source_get_name(obs_scene_get_source(
GetCurrentScene()))),
undo_redo, undo_redo, undo_data, redo_data, NULL);
obs_data_release(wrapper);
obs_data_release(rwrapper);
} }
void OBSBasic::on_actionVerticalCenter_triggered() void OBSBasic::on_actionVerticalCenter_triggered()
{ {
CenterType centerType = CenterType::Vertical; CenterType centerType = CenterType::Vertical;
obs_data_t *wrapper =
obs_scene_save_transform_states(GetCurrentScene(), false);
obs_scene_enum_items(GetCurrentScene(), center_to_scene, &centerType); obs_scene_enum_items(GetCurrentScene(), center_to_scene, &centerType);
obs_data_t *rwrapper =
obs_scene_save_transform_states(GetCurrentScene(), false);
std::string undo_data(obs_data_get_json(wrapper));
std::string redo_data(obs_data_get_json(rwrapper));
undo_s.add_action(QTStr("Undo.Transform.VCenter")
.arg(obs_source_get_name(obs_scene_get_source(
GetCurrentScene()))),
undo_redo, undo_redo, undo_data, redo_data, NULL);
obs_data_release(wrapper);
obs_data_release(rwrapper);
} }
void OBSBasic::on_actionHorizontalCenter_triggered() void OBSBasic::on_actionHorizontalCenter_triggered()
{ {
CenterType centerType = CenterType::Horizontal; CenterType centerType = CenterType::Horizontal;
obs_data_t *wrapper =
obs_scene_save_transform_states(GetCurrentScene(), false);
obs_scene_enum_items(GetCurrentScene(), center_to_scene, &centerType); obs_scene_enum_items(GetCurrentScene(), center_to_scene, &centerType);
obs_data_t *rwrapper =
obs_scene_save_transform_states(GetCurrentScene(), false);
std::string undo_data(obs_data_get_json(wrapper));
std::string redo_data(obs_data_get_json(rwrapper));
undo_s.add_action(QTStr("Undo.Transform.VCenter")
.arg(obs_source_get_name(obs_scene_get_source(
GetCurrentScene()))),
undo_redo, undo_redo, undo_data, redo_data, NULL);
obs_data_release(wrapper);
obs_data_release(rwrapper);
} }
void OBSBasic::EnablePreviewDisplay(bool enable) void OBSBasic::EnablePreviewDisplay(bool enable)
@ -7136,6 +7703,46 @@ void OBSBasic::Nudge(int dist, MoveDir dir)
break; break;
} }
if (!recent_nudge) {
recent_nudge = true;
obs_data_t *wrapper = obs_scene_save_transform_states(
GetCurrentScene(), true);
std::string undo_data(obs_data_get_json(wrapper));
nudge_timer = new QTimer;
QObject::connect(
nudge_timer, &QTimer::timeout,
[this, &recent_nudge = recent_nudge, undo_data]() {
obs_data_t *rwrapper =
obs_scene_save_transform_states(
GetCurrentScene(), true);
std::string redo_data(
obs_data_get_json(rwrapper));
undo_s.add_action(
QTStr("Undo.Transform")
.arg(obs_source_get_name(
GetCurrentSceneSource())),
undo_redo, undo_redo, undo_data,
redo_data, NULL);
recent_nudge = false;
obs_data_release(rwrapper);
});
connect(nudge_timer, &QTimer::timeout, nudge_timer,
&QTimer::deleteLater);
nudge_timer->setSingleShot(true);
obs_data_release(wrapper);
}
if (nudge_timer) {
nudge_timer->stop();
nudge_timer->start(1000);
} else {
blog(LOG_ERROR, "No nudge timer!");
}
obs_scene_enum_items(GetCurrentScene(), nudge_callback, &offset); obs_scene_enum_items(GetCurrentScene(), nudge_callback, &offset);
} }
@ -7767,6 +8374,16 @@ bool OBSBasic::sysTrayMinimizeToTray()
"SysTrayMinimizeToTray"); "SysTrayMinimizeToTray");
} }
void OBSBasic::on_actionMainUndo_triggered()
{
undo_s.undo();
}
void OBSBasic::on_actionMainRedo_triggered()
{
undo_s.redo();
}
void OBSBasic::on_actionCopySource_triggered() void OBSBasic::on_actionCopySource_triggered()
{ {
copyStrings.clear(); copyStrings.clear();

View file

@ -36,6 +36,7 @@
#include "window-basic-about.hpp" #include "window-basic-about.hpp"
#include "auth-base.hpp" #include "auth-base.hpp"
#include "log-viewer.hpp" #include "log-viewer.hpp"
#include "undo-stack-obs.hpp"
#include <obs-frontend-internal.hpp> #include <obs-frontend-internal.hpp>
@ -156,6 +157,7 @@ class OBSBasic : public OBSMainWindow {
friend class OBSBasicPreview; friend class OBSBasicPreview;
friend class OBSBasicStatusBar; friend class OBSBasicStatusBar;
friend class OBSBasicSourceSelect; friend class OBSBasicSourceSelect;
friend class OBSBasicTransform;
friend class OBSBasicSettings; friend class OBSBasicSettings;
friend class Auth; friend class Auth;
friend class AutoConfig; friend class AutoConfig;
@ -166,6 +168,7 @@ class OBSBasic : public OBSMainWindow {
friend class ExtraBrowsersDelegate; friend class ExtraBrowsersDelegate;
friend class DeviceCaptureToolbar; friend class DeviceCaptureToolbar;
friend class DeviceToolbarPropertiesThread; friend class DeviceToolbarPropertiesThread;
friend class OBSBasicSourceSelect;
friend struct BasicOutputHandler; friend struct BasicOutputHandler;
friend struct OBSStudioAPI; friend struct OBSStudioAPI;
@ -220,6 +223,9 @@ private:
QPointer<QTimer> cpuUsageTimer; QPointer<QTimer> cpuUsageTimer;
QPointer<QTimer> diskFullTimer; QPointer<QTimer> diskFullTimer;
QPointer<QTimer> nudge_timer;
bool recent_nudge = false;
os_cpu_usage_info_t *cpuUsageInfo = nullptr; os_cpu_usage_info_t *cpuUsageInfo = nullptr;
OBSService service; OBSService service;
@ -308,6 +314,7 @@ private:
void UploadLog(const char *subdir, const char *file, const bool crash); void UploadLog(const char *subdir, const char *file, const bool crash);
void Save(const char *file); void Save(const char *file);
void LoadData(obs_data_t *data, const char *file);
void Load(const char *file); void Load(const char *file);
void InitHotkeys(); void InitHotkeys();
@ -609,6 +616,10 @@ public slots:
void UnpauseRecording(); void UnpauseRecording();
private slots: private slots:
void on_actionMainUndo_triggered();
void on_actionMainRedo_triggered();
void AddSceneItem(OBSSceneItem item); void AddSceneItem(OBSSceneItem item);
void AddScene(OBSSource source); void AddScene(OBSSource source);
void RemoveScene(OBSSource source); void RemoveScene(OBSSource source);
@ -741,6 +752,7 @@ private:
OBSSource prevFTBSource = nullptr; OBSSource prevFTBSource = nullptr;
public: public:
undo_stack undo_s;
OBSSource GetProgramSource(); OBSSource GetProgramSource();
OBSScene GetCurrentScene(); OBSScene GetCurrentScene();

View file

@ -36,6 +36,9 @@ OBSBasicPreview::~OBSBasicPreview()
gs_vertexbuffer_destroy(rectFill); gs_vertexbuffer_destroy(rectFill);
obs_leave_graphics(); obs_leave_graphics();
if (wrapper)
obs_data_release(wrapper);
} }
vec2 OBSBasicPreview::GetMouseEventPos(QMouseEvent *event) vec2 OBSBasicPreview::GetMouseEventPos(QMouseEvent *event)
@ -581,6 +584,11 @@ void OBSBasicPreview::mousePressEvent(QMouseEvent *event)
vec2_zero(&lastMoveOffset); vec2_zero(&lastMoveOffset);
mousePos = startPos; mousePos = startPos;
if (wrapper)
obs_data_release(wrapper);
wrapper =
obs_scene_save_transform_states(main->GetCurrentScene(), true);
changed = false;
} }
void OBSBasicPreview::UpdateCursor(uint32_t &flags) void OBSBasicPreview::UpdateCursor(uint32_t &flags)
@ -713,6 +721,41 @@ void OBSBasicPreview::mouseReleaseEvent(QMouseEvent *event)
hoveredPreviewItems.push_back(item); hoveredPreviewItems.push_back(item);
selectedItems.clear(); selectedItems.clear();
} }
OBSBasic *main = reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
obs_data_t *rwrapper =
obs_scene_save_transform_states(main->GetCurrentScene(), false);
auto undo_redo = [](const std::string &data) {
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, "scene_name"));
reinterpret_cast<OBSBasic *>(App()->GetMainWindow())
->SetCurrentScene(source);
obs_source_release(source);
obs_data_release(dat);
obs_scene_load_transform_states(data.c_str());
};
if (wrapper && rwrapper) {
std::string undo_data(obs_data_get_json(wrapper));
std::string redo_data(obs_data_get_json(rwrapper));
if (changed && undo_data.compare(redo_data) != 0)
main->undo_s.add_action(
QTStr("Undo.Transform")
.arg(obs_source_get_name(
main->GetCurrentSceneSource())),
undo_redo, undo_redo, undo_data, redo_data,
NULL);
}
if (wrapper)
obs_data_release(wrapper);
if (rwrapper)
obs_data_release(rwrapper);
wrapper = NULL;
} }
struct SelectedItemBounds { struct SelectedItemBounds {
@ -1434,6 +1477,8 @@ void OBSBasicPreview::StretchItem(const vec2 &pos)
void OBSBasicPreview::mouseMoveEvent(QMouseEvent *event) void OBSBasicPreview::mouseMoveEvent(QMouseEvent *event)
{ {
changed = true;
if (scrollMode && event->buttons() == Qt::LeftButton) { if (scrollMode && event->buttons() == Qt::LeftButton) {
scrollingOffset.x += event->x() - scrollingFrom.x; scrollingOffset.x += event->x() - scrollingFrom.x;
scrollingOffset.y += event->y() - scrollingFrom.y; scrollingOffset.y += event->y() - scrollingFrom.y;

View file

@ -106,6 +106,9 @@ private:
void ProcessClick(const vec2 &pos); void ProcessClick(const vec2 &pos);
obs_data_t *wrapper = NULL;
bool changed;
public: public:
OBSBasicPreview(QWidget *parent, OBSBasicPreview(QWidget *parent,
Qt::WindowFlags flags = Qt::WindowFlags()); Qt::WindowFlags flags = Qt::WindowFlags());

View file

@ -26,6 +26,10 @@
#include <QScreen> #include <QScreen>
#include <QWindow> #include <QWindow>
#include <QMessageBox> #include <QMessageBox>
#include <obs-data.h>
#include <obs.h>
#include <qpointer.h>
#include <util/c99defs.h>
using namespace std; using namespace std;
@ -74,14 +78,28 @@ OBSBasicProperties::OBSBasicProperties(QWidget *parent, OBSSource source_)
/* The OBSData constructor increments the reference once */ /* The OBSData constructor increments the reference once */
obs_data_release(oldSettings); 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_apply(oldSettings, settings);
obs_data_release(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<obs_source_t *>(vp);
obs_source_update(source, new_settings);
UNUSED_PARAMETER(old_settings);
UNUSED_PARAMETER(vp);
};
view = new OBSPropertiesView( view = new OBSPropertiesView(
settings, source, nd_settings, source,
(PropertiesReloadCallback)obs_source_properties, (PropertiesReloadCallback)obs_source_properties,
(PropertiesUpdateCallback)obs_source_update); (PropertiesUpdateCallback)handle_memory,
(PropertiesVisualUpdateCb)obs_source_update);
view->setMinimumHeight(150); view->setMinimumHeight(150);
preview->setMinimumSize(20, 150); preview->setMinimumSize(20, 150);
@ -341,6 +359,51 @@ void OBSBasicProperties::on_buttonBox_clicked(QAbstractButton *button)
QDialogButtonBox::ButtonRole val = buttonBox->buttonRole(button); QDialogButtonBox::ButtonRole val = buttonBox->buttonRole(button);
if (val == QDialogButtonBox::AcceptRole) { 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; acceptClicked = true;
close(); close();

View file

@ -28,6 +28,9 @@ struct AddSourceData {
bool OBSBasicSourceSelect::EnumSources(void *data, obs_source_t *source) bool OBSBasicSourceSelect::EnumSources(void *data, obs_source_t *source)
{ {
if (obs_source_is_hidden(source))
return false;
OBSBasicSourceSelect *window = OBSBasicSourceSelect *window =
static_cast<OBSBasicSourceSelect *>(data); static_cast<OBSBasicSourceSelect *>(data);
const char *name = obs_source_get_name(source); const char *name = obs_source_get_name(source);
@ -179,7 +182,7 @@ bool AddNew(QWidget *parent, const char *id, const char *name,
return false; return false;
obs_source_t *source = obs_get_source_by_name(name); obs_source_t *source = obs_get_source_by_name(name);
if (source) { if (source && parent) {
OBSMessageBox::information(parent, QTStr("NameExists.Title"), OBSMessageBox::information(parent, QTStr("NameExists.Title"),
QTStr("NameExists.Text")); QTStr("NameExists.Text"));
@ -236,6 +239,63 @@ void OBSBasicSourceSelect::on_buttonBox_accepted()
if (!AddNew(this, id, QT_TO_UTF8(ui->sourceName->text()), if (!AddNew(this, id, QT_TO_UTF8(ui->sourceName->text()),
visible, newSource)) visible, newSource))
return; return;
OBSBasic *main =
reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
std::string scene_name =
obs_source_get_name(main->GetCurrentSceneSource());
auto undo = [scene_name, main](const std::string &data) {
obs_source_t *source =
obs_get_source_by_name(data.c_str());
obs_source_release(source);
obs_source_remove(source);
obs_source_t *scene_source =
obs_get_source_by_name(scene_name.c_str());
main->SetCurrentScene(scene_source);
obs_source_release(scene_source);
main->RefreshSources(main->GetCurrentScene());
};
obs_data_t *wrapper = obs_data_create();
obs_data_set_string(wrapper, "id", id);
obs_sceneitem_t *item = obs_scene_sceneitem_from_source(
main->GetCurrentScene(), newSource);
obs_data_set_int(wrapper, "item_id",
obs_sceneitem_get_id(item));
obs_data_set_string(
wrapper, "name",
ui->sourceName->text().toUtf8().constData());
obs_data_set_bool(wrapper, "visible", visible);
auto redo = [scene_name, main](const std::string &data) {
obs_data_t *dat =
obs_data_create_from_json(data.c_str());
OBSSource source;
AddNew(NULL, obs_data_get_string(dat, "id"),
obs_data_get_string(dat, "name"),
obs_data_get_bool(dat, "visible"), source);
obs_sceneitem_t *item = obs_scene_sceneitem_from_source(
main->GetCurrentScene(), source);
obs_sceneitem_set_id(item, (int64_t)obs_data_get_int(
dat, "item_id"));
obs_source_t *scene_source =
obs_get_source_by_name(scene_name.c_str());
main->SetCurrentScene(scene_source);
obs_source_release(scene_source);
main->RefreshSources(main->GetCurrentScene());
obs_data_release(dat);
obs_sceneitem_release(item);
};
undo_s.add_action(QTStr("Undo.Add").arg(ui->sourceName->text()),
undo, redo,
std::string(obs_source_get_name(newSource)),
std::string(obs_data_get_json(wrapper)),
NULL);
obs_data_release(wrapper);
obs_sceneitem_release(item);
} }
done(DialogCode::Accepted); done(DialogCode::Accepted);
@ -261,8 +321,12 @@ template<typename T> static inline T GetOBSRef(QListWidgetItem *item)
return item->data(static_cast<int>(QtDataRole::OBSRef)).value<T>(); return item->data(static_cast<int>(QtDataRole::OBSRef)).value<T>();
} }
OBSBasicSourceSelect::OBSBasicSourceSelect(OBSBasic *parent, const char *id_) OBSBasicSourceSelect::OBSBasicSourceSelect(OBSBasic *parent, const char *id_,
: QDialog(parent), ui(new Ui::OBSBasicSourceSelect), id(id_) undo_stack &undo_s)
: QDialog(parent),
ui(new Ui::OBSBasicSourceSelect),
id(id_),
undo_s(undo_s)
{ {
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);

View file

@ -21,6 +21,7 @@
#include <memory> #include <memory>
#include "ui_OBSBasicSourceSelect.h" #include "ui_OBSBasicSourceSelect.h"
#include "undo-stack-obs.hpp"
class OBSBasic; class OBSBasic;
@ -30,6 +31,7 @@ class OBSBasicSourceSelect : public QDialog {
private: private:
std::unique_ptr<Ui::OBSBasicSourceSelect> ui; std::unique_ptr<Ui::OBSBasicSourceSelect> ui;
const char *id; const char *id;
undo_stack &undo_s;
static bool EnumSources(void *data, obs_source_t *source); static bool EnumSources(void *data, obs_source_t *source);
static bool EnumGroups(void *data, obs_source_t *source); static bool EnumGroups(void *data, obs_source_t *source);
@ -45,7 +47,8 @@ private slots:
void SourceRemoved(OBSSource source); void SourceRemoved(OBSSource source);
public: public:
OBSBasicSourceSelect(OBSBasic *parent, const char *id); OBSBasicSourceSelect(OBSBasic *parent, const char *id,
undo_stack &undo_s);
OBSSource newSource; OBSSource newSource;

View file

@ -73,10 +73,42 @@ OBSBasicTransform::OBSBasicTransform(OBSBasic *parent)
SetScene(scene); SetScene(scene);
SetItem(item); SetItem(item);
obs_data_t *wrapper = obs_scene_save_transform_states(scene, false);
undo_data = std::string(obs_data_get_json(wrapper));
obs_data_release(wrapper);
channelChangedSignal.Connect(obs_get_signal_handler(), "channel_change", channelChangedSignal.Connect(obs_get_signal_handler(), "channel_change",
OBSChannelChanged, this); OBSChannelChanged, this);
} }
OBSBasicTransform::~OBSBasicTransform()
{
obs_data_t *wrapper =
obs_scene_save_transform_states(main->GetCurrentScene(), false);
auto undo_redo = [](const std::string &data) {
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, "scene_name"));
reinterpret_cast<OBSBasic *>(App()->GetMainWindow())
->SetCurrentScene(source);
obs_source_release(source);
obs_data_release(dat);
obs_scene_load_transform_states(data.c_str());
};
std::string redo_data(obs_data_get_json(wrapper));
if (undo_data.compare(redo_data) != 0)
main->undo_s.add_action(
QTStr("Undo.Transform")
.arg(obs_source_get_name(obs_scene_get_source(
main->GetCurrentScene()))),
undo_redo, undo_redo, undo_data, redo_data, NULL);
obs_data_release(wrapper);
}
void OBSBasicTransform::SetScene(OBSScene scene) void OBSBasicTransform::SetScene(OBSScene scene)
{ {
transformSignal.Disconnect(); transformSignal.Disconnect();

View file

@ -21,6 +21,8 @@ private:
OBSSignal selectSignal; OBSSignal selectSignal;
OBSSignal deselectSignal; OBSSignal deselectSignal;
std::string undo_data;
bool ignoreTransformSignal = false; bool ignoreTransformSignal = false;
bool ignoreItemChange = false; bool ignoreItemChange = false;
@ -46,4 +48,5 @@ private slots:
public: public:
OBSBasicTransform(OBSBasic *parent); OBSBasicTransform(OBSBasic *parent);
~OBSBasicTransform();
}; };

View file

@ -298,9 +298,37 @@ Scene Item Functions
--------------------- ---------------------
.. function:: obs_sceneitem_t *obs_scene_sceneitem_from_source(obs_scene_t *scene, obs_source_t *source)
This will add a reference to the sceneitem.
:return: The sceneitem associated with a source in a scene. Returns NULL if not found.
---------------------
.. function:: void obs_sceneitem_set_id(obs_sceneitem_t *item);
Sets the unique numeric identifier of the sceneitem. This is dangerous function and should not
normally be used. It can cause errors within obs.
---------------------
.. function:: int64_t obs_sceneitem_get_id(const obs_sceneitem_t *item) .. function:: int64_t obs_sceneitem_get_id(const obs_sceneitem_t *item)
:return: The unique numeric identifier of the scene item. This is a dangerous function and should not
normally be used. It can cause errors within obs.
:return: Gets the unique numeric identifier of the scene item.
---------------------
.. function:: obs_data_t *obs_scene_save_transform_states(obs_scene_t *scene, bool all_items)
.. function:: void obs_scene_load_transform_states(oconst char *states)
Saves all the transformation states for the sceneitms in scene. When all_items is false, it
will only save selected items
:return: Data containing transformation states for all* sceneitems in scene
--------------------- ---------------------
@ -358,6 +386,12 @@ Scene Item Functions
--------------------- ---------------------
.. function:: int obs_sceneitem_get_order_position(obs_sceneitem_t *item)
:return: Gets position of sceneitem in its scene.
---------------------
.. function:: void obs_sceneitem_set_bounds_type(obs_sceneitem_t *item, enum obs_bounds_type type) .. function:: void obs_sceneitem_set_bounds_type(obs_sceneitem_t *item, enum obs_bounds_type type)
enum obs_bounds_type obs_sceneitem_get_bounds_type(const obs_sceneitem_t *item) enum obs_bounds_type obs_sceneitem_get_bounds_type(const obs_sceneitem_t *item)

View file

@ -182,6 +182,12 @@ Default Value Functions
Default values are used to determine what value will be given if a value Default values are used to determine what value will be given if a value
is not set. is not set.
.. function:: obs_data_t *obs_data_get_defaults(obs_data_t *data);
:return: obs_data_t * with all default values (recursively for all objects as well).
-----------------------
.. function:: void obs_data_set_default_string(obs_data_t *data, const char *name, const char *val) .. function:: void obs_data_set_default_string(obs_data_t *data, const char *name, const char *val)
const char *obs_data_get_default_string(obs_data_t *data, const char *name) const char *obs_data_get_default_string(obs_data_t *data, const char *name)
@ -207,7 +213,10 @@ is not set.
:return: An incremented reference to a data object. :return: An incremented reference to a data object.
--------------------- ----------------------
.. function:: void obs_data_set_default_array(obs_data_t *data, const char *name, obs_data_array_t *arr)
obs_data_array_t *obs_data_get_default_array(obs_data_t *data, const char *name)
Autoselect Functions Autoselect Functions

View file

@ -753,6 +753,19 @@ General Source Functions
--------------------- ---------------------
.. function:: void obs_source_set_hidden(obs_source_t *source, bool hidden)
Sets the hidden flag that determines whether it should be hidden from the user.
Used when the source is still alive but should not be referenced.
---------------------
.. function:: bool obs_source_is_hidden(obs_source_t *source)
:return: *true* if source's 'hidden' is set true
---------------------
.. function:: uint32_t obs_source_get_output_flags(const obs_source_t *source) .. function:: uint32_t obs_source_get_output_flags(const obs_source_t *source)
uint32_t obs_get_source_output_flags(const char *id) uint32_t obs_get_source_output_flags(const char *id)

View file

@ -767,6 +767,97 @@ bool obs_data_save_json_safe(obs_data_t *data, const char *file,
return false; return false;
} }
static void get_defaults_array_cb(obs_data_t *data, void *vp)
{
obs_data_array_t *defs = (obs_data_array_t *)vp;
obs_data_t *obs_defaults = obs_data_get_defaults(data);
obs_data_array_push_back(defs, obs_defaults);
obs_data_release(obs_defaults);
}
obs_data_t *obs_data_get_defaults(obs_data_t *data)
{
obs_data_t *defaults = obs_data_create();
if (!data)
return defaults;
struct obs_data_item *item = data->first_item;
while (item) {
const char *name = get_item_name(item);
switch (item->type) {
case OBS_DATA_NULL:
break;
case OBS_DATA_STRING: {
const char *str =
obs_data_get_default_string(data, name);
obs_data_set_string(defaults, name, str);
break;
}
case OBS_DATA_NUMBER: {
switch (obs_data_item_numtype(item)) {
case OBS_DATA_NUM_DOUBLE: {
double val =
obs_data_get_default_double(data, name);
obs_data_set_double(defaults, name, val);
break;
}
case OBS_DATA_NUM_INT: {
int val = obs_data_get_default_int(data, name);
obs_data_set_int(defaults, name, val);
break;
}
case OBS_DATA_NUM_INVALID:
break;
}
break;
}
case OBS_DATA_BOOLEAN: {
bool val = obs_data_get_default_bool(data, name);
obs_data_set_bool(defaults, name, val);
break;
}
case OBS_DATA_OBJECT: {
obs_data_t *val = obs_data_get_default_obj(data, name);
obs_data_t *defs = obs_data_get_defaults(val);
obs_data_set_obj(defaults, name, defs);
obs_data_release(defs);
obs_data_release(val);
break;
}
case OBS_DATA_ARRAY: {
obs_data_array_t *arr =
obs_data_get_default_array(data, name);
obs_data_array_t *defs = obs_data_array_create();
obs_data_array_enum(arr, get_defaults_array_cb,
(void *)defs);
obs_data_set_array(defaults, name, defs);
obs_data_array_release(defs);
obs_data_array_release(arr);
break;
}
}
item = item->next;
}
return defaults;
}
static struct obs_data_item *get_item(struct obs_data *data, const char *name) static struct obs_data_item *get_item(struct obs_data *data, const char *name)
{ {
if (!data) if (!data)
@ -1134,6 +1225,12 @@ void obs_data_set_default_obj(obs_data_t *data, const char *name,
obs_set_obj(data, NULL, name, obj, set_item_def); obs_set_obj(data, NULL, name, obj, set_item_def);
} }
void obs_data_set_default_array(obs_data_t *data, const char *name,
obs_data_array_t *arr)
{
obs_set_array(data, NULL, name, arr, set_item_def);
}
void obs_data_set_autoselect_string(obs_data_t *data, const char *name, void obs_data_set_autoselect_string(obs_data_t *data, const char *name,
const char *val) const char *val)
{ {
@ -1351,6 +1448,16 @@ void obs_data_array_erase(obs_data_array_t *array, size_t idx)
} }
} }
void obs_data_array_enum(obs_data_array_t *array,
void (*cb)(obs_data_t *data, void *param), void *param)
{
if (array && cb) {
for (size_t i = 0; i < array->objects.num; i++) {
cb(array->objects.array[i], param);
}
}
}
/* ------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------- */
/* Item status inspection */ /* Item status inspection */
@ -1517,6 +1624,9 @@ enum obs_data_number_type obs_data_item_numtype(obs_data_item_t *item)
return OBS_DATA_NUM_INVALID; return OBS_DATA_NUM_INVALID;
num = get_item_data(item); num = get_item_data(item);
if (!num)
return OBS_DATA_NUM_INVALID;
return num->type; return num->type;
} }

View file

@ -91,6 +91,11 @@ EXPORT void obs_data_set_obj(obs_data_t *data, const char *name,
EXPORT void obs_data_set_array(obs_data_t *data, const char *name, EXPORT void obs_data_set_array(obs_data_t *data, const char *name,
obs_data_array_t *array); obs_data_array_t *array);
/*
* Creates an obs_data_t * filled with all default values.
*/
EXPORT obs_data_t *obs_data_get_defaults(obs_data_t *data);
/* /*
* Default value functions. * Default value functions.
*/ */
@ -104,6 +109,8 @@ EXPORT void obs_data_set_default_bool(obs_data_t *data, const char *name,
bool val); bool val);
EXPORT void obs_data_set_default_obj(obs_data_t *data, const char *name, EXPORT void obs_data_set_default_obj(obs_data_t *data, const char *name,
obs_data_t *obj); obs_data_t *obj);
EXPORT void obs_data_set_default_array(obs_data_t *data, const char *name,
obs_data_array_t *arr);
/* /*
* Application overrides * Application overrides
@ -166,6 +173,9 @@ EXPORT void obs_data_array_insert(obs_data_array_t *array, size_t idx,
EXPORT void obs_data_array_push_back_array(obs_data_array_t *array, EXPORT void obs_data_array_push_back_array(obs_data_array_t *array,
obs_data_array_t *array2); obs_data_array_t *array2);
EXPORT void obs_data_array_erase(obs_data_array_t *array, size_t idx); EXPORT void obs_data_array_erase(obs_data_array_t *array, size_t idx);
EXPORT void obs_data_array_enum(obs_data_array_t *array,
void (*cb)(obs_data_t *data, void *param),
void *param);
/* ------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------- */
/* Item status inspection */ /* Item status inspection */

View file

@ -621,6 +621,9 @@ struct obs_source {
* to handle things but it's the best option) */ * to handle things but it's the best option) */
bool removed; bool removed;
/* used to indicate if the source should show up when queried for user ui */
bool temp_removed;
bool active; bool active;
bool showing; bool showing;

View file

@ -1641,6 +1641,33 @@ obs_sceneitem_t *obs_scene_find_source_recursive(obs_scene_t *scene,
return item; return item;
} }
struct sceneitem_check {
obs_source_t *source_in;
obs_sceneitem_t *item_out;
};
bool check_sceneitem_exists(obs_scene_t *scene, obs_sceneitem_t *item,
void *vp_check)
{
UNUSED_PARAMETER(scene);
struct sceneitem_check *check = (struct sceneitem_check *)vp_check;
if (obs_sceneitem_get_source(item) == check->source_in) {
check->item_out = item;
obs_sceneitem_addref(item);
return false;
}
return true;
}
obs_sceneitem_t *obs_scene_sceneitem_from_source(obs_scene_t *scene,
obs_source_t *source)
{
struct sceneitem_check check = {source, NULL};
obs_scene_enum_items(scene, check_sceneitem_exists, (void *)&check);
return check.item_out;
}
obs_sceneitem_t *obs_scene_find_sceneitem_by_id(obs_scene_t *scene, int64_t id) obs_sceneitem_t *obs_scene_find_sceneitem_by_id(obs_scene_t *scene, int64_t id)
{ {
struct obs_scene_item *item; struct obs_scene_item *item;
@ -2001,6 +2028,22 @@ void obs_sceneitem_remove(obs_sceneitem_t *item)
obs_sceneitem_release(item); obs_sceneitem_release(item);
} }
void obs_sceneitem_save(obs_sceneitem_t *item, obs_data_array_t *arr)
{
scene_save_item(arr, item, NULL);
}
void sceneitem_restore(obs_data_t *data, void *vp)
{
obs_scene_t *scene = (obs_scene_t *)vp;
scene_load_item(scene, data);
}
void obs_sceneitems_add(obs_scene_t *scene, obs_data_array_t *data)
{
obs_data_array_enum(data, sceneitem_restore, scene);
}
obs_scene_t *obs_sceneitem_get_scene(const obs_sceneitem_t *item) obs_scene_t *obs_sceneitem_get_scene(const obs_sceneitem_t *item)
{ {
return item ? item->parent : NULL; return item ? item->parent : NULL;
@ -2018,6 +2061,113 @@ static void signal_parent(obs_scene_t *parent, const char *command,
signal_handler_signal(parent->source->context.signals, command, params); signal_handler_signal(parent->source->context.signals, command, params);
} }
struct passthrough {
obs_data_array_t *ids;
bool all_items;
};
bool save_transform_states(obs_scene_t *scene, obs_sceneitem_t *item,
void *vp_pass)
{
struct passthrough *pass = (struct passthrough *)vp_pass;
if (obs_sceneitem_selected(item) || pass->all_items) {
obs_data_t *temp = obs_data_create();
obs_data_array_t *item_ids = (obs_data_array_t *)pass->ids;
struct obs_transform_info info;
struct obs_sceneitem_crop crop;
obs_sceneitem_get_info(item, &info);
obs_sceneitem_get_crop(item, &crop);
struct vec2 pos = info.pos;
struct vec2 scale = info.scale;
float rot = info.rot;
uint32_t alignment = info.alignment;
uint32_t bounds_type = info.bounds_type;
uint32_t bounds_alignment = info.bounds_alignment;
struct vec2 bounds = info.bounds;
obs_data_set_int(temp, "id", obs_sceneitem_get_id(item));
obs_data_set_vec2(temp, "pos", &pos);
obs_data_set_vec2(temp, "scale", &scale);
obs_data_set_int(temp, "rot", rot);
obs_data_set_int(temp, "alignment", alignment);
obs_data_set_int(temp, "bounds_type", bounds_type);
obs_data_set_vec2(temp, "bounds", &bounds);
obs_data_set_int(temp, "bounds_alignment", bounds_alignment);
obs_data_set_int(temp, "top", crop.top);
obs_data_set_int(temp, "bottom", crop.bottom);
obs_data_set_int(temp, "left", crop.left);
obs_data_set_int(temp, "right", crop.right);
obs_data_array_push_back(item_ids, temp);
obs_data_release(temp);
}
UNUSED_PARAMETER(scene);
return true;
}
obs_data_t *obs_scene_save_transform_states(obs_scene_t *scene, bool all_items)
{
obs_data_t *wrapper = obs_data_create();
obs_data_array_t *item_ids = obs_data_array_create();
struct passthrough pass = {item_ids, all_items};
obs_scene_enum_items(scene, save_transform_states, (void *)&pass);
obs_data_set_array(wrapper, "item_ids", item_ids);
obs_data_set_string(wrapper, "scene_name",
obs_source_get_name(obs_scene_get_source(scene)));
obs_data_array_release(item_ids);
return wrapper;
}
void load_transform_states(obs_data_t *temp, void *vp_scene)
{
obs_scene_t *scene = (obs_scene_t *)vp_scene;
int64_t id = obs_data_get_int(temp, "id");
obs_sceneitem_t *item = obs_scene_find_sceneitem_by_id(scene, id);
struct obs_transform_info info;
struct obs_sceneitem_crop crop;
obs_data_get_vec2(temp, "pos", &info.pos);
obs_data_get_vec2(temp, "scale", &info.scale);
info.rot = obs_data_get_int(temp, "rot");
info.alignment = obs_data_get_int(temp, "alignment");
info.bounds_type =
(enum obs_bounds_type)obs_data_get_int(temp, "bounds_type");
info.bounds_alignment = obs_data_get_int(temp, "bounds_alignment");
obs_data_get_vec2(temp, "bounds", &info.bounds);
crop.top = obs_data_get_int(temp, "top");
crop.bottom = obs_data_get_int(temp, "bottom");
crop.left = obs_data_get_int(temp, "left");
crop.right = obs_data_get_int(temp, "right");
obs_sceneitem_defer_update_begin(item);
obs_sceneitem_set_info(item, &info);
obs_sceneitem_set_crop(item, &crop);
obs_sceneitem_defer_update_end(item);
}
void obs_scene_load_transform_states(const char *data)
{
obs_data_t *dat = obs_data_create_from_json(data);
obs_data_array_t *item_ids = obs_data_get_array(dat, "item_ids");
obs_source_t *source =
obs_get_source_by_name(obs_data_get_string(dat, "scene_name"));
obs_scene_t *scene = obs_scene_from_source(source);
obs_data_array_enum(item_ids, load_transform_states, (void *)scene);
obs_data_release(dat);
obs_data_array_release(item_ids);
obs_source_release(source);
}
void obs_sceneitem_select(obs_sceneitem_t *item, bool select) void obs_sceneitem_select(obs_sceneitem_t *item, bool select)
{ {
struct calldata params; struct calldata params;
@ -2148,6 +2298,24 @@ void obs_sceneitem_set_order(obs_sceneitem_t *item,
obs_scene_release(scene); obs_scene_release(scene);
} }
int obs_sceneitem_get_order_position(obs_sceneitem_t *item)
{
struct obs_scene *scene = item->parent;
struct obs_scene_item *next = scene->first_item;
full_lock(scene);
int index = 0;
while (next && next != item) {
next = next->next;
++index;
}
full_unlock(scene);
return index;
}
void obs_sceneitem_set_order_position(obs_sceneitem_t *item, int position) void obs_sceneitem_set_order_position(obs_sceneitem_t *item, int position)
{ {
if (!item) if (!item)
@ -2573,6 +2741,11 @@ int64_t obs_sceneitem_get_id(const obs_sceneitem_t *item)
return item->id; return item->id;
} }
void obs_sceneitem_set_id(obs_sceneitem_t *item, int64_t id)
{
item->id = id;
}
obs_data_t *obs_sceneitem_get_private_settings(obs_sceneitem_t *item) obs_data_t *obs_sceneitem_get_private_settings(obs_sceneitem_t *item)
{ {
if (!obs_ptr_valid(item, "obs_sceneitem_get_private_settings")) if (!obs_ptr_valid(item, "obs_sceneitem_get_private_settings"))

View file

@ -929,8 +929,9 @@ void obs_source_update(obs_source_t *source, obs_data_t *settings)
if (!obs_source_valid(source, "obs_source_update")) if (!obs_source_valid(source, "obs_source_update"))
return; return;
if (settings) if (settings) {
obs_data_apply(source->context.settings, settings); obs_data_apply(source->context.settings, settings);
}
if (source->info.output_flags & OBS_SOURCE_VIDEO) { if (source->info.output_flags & OBS_SOURCE_VIDEO) {
os_atomic_inc_long(&source->defer_update_count); os_atomic_inc_long(&source->defer_update_count);
@ -4283,6 +4284,16 @@ void obs_source_enum_filters(obs_source_t *source,
pthread_mutex_unlock(&source->filter_mutex); pthread_mutex_unlock(&source->filter_mutex);
} }
void obs_source_set_hidden(obs_source_t *source, bool hidden)
{
source->temp_removed = hidden;
}
bool obs_source_is_hidden(obs_source_t *source)
{
return source->temp_removed;
}
obs_source_t *obs_source_get_filter_by_name(obs_source_t *source, obs_source_t *obs_source_get_filter_by_name(obs_source_t *source,
const char *name) const char *name)
{ {

View file

@ -898,6 +898,14 @@ EXPORT void obs_source_remove(obs_source_t *source);
/** Returns true if the source should be released */ /** Returns true if the source should be released */
EXPORT bool obs_source_removed(const obs_source_t *source); EXPORT bool obs_source_removed(const obs_source_t *source);
/** The 'hidden' flag is not the same as a sceneitem's visibility. It is a
* property the determines if it can be found through searches. **/
/** Simply sets a 'hidden' flag when the source is still alive but shouldn't be found */
EXPORT void obs_source_set_hidden(obs_source_t *source, bool hidden);
/** Returns the current 'hidden' state on the source */
EXPORT bool obs_source_is_hidden(obs_source_t *source);
/** Returns capability flags of a source */ /** Returns capability flags of a source */
EXPORT uint32_t obs_source_get_output_flags(const obs_source_t *source); EXPORT uint32_t obs_source_get_output_flags(const obs_source_t *source);
@ -1581,6 +1589,29 @@ EXPORT void obs_sceneitem_release(obs_sceneitem_t *item);
/** Removes a scene item. */ /** Removes a scene item. */
EXPORT void obs_sceneitem_remove(obs_sceneitem_t *item); EXPORT void obs_sceneitem_remove(obs_sceneitem_t *item);
/** Adds a scene item. */
EXPORT void obs_sceneitems_add(obs_scene_t *scene, obs_data_array_t *data);
/** Saves Sceneitem into an array, arr **/
EXPORT void obs_sceneitem_save(obs_sceneitem_t *item, obs_data_array_t *arr);
/** Set the ID of a sceneitem */
EXPORT void obs_sceneitem_set_id(obs_sceneitem_t *sceneitem, int64_t id);
/** Tries to find the sceneitem of the source in a given scene. Returns NULL if not found */
EXPORT obs_sceneitem_t *obs_scene_sceneitem_from_source(obs_scene_t *scene,
obs_source_t *source);
/** Save all the transform states for a current scene's sceneitems */
EXPORT obs_data_t *obs_scene_save_transform_states(obs_scene_t *scene,
bool all_items);
/** Load all the transform states of sceneitems in that scene */
EXPORT void obs_scene_load_transform_states(const char *state);
/** Gets a sceneitem's order in its scene */
EXPORT int obs_sceneitem_get_order_position(obs_sceneitem_t *item);
/** Gets the scene parent associated with the scene item. */ /** Gets the scene parent associated with the scene item. */
EXPORT obs_scene_t *obs_sceneitem_get_scene(const obs_sceneitem_t *item); EXPORT obs_scene_t *obs_sceneitem_get_scene(const obs_sceneitem_t *item);

View file

@ -531,9 +531,14 @@ static void *ft2_source_create(obs_data_t *settings, obs_source_t *source,
obs_data_set_default_string(font_obj, "face", DEFAULT_FACE); obs_data_set_default_string(font_obj, "face", DEFAULT_FACE);
obs_data_set_default_int(font_obj, "size", font_size); obs_data_set_default_int(font_obj, "size", font_size);
obs_data_set_default_int(font_obj, "flags", 0);
obs_data_set_default_string(font_obj, "style", "");
obs_data_set_default_obj(settings, "font", font_obj); obs_data_set_default_obj(settings, "font", font_obj);
obs_data_set_default_bool(settings, "antialiasing", true); obs_data_set_default_bool(settings, "antialiasing", true);
obs_data_set_default_bool(settings, "word_wrap", false);
obs_data_set_default_bool(settings, "outline", false);
obs_data_set_default_bool(settings, "drop_shadow", false);
obs_data_set_default_int(settings, "log_lines", 6); obs_data_set_default_int(settings, "log_lines", 6);