OBSBasic_Scenes.cpp 29 KB

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