diff --git a/UI/CMakeLists.txt b/UI/CMakeLists.txt
index 4478e2b5c..503025e90 100644
--- a/UI/CMakeLists.txt
+++ b/UI/CMakeLists.txt
@@ -256,7 +256,8 @@ set(obs_SOURCES
obs-proxy-style.cpp
locked-checkbox.cpp
visibility-checkbox.cpp
- media-slider.cpp)
+ media-slider.cpp
+ undo-stack-obs.cpp)
set(obs_HEADERS
${obs_PLATFORM_HEADERS}
@@ -327,7 +328,8 @@ set(obs_HEADERS
log-viewer.hpp
obs-proxy-style.hpp
obs-proxy-style.hpp
- media-slider.hpp)
+ media-slider.hpp
+ undo-stack-obs.hpp)
set(obs_importers_HEADERS
importers/importers.hpp)
diff --git a/UI/forms/OBSBasic.ui b/UI/forms/OBSBasic.ui
index 5556d2691..261e2d39e 100644
--- a/UI/forms/OBSBasic.ui
+++ b/UI/forms/OBSBasic.ui
@@ -586,6 +586,9 @@
Paste.Filters
+
+
+
@@ -599,6 +602,7 @@
+
+
+
+ false
+
+
+ Undo
+
+
+
+
+ false
+
+
+ Redo
+
+
+
OBSBasicPreview
diff --git a/UI/source-tree.cpp b/UI/source-tree.cpp
index f7dd79ce2..cf2229531 100644
--- a/UI/source-tree.cpp
+++ b/UI/source-tree.cpp
@@ -403,6 +403,34 @@ void SourceTreeItem::ExitEditMode(bool save)
/* rename */
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());
label->setText(QT_UTF8(newName.c_str()));
}
diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp
index af3a84842..6a5a17fc4 100644
--- a/UI/window-basic-main.cpp
+++ b/UI/window-basic-main.cpp
@@ -17,7 +17,10 @@
along with this program. If not, see .
******************************************************************************/
+#include
#include
+#include
+#include
#include
#include
#include
@@ -58,6 +61,7 @@
#include "remote-text.hpp"
#include "ui-validation.hpp"
#include "media-controls.hpp"
+#include "undo-stack-obs.hpp"
#include
#include
@@ -203,7 +207,7 @@ extern void RegisterTwitchAuth();
extern void RegisterRestreamAuth();
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)
qRegisterMetaTypeStreamOperators>(
@@ -366,6 +370,16 @@ OBSBasic::OBSBasic(QWidget *parent)
assignDockToggle(ui->controlsDock, ui->toggleControls);
assignDockToggle(statsDock, ui->toggleStats);
+ // Register shortcuts for Undo/Redo
+ ui->actionMainUndo->setShortcut(Qt::CTRL + Qt::Key_Z);
+ QList 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
ui->toggleScenes->setChecked(false);
ui->toggleSources->setChecked(false);
@@ -3622,6 +3636,33 @@ void OBSBasic::DuplicateSelectedScene()
OBS_SCENE_DUP_REFS);
source = obs_scene_get_source(scene);
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);
break;
@@ -3631,15 +3672,107 @@ void OBSBasic::DuplicateSelectedScene()
void OBSBasic::RemoveSelectedScene()
{
OBSScene scene = GetCurrentScene();
- if (scene) {
- obs_source_t *source = obs_scene_get_source(scene);
- if (QueryRemoveSource(source)) {
- obs_source_remove(source);
+ obs_source_t *source = obs_scene_get_source(scene);
- if (api)
- api->on_event(
- OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED);
- }
+ OBSSource curProgramScene = OBSGetStrongRef(programScene);
+
+ if (source && QueryRemoveSource(source)) {
+ vector 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 &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_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)
+ api->on_event(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED);
}
}
@@ -4330,6 +4463,7 @@ void OBSBasic::closeEvent(QCloseEvent *event)
/* Clear all scene data (dialogs, widgets, widget sub-items, scenes,
* sources, etc) so that all references are released before shutdown */
+ undo_s.release();
ClearSceneData();
App()->quit();
@@ -4686,20 +4820,34 @@ void OBSBasic::on_actionAddScene_triggered()
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());
source = obs_scene_get_source(scene);
SetCurrentScene(source);
+ RefreshSources(scene);
obs_scene_release(scene);
}
}
void OBSBasic::on_actionRemoveScene_triggered()
{
- OBSScene scene = GetCurrentScene();
- obs_source_t *source = obs_scene_get_source(scene);
-
- if (source && QueryRemoveSource(source))
- obs_source_remove(source);
+ RemoveSelectedScene();
}
void OBSBasic::ChangeSceneIndex(bool relative, int offset, int invalidIdx)
@@ -5109,10 +5257,11 @@ void OBSBasic::on_scenes_itemDoubleClicked(QListWidgetItem *witem)
void OBSBasic::AddSource(const char *id)
{
if (id && *id) {
- OBSBasicSourceSelect sourceSelect(this, id);
+ OBSBasicSourceSelect sourceSelect(this, id, undo_s);
sourceSelect.exec();
- if (sourceSelect.newSource && strcmp(id, "group") != 0)
+ if (sourceSelect.newSource && strcmp(id, "group") != 0) {
CreatePropertiesWindow(sourceSelect.newSource);
+ }
}
}
@@ -5253,9 +5402,11 @@ void OBSBasic::on_actionRemoveSource_triggered()
if (!items.size())
return;
- auto removeMultiple = [this](size_t count) {
+ bool confirmed = false;
+
+ if (items.size() > 1) {
QString text = QTStr("ConfirmRemove.TextMultiple")
- .arg(QString::number(count));
+ .arg(QString::number(items.size()));
QMessageBox remove_items(this);
remove_items.setText(text);
@@ -5267,21 +5418,139 @@ void OBSBasic::on_actionRemoveSource_triggered()
remove_items.setWindowTitle(QTStr("ConfirmRemove.Title"));
remove_items.exec();
- return Yes == remove_items.clickedButton();
- };
-
- if (items.size() == 1) {
+ confirmed = remove_items.clickedButton();
+ } else {
OBSSceneItem &item = items[0];
obs_source_t *source = obs_sceneitem_get_source(item);
-
if (source && QueryRemoveSource(source))
- obs_sceneitem_remove(item);
- } else {
- if (removeMultiple(items.size())) {
- for (auto &item : items)
- obs_sceneitem_remove(item);
- }
+ 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 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_source_set_hidden(source, true);
+
+ 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)
+ obs_sceneitem_remove(item);
}
void OBSBasic::on_actionInteract_triggered()
@@ -5519,6 +5788,27 @@ static void RenameListItem(OBSBasic *parent, QListWidget *listWidget,
obs_source_release(foundSource);
} 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()));
obs_source_set_name(source, name.c_str());
}
@@ -7762,6 +8052,16 @@ bool OBSBasic::sysTrayMinimizeToTray()
"SysTrayMinimizeToTray");
}
+void OBSBasic::on_actionMainUndo_triggered()
+{
+ undo_s.undo();
+}
+
+void OBSBasic::on_actionMainRedo_triggered()
+{
+ undo_s.redo();
+}
+
void OBSBasic::on_actionCopySource_triggered()
{
copyStrings.clear();
diff --git a/UI/window-basic-main.hpp b/UI/window-basic-main.hpp
index 0ee5ec96b..40fd716ce 100644
--- a/UI/window-basic-main.hpp
+++ b/UI/window-basic-main.hpp
@@ -36,6 +36,7 @@
#include "window-basic-about.hpp"
#include "auth-base.hpp"
#include "log-viewer.hpp"
+#include "undo-stack-obs.hpp"
#include
@@ -166,6 +167,7 @@ class OBSBasic : public OBSMainWindow {
friend class ExtraBrowsersDelegate;
friend class DeviceCaptureToolbar;
friend class DeviceToolbarPropertiesThread;
+ friend class OBSBasicSourceSelect;
friend struct BasicOutputHandler;
friend struct OBSStudioAPI;
@@ -608,6 +610,10 @@ public slots:
void UnpauseRecording();
private slots:
+
+ void on_actionMainUndo_triggered();
+ void on_actionMainRedo_triggered();
+
void AddSceneItem(OBSSceneItem item);
void AddScene(OBSSource source);
void RemoveScene(OBSSource source);
@@ -740,6 +746,7 @@ private:
OBSSource prevFTBSource = nullptr;
public:
+ undo_stack undo_s;
OBSSource GetProgramSource();
OBSScene GetCurrentScene();
diff --git a/UI/window-basic-source-select.cpp b/UI/window-basic-source-select.cpp
index 57b7b121f..cee30e520 100644
--- a/UI/window-basic-source-select.cpp
+++ b/UI/window-basic-source-select.cpp
@@ -28,6 +28,9 @@ struct AddSourceData {
bool OBSBasicSourceSelect::EnumSources(void *data, obs_source_t *source)
{
+ if (obs_source_is_hidden(source))
+ return false;
+
OBSBasicSourceSelect *window =
static_cast(data);
const char *name = obs_source_get_name(source);
@@ -179,7 +182,7 @@ bool AddNew(QWidget *parent, const char *id, const char *name,
return false;
obs_source_t *source = obs_get_source_by_name(name);
- if (source) {
+ if (source && parent) {
OBSMessageBox::information(parent, QTStr("NameExists.Title"),
QTStr("NameExists.Text"));
@@ -236,6 +239,63 @@ void OBSBasicSourceSelect::on_buttonBox_accepted()
if (!AddNew(this, id, QT_TO_UTF8(ui->sourceName->text()),
visible, newSource))
return;
+
+ OBSBasic *main =
+ reinterpret_cast(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);
@@ -261,8 +321,12 @@ template static inline T GetOBSRef(QListWidgetItem *item)
return item->data(static_cast(QtDataRole::OBSRef)).value();
}
-OBSBasicSourceSelect::OBSBasicSourceSelect(OBSBasic *parent, const char *id_)
- : QDialog(parent), ui(new Ui::OBSBasicSourceSelect), id(id_)
+OBSBasicSourceSelect::OBSBasicSourceSelect(OBSBasic *parent, const char *id_,
+ undo_stack &undo_s)
+ : QDialog(parent),
+ ui(new Ui::OBSBasicSourceSelect),
+ id(id_),
+ undo_s(undo_s)
{
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
diff --git a/UI/window-basic-source-select.hpp b/UI/window-basic-source-select.hpp
index 90496798c..089dc60f3 100644
--- a/UI/window-basic-source-select.hpp
+++ b/UI/window-basic-source-select.hpp
@@ -21,6 +21,7 @@
#include
#include "ui_OBSBasicSourceSelect.h"
+#include "undo-stack-obs.hpp"
class OBSBasic;
@@ -30,6 +31,7 @@ class OBSBasicSourceSelect : public QDialog {
private:
std::unique_ptr ui;
const char *id;
+ undo_stack &undo_s;
static bool EnumSources(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);
public:
- OBSBasicSourceSelect(OBSBasic *parent, const char *id);
+ OBSBasicSourceSelect(OBSBasic *parent, const char *id,
+ undo_stack &undo_s);
OBSSource newSource;
diff --git a/libobs/obs-internal.h b/libobs/obs-internal.h
index 142c01235..83037fc25 100644
--- a/libobs/obs-internal.h
+++ b/libobs/obs-internal.h
@@ -621,6 +621,9 @@ struct obs_source {
* to handle things but it's the best option) */
bool removed;
+ /* used to indicate if the source should show up when queried for user ui */
+ bool temp_removed;
+
bool active;
bool showing;
diff --git a/libobs/obs-scene.c b/libobs/obs-scene.c
index 049a71d40..37eb40eea 100644
--- a/libobs/obs-scene.c
+++ b/libobs/obs-scene.c
@@ -1477,6 +1477,33 @@ obs_sceneitem_t *obs_scene_find_source_recursive(obs_scene_t *scene,
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)
{
struct obs_scene_item *item;
@@ -1830,6 +1857,22 @@ void obs_sceneitem_remove(obs_sceneitem_t *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)
{
return item ? item->parent : NULL;
@@ -1977,6 +2020,24 @@ void obs_sceneitem_set_order(obs_sceneitem_t *item,
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)
{
if (!item)
@@ -2385,6 +2446,11 @@ int64_t obs_sceneitem_get_id(const obs_sceneitem_t *item)
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)
{
if (!obs_ptr_valid(item, "obs_sceneitem_get_private_settings"))
diff --git a/libobs/obs-source.c b/libobs/obs-source.c
index e8c33be23..d148d17f2 100644
--- a/libobs/obs-source.c
+++ b/libobs/obs-source.c
@@ -929,8 +929,9 @@ void obs_source_update(obs_source_t *source, obs_data_t *settings)
if (!obs_source_valid(source, "obs_source_update"))
return;
- if (settings)
+ if (settings) {
obs_data_apply(source->context.settings, settings);
+ }
if (source->info.output_flags & OBS_SOURCE_VIDEO) {
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);
}
+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,
const char *name)
{
diff --git a/libobs/obs.h b/libobs/obs.h
index dd2a69fde..fd7f88e5f 100644
--- a/libobs/obs.h
+++ b/libobs/obs.h
@@ -898,6 +898,14 @@ EXPORT void obs_source_remove(obs_source_t *source);
/** Returns true if the source should be released */
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 */
EXPORT uint32_t obs_source_get_output_flags(const obs_source_t *source);
@@ -1578,6 +1586,21 @@ EXPORT void obs_sceneitem_release(obs_sceneitem_t *item);
/** Removes a scene 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);
+/** 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. */
EXPORT obs_scene_t *obs_sceneitem_get_scene(const obs_sceneitem_t *item);