frontend: Add copy paste functions

This commit is contained in:
Exeldro
2026-05-22 17:25:23 -04:00
committed by Ryan Foster
parent 17fc299b74
commit 854948b8df
9 changed files with 167 additions and 74 deletions
+26
View File
@@ -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
+22
View File
@@ -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--) {
+6
View File
@@ -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;
+19
View File
@@ -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
View File
@@ -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
+4
View File
@@ -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);
+5 -15
View File
@@ -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);
+5
View File
@@ -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
* -------------------------------------
+76 -59
View File
@@ -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, &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::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, &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();
@@ -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 &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)
@@ -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;
}