mirror of
https://github.com/obsproject/obs-studio
synced 2024-10-23 16:12:01 +00:00
Merge pull request #3426 from Programatic/undo_redo
UI: Implement Undo/Redo System
This commit is contained in:
commit
5d87f3c00b
|
@ -260,7 +260,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}
|
||||
|
@ -331,7 +332,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)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#include "window-basic-main.hpp"
|
||||
#include "context-bar-controls.hpp"
|
||||
#include "qt-wrappers.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)
|
||||
|
@ -163,8 +221,10 @@ void ComboSelectToolbar::on_device_currentIndexChanged(int idx)
|
|||
return;
|
||||
}
|
||||
|
||||
SaveOldProperties(source);
|
||||
UpdateSourceComboToolbarValue(ui->device, source, idx, prop_name,
|
||||
is_int);
|
||||
SetUndoProperties(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();
|
||||
|
||||
SaveOldProperties(source);
|
||||
obs_data_t *settings = obs_data_create();
|
||||
obs_data_set_string(settings, "capture_mode", QT_TO_UTF8(id));
|
||||
obs_source_update(source, settings);
|
||||
obs_data_release(settings);
|
||||
SetUndoProperties(source);
|
||||
|
||||
UpdateWindowVisibility();
|
||||
}
|
||||
|
@ -387,10 +449,12 @@ void GameCaptureToolbar::on_window_currentIndexChanged(int idx)
|
|||
|
||||
QString id = ui->window->itemData(idx).toString();
|
||||
|
||||
SaveOldProperties(source);
|
||||
obs_data_t *settings = obs_data_create();
|
||||
obs_data_set_string(settings, "window", QT_TO_UTF8(id));
|
||||
obs_source_update(source, settings);
|
||||
obs_data_release(settings);
|
||||
SetUndoProperties(source);
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
|
@ -434,10 +498,12 @@ void ImageSourceToolbar::on_browse_clicked()
|
|||
|
||||
ui->path->setText(path);
|
||||
|
||||
SaveOldProperties(source);
|
||||
obs_data_t *settings = obs_data_create();
|
||||
obs_data_set_string(settings, "file", QT_TO_UTF8(path));
|
||||
obs_source_update(source, settings);
|
||||
obs_data_release(settings);
|
||||
SetUndoProperties(source);
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
|
@ -518,10 +584,14 @@ void ColorSourceToolbar::on_choose_clicked()
|
|||
color = newColor;
|
||||
UpdateColor();
|
||||
|
||||
SaveOldProperties(source);
|
||||
|
||||
obs_data_t *settings = obs_data_create();
|
||||
obs_data_set_int(settings, "color", color_to_int(color));
|
||||
obs_source_update(source, settings);
|
||||
obs_data_release(settings);
|
||||
|
||||
SetUndoProperties(source);
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
|
@ -596,6 +666,8 @@ void TextSourceToolbar::on_selectFont_clicked()
|
|||
flags |= font.strikeOut() ? OBS_FONT_STRIKEOUT : 0;
|
||||
obs_data_set_int(font_obj, "flags", flags);
|
||||
|
||||
SaveOldProperties(source);
|
||||
|
||||
obs_data_t *settings = obs_data_create();
|
||||
|
||||
obs_data_set_obj(settings, "font", font_obj);
|
||||
|
@ -603,6 +675,8 @@ void TextSourceToolbar::on_selectFont_clicked()
|
|||
|
||||
obs_source_update(source, settings);
|
||||
obs_data_release(settings);
|
||||
|
||||
SetUndoProperties(source);
|
||||
}
|
||||
|
||||
void TextSourceToolbar::on_selectColor_clicked()
|
||||
|
@ -628,6 +702,8 @@ void TextSourceToolbar::on_selectColor_clicked()
|
|||
|
||||
color = newColor;
|
||||
|
||||
SaveOldProperties(source);
|
||||
|
||||
obs_data_t *settings = obs_data_create();
|
||||
if (!strncmp(obs_source_get_id(source), "text_ft2_source", 15)) {
|
||||
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_data_release(settings);
|
||||
|
||||
SetUndoProperties(source);
|
||||
}
|
||||
|
||||
void TextSourceToolbar::on_text_textChanged()
|
||||
|
|
|
@ -22,6 +22,10 @@ protected:
|
|||
std::unique_ptr<obs_properties_t, properties_delete_t>;
|
||||
|
||||
properties_t props;
|
||||
OBSData oldData;
|
||||
|
||||
void SaveOldProperties(obs_source_t *source);
|
||||
void SetUndoProperties(obs_source_t *source);
|
||||
|
||||
public:
|
||||
SourceToolbar(QWidget *parent, OBSSource source);
|
||||
|
|
|
@ -264,6 +264,34 @@ Basic.SceneTransitions="Scene Transitions"
|
|||
Basic.TransitionDuration="Duration"
|
||||
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
|
||||
TransitionNameDlg.Text="Please enter the name of the transition"
|
||||
TransitionNameDlg.Title="Transition Name"
|
||||
|
|
|
@ -586,6 +586,9 @@
|
|||
<string>Paste.Filters</string>
|
||||
</property>
|
||||
</action>
|
||||
<addaction name="actionMainUndo"/>
|
||||
<addaction name="actionMainRedo"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionCopySource"/>
|
||||
<addaction name="actionPasteRef"/>
|
||||
<addaction name="actionPasteDup"/>
|
||||
|
@ -599,6 +602,7 @@
|
|||
<addaction name="actionLockPreview"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionAdvAudioProperties"/>
|
||||
<addaction name="separator"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="profileMenu">
|
||||
<property name="title">
|
||||
|
@ -2034,7 +2038,23 @@
|
|||
<string>Basic.MainMenu.View.ContextBar</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<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>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>OBSBasicPreview</class>
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include <QStackedWidget>
|
||||
#include <QDir>
|
||||
#include <QGroupBox>
|
||||
#include <QObject>
|
||||
#include "double-slider.hpp"
|
||||
#include "slider-ignorewheel.hpp"
|
||||
#include "spinbox-ignorewheel.hpp"
|
||||
|
@ -31,6 +32,9 @@
|
|||
|
||||
#include <cstdlib>
|
||||
#include <initializer_list>
|
||||
#include <obs-data.h>
|
||||
#include <obs.h>
|
||||
#include <qtimer.h>
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
|
@ -171,13 +175,14 @@ void OBSPropertiesView::GetScrollPos(int &h, int &v)
|
|||
OBSPropertiesView::OBSPropertiesView(OBSData settings_, void *obj_,
|
||||
PropertiesReloadCallback reloadCallback,
|
||||
PropertiesUpdateCallback callback_,
|
||||
int minSize_)
|
||||
PropertiesVisualUpdateCb cb_, int minSize_)
|
||||
: VScrollArea(nullptr),
|
||||
properties(nullptr, obs_properties_destroy),
|
||||
settings(settings_),
|
||||
obj(obj_),
|
||||
reloadCallback(reloadCallback),
|
||||
callback(callback_),
|
||||
cb(cb_),
|
||||
minSize(minSize_)
|
||||
{
|
||||
setFrameShape(QFrame::NoFrame);
|
||||
|
@ -1885,6 +1890,12 @@ void WidgetInfo::ControlChanged()
|
|||
const char *setting = obs_property_name(property);
|
||||
obs_property_type type = obs_property_get_type(property);
|
||||
|
||||
if (!recently_updated) {
|
||||
old_settings_cache = obs_data_create();
|
||||
obs_data_apply(old_settings_cache, view->settings);
|
||||
obs_data_release(old_settings_cache);
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case OBS_PROPERTY_INVALID:
|
||||
return;
|
||||
|
@ -1933,8 +1944,32 @@ void WidgetInfo::ControlChanged()
|
|||
break;
|
||||
}
|
||||
|
||||
if (view->callback && !view->deferUpdate)
|
||||
view->callback(view->obj, view->settings);
|
||||
if (!recently_updated) {
|
||||
recently_updated = true;
|
||||
update_timer = new QTimer;
|
||||
connect(update_timer, &QTimer::timeout,
|
||||
[this, &ru = recently_updated]() {
|
||||
if (view->callback && !view->deferUpdate) {
|
||||
view->callback(view->obj,
|
||||
old_settings_cache,
|
||||
view->settings);
|
||||
}
|
||||
|
||||
ru = false;
|
||||
});
|
||||
connect(update_timer, &QTimer::timeout, &QTimer::deleteLater);
|
||||
update_timer->setSingleShot(true);
|
||||
}
|
||||
|
||||
if (update_timer) {
|
||||
update_timer->stop();
|
||||
update_timer->start(500);
|
||||
} else {
|
||||
blog(LOG_DEBUG, "No update timer or no callback!");
|
||||
}
|
||||
|
||||
if (view->cb && !view->deferUpdate)
|
||||
view->cb(view->obj, view->settings);
|
||||
|
||||
view->SignalChanged();
|
||||
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
#pragma once
|
||||
|
||||
#include "vertical-scroll-area.hpp"
|
||||
#include <obs-data.h>
|
||||
#include <obs.hpp>
|
||||
#include <qtimer.h>
|
||||
#include <QPointer>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
|
@ -10,7 +13,9 @@ class OBSPropertiesView;
|
|||
class QLabel;
|
||||
|
||||
typedef obs_properties_t *(*PropertiesReloadCallback)(void *obj);
|
||||
typedef void (*PropertiesUpdateCallback)(void *obj, obs_data_t *settings);
|
||||
typedef void (*PropertiesUpdateCallback)(void *obj, obs_data_t *old_settings,
|
||||
obs_data_t *new_settings);
|
||||
typedef void (*PropertiesVisualUpdateCb)(void *obj, obs_data_t *settings);
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
|
@ -23,6 +28,9 @@ private:
|
|||
OBSPropertiesView *view;
|
||||
obs_property_t *property;
|
||||
QWidget *widget;
|
||||
QPointer<QTimer> update_timer;
|
||||
bool recently_updated = false;
|
||||
OBSData old_settings_cache;
|
||||
|
||||
void BoolChanged(const char *setting);
|
||||
void IntChanged(const char *setting);
|
||||
|
@ -47,6 +55,15 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
~WidgetInfo()
|
||||
{
|
||||
if (update_timer) {
|
||||
update_timer->stop();
|
||||
update_timer->deleteLater();
|
||||
obs_data_release(old_settings_cache);
|
||||
}
|
||||
}
|
||||
|
||||
public slots:
|
||||
|
||||
void ControlChanged();
|
||||
|
@ -83,6 +100,7 @@ private:
|
|||
std::string type;
|
||||
PropertiesReloadCallback reloadCallback;
|
||||
PropertiesUpdateCallback callback = nullptr;
|
||||
PropertiesVisualUpdateCb cb = nullptr;
|
||||
int minSize;
|
||||
std::vector<std::unique_ptr<WidgetInfo>> children;
|
||||
std::string lastFocused;
|
||||
|
@ -135,13 +153,15 @@ signals:
|
|||
public:
|
||||
OBSPropertiesView(OBSData settings, void *obj,
|
||||
PropertiesReloadCallback reloadCallback,
|
||||
PropertiesUpdateCallback callback, int minSize = 0);
|
||||
PropertiesUpdateCallback callback,
|
||||
PropertiesVisualUpdateCb cb = nullptr,
|
||||
int minSize = 0);
|
||||
OBSPropertiesView(OBSData settings, const char *type,
|
||||
PropertiesReloadCallback reloadCallback,
|
||||
int minSize = 0);
|
||||
|
||||
inline obs_data_t *GetSettings() const { return settings; }
|
||||
|
||||
inline void UpdateSettings() { callback(obj, settings); }
|
||||
inline void UpdateSettings() { callback(obj, nullptr, settings); }
|
||||
inline bool DeferUpdate() const { return deferUpdate; }
|
||||
};
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
|
|
101
UI/undo-stack-obs.cpp
Normal file
101
UI/undo-stack-obs.cpp
Normal 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
46
UI/undo-stack-obs.hpp
Normal 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();
|
||||
};
|
|
@ -15,6 +15,7 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
#include "properties-view.hpp"
|
||||
#include "window-namedialog.hpp"
|
||||
#include "window-basic-main.hpp"
|
||||
#include "window-basic-filters.hpp"
|
||||
|
@ -23,9 +24,13 @@
|
|||
#include "visibility-item-widget.hpp"
|
||||
#include "item-widget-helpers.hpp"
|
||||
#include "obs-app.hpp"
|
||||
#include "undo-stack-obs.hpp"
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QCloseEvent>
|
||||
#include <obs-data.h>
|
||||
#include <obs.h>
|
||||
#include <util/base.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <QMenu>
|
||||
|
@ -202,10 +207,82 @@ void OBSBasicFilters::UpdatePropertiesView(int row, bool async)
|
|||
|
||||
obs_data_t *settings = obs_source_get_settings(filter);
|
||||
|
||||
auto filter_change = [](void *vp, obs_data_t *nd_old_settings,
|
||||
obs_data_t *new_settings) {
|
||||
obs_source_t *source = reinterpret_cast<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(
|
||||
settings, filter,
|
||||
(PropertiesReloadCallback)obs_source_properties,
|
||||
(PropertiesUpdateCallback)obs_source_update);
|
||||
(PropertiesUpdateCallback)filter_change,
|
||||
(PropertiesVisualUpdateCb)disabled_undo);
|
||||
|
||||
updatePropertiesSignal.Connect(obs_source_get_signal_handler(filter),
|
||||
"update_properties",
|
||||
|
@ -240,6 +317,7 @@ void OBSBasicFilters::AddFilter(OBSSource filter, bool focus)
|
|||
list->addItem(item);
|
||||
if (focus)
|
||||
list->setCurrentItem(item);
|
||||
|
||||
SetupVisibilityItem(list, item, filter);
|
||||
}
|
||||
|
||||
|
@ -486,6 +564,68 @@ void OBSBasicFilters::AddNewFilter(const char *id)
|
|||
return;
|
||||
}
|
||||
|
||||
obs_data_t *wrapper = obs_data_create();
|
||||
obs_data_set_string(wrapper, "sname",
|
||||
obs_source_get_name(source));
|
||||
obs_data_set_string(wrapper, "fname", name.c_str());
|
||||
std::string scene_name = obs_source_get_name(
|
||||
reinterpret_cast<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_create(id, name.c_str(), nullptr, nullptr);
|
||||
if (filter) {
|
||||
|
@ -674,8 +814,75 @@ void OBSBasicFilters::on_removeEffectFilter_clicked()
|
|||
{
|
||||
OBSSource filter = GetFilter(ui->effectFilters->currentRow(), false);
|
||||
if (filter) {
|
||||
if (QueryRemove(this, filter))
|
||||
if (QueryRemove(this, filter)) {
|
||||
obs_data_t *wrapper = obs_save_source(filter);
|
||||
std::string parent_name(obs_source_get_name(source));
|
||||
obs_data_set_string(wrapper, "undo_name",
|
||||
parent_name.c_str());
|
||||
|
||||
std::string scene_name = obs_source_get_name(
|
||||
reinterpret_cast<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_data_release(dat);
|
||||
obs_source_release(filter);
|
||||
obs_source_release(source);
|
||||
};
|
||||
|
||||
std::string undo_data(obs_data_get_json(wrapper));
|
||||
std::string redo_data(obs_data_get_json(rwrapper));
|
||||
main->undo_s.add_action(
|
||||
QTStr("Undo.Delete")
|
||||
.arg(obs_source_get_name(filter)),
|
||||
undo, redo, undo_data, redo_data, NULL);
|
||||
obs_source_filter_remove(source, filter);
|
||||
|
||||
obs_data_release(wrapper);
|
||||
obs_data_release(rwrapper);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -918,6 +1125,48 @@ void OBSBasicFilters::FilterNameEdited(QWidget *editor, QListWidget *list)
|
|||
|
||||
listItem->setText(QT_UTF8(name.c_str()));
|
||||
obs_source_set_name(filter, name.c_str());
|
||||
|
||||
std::string scene_name = obs_source_get_name(
|
||||
reinterpret_cast<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());
|
||||
|
|
|
@ -151,17 +151,55 @@ bool OBSBasic::AddSceneCollection(bool create_new, const QString &qname)
|
|||
}
|
||||
}
|
||||
|
||||
SaveProjectNow();
|
||||
auto new_collection = [this, create_new](const std::string &file,
|
||||
const std::string &name) {
|
||||
SaveProjectNow();
|
||||
|
||||
config_set_string(App()->GlobalConfig(), "Basic", "SceneCollection",
|
||||
name.c_str());
|
||||
config_set_string(App()->GlobalConfig(), "Basic", "SceneCollectionFile",
|
||||
file.c_str());
|
||||
if (create_new) {
|
||||
CreateDefaultScene(false);
|
||||
}
|
||||
SaveProjectNow();
|
||||
RefreshSceneCollections();
|
||||
config_set_string(App()->GlobalConfig(), "Basic",
|
||||
"SceneCollection", name.c_str());
|
||||
config_set_string(App()->GlobalConfig(), "Basic",
|
||||
"SceneCollectionFile", file.c_str());
|
||||
if (create_new) {
|
||||
CreateDefaultScene(false);
|
||||
}
|
||||
SaveProjectNow();
|
||||
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)",
|
||||
name.c_str(), create_new ? "clean" : "duplicate", file.c_str());
|
||||
|
@ -245,11 +283,13 @@ void OBSBasic::on_actionRenameSceneCollection_triggered()
|
|||
{
|
||||
std::string name;
|
||||
std::string file;
|
||||
std::string oname;
|
||||
|
||||
std::string oldFile = config_get_string(App()->GlobalConfig(), "Basic",
|
||||
"SceneCollectionFile");
|
||||
const char *oldName = config_get_string(App()->GlobalConfig(), "Basic",
|
||||
"SceneCollection");
|
||||
oname = std::string(oldName);
|
||||
|
||||
bool success = GetSceneCollectionName(this, name, file, oldName);
|
||||
if (!success)
|
||||
|
@ -268,6 +308,59 @@ void OBSBasic::on_actionRenameSceneCollection_triggered()
|
|||
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 += ".json";
|
||||
os_unlink(oldFile.c_str());
|
||||
|
@ -282,6 +375,9 @@ void OBSBasic::on_actionRenameSceneCollection_triggered()
|
|||
UpdateTitleBar();
|
||||
RefreshSceneCollections();
|
||||
|
||||
undo_s.add_action(QTStr("Undo.Rename").arg(name.c_str()), undo, redo,
|
||||
"", "", NULL);
|
||||
|
||||
if (api) {
|
||||
api->on_event(OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED);
|
||||
api->on_event(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED);
|
||||
|
@ -331,6 +427,32 @@ void OBSBasic::on_actionRemoveSceneCollection_triggered()
|
|||
|
||||
oldFile.insert(0, path);
|
||||
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());
|
||||
oldFile += ".bak";
|
||||
os_unlink(oldFile.c_str());
|
||||
|
@ -410,6 +532,9 @@ void OBSBasic::ChangeSceneCollection()
|
|||
|
||||
const char *oldName = config_get_string(App()->GlobalConfig(), "Basic",
|
||||
"SceneCollection");
|
||||
std::string oldFile = std::string(config_get_string(
|
||||
App()->GlobalConfig(), "Basic", "SceneCollectionFile"));
|
||||
|
||||
if (action->text().compare(QT_UTF8(oldName)) == 0) {
|
||||
action->setChecked(true);
|
||||
return;
|
||||
|
@ -431,6 +556,29 @@ void OBSBasic::ChangeSceneCollection()
|
|||
|
||||
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)
|
||||
api->on_event(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED);
|
||||
}
|
||||
|
|
|
@ -17,7 +17,10 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
******************************************************************************/
|
||||
|
||||
#include <cstddef>
|
||||
#include <ctime>
|
||||
#include <obs-data.h>
|
||||
#include <obs.h>
|
||||
#include <obs.hpp>
|
||||
#include <QGuiApplication>
|
||||
#include <QMessageBox>
|
||||
|
@ -58,6 +61,7 @@
|
|||
#include "remote-text.hpp"
|
||||
#include "ui-validation.hpp"
|
||||
#include "media-controls.hpp"
|
||||
#include "undo-stack-obs.hpp"
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
|
@ -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<SignalContainer<OBSScene>>(
|
||||
|
@ -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<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
|
||||
ui->toggleScenes->setChecked(false);
|
||||
ui->toggleSources->setChecked(false);
|
||||
|
@ -922,6 +936,11 @@ void OBSBasic::Load(const char *file)
|
|||
return;
|
||||
}
|
||||
|
||||
LoadData(data, file);
|
||||
}
|
||||
|
||||
void OBSBasic::LoadData(obs_data_t *data, const char *file)
|
||||
{
|
||||
ClearSceneData();
|
||||
InitDefaultTransitions();
|
||||
ClearContextBar();
|
||||
|
@ -3622,6 +3641,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 +3677,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<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_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 +4468,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();
|
||||
|
@ -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()
|
||||
{
|
||||
if (advAudioWindow != nullptr) {
|
||||
|
@ -4452,6 +4632,53 @@ void OBSBasic::on_actionAdvAudioProperties_triggered()
|
|||
|
||||
connect(advAudioWindow, SIGNAL(destroyed()), this,
|
||||
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()
|
||||
|
@ -4686,20 +4913,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)
|
||||
|
@ -5114,10 +5355,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5258,9 +5500,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);
|
||||
|
@ -5272,21 +5516,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<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_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()
|
||||
|
@ -5524,6 +5886,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());
|
||||
}
|
||||
|
@ -6739,8 +7122,23 @@ void OBSBasic::on_actionCopyTransform_triggered()
|
|||
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()
|
||||
{
|
||||
obs_data_t *wrapper =
|
||||
obs_scene_save_transform_states(GetCurrentScene(), false);
|
||||
auto func = [](obs_scene_t *scene, obs_sceneitem_t *item, void *param) {
|
||||
if (!obs_sceneitem_selected(item))
|
||||
return true;
|
||||
|
@ -6756,6 +7154,19 @@ void OBSBasic::on_actionPasteTransform_triggered()
|
|||
};
|
||||
|
||||
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)
|
||||
|
@ -6791,6 +7202,22 @@ static bool reset_tr(obs_scene_t *scene, obs_sceneitem_t *item, void *param)
|
|||
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -6868,19 +7295,61 @@ static bool RotateSelectedSources(obs_scene_t *scene, obs_sceneitem_t *item,
|
|||
void OBSBasic::on_actionRotate90CW_triggered()
|
||||
{
|
||||
float f90CW = 90.0f;
|
||||
obs_data_t *wrapper =
|
||||
obs_scene_save_transform_states(GetCurrentScene(), false);
|
||||
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()
|
||||
{
|
||||
float f90CCW = -90.0f;
|
||||
obs_data_t *wrapper =
|
||||
obs_scene_save_transform_states(GetCurrentScene(), false);
|
||||
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()
|
||||
{
|
||||
float f180 = 180.0f;
|
||||
obs_data_t *wrapper =
|
||||
obs_scene_save_transform_states(GetCurrentScene(), false);
|
||||
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,
|
||||
|
@ -6915,16 +7384,44 @@ void OBSBasic::on_actionFlipHorizontal_triggered()
|
|||
{
|
||||
vec2 scale;
|
||||
vec2_set(&scale, -1.0f, 1.0f);
|
||||
obs_data_t *wrapper =
|
||||
obs_scene_save_transform_states(GetCurrentScene(), false);
|
||||
obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale,
|
||||
&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()
|
||||
{
|
||||
vec2 scale;
|
||||
vec2_set(&scale, 1.0f, -1.0f);
|
||||
obs_data_t *wrapper =
|
||||
obs_scene_save_transform_states(GetCurrentScene(), false);
|
||||
obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale,
|
||||
&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,
|
||||
|
@ -6964,15 +7461,43 @@ static bool CenterAlignSelectedItems(obs_scene_t *scene, obs_sceneitem_t *item,
|
|||
void OBSBasic::on_actionFitToScreen_triggered()
|
||||
{
|
||||
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,
|
||||
&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()
|
||||
{
|
||||
obs_bounds_type boundsType = OBS_BOUNDS_STRETCH;
|
||||
obs_data_t *wrapper =
|
||||
obs_scene_save_transform_states(GetCurrentScene(), false);
|
||||
obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems,
|
||||
&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 {
|
||||
|
@ -7033,19 +7558,61 @@ static bool center_to_scene(obs_scene_t *, obs_sceneitem_t *item, void *param)
|
|||
void OBSBasic::on_actionCenterToScreen_triggered()
|
||||
{
|
||||
CenterType centerType = CenterType::Scene;
|
||||
obs_data_t *wrapper =
|
||||
obs_scene_save_transform_states(GetCurrentScene(), false);
|
||||
obs_scene_enum_items(GetCurrentScene(), center_to_scene, ¢erType);
|
||||
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()
|
||||
{
|
||||
CenterType centerType = CenterType::Vertical;
|
||||
obs_data_t *wrapper =
|
||||
obs_scene_save_transform_states(GetCurrentScene(), false);
|
||||
obs_scene_enum_items(GetCurrentScene(), center_to_scene, ¢erType);
|
||||
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()
|
||||
{
|
||||
CenterType centerType = CenterType::Horizontal;
|
||||
obs_data_t *wrapper =
|
||||
obs_scene_save_transform_states(GetCurrentScene(), false);
|
||||
obs_scene_enum_items(GetCurrentScene(), center_to_scene, ¢erType);
|
||||
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)
|
||||
|
@ -7136,6 +7703,46 @@ void OBSBasic::Nudge(int dist, MoveDir dir)
|
|||
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);
|
||||
}
|
||||
|
||||
|
@ -7767,6 +8374,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();
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
#include "window-basic-about.hpp"
|
||||
#include "auth-base.hpp"
|
||||
#include "log-viewer.hpp"
|
||||
#include "undo-stack-obs.hpp"
|
||||
|
||||
#include <obs-frontend-internal.hpp>
|
||||
|
||||
|
@ -156,6 +157,7 @@ class OBSBasic : public OBSMainWindow {
|
|||
friend class OBSBasicPreview;
|
||||
friend class OBSBasicStatusBar;
|
||||
friend class OBSBasicSourceSelect;
|
||||
friend class OBSBasicTransform;
|
||||
friend class OBSBasicSettings;
|
||||
friend class Auth;
|
||||
friend class AutoConfig;
|
||||
|
@ -166,6 +168,7 @@ class OBSBasic : public OBSMainWindow {
|
|||
friend class ExtraBrowsersDelegate;
|
||||
friend class DeviceCaptureToolbar;
|
||||
friend class DeviceToolbarPropertiesThread;
|
||||
friend class OBSBasicSourceSelect;
|
||||
friend struct BasicOutputHandler;
|
||||
friend struct OBSStudioAPI;
|
||||
|
||||
|
@ -220,6 +223,9 @@ private:
|
|||
QPointer<QTimer> cpuUsageTimer;
|
||||
QPointer<QTimer> diskFullTimer;
|
||||
|
||||
QPointer<QTimer> nudge_timer;
|
||||
bool recent_nudge = false;
|
||||
|
||||
os_cpu_usage_info_t *cpuUsageInfo = nullptr;
|
||||
|
||||
OBSService service;
|
||||
|
@ -308,6 +314,7 @@ private:
|
|||
void UploadLog(const char *subdir, const char *file, const bool crash);
|
||||
|
||||
void Save(const char *file);
|
||||
void LoadData(obs_data_t *data, const char *file);
|
||||
void Load(const char *file);
|
||||
|
||||
void InitHotkeys();
|
||||
|
@ -609,6 +616,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);
|
||||
|
@ -741,6 +752,7 @@ private:
|
|||
OBSSource prevFTBSource = nullptr;
|
||||
|
||||
public:
|
||||
undo_stack undo_s;
|
||||
OBSSource GetProgramSource();
|
||||
OBSScene GetCurrentScene();
|
||||
|
||||
|
|
|
@ -36,6 +36,9 @@ OBSBasicPreview::~OBSBasicPreview()
|
|||
gs_vertexbuffer_destroy(rectFill);
|
||||
|
||||
obs_leave_graphics();
|
||||
|
||||
if (wrapper)
|
||||
obs_data_release(wrapper);
|
||||
}
|
||||
|
||||
vec2 OBSBasicPreview::GetMouseEventPos(QMouseEvent *event)
|
||||
|
@ -581,6 +584,11 @@ void OBSBasicPreview::mousePressEvent(QMouseEvent *event)
|
|||
vec2_zero(&lastMoveOffset);
|
||||
|
||||
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)
|
||||
|
@ -713,6 +721,41 @@ void OBSBasicPreview::mouseReleaseEvent(QMouseEvent *event)
|
|||
hoveredPreviewItems.push_back(item);
|
||||
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 {
|
||||
|
@ -1434,6 +1477,8 @@ void OBSBasicPreview::StretchItem(const vec2 &pos)
|
|||
|
||||
void OBSBasicPreview::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
changed = true;
|
||||
|
||||
if (scrollMode && event->buttons() == Qt::LeftButton) {
|
||||
scrollingOffset.x += event->x() - scrollingFrom.x;
|
||||
scrollingOffset.y += event->y() - scrollingFrom.y;
|
||||
|
|
|
@ -106,6 +106,9 @@ private:
|
|||
|
||||
void ProcessClick(const vec2 &pos);
|
||||
|
||||
obs_data_t *wrapper = NULL;
|
||||
bool changed;
|
||||
|
||||
public:
|
||||
OBSBasicPreview(QWidget *parent,
|
||||
Qt::WindowFlags flags = Qt::WindowFlags());
|
||||
|
|
|
@ -26,6 +26,10 @@
|
|||
#include <QScreen>
|
||||
#include <QWindow>
|
||||
#include <QMessageBox>
|
||||
#include <obs-data.h>
|
||||
#include <obs.h>
|
||||
#include <qpointer.h>
|
||||
#include <util/c99defs.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
@ -74,14 +78,28 @@ OBSBasicProperties::OBSBasicProperties(QWidget *parent, OBSSource source_)
|
|||
/* The OBSData constructor increments the reference once */
|
||||
obs_data_release(oldSettings);
|
||||
|
||||
OBSData settings = obs_source_get_settings(source);
|
||||
OBSData nd_settings = obs_source_get_settings(source);
|
||||
OBSData settings = obs_data_get_defaults(nd_settings);
|
||||
obs_data_apply(settings, nd_settings);
|
||||
obs_data_apply(oldSettings, settings);
|
||||
obs_data_release(settings);
|
||||
obs_data_release(nd_settings);
|
||||
|
||||
auto handle_memory = [](void *vp, obs_data_t *old_settings,
|
||||
obs_data_t *new_settings) {
|
||||
obs_source_t *source = reinterpret_cast<obs_source_t *>(vp);
|
||||
|
||||
obs_source_update(source, new_settings);
|
||||
|
||||
UNUSED_PARAMETER(old_settings);
|
||||
UNUSED_PARAMETER(vp);
|
||||
};
|
||||
|
||||
view = new OBSPropertiesView(
|
||||
settings, source,
|
||||
nd_settings, source,
|
||||
(PropertiesReloadCallback)obs_source_properties,
|
||||
(PropertiesUpdateCallback)obs_source_update);
|
||||
(PropertiesUpdateCallback)handle_memory,
|
||||
(PropertiesVisualUpdateCb)obs_source_update);
|
||||
view->setMinimumHeight(150);
|
||||
|
||||
preview->setMinimumSize(20, 150);
|
||||
|
@ -341,6 +359,51 @@ void OBSBasicProperties::on_buttonBox_clicked(QAbstractButton *button)
|
|||
QDialogButtonBox::ButtonRole val = buttonBox->buttonRole(button);
|
||||
|
||||
if (val == QDialogButtonBox::AcceptRole) {
|
||||
|
||||
std::string scene_name =
|
||||
obs_source_get_name(main->GetCurrentSceneSource());
|
||||
|
||||
auto undo_redo = [scene_name](const std::string &data) {
|
||||
obs_data_t *settings =
|
||||
obs_data_create_from_json(data.c_str());
|
||||
obs_source_t *source = obs_get_source_by_name(
|
||||
obs_data_get_string(settings, "undo_sname"));
|
||||
obs_source_update(source, settings);
|
||||
|
||||
obs_source_update_properties(source);
|
||||
|
||||
obs_source_t *scene_source =
|
||||
obs_get_source_by_name(scene_name.c_str());
|
||||
|
||||
OBSBasic::Get()->SetCurrentScene(source);
|
||||
|
||||
obs_source_release(scene_source);
|
||||
|
||||
obs_data_release(settings);
|
||||
obs_source_release(source);
|
||||
};
|
||||
|
||||
obs_data_t *new_settings = obs_data_create();
|
||||
obs_data_t *curr_settings = obs_source_get_settings(source);
|
||||
obs_data_apply(new_settings, curr_settings);
|
||||
obs_data_set_string(new_settings, "undo_sname",
|
||||
obs_source_get_name(source));
|
||||
obs_data_set_string(oldSettings, "undo_sname",
|
||||
obs_source_get_name(source));
|
||||
|
||||
std::string undo_data(obs_data_get_json(oldSettings));
|
||||
std::string redo_data(obs_data_get_json(new_settings));
|
||||
|
||||
if (undo_data.compare(redo_data) != 0)
|
||||
main->undo_s.add_action(
|
||||
QTStr("Undo.Properties")
|
||||
.arg(obs_source_get_name(source)),
|
||||
undo_redo, undo_redo, undo_data, redo_data,
|
||||
NULL);
|
||||
|
||||
obs_data_release(new_settings);
|
||||
obs_data_release(curr_settings);
|
||||
|
||||
acceptClicked = true;
|
||||
close();
|
||||
|
||||
|
|
|
@ -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<OBSBasicSourceSelect *>(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<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);
|
||||
|
@ -261,8 +321,12 @@ template<typename T> static inline T GetOBSRef(QListWidgetItem *item)
|
|||
return item->data(static_cast<int>(QtDataRole::OBSRef)).value<T>();
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include <memory>
|
||||
|
||||
#include "ui_OBSBasicSourceSelect.h"
|
||||
#include "undo-stack-obs.hpp"
|
||||
|
||||
class OBSBasic;
|
||||
|
||||
|
@ -30,6 +31,7 @@ class OBSBasicSourceSelect : public QDialog {
|
|||
private:
|
||||
std::unique_ptr<Ui::OBSBasicSourceSelect> 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;
|
||||
|
||||
|
|
|
@ -73,10 +73,42 @@ OBSBasicTransform::OBSBasicTransform(OBSBasic *parent)
|
|||
SetScene(scene);
|
||||
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",
|
||||
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)
|
||||
{
|
||||
transformSignal.Disconnect();
|
||||
|
|
|
@ -21,6 +21,8 @@ private:
|
|||
OBSSignal selectSignal;
|
||||
OBSSignal deselectSignal;
|
||||
|
||||
std::string undo_data;
|
||||
|
||||
bool ignoreTransformSignal = false;
|
||||
bool ignoreItemChange = false;
|
||||
|
||||
|
@ -46,4 +48,5 @@ private slots:
|
|||
|
||||
public:
|
||||
OBSBasicTransform(OBSBasic *parent);
|
||||
~OBSBasicTransform();
|
||||
};
|
||||
|
|
|
@ -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)
|
||||
|
||||
: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
|
||||
|
||||
---------------------
|
||||
|
||||
|
@ -354,7 +382,13 @@ Scene Item Functions
|
|||
|
||||
.. function:: void obs_sceneitem_set_order_position(obs_sceneitem_t *item, int position)
|
||||
|
||||
Changes the scene item's order index.
|
||||
Changes the sceneitem's order index.
|
||||
|
||||
---------------------
|
||||
|
||||
.. function:: int obs_sceneitem_get_order_position(obs_sceneitem_t *item)
|
||||
|
||||
:return: Gets position of sceneitem in its scene.
|
||||
|
||||
---------------------
|
||||
|
||||
|
|
|
@ -182,6 +182,12 @@ Default Value Functions
|
|||
Default values are used to determine what value will be given if a value
|
||||
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)
|
||||
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.
|
||||
|
||||
---------------------
|
||||
----------------------
|
||||
|
||||
.. 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
|
||||
|
|
|
@ -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)
|
||||
uint32_t obs_get_source_output_flags(const char *id)
|
||||
|
||||
|
|
|
@ -767,6 +767,97 @@ bool obs_data_save_json_safe(obs_data_t *data, const char *file,
|
|||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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,
|
||||
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 */
|
||||
|
||||
|
@ -1517,6 +1624,9 @@ enum obs_data_number_type obs_data_item_numtype(obs_data_item_t *item)
|
|||
return OBS_DATA_NUM_INVALID;
|
||||
|
||||
num = get_item_data(item);
|
||||
if (!num)
|
||||
return OBS_DATA_NUM_INVALID;
|
||||
|
||||
return num->type;
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
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.
|
||||
*/
|
||||
|
@ -104,6 +109,8 @@ EXPORT void obs_data_set_default_bool(obs_data_t *data, const char *name,
|
|||
bool val);
|
||||
EXPORT void obs_data_set_default_obj(obs_data_t *data, const char *name,
|
||||
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
|
||||
|
@ -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,
|
||||
obs_data_array_t *array2);
|
||||
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 */
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -1641,6 +1641,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;
|
||||
|
@ -2001,6 +2028,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;
|
||||
|
@ -2018,6 +2061,113 @@ static void signal_parent(obs_scene_t *parent, const char *command,
|
|||
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)
|
||||
{
|
||||
struct calldata params;
|
||||
|
@ -2148,6 +2298,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)
|
||||
|
@ -2573,6 +2741,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"))
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
31
libobs/obs.h
31
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);
|
||||
|
||||
|
@ -1581,6 +1589,29 @@ 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);
|
||||
|
||||
/** 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. */
|
||||
EXPORT obs_scene_t *obs_sceneitem_get_scene(const obs_sceneitem_t *item);
|
||||
|
||||
|
|
|
@ -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_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_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);
|
||||
|
||||
|
|
Loading…
Reference in a new issue