OBSBasic_Scenes.cpp 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974
  1. /******************************************************************************
  2. Copyright (C) 2023 by Lain Bailey <[email protected]>
  3. Zachary Lund <[email protected]>
  4. Philippe Groarke <[email protected]>
  5. This program is free software: you can redistribute it and/or modify
  6. it under the terms of the GNU General Public License as published by
  7. the Free Software Foundation, either version 2 of the License, or
  8. (at your option) any later version.
  9. This program is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. GNU General Public License for more details.
  13. You should have received a copy of the GNU General Public License
  14. along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. ******************************************************************************/
  16. #include "OBSBasic.hpp"
  17. #include "OBSProjector.hpp"
  18. #include <dialogs/NameDialog.hpp>
  19. #include <qt-wrappers.hpp>
  20. #include <QLineEdit>
  21. #include <QWidgetAction>
  22. #include <vector>
  23. using namespace std;
  24. namespace {
  25. template<typename OBSRef> struct SignalContainer {
  26. OBSRef ref;
  27. vector<shared_ptr<OBSSignal>> handlers;
  28. };
  29. } // namespace
  30. Q_DECLARE_METATYPE(obs_order_movement);
  31. Q_DECLARE_METATYPE(SignalContainer<OBSScene>);
  32. extern void undo_redo(const std::string &data);
  33. obs_data_array_t *OBSBasic::SaveSceneListOrder()
  34. {
  35. obs_data_array_t *sceneOrder = obs_data_array_create();
  36. for (int i = 0; i < ui->scenes->count(); i++) {
  37. OBSDataAutoRelease data = obs_data_create();
  38. obs_data_set_string(data, "name", QT_TO_UTF8(ui->scenes->item(i)->text()));
  39. obs_data_array_push_back(sceneOrder, data);
  40. }
  41. return sceneOrder;
  42. }
  43. static void ReorderItemByName(QListWidget *lw, const char *name, int newIndex)
  44. {
  45. for (int i = 0; i < lw->count(); i++) {
  46. QListWidgetItem *item = lw->item(i);
  47. if (strcmp(name, QT_TO_UTF8(item->text())) == 0) {
  48. if (newIndex != i) {
  49. item = lw->takeItem(i);
  50. lw->insertItem(newIndex, item);
  51. }
  52. break;
  53. }
  54. }
  55. }
  56. void OBSBasic::LoadSceneListOrder(obs_data_array_t *array)
  57. {
  58. size_t num = obs_data_array_count(array);
  59. for (size_t i = 0; i < num; i++) {
  60. OBSDataAutoRelease data = obs_data_array_item(array, i);
  61. const char *name = obs_data_get_string(data, "name");
  62. ReorderItemByName(ui->scenes, name, (int)i);
  63. }
  64. }
  65. OBSScene OBSBasic::GetCurrentScene()
  66. {
  67. return currentScene.load();
  68. }
  69. void OBSBasic::AddScene(OBSSource source)
  70. {
  71. const char *name = obs_source_get_name(source);
  72. obs_scene_t *scene = obs_scene_from_source(source);
  73. QListWidgetItem *item = new QListWidgetItem(QT_UTF8(name));
  74. SetOBSRef(item, OBSScene(scene));
  75. ui->scenes->insertItem(ui->scenes->currentRow() + 1, item);
  76. obs_hotkey_register_source(
  77. source, "OBSBasic.SelectScene", Str("Basic.Hotkeys.SelectScene"),
  78. [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) {
  79. OBSBasic *main = OBSBasic::Get();
  80. auto potential_source = static_cast<obs_source_t *>(data);
  81. OBSSourceAutoRelease source = obs_source_get_ref(potential_source);
  82. if (source && pressed)
  83. main->SetCurrentScene(source.Get());
  84. },
  85. static_cast<obs_source_t *>(source));
  86. signal_handler_t *handler = obs_source_get_signal_handler(source);
  87. SignalContainer<OBSScene> container;
  88. container.ref = scene;
  89. container.handlers.assign({
  90. std::make_shared<OBSSignal>(handler, "item_add", OBSBasic::SceneItemAdded, this),
  91. std::make_shared<OBSSignal>(handler, "reorder", OBSBasic::SceneReordered, this),
  92. std::make_shared<OBSSignal>(handler, "refresh", OBSBasic::SceneRefreshed, this),
  93. });
  94. item->setData(static_cast<int>(QtDataRole::OBSSignals), QVariant::fromValue(container));
  95. /* if the scene already has items (a duplicated scene) add them */
  96. auto addSceneItem = [this](obs_sceneitem_t *item) {
  97. AddSceneItem(item);
  98. };
  99. using addSceneItem_t = decltype(addSceneItem);
  100. obs_scene_enum_items(
  101. scene,
  102. [](obs_scene_t *, obs_sceneitem_t *item, void *param) {
  103. addSceneItem_t *func;
  104. func = reinterpret_cast<addSceneItem_t *>(param);
  105. (*func)(item);
  106. return true;
  107. },
  108. &addSceneItem);
  109. SaveProject();
  110. if (!disableSaving) {
  111. obs_source_t *source = obs_scene_get_source(scene);
  112. blog(LOG_INFO, "User added scene '%s'", obs_source_get_name(source));
  113. OBSProjector::UpdateMultiviewProjectors();
  114. }
  115. OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED);
  116. }
  117. void OBSBasic::RemoveScene(OBSSource source)
  118. {
  119. obs_scene_t *scene = obs_scene_from_source(source);
  120. QListWidgetItem *sel = nullptr;
  121. int count = ui->scenes->count();
  122. for (int i = 0; i < count; i++) {
  123. auto item = ui->scenes->item(i);
  124. auto cur_scene = GetOBSRef<OBSScene>(item);
  125. if (cur_scene != scene)
  126. continue;
  127. sel = item;
  128. break;
  129. }
  130. if (sel != nullptr) {
  131. if (sel == ui->scenes->currentItem())
  132. ui->sources->Clear();
  133. delete sel;
  134. }
  135. SaveProject();
  136. if (!disableSaving) {
  137. blog(LOG_INFO, "User Removed scene '%s'", obs_source_get_name(source));
  138. OBSProjector::UpdateMultiviewProjectors();
  139. }
  140. OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED);
  141. }
  142. static bool select_one(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param)
  143. {
  144. obs_sceneitem_t *selectedItem = reinterpret_cast<obs_sceneitem_t *>(param);
  145. if (obs_sceneitem_is_group(item))
  146. obs_sceneitem_group_enum_items(item, select_one, param);
  147. obs_sceneitem_select(item, (selectedItem == item));
  148. return true;
  149. }
  150. void OBSBasic::AddSceneItem(OBSSceneItem item)
  151. {
  152. obs_scene_t *scene = obs_sceneitem_get_scene(item);
  153. if (GetCurrentScene() == scene)
  154. ui->sources->Add(item);
  155. SaveProject();
  156. if (!disableSaving) {
  157. obs_source_t *sceneSource = obs_scene_get_source(scene);
  158. obs_source_t *itemSource = obs_sceneitem_get_source(item);
  159. blog(LOG_INFO, "User added source '%s' (%s) to scene '%s'", obs_source_get_name(itemSource),
  160. obs_source_get_id(itemSource), obs_source_get_name(sceneSource));
  161. obs_scene_enum_items(scene, select_one, (obs_sceneitem_t *)item);
  162. }
  163. }
  164. void OBSBasic::DuplicateSelectedScene()
  165. {
  166. OBSScene curScene = GetCurrentScene();
  167. if (!curScene)
  168. return;
  169. OBSSource curSceneSource = obs_scene_get_source(curScene);
  170. QString format{obs_source_get_name(curSceneSource)};
  171. format += " %1";
  172. int i = 2;
  173. QString placeHolderText = format.arg(i);
  174. OBSSourceAutoRelease source = nullptr;
  175. while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) {
  176. placeHolderText = format.arg(++i);
  177. }
  178. for (;;) {
  179. string name;
  180. bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"),
  181. QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText);
  182. if (!accepted)
  183. return;
  184. if (name.empty()) {
  185. OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text"));
  186. continue;
  187. }
  188. obs_source_t *source = obs_get_source_by_name(name.c_str());
  189. if (source) {
  190. OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text"));
  191. obs_source_release(source);
  192. continue;
  193. }
  194. OBSSceneAutoRelease scene = obs_scene_duplicate(curScene, name.c_str(), OBS_SCENE_DUP_REFS);
  195. source = obs_scene_get_source(scene);
  196. SetCurrentScene(source, true);
  197. auto undo = [](const std::string &data) {
  198. OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str());
  199. obs_source_remove(source);
  200. };
  201. auto redo = [this, name](const std::string &data) {
  202. OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str());
  203. obs_scene_t *scene = obs_scene_from_source(source);
  204. scene = obs_scene_duplicate(scene, name.c_str(), OBS_SCENE_DUP_REFS);
  205. source = obs_scene_get_source(scene);
  206. SetCurrentScene(source.Get(), true);
  207. };
  208. undo_s.add_action(QTStr("Undo.Scene.Duplicate").arg(obs_source_get_name(source)), undo, redo,
  209. obs_source_get_name(source), obs_source_get_name(obs_scene_get_source(curScene)));
  210. break;
  211. }
  212. }
  213. static bool save_undo_source_enum(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *p)
  214. {
  215. obs_source_t *source = obs_sceneitem_get_source(item);
  216. if (obs_obj_is_private(source) && !obs_source_removed(source))
  217. return true;
  218. obs_data_array_t *array = (obs_data_array_t *)p;
  219. /* check if the source is already stored in the array */
  220. const char *name = obs_source_get_name(source);
  221. const size_t count = obs_data_array_count(array);
  222. for (size_t i = 0; i < count; i++) {
  223. OBSDataAutoRelease sourceData = obs_data_array_item(array, i);
  224. if (strcmp(name, obs_data_get_string(sourceData, "name")) == 0)
  225. return true;
  226. }
  227. if (obs_source_is_group(source))
  228. obs_scene_enum_items(obs_group_from_source(source), save_undo_source_enum, p);
  229. OBSDataAutoRelease source_data = obs_save_source(source);
  230. obs_data_array_push_back(array, source_data);
  231. return true;
  232. }
  233. static inline void RemoveSceneAndReleaseNested(obs_source_t *source)
  234. {
  235. obs_source_remove(source);
  236. auto cb = [](void *, obs_source_t *source) {
  237. if (strcmp(obs_source_get_id(source), "scene") == 0)
  238. obs_scene_prune_sources(obs_scene_from_source(source));
  239. return true;
  240. };
  241. obs_enum_scenes(cb, NULL);
  242. }
  243. void OBSBasic::RemoveSelectedScene()
  244. {
  245. OBSScene scene = GetCurrentScene();
  246. obs_source_t *source = obs_scene_get_source(scene);
  247. if (!source || !QueryRemoveSource(source)) {
  248. return;
  249. }
  250. /* ------------------------------ */
  251. /* save all sources in scene */
  252. OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_array_create();
  253. obs_scene_enum_items(scene, save_undo_source_enum, sources_in_deleted_scene);
  254. OBSDataAutoRelease scene_data = obs_save_source(source);
  255. obs_data_array_push_back(sources_in_deleted_scene, scene_data);
  256. /* ----------------------------------------------- */
  257. /* save all scenes and groups the scene is used in */
  258. OBSDataArrayAutoRelease scene_used_in_other_scenes = obs_data_array_create();
  259. struct other_scenes_cb_data {
  260. obs_source_t *oldScene;
  261. obs_data_array_t *scene_used_in_other_scenes;
  262. } other_scenes_cb_data;
  263. other_scenes_cb_data.oldScene = source;
  264. other_scenes_cb_data.scene_used_in_other_scenes = scene_used_in_other_scenes;
  265. auto other_scenes_cb = [](void *data_ptr, obs_source_t *scene) {
  266. struct other_scenes_cb_data *data = (struct other_scenes_cb_data *)data_ptr;
  267. if (strcmp(obs_source_get_name(scene), obs_source_get_name(data->oldScene)) == 0)
  268. return true;
  269. obs_sceneitem_t *item = obs_scene_find_source(obs_group_or_scene_from_source(scene),
  270. obs_source_get_name(data->oldScene));
  271. if (item) {
  272. OBSDataAutoRelease scene_data =
  273. obs_save_source(obs_scene_get_source(obs_sceneitem_get_scene(item)));
  274. obs_data_array_push_back(data->scene_used_in_other_scenes, scene_data);
  275. }
  276. return true;
  277. };
  278. obs_enum_scenes(other_scenes_cb, &other_scenes_cb_data);
  279. /* --------------------------- */
  280. /* undo/redo */
  281. auto undo = [this](const std::string &json) {
  282. OBSDataAutoRelease base = obs_data_create_from_json(json.c_str());
  283. OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_get_array(base, "sources_in_deleted_scene");
  284. OBSDataArrayAutoRelease scene_used_in_other_scenes =
  285. obs_data_get_array(base, "scene_used_in_other_scenes");
  286. int savedIndex = (int)obs_data_get_int(base, "index");
  287. std::vector<OBSSource> sources;
  288. /* create missing sources */
  289. size_t count = obs_data_array_count(sources_in_deleted_scene);
  290. sources.reserve(count);
  291. for (size_t i = 0; i < count; i++) {
  292. OBSDataAutoRelease data = obs_data_array_item(sources_in_deleted_scene, i);
  293. const char *name = obs_data_get_string(data, "name");
  294. OBSSourceAutoRelease source = obs_get_source_by_name(name);
  295. if (!source) {
  296. source = obs_load_source(data);
  297. sources.push_back(source.Get());
  298. }
  299. }
  300. /* actually load sources now */
  301. for (obs_source_t *source : sources)
  302. obs_source_load2(source);
  303. /* Add scene to scenes and groups it was nested in */
  304. for (size_t i = 0; i < obs_data_array_count(scene_used_in_other_scenes); i++) {
  305. OBSDataAutoRelease data = obs_data_array_item(scene_used_in_other_scenes, i);
  306. const char *name = obs_data_get_string(data, "name");
  307. OBSSourceAutoRelease source = obs_get_source_by_name(name);
  308. OBSDataAutoRelease settings = obs_data_get_obj(data, "settings");
  309. OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items");
  310. /* Clear scene, but keep a reference to all sources in the scene to make sure they don't get destroyed */
  311. std::vector<OBSSource> existing_sources;
  312. auto cb = [](obs_scene_t *, obs_sceneitem_t *item, void *data) {
  313. std::vector<OBSSource> *existing = (std::vector<OBSSource> *)data;
  314. OBSSource source = obs_sceneitem_get_source(item);
  315. obs_sceneitem_remove(item);
  316. existing->push_back(source);
  317. return true;
  318. };
  319. obs_scene_enum_items(obs_group_or_scene_from_source(source), cb, (void *)&existing_sources);
  320. /* Re-add sources to the scene */
  321. obs_sceneitems_add(obs_group_or_scene_from_source(source), items);
  322. }
  323. obs_source_t *scene_source = sources.back();
  324. OBSScene scene = obs_scene_from_source(scene_source);
  325. SetCurrentScene(scene, true);
  326. /* set original index in list box */
  327. ui->scenes->blockSignals(true);
  328. int curIndex = ui->scenes->currentRow();
  329. QListWidgetItem *item = ui->scenes->takeItem(curIndex);
  330. ui->scenes->insertItem(savedIndex, item);
  331. ui->scenes->setCurrentRow(savedIndex);
  332. currentScene = scene.Get();
  333. ui->scenes->blockSignals(false);
  334. };
  335. auto redo = [](const std::string &name) {
  336. OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str());
  337. RemoveSceneAndReleaseNested(source);
  338. };
  339. OBSDataAutoRelease data = obs_data_create();
  340. obs_data_set_array(data, "sources_in_deleted_scene", sources_in_deleted_scene);
  341. obs_data_set_array(data, "scene_used_in_other_scenes", scene_used_in_other_scenes);
  342. obs_data_set_int(data, "index", ui->scenes->currentRow());
  343. const char *scene_name = obs_source_get_name(source);
  344. undo_s.add_action(QTStr("Undo.Delete").arg(scene_name), undo, redo, obs_data_get_json(data), scene_name);
  345. /* --------------------------- */
  346. /* remove */
  347. RemoveSceneAndReleaseNested(source);
  348. OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED);
  349. }
  350. void OBSBasic::SceneReordered(void *data, calldata_t *params)
  351. {
  352. OBSBasic *window = static_cast<OBSBasic *>(data);
  353. obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene");
  354. QMetaObject::invokeMethod(window, "ReorderSources", Q_ARG(OBSScene, OBSScene(scene)));
  355. }
  356. void OBSBasic::SceneRefreshed(void *data, calldata_t *params)
  357. {
  358. OBSBasic *window = static_cast<OBSBasic *>(data);
  359. obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene");
  360. QMetaObject::invokeMethod(window, "RefreshSources", Q_ARG(OBSScene, OBSScene(scene)));
  361. }
  362. void OBSBasic::SceneItemAdded(void *data, calldata_t *params)
  363. {
  364. OBSBasic *window = static_cast<OBSBasic *>(data);
  365. obs_sceneitem_t *item = (obs_sceneitem_t *)calldata_ptr(params, "item");
  366. QMetaObject::invokeMethod(window, "AddSceneItem", Q_ARG(OBSSceneItem, OBSSceneItem(item)));
  367. }
  368. void OBSBasic::on_scenes_currentItemChanged(QListWidgetItem *current, QListWidgetItem *)
  369. {
  370. OBSSource source;
  371. if (current) {
  372. OBSScene scene = GetOBSRef<OBSScene>(current);
  373. source = obs_scene_get_source(scene);
  374. currentScene = scene;
  375. } else {
  376. currentScene = NULL;
  377. }
  378. SetCurrentScene(source);
  379. if (vcamEnabled && vcamConfig.type == VCamOutputType::PreviewOutput)
  380. outputHandler->UpdateVirtualCamOutputSource();
  381. OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED);
  382. UpdateContextBar();
  383. }
  384. void OBSBasic::EditSceneName()
  385. {
  386. ui->scenesDock->removeAction(renameScene);
  387. QListWidgetItem *item = ui->scenes->currentItem();
  388. Qt::ItemFlags flags = item->flags();
  389. item->setFlags(flags | Qt::ItemIsEditable);
  390. ui->scenes->editItem(item);
  391. item->setFlags(flags);
  392. }
  393. void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos)
  394. {
  395. QListWidgetItem *item = ui->scenes->itemAt(pos);
  396. QMenu popup(this);
  397. QMenu order(QTStr("Basic.MainMenu.Edit.Order"), this);
  398. popup.addAction(QTStr("Add"), this, &OBSBasic::on_actionAddScene_triggered);
  399. if (item) {
  400. QAction *copyFilters = new QAction(QTStr("Copy.Filters"), this);
  401. copyFilters->setEnabled(false);
  402. connect(copyFilters, &QAction::triggered, this, &OBSBasic::SceneCopyFilters);
  403. QAction *pasteFilters = new QAction(QTStr("Paste.Filters"), this);
  404. pasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource));
  405. connect(pasteFilters, &QAction::triggered, this, &OBSBasic::ScenePasteFilters);
  406. popup.addSeparator();
  407. popup.addAction(QTStr("Duplicate"), this, &OBSBasic::DuplicateSelectedScene);
  408. popup.addAction(copyFilters);
  409. popup.addAction(pasteFilters);
  410. popup.addSeparator();
  411. popup.addAction(renameScene);
  412. popup.addAction(ui->actionRemoveScene);
  413. popup.addSeparator();
  414. order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveUp"), this, &OBSBasic::on_actionSceneUp_triggered);
  415. order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveDown"), this,
  416. &OBSBasic::on_actionSceneDown_triggered);
  417. order.addSeparator();
  418. order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToTop"), this, &OBSBasic::MoveSceneToTop);
  419. order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToBottom"), this, &OBSBasic::MoveSceneToBottom);
  420. popup.addMenu(&order);
  421. popup.addSeparator();
  422. delete sceneProjectorMenu;
  423. sceneProjectorMenu = new QMenu(QTStr("Projector.Open.Scene"));
  424. AddProjectorMenuMonitors(sceneProjectorMenu, this, &OBSBasic::OpenSceneProjector);
  425. sceneProjectorMenu->addSeparator();
  426. sceneProjectorMenu->addAction(QTStr("Projector.Window"), this, &OBSBasic::OpenSceneWindow);
  427. popup.addMenu(sceneProjectorMenu);
  428. popup.addSeparator();
  429. popup.addAction(QTStr("Screenshot.Scene"), this, &OBSBasic::ScreenshotScene);
  430. popup.addSeparator();
  431. popup.addAction(QTStr("Filters"), this, &OBSBasic::OpenSceneFilters);
  432. popup.addSeparator();
  433. delete perSceneTransitionMenu;
  434. perSceneTransitionMenu = CreatePerSceneTransitionMenu();
  435. popup.addMenu(perSceneTransitionMenu);
  436. /* ---------------------- */
  437. QAction *multiviewAction = popup.addAction(QTStr("ShowInMultiview"));
  438. OBSSource source = GetCurrentSceneSource();
  439. OBSDataAutoRelease data = obs_source_get_private_settings(source);
  440. obs_data_set_default_bool(data, "show_in_multiview", true);
  441. bool show = obs_data_get_bool(data, "show_in_multiview");
  442. multiviewAction->setCheckable(true);
  443. multiviewAction->setChecked(show);
  444. auto showInMultiview = [](OBSData data) {
  445. bool show = obs_data_get_bool(data, "show_in_multiview");
  446. obs_data_set_bool(data, "show_in_multiview", !show);
  447. OBSProjector::UpdateMultiviewProjectors();
  448. };
  449. connect(multiviewAction, &QAction::triggered, std::bind(showInMultiview, data.Get()));
  450. copyFilters->setEnabled(obs_source_filter_count(source) > 0);
  451. }
  452. popup.addSeparator();
  453. bool grid = ui->scenes->GetGridMode();
  454. QAction *gridAction = new QAction(grid ? QTStr("Basic.Main.ListMode") : QTStr("Basic.Main.GridMode"), this);
  455. connect(gridAction, &QAction::triggered, this, &OBSBasic::GridActionClicked);
  456. popup.addAction(gridAction);
  457. popup.exec(QCursor::pos());
  458. }
  459. void OBSBasic::on_actionSceneListMode_triggered()
  460. {
  461. ui->scenes->SetGridMode(false);
  462. config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false);
  463. }
  464. void OBSBasic::on_actionSceneGridMode_triggered()
  465. {
  466. ui->scenes->SetGridMode(true);
  467. config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", true);
  468. }
  469. void OBSBasic::GridActionClicked()
  470. {
  471. bool gridMode = !ui->scenes->GetGridMode();
  472. ui->scenes->SetGridMode(gridMode);
  473. if (gridMode)
  474. ui->actionSceneGridMode->setChecked(true);
  475. else
  476. ui->actionSceneListMode->setChecked(true);
  477. config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", gridMode);
  478. }
  479. void OBSBasic::on_actionAddScene_triggered()
  480. {
  481. string name;
  482. QString format{QTStr("Basic.Main.DefaultSceneName.Text")};
  483. int i = 2;
  484. QString placeHolderText = format.arg(i);
  485. OBSSourceAutoRelease source = nullptr;
  486. while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) {
  487. placeHolderText = format.arg(++i);
  488. }
  489. bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"),
  490. QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText);
  491. if (accepted) {
  492. if (name.empty()) {
  493. OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text"));
  494. on_actionAddScene_triggered();
  495. return;
  496. }
  497. OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str());
  498. if (source) {
  499. OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text"));
  500. on_actionAddScene_triggered();
  501. return;
  502. }
  503. auto undo_fn = [](const std::string &data) {
  504. obs_source_t *t = obs_get_source_by_name(data.c_str());
  505. if (t) {
  506. obs_source_remove(t);
  507. obs_source_release(t);
  508. }
  509. };
  510. auto redo_fn = [this](const std::string &data) {
  511. OBSSceneAutoRelease scene = obs_scene_create(data.c_str());
  512. obs_source_t *source = obs_scene_get_source(scene);
  513. SetCurrentScene(source, true);
  514. };
  515. undo_s.add_action(QTStr("Undo.Add").arg(QString(name.c_str())), undo_fn, redo_fn, name, name);
  516. OBSSceneAutoRelease scene = obs_scene_create(name.c_str());
  517. obs_source_t *scene_source = obs_scene_get_source(scene);
  518. SetCurrentScene(scene_source);
  519. }
  520. }
  521. void OBSBasic::on_actionRemoveScene_triggered()
  522. {
  523. RemoveSelectedScene();
  524. }
  525. void OBSBasic::ChangeSceneIndex(bool relative, int offset, int invalidIdx)
  526. {
  527. int idx = ui->scenes->currentRow();
  528. if (idx == -1 || idx == invalidIdx)
  529. return;
  530. ui->scenes->blockSignals(true);
  531. QListWidgetItem *item = ui->scenes->takeItem(idx);
  532. if (!relative)
  533. idx = 0;
  534. ui->scenes->insertItem(idx + offset, item);
  535. ui->scenes->setCurrentRow(idx + offset);
  536. item->setSelected(true);
  537. currentScene = GetOBSRef<OBSScene>(item).Get();
  538. ui->scenes->blockSignals(false);
  539. OBSProjector::UpdateMultiviewProjectors();
  540. }
  541. void OBSBasic::on_actionSceneUp_triggered()
  542. {
  543. ChangeSceneIndex(true, -1, 0);
  544. }
  545. void OBSBasic::on_actionSceneDown_triggered()
  546. {
  547. ChangeSceneIndex(true, 1, ui->scenes->count() - 1);
  548. }
  549. void OBSBasic::MoveSceneToTop()
  550. {
  551. ChangeSceneIndex(false, 0, 0);
  552. }
  553. void OBSBasic::MoveSceneToBottom()
  554. {
  555. ChangeSceneIndex(false, ui->scenes->count() - 1, ui->scenes->count() - 1);
  556. }
  557. void OBSBasic::EditSceneItemName()
  558. {
  559. int idx = GetTopSelectedSourceItem();
  560. ui->sources->Edit(idx);
  561. }
  562. void OBSBasic::on_scenes_itemDoubleClicked(QListWidgetItem *witem)
  563. {
  564. if (!witem)
  565. return;
  566. if (IsPreviewProgramMode()) {
  567. bool doubleClickSwitch =
  568. config_get_bool(App()->GetUserConfig(), "BasicWindow", "TransitionOnDoubleClick");
  569. if (doubleClickSwitch)
  570. TransitionClicked();
  571. }
  572. }
  573. OBSData OBSBasic::BackupScene(obs_scene_t *scene, std::vector<obs_source_t *> *sources)
  574. {
  575. OBSDataArrayAutoRelease undo_array = obs_data_array_create();
  576. if (!sources) {
  577. obs_scene_enum_items(scene, save_undo_source_enum, undo_array);
  578. } else {
  579. for (obs_source_t *source : *sources) {
  580. obs_data_t *source_data = obs_save_source(source);
  581. obs_data_array_push_back(undo_array, source_data);
  582. obs_data_release(source_data);
  583. }
  584. }
  585. OBSDataAutoRelease scene_data = obs_save_source(obs_scene_get_source(scene));
  586. obs_data_array_push_back(undo_array, scene_data);
  587. OBSDataAutoRelease data = obs_data_create();
  588. obs_data_set_array(data, "array", undo_array);
  589. obs_data_get_json(data);
  590. return data.Get();
  591. }
  592. static bool add_source_enum(obs_scene_t *, obs_sceneitem_t *item, void *p)
  593. {
  594. auto sources = static_cast<std::vector<OBSSource> *>(p);
  595. sources->push_back(obs_sceneitem_get_source(item));
  596. return true;
  597. }
  598. void OBSBasic::CreateSceneUndoRedoAction(const QString &action_name, OBSData undo_data, OBSData redo_data)
  599. {
  600. auto undo_redo = [this](const std::string &json) {
  601. OBSDataAutoRelease base = obs_data_create_from_json(json.c_str());
  602. OBSDataArrayAutoRelease array = obs_data_get_array(base, "array");
  603. std::vector<OBSSource> sources;
  604. std::vector<OBSSource> old_sources;
  605. /* create missing sources */
  606. const size_t count = obs_data_array_count(array);
  607. sources.reserve(count);
  608. for (size_t i = 0; i < count; i++) {
  609. OBSDataAutoRelease data = obs_data_array_item(array, i);
  610. const char *name = obs_data_get_string(data, "name");
  611. OBSSourceAutoRelease source = obs_get_source_by_name(name);
  612. if (!source)
  613. source = obs_load_source(data);
  614. sources.push_back(source.Get());
  615. /* update scene/group settings to restore their
  616. * contents to their saved settings */
  617. obs_scene_t *scene = obs_group_or_scene_from_source(source);
  618. if (scene) {
  619. obs_scene_enum_items(scene, add_source_enum, &old_sources);
  620. OBSDataAutoRelease scene_settings = obs_data_get_obj(data, "settings");
  621. obs_source_update(source, scene_settings);
  622. }
  623. }
  624. /* actually load sources now */
  625. for (obs_source_t *source : sources)
  626. obs_source_load2(source);
  627. ui->sources->RefreshItems();
  628. };
  629. const char *undo_json = obs_data_get_last_json(undo_data);
  630. const char *redo_json = obs_data_get_last_json(redo_data);
  631. undo_s.add_action(action_name, undo_redo, undo_redo, undo_json, redo_json);
  632. }
  633. void OBSBasic::MoveSceneItem(enum obs_order_movement movement, const QString &action_name)
  634. {
  635. OBSSceneItem item = GetCurrentSceneItem();
  636. obs_source_t *source = obs_sceneitem_get_source(item);
  637. if (!source)
  638. return;
  639. OBSScene scene = GetCurrentScene();
  640. std::vector<obs_source_t *> sources;
  641. if (scene != obs_sceneitem_get_scene(item))
  642. sources.push_back(obs_scene_get_source(obs_sceneitem_get_scene(item)));
  643. OBSData undo_data = BackupScene(scene, &sources);
  644. obs_sceneitem_set_order(item, movement);
  645. const char *source_name = obs_source_get_name(source);
  646. const char *scene_name = obs_source_get_name(obs_scene_get_source(scene));
  647. OBSData redo_data = BackupScene(scene, &sources);
  648. CreateSceneUndoRedoAction(action_name.arg(source_name, scene_name), undo_data, redo_data);
  649. }
  650. static void RenameListItem(OBSBasic *parent, QListWidget *listWidget, obs_source_t *source, const string &name)
  651. {
  652. const char *prevName = obs_source_get_name(source);
  653. if (name == prevName)
  654. return;
  655. OBSSourceAutoRelease foundSource = obs_get_source_by_name(name.c_str());
  656. QListWidgetItem *listItem = listWidget->currentItem();
  657. if (foundSource || name.empty()) {
  658. listItem->setText(QT_UTF8(prevName));
  659. if (foundSource) {
  660. OBSMessageBox::warning(parent, QTStr("NameExists.Title"), QTStr("NameExists.Text"));
  661. } else if (name.empty()) {
  662. OBSMessageBox::warning(parent, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text"));
  663. }
  664. } else {
  665. auto undo = [prev = std::string(prevName)](const std::string &data) {
  666. OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str());
  667. obs_source_set_name(source, prev.c_str());
  668. };
  669. auto redo = [name](const std::string &data) {
  670. OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str());
  671. obs_source_set_name(source, name.c_str());
  672. };
  673. std::string source_uuid(obs_source_get_uuid(source));
  674. parent->undo_s.add_action(QTStr("Undo.Rename").arg(name.c_str()), undo, redo, source_uuid, source_uuid);
  675. listItem->setText(QT_UTF8(name.c_str()));
  676. obs_source_set_name(source, name.c_str());
  677. }
  678. }
  679. void OBSBasic::SceneNameEdited(QWidget *editor)
  680. {
  681. OBSScene scene = GetCurrentScene();
  682. QLineEdit *edit = qobject_cast<QLineEdit *>(editor);
  683. string text = QT_TO_UTF8(edit->text().trimmed());
  684. if (!scene)
  685. return;
  686. obs_source_t *source = obs_scene_get_source(scene);
  687. RenameListItem(this, ui->scenes, source, text);
  688. ui->scenesDock->addAction(renameScene);
  689. OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED);
  690. }
  691. void OBSBasic::OpenSceneFilters()
  692. {
  693. OBSScene scene = GetCurrentScene();
  694. OBSSource source = obs_scene_get_source(scene);
  695. CreateFiltersWindow(source);
  696. }
  697. static bool reset_tr(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *)
  698. {
  699. if (obs_sceneitem_is_group(item))
  700. obs_sceneitem_group_enum_items(item, reset_tr, nullptr);
  701. if (!obs_sceneitem_selected(item))
  702. return true;
  703. if (obs_sceneitem_locked(item))
  704. return true;
  705. obs_sceneitem_defer_update_begin(item);
  706. obs_transform_info info;
  707. vec2_set(&info.pos, 0.0f, 0.0f);
  708. vec2_set(&info.scale, 1.0f, 1.0f);
  709. info.rot = 0.0f;
  710. info.alignment = OBS_ALIGN_TOP | OBS_ALIGN_LEFT;
  711. info.bounds_type = OBS_BOUNDS_NONE;
  712. info.bounds_alignment = OBS_ALIGN_CENTER;
  713. info.crop_to_bounds = false;
  714. vec2_set(&info.bounds, 0.0f, 0.0f);
  715. obs_sceneitem_set_info2(item, &info);
  716. obs_sceneitem_crop crop = {};
  717. obs_sceneitem_set_crop(item, &crop);
  718. obs_sceneitem_defer_update_end(item);
  719. return true;
  720. }
  721. void OBSBasic::on_actionResetTransform_triggered()
  722. {
  723. OBSScene scene = GetCurrentScene();
  724. OBSDataAutoRelease wrapper = obs_scene_save_transform_states(scene, false);
  725. obs_scene_enum_items(scene, reset_tr, nullptr);
  726. OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(scene, false);
  727. std::string undo_data(obs_data_get_json(wrapper));
  728. std::string redo_data(obs_data_get_json(rwrapper));
  729. undo_s.add_action(QTStr("Undo.Transform.Reset").arg(obs_source_get_name(obs_scene_get_source(scene))),
  730. undo_redo, undo_redo, undo_data, redo_data);
  731. obs_scene_enum_items(GetCurrentScene(), reset_tr, nullptr);
  732. }
  733. SourceTreeItem *OBSBasic::GetItemWidgetFromSceneItem(obs_sceneitem_t *sceneItem)
  734. {
  735. int i = 0;
  736. SourceTreeItem *treeItem = ui->sources->GetItemWidget(i);
  737. OBSSceneItem item = ui->sources->Get(i);
  738. int64_t id = obs_sceneitem_get_id(sceneItem);
  739. while (treeItem && obs_sceneitem_get_id(item) != id) {
  740. i++;
  741. treeItem = ui->sources->GetItemWidget(i);
  742. item = ui->sources->Get(i);
  743. }
  744. if (treeItem)
  745. return treeItem;
  746. return nullptr;
  747. }
  748. void OBSBasic::on_actionSceneFilters_triggered()
  749. {
  750. OBSSource sceneSource = GetCurrentSceneSource();
  751. if (sceneSource)
  752. OpenFilters(sceneSource);
  753. }