Files
obs-studio/frontend/widgets/OBSBasic_Scenes.cpp
gxalpha 5129612b6c frontend,shared: Replace and disable contextless connect calls
# Conflicts:
#	frontend/OBSApp.cpp
2025-12-17 16:35:45 -05:00

993 lines
29 KiB
C++

/******************************************************************************
Copyright (C) 2023 by Lain Bailey <lain@obsproject.com>
Zachary Lund <admin@computerquip.com>
Philippe Groarke <philippe.groarke@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#include "OBSBasic.hpp"
#include "OBSProjector.hpp"
#include <dialogs/NameDialog.hpp>
#include <qt-wrappers.hpp>
#include <QLineEdit>
#include <QWidgetAction>
#include <vector>
using namespace std;
namespace {
template<typename OBSRef> struct SignalContainer {
OBSRef ref;
vector<shared_ptr<OBSSignal>> handlers;
};
QDataStream &operator<<(QDataStream &out, const SignalContainer<OBSScene> &v)
{
out << v.ref;
return out;
}
QDataStream &operator>>(QDataStream &in, SignalContainer<OBSScene> &v)
{
in >> v.ref;
return in;
}
} // namespace
Q_DECLARE_METATYPE(obs_order_movement);
Q_DECLARE_METATYPE(SignalContainer<OBSScene>);
extern void undo_redo(const std::string &data);
obs_data_array_t *OBSBasic::SaveSceneListOrder()
{
obs_data_array_t *sceneOrder = obs_data_array_create();
for (int i = 0; i < ui->scenes->count(); i++) {
OBSDataAutoRelease data = obs_data_create();
obs_data_set_string(data, "name", QT_TO_UTF8(ui->scenes->item(i)->text()));
obs_data_array_push_back(sceneOrder, data);
}
return sceneOrder;
}
static void ReorderItemByName(QListWidget *lw, const char *name, int newIndex)
{
for (int i = 0; i < lw->count(); i++) {
QListWidgetItem *item = lw->item(i);
if (strcmp(name, QT_TO_UTF8(item->text())) == 0) {
if (newIndex != i) {
item = lw->takeItem(i);
lw->insertItem(newIndex, item);
}
break;
}
}
}
void OBSBasic::LoadSceneListOrder(obs_data_array_t *array)
{
size_t num = obs_data_array_count(array);
for (size_t i = 0; i < num; i++) {
OBSDataAutoRelease data = obs_data_array_item(array, i);
const char *name = obs_data_get_string(data, "name");
ReorderItemByName(ui->scenes, name, (int)i);
}
}
OBSScene OBSBasic::GetCurrentScene()
{
return currentScene.load();
}
void OBSBasic::AddScene(OBSSource source)
{
const char *name = obs_source_get_name(source);
obs_scene_t *scene = obs_scene_from_source(source);
QListWidgetItem *item = new QListWidgetItem(QT_UTF8(name));
SetOBSRef(item, OBSScene(scene));
ui->scenes->insertItem(ui->scenes->currentRow() + 1, item);
obs_hotkey_register_source(
source, "OBSBasic.SelectScene", Str("Basic.Hotkeys.SelectScene"),
[](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) {
OBSBasic *main = OBSBasic::Get();
auto potential_source = static_cast<obs_source_t *>(data);
OBSSourceAutoRelease source = obs_source_get_ref(potential_source);
if (source && pressed)
main->SetCurrentScene(source.Get());
},
static_cast<obs_source_t *>(source));
signal_handler_t *handler = obs_source_get_signal_handler(source);
SignalContainer<OBSScene> container;
container.ref = scene;
container.handlers.assign({
std::make_shared<OBSSignal>(handler, "item_add", OBSBasic::SceneItemAdded, this),
std::make_shared<OBSSignal>(handler, "reorder", OBSBasic::SceneReordered, this),
std::make_shared<OBSSignal>(handler, "refresh", OBSBasic::SceneRefreshed, this),
});
item->setData(static_cast<int>(QtDataRole::OBSSignals), QVariant::fromValue(container));
/* if the scene already has items (a duplicated scene) add them */
auto addSceneItem = [this](obs_sceneitem_t *item) {
AddSceneItem(item);
};
using addSceneItem_t = decltype(addSceneItem);
obs_scene_enum_items(
scene,
[](obs_scene_t *, obs_sceneitem_t *item, void *param) {
addSceneItem_t *func;
func = static_cast<addSceneItem_t *>(param);
(*func)(item);
return true;
},
&addSceneItem);
SaveProject();
if (!disableSaving) {
obs_source_t *source = obs_scene_get_source(scene);
blog(LOG_INFO, "User added scene '%s'", obs_source_get_name(source));
OBSProjector::UpdateMultiviewProjectors();
}
OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED);
}
void OBSBasic::RemoveScene(OBSSource source)
{
obs_scene_t *scene = obs_scene_from_source(source);
QListWidgetItem *sel = nullptr;
int count = ui->scenes->count();
for (int i = 0; i < count; i++) {
auto item = ui->scenes->item(i);
auto cur_scene = GetOBSRef<OBSScene>(item);
if (cur_scene != scene)
continue;
sel = item;
break;
}
if (sel != nullptr) {
if (sel == ui->scenes->currentItem())
ui->sources->Clear();
delete sel;
}
SaveProject();
if (!disableSaving) {
blog(LOG_INFO, "User Removed scene '%s'", obs_source_get_name(source));
OBSProjector::UpdateMultiviewProjectors();
}
OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED);
}
static bool select_one(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param)
{
obs_sceneitem_t *selectedItem = static_cast<obs_sceneitem_t *>(param);
if (obs_sceneitem_is_group(item))
obs_sceneitem_group_enum_items(item, select_one, param);
obs_sceneitem_select(item, (selectedItem == item));
return true;
}
void OBSBasic::AddSceneItem(OBSSceneItem item)
{
obs_scene_t *scene = obs_sceneitem_get_scene(item);
if (GetCurrentScene() == scene)
ui->sources->Add(item);
SaveProject();
if (!disableSaving) {
obs_source_t *sceneSource = obs_scene_get_source(scene);
obs_source_t *itemSource = obs_sceneitem_get_source(item);
blog(LOG_INFO, "User added source '%s' (%s) to scene '%s'", obs_source_get_name(itemSource),
obs_source_get_id(itemSource), obs_source_get_name(sceneSource));
obs_scene_enum_items(scene, select_one, (obs_sceneitem_t *)item);
}
}
void OBSBasic::DuplicateSelectedScene()
{
OBSScene curScene = GetCurrentScene();
if (!curScene)
return;
OBSSource curSceneSource = obs_scene_get_source(curScene);
QString format{obs_source_get_name(curSceneSource)};
format += " %1";
int i = 2;
QString placeHolderText = format.arg(i);
OBSSourceAutoRelease source = nullptr;
while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) {
placeHolderText = format.arg(++i);
}
for (;;) {
string name;
bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"),
QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText);
if (!accepted)
return;
if (name.empty()) {
OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text"));
continue;
}
obs_source_t *source = obs_get_source_by_name(name.c_str());
if (source) {
OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text"));
obs_source_release(source);
continue;
}
OBSSceneAutoRelease scene = obs_scene_duplicate(curScene, name.c_str(), OBS_SCENE_DUP_REFS);
source = obs_scene_get_source(scene);
SetCurrentScene(source, true);
auto undo = [](const std::string &data) {
OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str());
obs_source_remove(source);
};
auto redo = [this, name](const std::string &data) {
OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str());
obs_scene_t *scene = obs_scene_from_source(source);
scene = obs_scene_duplicate(scene, name.c_str(), OBS_SCENE_DUP_REFS);
source = obs_scene_get_source(scene);
SetCurrentScene(source.Get(), true);
};
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)));
break;
}
}
static bool save_undo_source_enum(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *p)
{
obs_source_t *source = obs_sceneitem_get_source(item);
if (obs_obj_is_private(source) && !obs_source_removed(source))
return true;
obs_data_array_t *array = (obs_data_array_t *)p;
/* check if the source is already stored in the array */
const char *name = obs_source_get_name(source);
const size_t count = obs_data_array_count(array);
for (size_t i = 0; i < count; i++) {
OBSDataAutoRelease sourceData = obs_data_array_item(array, i);
if (strcmp(name, obs_data_get_string(sourceData, "name")) == 0)
return true;
}
if (obs_source_is_group(source))
obs_scene_enum_items(obs_group_from_source(source), save_undo_source_enum, p);
OBSDataAutoRelease source_data = obs_save_source(source);
obs_data_array_push_back(array, source_data);
return true;
}
static inline void RemoveSceneAndReleaseNested(obs_source_t *source)
{
obs_source_remove(source);
auto cb = [](void *, obs_source_t *source) {
if (strcmp(obs_source_get_id(source), "scene") == 0)
obs_scene_prune_sources(obs_scene_from_source(source));
return true;
};
obs_enum_scenes(cb, NULL);
}
void OBSBasic::RemoveSelectedScene()
{
OBSScene scene = GetCurrentScene();
obs_source_t *source = obs_scene_get_source(scene);
if (!source || !QueryRemoveSource(source)) {
return;
}
/* ------------------------------ */
/* save all sources in scene */
OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_array_create();
obs_scene_enum_items(scene, save_undo_source_enum, sources_in_deleted_scene);
OBSDataAutoRelease scene_data = obs_save_source(source);
obs_data_array_push_back(sources_in_deleted_scene, scene_data);
/* ----------------------------------------------- */
/* save all scenes and groups the scene is used in */
OBSDataArrayAutoRelease scene_used_in_other_scenes = obs_data_array_create();
struct other_scenes_cb_data {
obs_source_t *oldScene;
obs_data_array_t *scene_used_in_other_scenes;
} other_scenes_cb_data;
other_scenes_cb_data.oldScene = source;
other_scenes_cb_data.scene_used_in_other_scenes = scene_used_in_other_scenes;
auto other_scenes_cb = [](void *data_ptr, obs_source_t *scene) {
struct other_scenes_cb_data *data = (struct other_scenes_cb_data *)data_ptr;
if (strcmp(obs_source_get_name(scene), obs_source_get_name(data->oldScene)) == 0)
return true;
obs_sceneitem_t *item = obs_scene_find_source(obs_group_or_scene_from_source(scene),
obs_source_get_name(data->oldScene));
if (item) {
OBSDataAutoRelease scene_data =
obs_save_source(obs_scene_get_source(obs_sceneitem_get_scene(item)));
obs_data_array_push_back(data->scene_used_in_other_scenes, scene_data);
}
return true;
};
obs_enum_scenes(other_scenes_cb, &other_scenes_cb_data);
/* --------------------------- */
/* undo/redo */
auto undo = [this](const std::string &json) {
OBSDataAutoRelease base = obs_data_create_from_json(json.c_str());
OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_get_array(base, "sources_in_deleted_scene");
OBSDataArrayAutoRelease scene_used_in_other_scenes =
obs_data_get_array(base, "scene_used_in_other_scenes");
int savedIndex = (int)obs_data_get_int(base, "index");
std::vector<OBSSource> sources;
/* create missing sources */
size_t count = obs_data_array_count(sources_in_deleted_scene);
sources.reserve(count);
for (size_t i = 0; i < count; i++) {
OBSDataAutoRelease data = obs_data_array_item(sources_in_deleted_scene, i);
const char *name = obs_data_get_string(data, "name");
OBSSourceAutoRelease source = obs_get_source_by_name(name);
if (!source) {
source = obs_load_source(data);
sources.push_back(source.Get());
}
}
/* actually load sources now */
for (obs_source_t *source : sources)
obs_source_load2(source);
/* Add scene to scenes and groups it was nested in */
for (size_t i = 0; i < obs_data_array_count(scene_used_in_other_scenes); i++) {
OBSDataAutoRelease data = obs_data_array_item(scene_used_in_other_scenes, i);
const char *name = obs_data_get_string(data, "name");
OBSSourceAutoRelease source = obs_get_source_by_name(name);
OBSDataAutoRelease settings = obs_data_get_obj(data, "settings");
OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items");
/* Clear scene, but keep a reference to all sources in the scene to make sure they don't get destroyed */
std::vector<OBSSource> existing_sources;
auto cb = [](obs_scene_t *, obs_sceneitem_t *item, void *data) {
std::vector<OBSSource> *existing = (std::vector<OBSSource> *)data;
OBSSource source = obs_sceneitem_get_source(item);
obs_sceneitem_remove(item);
existing->push_back(source);
return true;
};
obs_scene_enum_items(obs_group_or_scene_from_source(source), cb, (void *)&existing_sources);
/* Re-add sources to the scene */
obs_sceneitems_add(obs_group_or_scene_from_source(source), items);
}
obs_source_t *scene_source = sources.back();
OBSScene scene = obs_scene_from_source(scene_source);
SetCurrentScene(scene, true);
/* set original index in list box */
ui->scenes->blockSignals(true);
int curIndex = ui->scenes->currentRow();
QListWidgetItem *item = ui->scenes->takeItem(curIndex);
ui->scenes->insertItem(savedIndex, item);
ui->scenes->setCurrentRow(savedIndex);
currentScene = scene.Get();
ui->scenes->blockSignals(false);
};
auto redo = [](const std::string &name) {
OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str());
RemoveSceneAndReleaseNested(source);
};
OBSDataAutoRelease data = obs_data_create();
obs_data_set_array(data, "sources_in_deleted_scene", sources_in_deleted_scene);
obs_data_set_array(data, "scene_used_in_other_scenes", scene_used_in_other_scenes);
obs_data_set_int(data, "index", ui->scenes->currentRow());
const char *scene_name = obs_source_get_name(source);
undo_s.add_action(QTStr("Undo.Delete").arg(scene_name), undo, redo, obs_data_get_json(data), scene_name);
/* --------------------------- */
/* remove */
RemoveSceneAndReleaseNested(source);
OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED);
}
void OBSBasic::SceneReordered(void *data, calldata_t *params)
{
OBSBasic *window = static_cast<OBSBasic *>(data);
obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene");
QMetaObject::invokeMethod(window, "ReorderSources", Q_ARG(OBSScene, OBSScene(scene)));
}
void OBSBasic::SceneRefreshed(void *data, calldata_t *params)
{
OBSBasic *window = static_cast<OBSBasic *>(data);
obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene");
QMetaObject::invokeMethod(window, "RefreshSources", Q_ARG(OBSScene, OBSScene(scene)));
}
void OBSBasic::SceneItemAdded(void *data, calldata_t *params)
{
OBSBasic *window = static_cast<OBSBasic *>(data);
obs_sceneitem_t *item = (obs_sceneitem_t *)calldata_ptr(params, "item");
QMetaObject::invokeMethod(window, "AddSceneItem", Q_ARG(OBSSceneItem, OBSSceneItem(item)));
}
void OBSBasic::on_scenes_currentItemChanged(QListWidgetItem *current, QListWidgetItem *)
{
OBSSource source;
bool forceSceneChange = false;
if (current) {
OBSScene scene = GetOBSRef<OBSScene>(current);
source = obs_scene_get_source(scene);
bool oldSceneIsRemoved = obs_source_removed(obs_scene_get_source(currentScene));
forceSceneChange = oldSceneIsRemoved;
currentScene = scene;
} else {
currentScene = NULL;
}
SetCurrentScene(source, forceSceneChange);
if (vcamEnabled && vcamConfig.type == VCamOutputType::PreviewOutput)
outputHandler->UpdateVirtualCamOutputSource();
OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED);
UpdateContextBar();
}
void OBSBasic::EditSceneName()
{
ui->scenesDock->removeAction(renameScene);
QListWidgetItem *item = ui->scenes->currentItem();
Qt::ItemFlags flags = item->flags();
item->setFlags(flags | Qt::ItemIsEditable);
ui->scenes->editItem(item);
item->setFlags(flags);
}
void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos)
{
QListWidgetItem *item = ui->scenes->itemAt(pos);
QMenu popup(this);
QMenu order(QTStr("Basic.MainMenu.Edit.Order"), this);
popup.addAction(QTStr("AddScene") + "...", this, &OBSBasic::on_actionAddScene_triggered);
if (item) {
QAction *copyFilters = new QAction(QTStr("Copy.Filters"), this);
copyFilters->setEnabled(false);
connect(copyFilters, &QAction::triggered, this, &OBSBasic::SceneCopyFilters);
QAction *pasteFilters = new QAction(QTStr("Paste.Filters"), this);
pasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource));
connect(pasteFilters, &QAction::triggered, this, &OBSBasic::ScenePasteFilters);
popup.addSeparator();
popup.addAction(QTStr("Duplicate"), this, &OBSBasic::DuplicateSelectedScene);
popup.addAction(copyFilters);
popup.addAction(pasteFilters);
popup.addSeparator();
popup.addAction(renameScene);
popup.addAction(ui->actionRemoveScene);
popup.addSeparator();
order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveUp"), this, &OBSBasic::on_actionSceneUp_triggered);
order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveDown"), this,
&OBSBasic::on_actionSceneDown_triggered);
order.addSeparator();
order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToTop"), this, &OBSBasic::MoveSceneToTop);
order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToBottom"), this, &OBSBasic::MoveSceneToBottom);
popup.addMenu(&order);
popup.addSeparator();
delete sceneProjectorMenu;
sceneProjectorMenu = new QMenu(QTStr("Projector.Open.Scene"));
AddProjectorMenuMonitors(sceneProjectorMenu, this, &OBSBasic::OpenSceneProjector);
sceneProjectorMenu->addSeparator();
sceneProjectorMenu->addAction(QTStr("Projector.Window"), this, &OBSBasic::OpenSceneWindow);
popup.addMenu(sceneProjectorMenu);
popup.addSeparator();
popup.addAction(QTStr("Screenshot.Scene"), this, &OBSBasic::ScreenshotScene);
popup.addSeparator();
popup.addAction(QTStr("Filters"), this, &OBSBasic::OpenSceneFilters);
popup.addSeparator();
delete perSceneTransitionMenu;
perSceneTransitionMenu = CreatePerSceneTransitionMenu();
popup.addMenu(perSceneTransitionMenu);
/* ---------------------- */
QAction *multiviewAction = popup.addAction(QTStr("ShowInMultiview"));
OBSSource source = GetCurrentSceneSource();
OBSDataAutoRelease data = obs_source_get_private_settings(source);
obs_data_set_default_bool(data, "show_in_multiview", true);
bool show = obs_data_get_bool(data, "show_in_multiview");
multiviewAction->setCheckable(true);
multiviewAction->setChecked(show);
auto showInMultiview = [](OBSData data) {
bool show = obs_data_get_bool(data, "show_in_multiview");
obs_data_set_bool(data, "show_in_multiview", !show);
OBSProjector::UpdateMultiviewProjectors();
};
connect(multiviewAction, &QAction::triggered, multiviewAction, std::bind(showInMultiview, data.Get()));
copyFilters->setEnabled(obs_source_filter_count(source) > 0);
}
popup.addSeparator();
bool grid = ui->scenes->GetGridMode();
QAction *gridAction = new QAction(grid ? QTStr("Basic.Main.ListMode") : QTStr("Basic.Main.GridMode"), this);
connect(gridAction, &QAction::triggered, this, &OBSBasic::GridActionClicked);
popup.addAction(gridAction);
popup.exec(QCursor::pos());
}
void OBSBasic::on_actionSceneListMode_triggered()
{
ui->scenes->SetGridMode(false);
config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false);
}
void OBSBasic::on_actionSceneGridMode_triggered()
{
ui->scenes->SetGridMode(true);
config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", true);
}
void OBSBasic::GridActionClicked()
{
bool gridMode = !ui->scenes->GetGridMode();
ui->scenes->SetGridMode(gridMode);
if (gridMode)
ui->actionSceneGridMode->setChecked(true);
else
ui->actionSceneListMode->setChecked(true);
config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", gridMode);
}
void OBSBasic::on_actionAddScene_triggered()
{
string name;
QString format{QTStr("Basic.Main.DefaultSceneName.Text")};
int i = 2;
QString placeHolderText = format.arg(i);
OBSSourceAutoRelease source = nullptr;
while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) {
placeHolderText = format.arg(++i);
}
bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"),
QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText);
if (accepted) {
if (name.empty()) {
OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text"));
on_actionAddScene_triggered();
return;
}
OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str());
if (source) {
OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text"));
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_remove(t);
obs_source_release(t);
}
};
auto redo_fn = [this](const std::string &data) {
OBSSceneAutoRelease scene = obs_scene_create(data.c_str());
obs_source_t *source = obs_scene_get_source(scene);
SetCurrentScene(source, true);
};
undo_s.add_action(QTStr("Undo.Add").arg(QString(name.c_str())), undo_fn, redo_fn, name, name);
OBSSceneAutoRelease scene = obs_scene_create(name.c_str());
obs_source_t *scene_source = obs_scene_get_source(scene);
SetCurrentScene(scene_source);
}
}
void OBSBasic::on_actionRemoveScene_triggered()
{
RemoveSelectedScene();
}
void OBSBasic::ChangeSceneIndex(bool relative, int offset, int invalidIdx)
{
int idx = ui->scenes->currentRow();
if (idx == -1 || idx == invalidIdx)
return;
ui->scenes->blockSignals(true);
QListWidgetItem *item = ui->scenes->takeItem(idx);
if (!relative)
idx = 0;
ui->scenes->insertItem(idx + offset, item);
ui->scenes->setCurrentRow(idx + offset);
item->setSelected(true);
currentScene = GetOBSRef<OBSScene>(item).Get();
ui->scenes->blockSignals(false);
OBSProjector::UpdateMultiviewProjectors();
}
void OBSBasic::on_actionSceneUp_triggered()
{
ChangeSceneIndex(true, -1, 0);
}
void OBSBasic::on_actionSceneDown_triggered()
{
ChangeSceneIndex(true, 1, ui->scenes->count() - 1);
}
void OBSBasic::MoveSceneToTop()
{
ChangeSceneIndex(false, 0, 0);
}
void OBSBasic::MoveSceneToBottom()
{
ChangeSceneIndex(false, ui->scenes->count() - 1, ui->scenes->count() - 1);
}
void OBSBasic::EditSceneItemName()
{
int idx = GetTopSelectedSourceItem();
ui->sources->Edit(idx);
}
void OBSBasic::on_scenes_itemDoubleClicked(QListWidgetItem *witem)
{
if (!witem)
return;
if (IsPreviewProgramMode()) {
bool doubleClickSwitch =
config_get_bool(App()->GetUserConfig(), "BasicWindow", "TransitionOnDoubleClick");
if (doubleClickSwitch)
TransitionClicked();
}
}
OBSData OBSBasic::BackupScene(obs_scene_t *scene, std::vector<obs_source_t *> *sources)
{
OBSDataArrayAutoRelease undo_array = obs_data_array_create();
if (!sources) {
obs_scene_enum_items(scene, save_undo_source_enum, undo_array);
} else {
for (obs_source_t *source : *sources) {
obs_data_t *source_data = obs_save_source(source);
obs_data_array_push_back(undo_array, source_data);
obs_data_release(source_data);
}
}
OBSDataAutoRelease scene_data = obs_save_source(obs_scene_get_source(scene));
obs_data_array_push_back(undo_array, scene_data);
OBSDataAutoRelease data = obs_data_create();
obs_data_set_array(data, "array", undo_array);
obs_data_get_json(data);
return data.Get();
}
static bool add_source_enum(obs_scene_t *, obs_sceneitem_t *item, void *p)
{
auto sources = static_cast<std::vector<OBSSource> *>(p);
sources->push_back(obs_sceneitem_get_source(item));
return true;
}
void OBSBasic::CreateSceneUndoRedoAction(const QString &action_name, OBSData undo_data, OBSData redo_data)
{
auto undo_redo = [this](const std::string &json) {
OBSDataAutoRelease base = obs_data_create_from_json(json.c_str());
OBSDataArrayAutoRelease array = obs_data_get_array(base, "array");
std::vector<OBSSource> sources;
std::vector<OBSSource> old_sources;
/* create missing sources */
const size_t count = obs_data_array_count(array);
sources.reserve(count);
for (size_t i = 0; i < count; i++) {
OBSDataAutoRelease data = obs_data_array_item(array, i);
const char *name = obs_data_get_string(data, "name");
OBSSourceAutoRelease source = obs_get_source_by_name(name);
if (!source)
source = obs_load_source(data);
sources.push_back(source.Get());
/* update scene/group settings to restore their
* contents to their saved settings */
obs_scene_t *scene = obs_group_or_scene_from_source(source);
if (scene) {
obs_scene_enum_items(scene, add_source_enum, &old_sources);
OBSDataAutoRelease scene_settings = obs_data_get_obj(data, "settings");
obs_source_update(source, scene_settings);
}
}
/* actually load sources now */
for (obs_source_t *source : sources)
obs_source_load2(source);
ui->sources->RefreshItems();
};
const char *undo_json = obs_data_get_last_json(undo_data);
const char *redo_json = obs_data_get_last_json(redo_data);
undo_s.add_action(action_name, undo_redo, undo_redo, undo_json, redo_json);
}
void OBSBasic::MoveSceneItem(enum obs_order_movement movement, const QString &action_name)
{
OBSSceneItem item = GetCurrentSceneItem();
obs_source_t *source = obs_sceneitem_get_source(item);
if (!source)
return;
OBSScene scene = GetCurrentScene();
std::vector<obs_source_t *> sources;
if (scene != obs_sceneitem_get_scene(item))
sources.push_back(obs_scene_get_source(obs_sceneitem_get_scene(item)));
OBSData undo_data = BackupScene(scene, &sources);
obs_sceneitem_set_order(item, movement);
const char *source_name = obs_source_get_name(source);
const char *scene_name = obs_source_get_name(obs_scene_get_source(scene));
OBSData redo_data = BackupScene(scene, &sources);
CreateSceneUndoRedoAction(action_name.arg(source_name, scene_name), undo_data, redo_data);
}
static void RenameListItem(OBSBasic *parent, QListWidget *listWidget, obs_source_t *source, const string &name)
{
const char *prevName = obs_source_get_name(source);
if (name == prevName)
return;
OBSSourceAutoRelease foundSource = obs_get_source_by_name(name.c_str());
QListWidgetItem *listItem = listWidget->currentItem();
if (foundSource || name.empty()) {
listItem->setText(QT_UTF8(prevName));
if (foundSource) {
OBSMessageBox::warning(parent, QTStr("NameExists.Title"), QTStr("NameExists.Text"));
} else if (name.empty()) {
OBSMessageBox::warning(parent, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text"));
}
} else {
auto undo = [prev = std::string(prevName)](const std::string &data) {
OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str());
obs_source_set_name(source, prev.c_str());
};
auto redo = [name](const std::string &data) {
OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str());
obs_source_set_name(source, name.c_str());
};
std::string source_uuid(obs_source_get_uuid(source));
parent->undo_s.add_action(QTStr("Undo.Rename").arg(name.c_str()), undo, redo, source_uuid, source_uuid);
listItem->setText(QT_UTF8(name.c_str()));
obs_source_set_name(source, name.c_str());
}
}
void OBSBasic::SceneNameEdited(QWidget *editor)
{
OBSScene scene = GetCurrentScene();
QLineEdit *edit = qobject_cast<QLineEdit *>(editor);
string text = QT_TO_UTF8(edit->text().trimmed());
if (!scene)
return;
obs_source_t *source = obs_scene_get_source(scene);
RenameListItem(this, ui->scenes, source, text);
ui->scenesDock->addAction(renameScene);
OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED);
}
void OBSBasic::OpenSceneFilters()
{
OBSScene scene = GetCurrentScene();
OBSSource source = obs_scene_get_source(scene);
CreateFiltersWindow(source);
}
static bool reset_tr(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *)
{
if (obs_sceneitem_is_group(item))
obs_sceneitem_group_enum_items(item, reset_tr, nullptr);
if (!obs_sceneitem_selected(item))
return true;
if (obs_sceneitem_locked(item))
return true;
obs_sceneitem_defer_update_begin(item);
obs_transform_info info;
vec2_set(&info.pos, 0.0f, 0.0f);
vec2_set(&info.scale, 1.0f, 1.0f);
info.rot = 0.0f;
info.alignment = OBS_ALIGN_TOP | OBS_ALIGN_LEFT;
info.bounds_type = OBS_BOUNDS_NONE;
info.bounds_alignment = OBS_ALIGN_CENTER;
info.crop_to_bounds = false;
vec2_set(&info.bounds, 0.0f, 0.0f);
obs_sceneitem_set_info2(item, &info);
obs_sceneitem_crop crop = {};
obs_sceneitem_set_crop(item, &crop);
obs_sceneitem_defer_update_end(item);
return true;
}
void OBSBasic::on_actionResetTransform_triggered()
{
OBSScene scene = GetCurrentScene();
OBSDataAutoRelease wrapper = obs_scene_save_transform_states(scene, false);
obs_scene_enum_items(scene, reset_tr, nullptr);
OBSDataAutoRelease 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);
obs_scene_enum_items(GetCurrentScene(), reset_tr, nullptr);
}
SourceTreeItem *OBSBasic::GetItemWidgetFromSceneItem(obs_sceneitem_t *sceneItem)
{
int i = 0;
SourceTreeItem *treeItem = ui->sources->GetItemWidget(i);
OBSSceneItem item = ui->sources->Get(i);
int64_t id = obs_sceneitem_get_id(sceneItem);
while (treeItem && obs_sceneitem_get_id(item) != id) {
i++;
treeItem = ui->sources->GetItemWidget(i);
item = ui->sources->Get(i);
}
if (treeItem)
return treeItem;
return nullptr;
}
void OBSBasic::on_actionSceneFilters_triggered()
{
OBSSource sceneSource = GetCurrentSceneSource();
if (sceneSource)
OpenFilters(sceneSource);
}