Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions docs/sphinx/reference-frontend-api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -959,3 +959,29 @@ Functions
This uses the undo action from the first and the redo action from the last action.

.. versionadded:: 29.1

---------------------------------------

.. function:: void obs_frontend_copy_sceneitem(obs_sceneitem_t *item)

:param item: The scene item to copy

.. versionadded:: 32.0

---------------------------------------

.. function:: bool obs_frontend_can_paste_sceneitem(bool duplicate)

:param duplicate: Check if the copied source allows duplication.
:return: Whether there is a scene item copied and can be pasted

.. versionadded:: 32.0

---------------------------------------

.. function:: void obs_frontend_paste_sceneitem(obs_scene_t *scene, bool duplicate)

:param scene: The scene to paste on
:param duplicate: *true* for paste duplicate, *false* for paste reference

.. versionadded:: 32.0
22 changes: 22 additions & 0 deletions frontend/OBSStudioAPI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,28 @@ bool OBSStudioAPI::obs_frontend_remove_canvas(obs_canvas_t *canvas)
return main->RemoveCanvas(canvas);
}

void OBSStudioAPI::obs_frontend_copy_sceneitem(obs_sceneitem_t *item)
{
main->clipboard.clear();
main->copySceneItem(item);
main->UpdateEditMenu();
}

bool OBSStudioAPI::obs_frontend_can_paste_sceneitem(bool duplicate)
{
auto pasteType = main->getItemPasteType();
if (duplicate) {
return pasteType == OBS::ItemPasteType::Duplicate || pasteType == OBS::ItemPasteType::Both;
} else {
return pasteType == OBS::ItemPasteType::Reference || pasteType == OBS::ItemPasteType::Both;
}
}

void OBSStudioAPI::obs_frontend_paste_sceneitem(obs_scene_t *scene, bool duplicate)
{
main->pasteSceneItem(scene, duplicate);
}

void OBSStudioAPI::on_load(obs_data_t *settings)
{
for (size_t i = saveCallbacks.size(); i > 0; i--) {
Expand Down
6 changes: 6 additions & 0 deletions frontend/OBSStudioAPI.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,12 @@ struct OBSStudioAPI : obs_frontend_callbacks {

bool obs_frontend_remove_canvas(obs_canvas_t *canvas) override;

void obs_frontend_copy_sceneitem(obs_sceneitem_t *item) override;

bool obs_frontend_can_paste_sceneitem(bool duplicate) override;

void obs_frontend_paste_sceneitem(obs_scene_t *scene, bool duplicate) override;

void on_load(obs_data_t *settings) override;

void on_preload(obs_data_t *settings) override;
Expand Down
19 changes: 19 additions & 0 deletions frontend/api/obs-frontend-api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -608,3 +608,22 @@ bool obs_frontend_remove_canvas(obs_canvas_t *canvas)
{
return !!callbacks_valid() ? c->obs_frontend_remove_canvas(canvas) : false;
}

void obs_frontend_copy_sceneitem(obs_sceneitem_t *item)
{
if (callbacks_valid())
return c->obs_frontend_copy_sceneitem(item);
}

bool obs_frontend_can_paste_sceneitem(bool duplicate)
{
if (!callbacks_valid())
return false;
return c->obs_frontend_can_paste_sceneitem(duplicate);
}

void obs_frontend_paste_sceneitem(obs_scene_t *scene, bool duplicate)
{
if (callbacks_valid())
return c->obs_frontend_paste_sceneitem(scene, duplicate);
}
4 changes: 4 additions & 0 deletions frontend/api/obs-frontend-api.h
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,10 @@ EXPORT void obs_frontend_get_canvases(struct obs_frontend_canvas_list *canvas_li
EXPORT obs_canvas_t *obs_frontend_add_canvas(const char *name, struct obs_video_info *ovi, int flags);
EXPORT bool obs_frontend_remove_canvas(obs_canvas_t *canvas);

EXPORT void obs_frontend_copy_sceneitem(obs_sceneitem_t *item);
EXPORT bool obs_frontend_can_paste_sceneitem(bool duplicate);
EXPORT void obs_frontend_paste_sceneitem(obs_scene_t *scene, bool duplicate);

/* ------------------------------------------------------------------------- */

#ifdef __cplusplus
Expand Down
4 changes: 4 additions & 0 deletions frontend/api/obs-frontend-internal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ struct obs_frontend_callbacks {
virtual obs_canvas_t *obs_frontend_add_canvas(const char *name, obs_video_info *ovi, int flags) = 0;
virtual bool obs_frontend_remove_canvas(obs_canvas_t *canvas) = 0;
virtual void obs_frontend_get_canvases(obs_frontend_canvas_list *canvas_list) = 0;

virtual void obs_frontend_copy_sceneitem(obs_sceneitem_t *item) = 0;
virtual bool obs_frontend_can_paste_sceneitem(bool duplicate) = 0;
virtual void obs_frontend_paste_sceneitem(obs_scene_t *scene, bool duplicate) = 0;
};

EXPORT void obs_frontend_set_callbacks_internal(obs_frontend_callbacks *callbacks);
20 changes: 5 additions & 15 deletions frontend/widgets/OBSBasic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2022,19 +2022,6 @@ void OBSBasic::UpdateEditMenu()
filter_count = obs_source_filter_count(source);
}

bool allowPastingDuplicate = !!clipboard.size();
for (size_t i = clipboard.size(); i > 0; i--) {
const size_t idx = i - 1;
OBSWeakSource &weak = clipboard[idx].weak_source;
if (obs_weak_source_expired(weak)) {
clipboard.erase(clipboard.begin() + idx);
continue;
}
OBSSourceAutoRelease strong = obs_weak_source_get_source(weak.Get());
if (allowPastingDuplicate && obs_source_get_output_flags(strong) & OBS_SOURCE_DO_NOT_DUPLICATE)
allowPastingDuplicate = false;
}

int videoCount = 0;
bool canTransformMultiple = false;
for (int i = 0; i < totalCount; i++) {
Expand All @@ -2059,8 +2046,11 @@ void OBSBasic::UpdateEditMenu()
ui->actionPasteTransform->setEnabled(canTransformMultiple && hasCopiedTransform && videoCount > 0);
ui->actionCopyFilters->setEnabled(filter_count > 0);
ui->actionPasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource()) && totalCount > 0);
ui->actionPasteRef->setEnabled(!!clipboard.size());
ui->actionPasteDup->setEnabled(allowPastingDuplicate);
auto pasteType = getItemPasteType();
ui->actionPasteRef->setEnabled(pasteType == OBS::ItemPasteType::Reference ||
pasteType == OBS::ItemPasteType::Both);
ui->actionPasteDup->setEnabled(pasteType == OBS::ItemPasteType::Duplicate ||
pasteType == OBS::ItemPasteType::Both);

ui->actionMoveUp->setEnabled(totalCount > 0);
ui->actionMoveDown->setEnabled(totalCount > 0);
Expand Down
5 changes: 5 additions & 0 deletions frontend/widgets/OBSBasic.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ namespace OBS {
class SceneCollection;
struct Rect;
enum class LogFileType;
enum class ItemPasteType { Invalid, Reference, Duplicate, Both };
} // namespace OBS

#define SIMPLE_ENCODER_X264 "x264"
Expand Down Expand Up @@ -405,6 +406,10 @@ public slots:
void CreateFilterPasteUndoRedoAction(const QString &text, obs_source_t *source, obs_data_array_t *undo_array,
obs_data_array_t *redo_array);

void copySceneItem(OBSSceneItem item);
OBS::ItemPasteType getItemPasteType();
void pasteSceneItem(OBSScene scene, bool duplicate);

/* -------------------------------------
* MARK: - OBSBasic_ContextToolbar
* -------------------------------------
Expand Down
132 changes: 72 additions & 60 deletions frontend/widgets/OBSBasic_Clipboard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,52 +70,61 @@ void OBSBasic::on_actionCopySource_triggered()
clipboard.clear();

for (auto &selectedSource : GetAllSelectedSourceItems()) {
OBSSceneItem item = ui->sources->Get(selectedSource.row());
if (!item)
continue;

OBSSource source = obs_sceneitem_get_source(item);

SourceCopyInfo copyInfo;
copyInfo.weak_source = OBSGetWeakRef(source);
obs_sceneitem_get_info2(item, &copyInfo.transform);
obs_sceneitem_get_crop(item, &copyInfo.crop);
copyInfo.blend_method = obs_sceneitem_get_blending_method(item);
copyInfo.blend_mode = obs_sceneitem_get_blending_mode(item);
copyInfo.visible = obs_sceneitem_visible(item);
copyInfo.scale_type = obs_sceneitem_get_scale_filter(item);
copyInfo.show_transition_id = obs_source_get_id(obs_sceneitem_get_transition(item, true));
copyInfo.hide_transition_id = obs_source_get_id(obs_sceneitem_get_transition(item, false));

OBSDataAutoRelease newShowData = obs_data_create();
OBSDataAutoRelease oldShowData = obs_source_get_settings(obs_sceneitem_get_transition(item, true));
obs_data_apply(newShowData, oldShowData);
copyInfo.show_transition_settings = newShowData.Get();

OBSDataAutoRelease newHideData = obs_data_create();
OBSDataAutoRelease oldHideData = obs_source_get_settings(obs_sceneitem_get_transition(item, false));
obs_data_apply(newHideData, oldHideData);
copyInfo.hide_transition_settings = newHideData.Get();

copyInfo.show_transition_duration = obs_sceneitem_get_transition_duration(item, true);
copyInfo.hide_transition_duration = obs_sceneitem_get_transition_duration(item, false);

OBSDataAutoRelease newPrivData = obs_data_create();
OBSDataAutoRelease oldPrivData = obs_sceneitem_get_private_settings(item);
obs_data_apply(newPrivData, oldPrivData);
copyInfo.private_settings = newPrivData.Get();

clipboard.push_back(copyInfo);
copySceneItem(ui->sources->Get(selectedSource.row()));
}

UpdateEditMenu();
}

void OBSBasic::copySceneItem(OBSSceneItem item)
{
if (!item)
return;

OBSSource source = obs_sceneitem_get_source(item);

SourceCopyInfo copyInfo;
copyInfo.weak_source = OBSGetWeakRef(source);
obs_sceneitem_get_info2(item, &copyInfo.transform);
obs_sceneitem_get_crop(item, &copyInfo.crop);
copyInfo.blend_method = obs_sceneitem_get_blending_method(item);
copyInfo.blend_mode = obs_sceneitem_get_blending_mode(item);
copyInfo.visible = obs_sceneitem_visible(item);
copyInfo.scale_type = obs_sceneitem_get_scale_filter(item);
copyInfo.show_transition_id = obs_source_get_id(obs_sceneitem_get_transition(item, true));
copyInfo.hide_transition_id = obs_source_get_id(obs_sceneitem_get_transition(item, false));

OBSDataAutoRelease newShowData = obs_data_create();
OBSDataAutoRelease oldShowData = obs_source_get_settings(obs_sceneitem_get_transition(item, true));
obs_data_apply(newShowData, oldShowData);
copyInfo.show_transition_settings = newShowData.Get();

OBSDataAutoRelease newHideData = obs_data_create();
OBSDataAutoRelease oldHideData = obs_source_get_settings(obs_sceneitem_get_transition(item, false));
obs_data_apply(newHideData, oldHideData);
copyInfo.hide_transition_settings = newHideData.Get();

copyInfo.show_transition_duration = obs_sceneitem_get_transition_duration(item, true);
copyInfo.hide_transition_duration = obs_sceneitem_get_transition_duration(item, false);

OBSDataAutoRelease newPrivData = obs_data_create();
OBSDataAutoRelease oldPrivData = obs_sceneitem_get_private_settings(item);
obs_data_apply(newPrivData, oldPrivData);
copyInfo.private_settings = newPrivData.Get();

clipboard.push_back(copyInfo);
}

void OBSBasic::on_actionPasteRef_triggered()
{
OBSSource scene_source = GetCurrentSceneSource();
pasteSceneItem(GetCurrentScene(), false);
}

void OBSBasic::pasteSceneItem(OBSScene scene, bool duplicate)
{
if (!scene || !clipboard.size())
return;
OBSSource scene_source = obs_scene_get_source(scene);
OBSData undo_data = BackupScene(scene_source);
OBSScene scene = GetCurrentScene();

undo_s.push_disabled();

Expand All @@ -130,17 +139,17 @@ void OBSBasic::on_actionPasteRef_triggered()

/* do not allow duplicate refs of the same group in the same
* scene */
if (!!obs_scene_get_group(scene, name)) {
if (!duplicate && !!obs_scene_get_group(scene, name)) {
continue;
}

OBSBasicSourceSelect::SourcePaste(copyInfo, false);
OBSBasicSourceSelect::SourcePaste(copyInfo, duplicate);
RefreshSources(scene);
}

undo_s.pop_disabled();

QString action_name = QTStr("Undo.PasteSourceRef");
QString action_name = QTStr(duplicate ? "Undo.PasteSource" : "Undo.PasteSourceRef");
const char *scene_name = obs_source_get_name(scene_source);

OBSData redo_data = BackupScene(scene_source);
Expand All @@ -149,24 +158,7 @@ void OBSBasic::on_actionPasteRef_triggered()

void OBSBasic::on_actionPasteDup_triggered()
{
OBSSource scene_source = GetCurrentSceneSource();
OBSData undo_data = BackupScene(scene_source);

undo_s.push_disabled();

for (size_t i = clipboard.size(); i > 0; i--) {
SourceCopyInfo &copyInfo = clipboard[i - 1];
OBSBasicSourceSelect::SourcePaste(copyInfo, true);
RefreshSources(GetCurrentScene());
}

undo_s.pop_disabled();

QString action_name = QTStr("Undo.PasteSource");
const char *scene_name = obs_source_get_name(scene_source);

OBSData redo_data = BackupScene(scene_source);
CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data);
pasteSceneItem(GetCurrentScene(), true);
}

void OBSBasic::SourcePasteFilters(OBSSource source, OBSSource dstSource)
Expand Down Expand Up @@ -276,3 +268,23 @@ void OBSBasic::on_actionPasteFilters_triggered()

SourcePasteFilters(source.Get(), dstSource);
}

OBS::ItemPasteType OBSBasic::getItemPasteType()
{
if (clipboard.empty())
return OBS::ItemPasteType::Invalid;

bool allowPastingDuplicate = true;
for (size_t i = clipboard.size(); i > 0; i--) {
const size_t idx = i - 1;
OBSWeakSource &weak = clipboard[idx].weak_source;
if (obs_weak_source_expired(weak)) {
clipboard.erase(clipboard.begin() + idx);
continue;
}
OBSSourceAutoRelease strong = obs_weak_source_get_source(weak.Get());
if (allowPastingDuplicate && obs_source_get_output_flags(strong) & OBS_SOURCE_DO_NOT_DUPLICATE)
allowPastingDuplicate = false;
}
return allowPastingDuplicate ? OBS::ItemPasteType::Both : OBS::ItemPasteType::Reference;
}