diff --git a/docs/sphinx/reference-frontend-api.rst b/docs/sphinx/reference-frontend-api.rst index 7d234b7ac..6ef7854d1 100644 --- a/docs/sphinx/reference-frontend-api.rst +++ b/docs/sphinx/reference-frontend-api.rst @@ -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.2 + +--------------------------------------- + +.. 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.2 + +--------------------------------------- + +.. 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.2 diff --git a/frontend/OBSStudioAPI.cpp b/frontend/OBSStudioAPI.cpp index 76678992f..bb65dd3e7 100644 --- a/frontend/OBSStudioAPI.cpp +++ b/frontend/OBSStudioAPI.cpp @@ -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--) { diff --git a/frontend/OBSStudioAPI.hpp b/frontend/OBSStudioAPI.hpp index d318897c3..21a9e78a3 100644 --- a/frontend/OBSStudioAPI.hpp +++ b/frontend/OBSStudioAPI.hpp @@ -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; diff --git a/frontend/api/obs-frontend-api.cpp b/frontend/api/obs-frontend-api.cpp index fa72ba796..e09b649e4 100644 --- a/frontend/api/obs-frontend-api.cpp +++ b/frontend/api/obs-frontend-api.cpp @@ -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); +} diff --git a/frontend/api/obs-frontend-api.h b/frontend/api/obs-frontend-api.h index cc50c7b17..54cc6bf56 100644 --- a/frontend/api/obs-frontend-api.h +++ b/frontend/api/obs-frontend-api.h @@ -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 diff --git a/frontend/api/obs-frontend-internal.hpp b/frontend/api/obs-frontend-internal.hpp index 1c08b8c44..eac64bcfd 100644 --- a/frontend/api/obs-frontend-internal.hpp +++ b/frontend/api/obs-frontend-internal.hpp @@ -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); diff --git a/frontend/widgets/OBSBasic.cpp b/frontend/widgets/OBSBasic.cpp index 7ce72dbce..91f72b8ce 100644 --- a/frontend/widgets/OBSBasic.cpp +++ b/frontend/widgets/OBSBasic.cpp @@ -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++) { @@ -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); diff --git a/frontend/widgets/OBSBasic.hpp b/frontend/widgets/OBSBasic.hpp index efb90ac33..18ccb370a 100644 --- a/frontend/widgets/OBSBasic.hpp +++ b/frontend/widgets/OBSBasic.hpp @@ -69,6 +69,7 @@ namespace OBS { class SceneCollection; struct Rect; enum class LogFileType; +enum class ItemPasteType { Invalid, Reference, Duplicate, Both }; } // namespace OBS #define SIMPLE_ENCODER_X264 "x264" @@ -406,6 +407,10 @@ public: 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 * ------------------------------------- diff --git a/frontend/widgets/OBSBasic_Clipboard.cpp b/frontend/widgets/OBSBasic_Clipboard.cpp index 8f8207f82..b663d6ff2 100644 --- a/frontend/widgets/OBSBasic_Clipboard.cpp +++ b/frontend/widgets/OBSBasic_Clipboard.cpp @@ -70,52 +70,64 @@ void OBSBasic::on_actionCopySource_triggered() clipboard.clear(); for (auto &selectedSource : GetAllSelectedSourceItems()) { - OBSSceneItem item = ui->sources->Get(selectedSource.row()); - if (!item) - continue; + copySceneItem(ui->sources->Get(selectedSource.row())); + } + UpdateEditMenu(); +} - OBSSource source = obs_sceneitem_get_source(item); - - SourceCopyInfo copyInfo; - copyInfo.weak_source = OBSGetWeakRef(source); - obs_sceneitem_get_info2(item, ©Info.transform); - obs_sceneitem_get_crop(item, ©Info.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::copySceneItem(OBSSceneItem item) +{ + if (!item) { + return; } - UpdateEditMenu(); + OBSSource source = obs_sceneitem_get_source(item); + + SourceCopyInfo copyInfo; + copyInfo.weak_source = OBSGetWeakRef(source); + obs_sceneitem_get_info2(item, ©Info.transform); + obs_sceneitem_get_crop(item, ©Info.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(); @@ -130,17 +142,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); @@ -149,24 +161,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 ©Info = 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) @@ -276,3 +271,25 @@ 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; +}