1
0

OBSBasic_Scenes.cpp 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992
  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. bool forceSceneChange = false;
  382. if (current) {
  383. OBSScene scene = GetOBSRef<OBSScene>(current);
  384. source = obs_scene_get_source(scene);
  385. bool oldSceneIsRemoved = obs_source_removed(obs_scene_get_source(currentScene));
  386. forceSceneChange = oldSceneIsRemoved;
  387. currentScene = scene;
  388. } else {
  389. currentScene = NULL;
  390. }
  391. SetCurrentScene(source, forceSceneChange);
  392. if (vcamEnabled && vcamConfig.type == VCamOutputType::PreviewOutput)
  393. outputHandler->UpdateVirtualCamOutputSource();
  394. OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED);
  395. UpdateContextBar();
  396. }
  397. void OBSBasic::EditSceneName()
  398. {
  399. ui->scenesDock->removeAction(renameScene);
  400. QListWidgetItem *item = ui->scenes->currentItem();
  401. Qt::ItemFlags flags = item->flags();
  402. item->setFlags(flags | Qt::ItemIsEditable);
  403. ui->scenes->editItem(item);
  404. item->setFlags(flags);
  405. }
  406. void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos)
  407. {
  408. QListWidgetItem *item = ui->scenes->itemAt(pos);
  409. QMenu popup(this);
  410. QMenu order(QTStr("Basic.MainMenu.Edit.Order"), this);
  411. popup.addAction(QTStr("AddScene") + "...", this, &OBSBasic::on_actionAddScene_triggered);
  412. if (item) {
  413. QAction *copyFilters = new QAction(QTStr("Copy.Filters"), this);
  414. copyFilters->setEnabled(false);
  415. connect(copyFilters, &QAction::triggered, this, &OBSBasic::SceneCopyFilters);
  416. QAction *pasteFilters = new QAction(QTStr("Paste.Filters"), this);
  417. pasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource()));
  418. connect(pasteFilters, &QAction::triggered, this, &OBSBasic::ScenePasteFilters);
  419. popup.addSeparator();
  420. popup.addAction(QTStr("Duplicate"), this, &OBSBasic::DuplicateSelectedScene);
  421. popup.addAction(copyFilters);
  422. popup.addAction(pasteFilters);
  423. popup.addSeparator();
  424. popup.addAction(renameScene);
  425. popup.addAction(ui->actionRemoveScene);
  426. popup.addSeparator();
  427. order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveUp"), this, &OBSBasic::on_actionSceneUp_triggered);
  428. order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveDown"), this,
  429. &OBSBasic::on_actionSceneDown_triggered);
  430. order.addSeparator();
  431. order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToTop"), this, &OBSBasic::MoveSceneToTop);
  432. order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToBottom"), this, &OBSBasic::MoveSceneToBottom);
  433. popup.addMenu(&order);
  434. popup.addSeparator();
  435. delete sceneProjectorMenu;
  436. sceneProjectorMenu = new QMenu(QTStr("Projector.Open.Scene"));
  437. AddProjectorMenuMonitors(sceneProjectorMenu, this, &OBSBasic::OpenSceneProjector);
  438. sceneProjectorMenu->addSeparator();
  439. sceneProjectorMenu->addAction(QTStr("Projector.Window"), this, &OBSBasic::OpenSceneWindow);
  440. popup.addMenu(sceneProjectorMenu);
  441. popup.addSeparator();
  442. popup.addAction(QTStr("Screenshot.Scene"), this, &OBSBasic::ScreenshotScene);
  443. popup.addSeparator();
  444. popup.addAction(QTStr("Filters"), this, &OBSBasic::OpenSceneFilters);
  445. popup.addSeparator();
  446. delete perSceneTransitionMenu;
  447. perSceneTransitionMenu = CreatePerSceneTransitionMenu();
  448. popup.addMenu(perSceneTransitionMenu);
  449. /* ---------------------- */
  450. QAction *multiviewAction = popup.addAction(QTStr("ShowInMultiview"));
  451. OBSSource source = GetCurrentSceneSource();
  452. OBSDataAutoRelease data = obs_source_get_private_settings(source);
  453. obs_data_set_default_bool(data, "show_in_multiview", true);
  454. bool show = obs_data_get_bool(data, "show_in_multiview");
  455. multiviewAction->setCheckable(true);
  456. multiviewAction->setChecked(show);
  457. auto showInMultiview = [](OBSData data) {
  458. bool show = obs_data_get_bool(data, "show_in_multiview");
  459. obs_data_set_bool(data, "show_in_multiview", !show);
  460. OBSProjector::UpdateMultiviewProjectors();
  461. };
  462. connect(multiviewAction, &QAction::triggered, multiviewAction, std::bind(showInMultiview, data.Get()));
  463. copyFilters->setEnabled(obs_source_filter_count(source) > 0);
  464. }
  465. popup.addSeparator();
  466. bool grid = ui->scenes->GetGridMode();
  467. QAction *gridAction = new QAction(grid ? QTStr("Basic.Main.ListMode") : QTStr("Basic.Main.GridMode"), this);
  468. connect(gridAction, &QAction::triggered, this, &OBSBasic::GridActionClicked);
  469. popup.addAction(gridAction);
  470. popup.exec(QCursor::pos());
  471. }
  472. void OBSBasic::on_actionSceneListMode_triggered()
  473. {
  474. ui->scenes->SetGridMode(false);
  475. config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false);
  476. }
  477. void OBSBasic::on_actionSceneGridMode_triggered()
  478. {
  479. ui->scenes->SetGridMode(true);
  480. config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", true);
  481. }
  482. void OBSBasic::GridActionClicked()
  483. {
  484. bool gridMode = !ui->scenes->GetGridMode();
  485. ui->scenes->SetGridMode(gridMode);
  486. if (gridMode)
  487. ui->actionSceneGridMode->setChecked(true);
  488. else
  489. ui->actionSceneListMode->setChecked(true);
  490. config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", gridMode);
  491. }
  492. void OBSBasic::on_actionAddScene_triggered()
  493. {
  494. string name;
  495. QString format{QTStr("Basic.Main.DefaultSceneName.Text")};
  496. int i = 2;
  497. QString placeHolderText = format.arg(i);
  498. OBSSourceAutoRelease source = nullptr;
  499. while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) {
  500. placeHolderText = format.arg(++i);
  501. }
  502. bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"),
  503. QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText);
  504. if (accepted) {
  505. if (name.empty()) {
  506. OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text"));
  507. on_actionAddScene_triggered();
  508. return;
  509. }
  510. OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str());
  511. if (source) {
  512. OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text"));
  513. on_actionAddScene_triggered();
  514. return;
  515. }
  516. auto undo_fn = [](const std::string &data) {
  517. obs_source_t *t = obs_get_source_by_name(data.c_str());
  518. if (t) {
  519. obs_source_remove(t);
  520. obs_source_release(t);
  521. }
  522. };
  523. auto redo_fn = [this](const std::string &data) {
  524. OBSSceneAutoRelease scene = obs_scene_create(data.c_str());
  525. obs_source_t *source = obs_scene_get_source(scene);
  526. SetCurrentScene(source, true);
  527. };
  528. undo_s.add_action(QTStr("Undo.Add").arg(QString(name.c_str())), undo_fn, redo_fn, name, name);
  529. OBSSceneAutoRelease scene = obs_scene_create(name.c_str());
  530. obs_source_t *scene_source = obs_scene_get_source(scene);
  531. SetCurrentScene(scene_source);
  532. }
  533. }
  534. void OBSBasic::on_actionRemoveScene_triggered()
  535. {
  536. RemoveSelectedScene();
  537. }
  538. void OBSBasic::ChangeSceneIndex(bool relative, int offset, int invalidIdx)
  539. {
  540. int idx = ui->scenes->currentRow();
  541. if (idx == -1 || idx == invalidIdx)
  542. return;
  543. ui->scenes->blockSignals(true);
  544. QListWidgetItem *item = ui->scenes->takeItem(idx);
  545. if (!relative)
  546. idx = 0;
  547. ui->scenes->insertItem(idx + offset, item);
  548. ui->scenes->setCurrentRow(idx + offset);
  549. item->setSelected(true);
  550. currentScene = GetOBSRef<OBSScene>(item).Get();
  551. ui->scenes->blockSignals(false);
  552. OBSProjector::UpdateMultiviewProjectors();
  553. }
  554. void OBSBasic::on_actionSceneUp_triggered()
  555. {
  556. ChangeSceneIndex(true, -1, 0);
  557. }
  558. void OBSBasic::on_actionSceneDown_triggered()
  559. {
  560. ChangeSceneIndex(true, 1, ui->scenes->count() - 1);
  561. }
  562. void OBSBasic::MoveSceneToTop()
  563. {
  564. ChangeSceneIndex(false, 0, 0);
  565. }
  566. void OBSBasic::MoveSceneToBottom()
  567. {
  568. ChangeSceneIndex(false, ui->scenes->count() - 1, ui->scenes->count() - 1);
  569. }
  570. void OBSBasic::EditSceneItemName()
  571. {
  572. int idx = GetTopSelectedSourceItem();
  573. ui->sources->Edit(idx);
  574. }
  575. void OBSBasic::on_scenes_itemDoubleClicked(QListWidgetItem *witem)
  576. {
  577. if (!witem)
  578. return;
  579. if (IsPreviewProgramMode()) {
  580. bool doubleClickSwitch =
  581. config_get_bool(App()->GetUserConfig(), "BasicWindow", "TransitionOnDoubleClick");
  582. if (doubleClickSwitch)
  583. TransitionClicked();
  584. }
  585. }
  586. OBSData OBSBasic::BackupScene(obs_scene_t *scene, std::vector<obs_source_t *> *sources)
  587. {
  588. OBSDataArrayAutoRelease undo_array = obs_data_array_create();
  589. if (!sources) {
  590. obs_scene_enum_items(scene, save_undo_source_enum, undo_array);
  591. } else {
  592. for (obs_source_t *source : *sources) {
  593. obs_data_t *source_data = obs_save_source(source);
  594. obs_data_array_push_back(undo_array, source_data);
  595. obs_data_release(source_data);
  596. }
  597. }
  598. OBSDataAutoRelease scene_data = obs_save_source(obs_scene_get_source(scene));
  599. obs_data_array_push_back(undo_array, scene_data);
  600. OBSDataAutoRelease data = obs_data_create();
  601. obs_data_set_array(data, "array", undo_array);
  602. obs_data_get_json(data);
  603. return data.Get();
  604. }
  605. static bool add_source_enum(obs_scene_t *, obs_sceneitem_t *item, void *p)
  606. {
  607. auto sources = static_cast<std::vector<OBSSource> *>(p);
  608. sources->push_back(obs_sceneitem_get_source(item));
  609. return true;
  610. }
  611. void OBSBasic::CreateSceneUndoRedoAction(const QString &action_name, OBSData undo_data, OBSData redo_data)
  612. {
  613. auto undo_redo = [this](const std::string &json) {
  614. OBSDataAutoRelease base = obs_data_create_from_json(json.c_str());
  615. OBSDataArrayAutoRelease array = obs_data_get_array(base, "array");
  616. std::vector<OBSSource> sources;
  617. std::vector<OBSSource> old_sources;
  618. /* create missing sources */
  619. const size_t count = obs_data_array_count(array);
  620. sources.reserve(count);
  621. for (size_t i = 0; i < count; i++) {
  622. OBSDataAutoRelease data = obs_data_array_item(array, i);
  623. const char *name = obs_data_get_string(data, "name");
  624. OBSSourceAutoRelease source = obs_get_source_by_name(name);
  625. if (!source)
  626. source = obs_load_source(data);
  627. sources.push_back(source.Get());
  628. /* update scene/group settings to restore their
  629. * contents to their saved settings */
  630. obs_scene_t *scene = obs_group_or_scene_from_source(source);
  631. if (scene) {
  632. obs_scene_enum_items(scene, add_source_enum, &old_sources);
  633. OBSDataAutoRelease scene_settings = obs_data_get_obj(data, "settings");
  634. obs_source_update(source, scene_settings);
  635. }
  636. }
  637. /* actually load sources now */
  638. for (obs_source_t *source : sources)
  639. obs_source_load2(source);
  640. ui->sources->RefreshItems();
  641. };
  642. const char *undo_json = obs_data_get_last_json(undo_data);
  643. const char *redo_json = obs_data_get_last_json(redo_data);
  644. undo_s.add_action(action_name, undo_redo, undo_redo, undo_json, redo_json);
  645. }
  646. void OBSBasic::MoveSceneItem(enum obs_order_movement movement, const QString &action_name)
  647. {
  648. OBSSceneItem item = GetCurrentSceneItem();
  649. obs_source_t *source = obs_sceneitem_get_source(item);
  650. if (!source)
  651. return;
  652. OBSScene scene = GetCurrentScene();
  653. std::vector<obs_source_t *> sources;
  654. if (scene != obs_sceneitem_get_scene(item))
  655. sources.push_back(obs_scene_get_source(obs_sceneitem_get_scene(item)));
  656. OBSData undo_data = BackupScene(scene, &sources);
  657. obs_sceneitem_set_order(item, movement);
  658. const char *source_name = obs_source_get_name(source);
  659. const char *scene_name = obs_source_get_name(obs_scene_get_source(scene));
  660. OBSData redo_data = BackupScene(scene, &sources);
  661. CreateSceneUndoRedoAction(action_name.arg(source_name, scene_name), undo_data, redo_data);
  662. }
  663. static void RenameListItem(OBSBasic *parent, QListWidget *listWidget, obs_source_t *source, const string &name)
  664. {
  665. const char *prevName = obs_source_get_name(source);
  666. if (name == prevName)
  667. return;
  668. OBSSourceAutoRelease foundSource = obs_get_source_by_name(name.c_str());
  669. QListWidgetItem *listItem = listWidget->currentItem();
  670. if (foundSource || name.empty()) {
  671. listItem->setText(QT_UTF8(prevName));
  672. if (foundSource) {
  673. OBSMessageBox::warning(parent, QTStr("NameExists.Title"), QTStr("NameExists.Text"));
  674. } else if (name.empty()) {
  675. OBSMessageBox::warning(parent, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text"));
  676. }
  677. } else {
  678. auto undo = [prev = std::string(prevName)](const std::string &data) {
  679. OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str());
  680. obs_source_set_name(source, prev.c_str());
  681. };
  682. auto redo = [name](const std::string &data) {
  683. OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str());
  684. obs_source_set_name(source, name.c_str());
  685. };
  686. std::string source_uuid(obs_source_get_uuid(source));
  687. parent->undo_s.add_action(QTStr("Undo.Rename").arg(name.c_str()), undo, redo, source_uuid, source_uuid);
  688. listItem->setText(QT_UTF8(name.c_str()));
  689. obs_source_set_name(source, name.c_str());
  690. }
  691. }
  692. void OBSBasic::SceneNameEdited(QWidget *editor)
  693. {
  694. OBSScene scene = GetCurrentScene();
  695. QLineEdit *edit = qobject_cast<QLineEdit *>(editor);
  696. string text = QT_TO_UTF8(edit->text().trimmed());
  697. if (!scene)
  698. return;
  699. obs_source_t *source = obs_scene_get_source(scene);
  700. RenameListItem(this, ui->scenes, source, text);
  701. ui->scenesDock->addAction(renameScene);
  702. OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED);
  703. }
  704. void OBSBasic::OpenSceneFilters()
  705. {
  706. OBSScene scene = GetCurrentScene();
  707. OBSSource source = obs_scene_get_source(scene);
  708. CreateFiltersWindow(source);
  709. }
  710. static bool reset_tr(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *)
  711. {
  712. if (obs_sceneitem_is_group(item))
  713. obs_sceneitem_group_enum_items(item, reset_tr, nullptr);
  714. if (!obs_sceneitem_selected(item))
  715. return true;
  716. if (obs_sceneitem_locked(item))
  717. return true;
  718. obs_sceneitem_defer_update_begin(item);
  719. obs_transform_info info;
  720. vec2_set(&info.pos, 0.0f, 0.0f);
  721. vec2_set(&info.scale, 1.0f, 1.0f);
  722. info.rot = 0.0f;
  723. info.alignment = OBS_ALIGN_TOP | OBS_ALIGN_LEFT;
  724. info.bounds_type = OBS_BOUNDS_NONE;
  725. info.bounds_alignment = OBS_ALIGN_CENTER;
  726. info.crop_to_bounds = false;
  727. vec2_set(&info.bounds, 0.0f, 0.0f);
  728. obs_sceneitem_set_info2(item, &info);
  729. obs_sceneitem_crop crop = {};
  730. obs_sceneitem_set_crop(item, &crop);
  731. obs_sceneitem_defer_update_end(item);
  732. return true;
  733. }
  734. void OBSBasic::on_actionResetTransform_triggered()
  735. {
  736. OBSScene scene = GetCurrentScene();
  737. OBSDataAutoRelease wrapper = obs_scene_save_transform_states(scene, false);
  738. obs_scene_enum_items(scene, reset_tr, nullptr);
  739. OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(scene, false);
  740. std::string undo_data(obs_data_get_json(wrapper));
  741. std::string redo_data(obs_data_get_json(rwrapper));
  742. undo_s.add_action(QTStr("Undo.Transform.Reset").arg(obs_source_get_name(obs_scene_get_source(scene))),
  743. undo_redo, undo_redo, undo_data, redo_data);
  744. obs_scene_enum_items(GetCurrentScene(), reset_tr, nullptr);
  745. }
  746. SourceTreeItem *OBSBasic::GetItemWidgetFromSceneItem(obs_sceneitem_t *sceneItem)
  747. {
  748. int i = 0;
  749. SourceTreeItem *treeItem = ui->sources->GetItemWidget(i);
  750. OBSSceneItem item = ui->sources->Get(i);
  751. int64_t id = obs_sceneitem_get_id(sceneItem);
  752. while (treeItem && obs_sceneitem_get_id(item) != id) {
  753. i++;
  754. treeItem = ui->sources->GetItemWidget(i);
  755. item = ui->sources->Get(i);
  756. }
  757. if (treeItem)
  758. return treeItem;
  759. return nullptr;
  760. }
  761. void OBSBasic::on_actionSceneFilters_triggered()
  762. {
  763. OBSSource sceneSource = GetCurrentSceneSource();
  764. if (sceneSource)
  765. OpenFilters(sceneSource);
  766. }