source-tree.cpp 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386
  1. #include "window-basic-main.hpp"
  2. #include "obs-app.hpp"
  3. #include "source-tree.hpp"
  4. #include "qt-wrappers.hpp"
  5. #include "visibility-checkbox.hpp"
  6. #include "locked-checkbox.hpp"
  7. #include "expand-checkbox.hpp"
  8. #include <obs-frontend-api.h>
  9. #include <obs.h>
  10. #include <string>
  11. #include <QLabel>
  12. #include <QLineEdit>
  13. #include <QSpacerItem>
  14. #include <QPushButton>
  15. #include <QVBoxLayout>
  16. #include <QHBoxLayout>
  17. #include <QMouseEvent>
  18. #include <QStylePainter>
  19. #include <QStyleOptionFocusRect>
  20. static inline OBSScene GetCurrentScene()
  21. {
  22. OBSBasic *main = reinterpret_cast<OBSBasic*>(App()->GetMainWindow());
  23. return main->GetCurrentScene();
  24. }
  25. /* ========================================================================= */
  26. SourceTreeItem::SourceTreeItem(SourceTree *tree_, OBSSceneItem sceneitem_)
  27. : tree (tree_),
  28. sceneitem (sceneitem_)
  29. {
  30. setAttribute(Qt::WA_TranslucentBackground);
  31. obs_source_t *source = obs_sceneitem_get_source(sceneitem);
  32. const char *name = obs_source_get_name(source);
  33. obs_data_t *privData = obs_sceneitem_get_private_settings(sceneitem);
  34. int preset = obs_data_get_int(privData, "color-preset");
  35. if (preset == 1) {
  36. const char *color = obs_data_get_string(privData, "color");
  37. std::string col = "background: ";
  38. col += color;
  39. setStyleSheet(col.c_str());
  40. } else if (preset > 1) {
  41. setStyleSheet("");
  42. setProperty("bgColor", preset - 1);
  43. } else {
  44. setStyleSheet("background: none");
  45. }
  46. obs_data_release(privData);
  47. vis = new VisibilityCheckBox();
  48. vis->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
  49. vis->setMaximumSize(16, 16);
  50. vis->setChecked(obs_sceneitem_visible(sceneitem));
  51. lock = new LockedCheckBox();
  52. lock->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
  53. lock->setMaximumSize(16, 16);
  54. lock->setChecked(obs_sceneitem_locked(sceneitem));
  55. label = new QLabel(QT_UTF8(name));
  56. label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
  57. label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
  58. label->setAttribute(Qt::WA_TranslucentBackground);
  59. #ifdef __APPLE__
  60. vis->setAttribute(Qt::WA_LayoutUsesWidgetRect);
  61. lock->setAttribute(Qt::WA_LayoutUsesWidgetRect);
  62. #endif
  63. boxLayout = new QHBoxLayout();
  64. boxLayout->setContentsMargins(1, 1, 2, 1);
  65. boxLayout->setSpacing(1);
  66. boxLayout->addWidget(label);
  67. boxLayout->addWidget(vis);
  68. boxLayout->addWidget(lock);
  69. Update(false);
  70. setLayout(boxLayout);
  71. /* --------------------------------------------------------- */
  72. auto setItemVisible = [this] (bool checked)
  73. {
  74. SignalBlocker sourcesSignalBlocker(this);
  75. obs_sceneitem_set_visible(sceneitem, checked);
  76. };
  77. auto setItemLocked = [this] (bool checked)
  78. {
  79. SignalBlocker sourcesSignalBlocker(this);
  80. obs_sceneitem_set_locked(sceneitem, checked);
  81. };
  82. connect(vis, &QAbstractButton::clicked, setItemVisible);
  83. connect(lock, &QAbstractButton::clicked, setItemLocked);
  84. }
  85. void SourceTreeItem::paintEvent(QPaintEvent *event)
  86. {
  87. QStyleOption opt;
  88. opt.init(this);
  89. QPainter p(this);
  90. style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
  91. QWidget::paintEvent(event);
  92. }
  93. void SourceTreeItem::DisconnectSignals()
  94. {
  95. sceneRemoveSignal.Disconnect();
  96. itemRemoveSignal.Disconnect();
  97. deselectSignal.Disconnect();
  98. visibleSignal.Disconnect();
  99. renameSignal.Disconnect();
  100. removeSignal.Disconnect();
  101. }
  102. void SourceTreeItem::ReconnectSignals()
  103. {
  104. if (!sceneitem)
  105. return;
  106. DisconnectSignals();
  107. /* --------------------------------------------------------- */
  108. auto removeItem = [] (void *data, calldata_t *cd)
  109. {
  110. SourceTreeItem *this_ = reinterpret_cast<SourceTreeItem*>(data);
  111. obs_sceneitem_t *curItem =
  112. (obs_sceneitem_t*)calldata_ptr(cd, "item");
  113. if (curItem == this_->sceneitem) {
  114. QMetaObject::invokeMethod(this_->tree,
  115. "Remove",
  116. Q_ARG(OBSSceneItem, curItem));
  117. curItem = nullptr;
  118. }
  119. if (!curItem) {
  120. this_->DisconnectSignals();
  121. this_->sceneitem = nullptr;
  122. }
  123. };
  124. auto itemVisible = [] (void *data, calldata_t *cd)
  125. {
  126. SourceTreeItem *this_ = reinterpret_cast<SourceTreeItem*>(data);
  127. obs_sceneitem_t *curItem =
  128. (obs_sceneitem_t*)calldata_ptr(cd, "item");
  129. bool visible = calldata_bool(cd, "visible");
  130. if (curItem == this_->sceneitem)
  131. QMetaObject::invokeMethod(this_, "VisibilityChanged",
  132. Q_ARG(bool, visible));
  133. };
  134. auto itemDeselect = [] (void *data, calldata_t *cd)
  135. {
  136. SourceTreeItem *this_ = reinterpret_cast<SourceTreeItem*>(data);
  137. obs_sceneitem_t *curItem =
  138. (obs_sceneitem_t*)calldata_ptr(cd, "item");
  139. if (curItem == this_->sceneitem)
  140. QMetaObject::invokeMethod(this_, "Deselect");
  141. };
  142. auto reorderGroup = [] (void *data, calldata_t*)
  143. {
  144. SourceTreeItem *this_ = reinterpret_cast<SourceTreeItem*>(data);
  145. QMetaObject::invokeMethod(this_->tree, "ReorderItems");
  146. };
  147. obs_scene_t *scene = obs_sceneitem_get_scene(sceneitem);
  148. obs_source_t *sceneSource = obs_scene_get_source(scene);
  149. signal_handler_t *signal = obs_source_get_signal_handler(sceneSource);
  150. sceneRemoveSignal.Connect(signal, "remove", removeItem, this);
  151. itemRemoveSignal.Connect(signal, "item_remove", removeItem, this);
  152. visibleSignal.Connect(signal, "item_visible", itemVisible, this);
  153. if (obs_sceneitem_is_group(sceneitem)) {
  154. obs_source_t *source = obs_sceneitem_get_source(sceneitem);
  155. signal = obs_source_get_signal_handler(source);
  156. groupReorderSignal.Connect(signal, "reorder", reorderGroup,
  157. this);
  158. }
  159. if (scene != GetCurrentScene())
  160. deselectSignal.Connect(signal, "item_deselect", itemDeselect,
  161. this);
  162. /* --------------------------------------------------------- */
  163. auto renamed = [] (void *data, calldata_t *cd)
  164. {
  165. SourceTreeItem *this_ = reinterpret_cast<SourceTreeItem*>(data);
  166. const char *name = calldata_string(cd, "new_name");
  167. QMetaObject::invokeMethod(this_, "Renamed",
  168. Q_ARG(QString, QT_UTF8(name)));
  169. };
  170. auto removeSource = [] (void *data, calldata_t *)
  171. {
  172. SourceTreeItem *this_ = reinterpret_cast<SourceTreeItem*>(data);
  173. this_->DisconnectSignals();
  174. this_->sceneitem = nullptr;
  175. };
  176. obs_source_t *source = obs_sceneitem_get_source(sceneitem);
  177. signal = obs_source_get_signal_handler(source);
  178. renameSignal.Connect(signal, "rename", renamed, this);
  179. removeSignal.Connect(signal, "remove", removeSource, this);
  180. }
  181. void SourceTreeItem::mouseDoubleClickEvent(QMouseEvent *event)
  182. {
  183. QWidget::mouseDoubleClickEvent(event);
  184. if (expand) {
  185. expand->setChecked(!expand->isChecked());
  186. } else {
  187. obs_source_t *source = obs_sceneitem_get_source(sceneitem);
  188. OBSBasic *main =
  189. reinterpret_cast<OBSBasic*>(App()->GetMainWindow());
  190. if (source) {
  191. main->CreatePropertiesWindow(source);
  192. }
  193. }
  194. }
  195. void SourceTreeItem::EnterEditMode()
  196. {
  197. setFocusPolicy(Qt::StrongFocus);
  198. boxLayout->removeWidget(label);
  199. editor = new QLineEdit(label->text());
  200. editor->installEventFilter(this);
  201. boxLayout->insertWidget(1, editor);
  202. setFocusProxy(editor);
  203. }
  204. void SourceTreeItem::ExitEditMode(bool save)
  205. {
  206. if (!editor)
  207. return;
  208. OBSBasic *main = reinterpret_cast<OBSBasic*>(App()->GetMainWindow());
  209. OBSScene scene = main->GetCurrentScene();
  210. std::string newName = QT_TO_UTF8(editor->text());
  211. setFocusProxy(nullptr);
  212. boxLayout->removeWidget(editor);
  213. delete editor;
  214. editor = nullptr;
  215. setFocusPolicy(Qt::NoFocus);
  216. boxLayout->insertWidget(1, label);
  217. /* ----------------------------------------- */
  218. /* check for empty string */
  219. if (!save)
  220. return;
  221. if (newName.empty()) {
  222. OBSMessageBox::information(main,
  223. QTStr("NoNameEntered.Title"),
  224. QTStr("NoNameEntered.Text"));
  225. return;
  226. }
  227. /* ----------------------------------------- */
  228. /* Check for same name */
  229. obs_source_t *source = obs_sceneitem_get_source(sceneitem);
  230. if (newName == obs_source_get_name(source))
  231. return;
  232. /* ----------------------------------------- */
  233. /* check for existing source */
  234. obs_source_t *existingSource =
  235. obs_get_source_by_name(newName.c_str());
  236. obs_source_release(existingSource);
  237. bool exists = !!existingSource;
  238. if (exists) {
  239. OBSMessageBox::information(main,
  240. QTStr("NameExists.Title"),
  241. QTStr("NameExists.Text"));
  242. return;
  243. }
  244. /* ----------------------------------------- */
  245. /* rename */
  246. SignalBlocker sourcesSignalBlocker(this);
  247. obs_source_set_name(source, newName.c_str());
  248. label->setText(QT_UTF8(newName.c_str()));
  249. }
  250. bool SourceTreeItem::eventFilter(QObject *object, QEvent *event)
  251. {
  252. if (editor != object)
  253. return false;
  254. if (event->type() == QEvent::KeyPress) {
  255. QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
  256. switch (keyEvent->key()) {
  257. case Qt::Key_Escape:
  258. QMetaObject::invokeMethod(this, "ExitEditMode",
  259. Qt::QueuedConnection,
  260. Q_ARG(bool, false));
  261. return true;
  262. case Qt::Key_Tab:
  263. case Qt::Key_Backtab:
  264. case Qt::Key_Enter:
  265. case Qt::Key_Return:
  266. QMetaObject::invokeMethod(this, "ExitEditMode",
  267. Qt::QueuedConnection,
  268. Q_ARG(bool, true));
  269. return true;
  270. }
  271. } else if (event->type() == QEvent::FocusOut) {
  272. QMetaObject::invokeMethod(this, "ExitEditMode",
  273. Qt::QueuedConnection,
  274. Q_ARG(bool, true));
  275. return true;
  276. }
  277. return false;
  278. }
  279. void SourceTreeItem::VisibilityChanged(bool visible)
  280. {
  281. vis->setChecked(visible);
  282. }
  283. void SourceTreeItem::Renamed(const QString &name)
  284. {
  285. label->setText(name);
  286. }
  287. void SourceTreeItem::Update(bool force)
  288. {
  289. OBSScene scene = GetCurrentScene();
  290. obs_scene_t *itemScene = obs_sceneitem_get_scene(sceneitem);
  291. Type newType;
  292. /* ------------------------------------------------- */
  293. /* if it's a group item, insert group checkbox */
  294. if (obs_sceneitem_is_group(sceneitem)) {
  295. newType = Type::Group;
  296. /* ------------------------------------------------- */
  297. /* if it's a group sub-item */
  298. } else if (itemScene != scene) {
  299. newType = Type::SubItem;
  300. /* ------------------------------------------------- */
  301. /* if it's a regular item */
  302. } else {
  303. newType = Type::Item;
  304. }
  305. /* ------------------------------------------------- */
  306. if (!force && newType == type) {
  307. return;
  308. }
  309. /* ------------------------------------------------- */
  310. ReconnectSignals();
  311. if (spacer) {
  312. boxLayout->removeItem(spacer);
  313. delete spacer;
  314. spacer = nullptr;
  315. }
  316. if (type == Type::Group) {
  317. boxLayout->removeWidget(expand);
  318. expand->deleteLater();
  319. expand = nullptr;
  320. }
  321. type = newType;
  322. if (type == Type::SubItem) {
  323. spacer = new QSpacerItem(16, 1);
  324. boxLayout->insertItem(0, spacer);
  325. } else if (type == Type::Group) {
  326. expand = new SourceTreeSubItemCheckBox();
  327. expand->setSizePolicy(
  328. QSizePolicy::Maximum,
  329. QSizePolicy::Maximum);
  330. expand->setMaximumSize(10, 16);
  331. expand->setMinimumSize(10, 0);
  332. #ifdef __APPLE__
  333. expand->setAttribute(Qt::WA_LayoutUsesWidgetRect);
  334. #endif
  335. boxLayout->insertWidget(0, expand);
  336. obs_data_t *data = obs_sceneitem_get_private_settings(sceneitem);
  337. expand->blockSignals(true);
  338. expand->setChecked(obs_data_get_bool(data, "collapsed"));
  339. expand->blockSignals(false);
  340. obs_data_release(data);
  341. connect(expand, &QPushButton::toggled,
  342. this, &SourceTreeItem::ExpandClicked);
  343. } else {
  344. spacer = new QSpacerItem(3, 1);
  345. boxLayout->insertItem(0, spacer);
  346. }
  347. }
  348. void SourceTreeItem::ExpandClicked(bool checked)
  349. {
  350. OBSData data = obs_sceneitem_get_private_settings(sceneitem);
  351. obs_data_release(data);
  352. obs_data_set_bool(data, "collapsed", checked);
  353. if (!checked)
  354. tree->GetStm()->ExpandGroup(sceneitem);
  355. else
  356. tree->GetStm()->CollapseGroup(sceneitem);
  357. }
  358. void SourceTreeItem::Deselect()
  359. {
  360. tree->SelectItem(sceneitem, false);
  361. }
  362. /* ========================================================================= */
  363. void SourceTreeModel::OBSFrontendEvent(enum obs_frontend_event event, void *ptr)
  364. {
  365. SourceTreeModel *stm = reinterpret_cast<SourceTreeModel *>(ptr);
  366. switch ((int)event) {
  367. case OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED:
  368. stm->SceneChanged();
  369. break;
  370. case OBS_FRONTEND_EVENT_EXIT:
  371. case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP:
  372. stm->Clear();
  373. break;
  374. }
  375. }
  376. void SourceTreeModel::Clear()
  377. {
  378. beginResetModel();
  379. items.clear();
  380. endResetModel();
  381. hasGroups = false;
  382. }
  383. static bool enumItem(obs_scene_t*, obs_sceneitem_t *item, void *ptr)
  384. {
  385. QVector<OBSSceneItem> &items =
  386. *reinterpret_cast<QVector<OBSSceneItem>*>(ptr);
  387. if (obs_sceneitem_is_group(item)) {
  388. obs_data_t *data = obs_sceneitem_get_private_settings(item);
  389. bool collapse = obs_data_get_bool(data, "collapsed");
  390. if (!collapse) {
  391. obs_scene_t *scene =
  392. obs_sceneitem_group_get_scene(item);
  393. obs_scene_enum_items(scene, enumItem, &items);
  394. }
  395. obs_data_release(data);
  396. }
  397. items.insert(0, item);
  398. return true;
  399. }
  400. void SourceTreeModel::SceneChanged()
  401. {
  402. OBSScene scene = GetCurrentScene();
  403. beginResetModel();
  404. items.clear();
  405. obs_scene_enum_items(scene, enumItem, &items);
  406. endResetModel();
  407. UpdateGroupState(false);
  408. st->ResetWidgets();
  409. for (int i = 0; i < items.count(); i++) {
  410. bool select = obs_sceneitem_selected(items[i]);
  411. QModelIndex index = createIndex(i, 0);
  412. st->selectionModel()->select(index, select
  413. ? QItemSelectionModel::Select
  414. : QItemSelectionModel::Deselect);
  415. }
  416. }
  417. /* moves a scene item index (blame linux distros for using older Qt builds) */
  418. static inline void MoveItem(QVector<OBSSceneItem> &items, int oldIdx, int newIdx)
  419. {
  420. OBSSceneItem item = items[oldIdx];
  421. items.remove(oldIdx);
  422. items.insert(newIdx, item);
  423. }
  424. /* reorders list optimally with model reorder funcs */
  425. void SourceTreeModel::ReorderItems()
  426. {
  427. OBSScene scene = GetCurrentScene();
  428. QVector<OBSSceneItem> newitems;
  429. obs_scene_enum_items(scene, enumItem, &newitems);
  430. /* if item list has changed size, do full reset */
  431. if (newitems.count() != items.count()) {
  432. SceneChanged();
  433. return;
  434. }
  435. for (;;) {
  436. int idx1Old = 0;
  437. int idx1New = 0;
  438. int count;
  439. int i;
  440. /* find first starting changed item index */
  441. for (i = 0; i < newitems.count(); i++) {
  442. obs_sceneitem_t *oldItem = items[i];
  443. obs_sceneitem_t *newItem = newitems[i];
  444. if (oldItem != newItem) {
  445. idx1Old = i;
  446. break;
  447. }
  448. }
  449. /* if everything is the same, break */
  450. if (i == newitems.count()) {
  451. break;
  452. }
  453. /* find new starting index */
  454. for (i = idx1Old + 1; i < newitems.count(); i++) {
  455. obs_sceneitem_t *oldItem = items[idx1Old];
  456. obs_sceneitem_t *newItem = newitems[i];
  457. if (oldItem == newItem) {
  458. idx1New = i;
  459. break;
  460. }
  461. }
  462. /* if item could not be found, do full reset */
  463. if (i == newitems.count()) {
  464. SceneChanged();
  465. return;
  466. }
  467. /* get move count */
  468. for (count = 1; (idx1New + count) < newitems.count(); count++) {
  469. int oldIdx = idx1Old + count;
  470. int newIdx = idx1New + count;
  471. obs_sceneitem_t *oldItem = items[oldIdx];
  472. obs_sceneitem_t *newItem = newitems[newIdx];
  473. if (oldItem != newItem) {
  474. break;
  475. }
  476. }
  477. /* move items */
  478. beginMoveRows(QModelIndex(), idx1Old, idx1Old + count - 1,
  479. QModelIndex(), idx1New + count);
  480. for (i = 0; i < count; i++) {
  481. int to = idx1New + count;
  482. if (to > idx1Old)
  483. to--;
  484. MoveItem(items, idx1Old, to);
  485. }
  486. endMoveRows();
  487. }
  488. }
  489. void SourceTreeModel::Add(obs_sceneitem_t *item)
  490. {
  491. if (obs_sceneitem_is_group(item)) {
  492. SceneChanged();
  493. } else {
  494. beginInsertRows(QModelIndex(), 0, 0);
  495. items.insert(0, item);
  496. endInsertRows();
  497. st->UpdateWidget(createIndex(0, 0, nullptr), item);
  498. }
  499. }
  500. void SourceTreeModel::Remove(obs_sceneitem_t *item)
  501. {
  502. int idx = -1;
  503. for (int i = 0; i < items.count(); i++) {
  504. if (items[i] == item) {
  505. idx = i;
  506. break;
  507. }
  508. }
  509. if (idx == -1)
  510. return;
  511. int startIdx = idx;
  512. int endIdx = idx;
  513. bool is_group = obs_sceneitem_is_group(item);
  514. if (is_group) {
  515. obs_scene_t *scene = obs_sceneitem_group_get_scene(item);
  516. for (int i = endIdx + 1; i < items.count(); i++) {
  517. obs_sceneitem_t *subitem = items[i];
  518. obs_scene_t *subscene =
  519. obs_sceneitem_get_scene(subitem);
  520. if (subscene == scene)
  521. endIdx = i;
  522. else
  523. break;
  524. }
  525. }
  526. beginRemoveRows(QModelIndex(), startIdx, endIdx);
  527. items.remove(idx, endIdx - startIdx + 1);
  528. endRemoveRows();
  529. if (is_group)
  530. UpdateGroupState(true);
  531. }
  532. OBSSceneItem SourceTreeModel::Get(int idx)
  533. {
  534. if (idx == -1 || idx >= items.count())
  535. return OBSSceneItem();
  536. return items[idx];
  537. }
  538. SourceTreeModel::SourceTreeModel(SourceTree *st_)
  539. : QAbstractListModel (st_),
  540. st (st_)
  541. {
  542. obs_frontend_add_event_callback(OBSFrontendEvent, this);
  543. }
  544. SourceTreeModel::~SourceTreeModel()
  545. {
  546. obs_frontend_remove_event_callback(OBSFrontendEvent, this);
  547. }
  548. int SourceTreeModel::rowCount(const QModelIndex &parent) const
  549. {
  550. return parent.isValid() ? 0 : items.count();
  551. }
  552. QVariant SourceTreeModel::data(const QModelIndex &, int) const
  553. {
  554. return QVariant();
  555. }
  556. Qt::ItemFlags SourceTreeModel::flags(const QModelIndex &index) const
  557. {
  558. if (!index.isValid())
  559. return QAbstractListModel::flags(index) | Qt::ItemIsDropEnabled;
  560. obs_sceneitem_t *item = items[index.row()];
  561. bool is_group = obs_sceneitem_is_group(item);
  562. return QAbstractListModel::flags(index) |
  563. Qt::ItemIsEditable |
  564. Qt::ItemIsDragEnabled |
  565. (is_group ? Qt::ItemIsDropEnabled : Qt::NoItemFlags);
  566. }
  567. Qt::DropActions SourceTreeModel::supportedDropActions() const
  568. {
  569. return QAbstractItemModel::supportedDropActions() | Qt::MoveAction;
  570. }
  571. QString SourceTreeModel::GetNewGroupName()
  572. {
  573. OBSScene scene = GetCurrentScene();
  574. QString name = QTStr("Group");
  575. int i = 2;
  576. for (;;) {
  577. obs_source_t *group = obs_get_source_by_name(QT_TO_UTF8(name));
  578. obs_source_release(group);
  579. if (!group)
  580. break;
  581. name = QTStr("Basic.Main.Group").arg(QString::number(i++));
  582. }
  583. return name;
  584. }
  585. void SourceTreeModel::AddGroup()
  586. {
  587. QString name = GetNewGroupName();
  588. obs_sceneitem_t *group = obs_scene_add_group(GetCurrentScene(),
  589. QT_TO_UTF8(name));
  590. if (!group)
  591. return;
  592. beginInsertRows(QModelIndex(), 0, 0);
  593. items.insert(0, group);
  594. endInsertRows();
  595. st->UpdateWidget(createIndex(0, 0, nullptr), group);
  596. UpdateGroupState(true);
  597. QMetaObject::invokeMethod(st, "Edit", Qt::QueuedConnection,
  598. Q_ARG(int, 0));
  599. }
  600. void SourceTreeModel::GroupSelectedItems(QModelIndexList &indices)
  601. {
  602. if (indices.count() == 0)
  603. return;
  604. OBSScene scene = GetCurrentScene();
  605. QString name = GetNewGroupName();
  606. QVector<obs_sceneitem_t *> item_order;
  607. for (int i = indices.count() - 1; i >= 0; i--) {
  608. obs_sceneitem_t *item = items[indices[i].row()];
  609. item_order << item;
  610. }
  611. obs_sceneitem_t *item = obs_scene_insert_group(
  612. scene, QT_TO_UTF8(name),
  613. item_order.data(), item_order.size());
  614. if (!item) {
  615. return;
  616. }
  617. for (obs_sceneitem_t *item : item_order)
  618. obs_sceneitem_select(item, false);
  619. int newIdx = indices[0].row();
  620. beginInsertRows(QModelIndex(), newIdx, newIdx);
  621. items.insert(newIdx, item);
  622. endInsertRows();
  623. for (int i = 0; i < indices.size(); i++) {
  624. int fromIdx = indices[i].row() + 1;
  625. int toIdx = newIdx + i + 1;
  626. if (fromIdx != toIdx) {
  627. beginMoveRows(QModelIndex(), fromIdx, fromIdx,
  628. QModelIndex(), toIdx);
  629. MoveItem(items, fromIdx, toIdx);
  630. endMoveRows();
  631. }
  632. }
  633. hasGroups = true;
  634. st->UpdateWidgets(true);
  635. obs_sceneitem_select(item, true);
  636. QMetaObject::invokeMethod(st, "Edit", Qt::QueuedConnection,
  637. Q_ARG(int, newIdx));
  638. }
  639. void SourceTreeModel::UngroupSelectedGroups(QModelIndexList &indices)
  640. {
  641. if (indices.count() == 0)
  642. return;
  643. for (int i = indices.count() - 1; i >= 0; i--) {
  644. obs_sceneitem_t *item = items[indices[i].row()];
  645. obs_sceneitem_group_ungroup(item);
  646. }
  647. SceneChanged();
  648. }
  649. void SourceTreeModel::ExpandGroup(obs_sceneitem_t *item)
  650. {
  651. int itemIdx = items.indexOf(item);
  652. if (itemIdx == -1)
  653. return;
  654. itemIdx++;
  655. obs_scene_t *scene = obs_sceneitem_group_get_scene(item);
  656. QVector<OBSSceneItem> subItems;
  657. obs_scene_enum_items(scene, enumItem, &subItems);
  658. if (!subItems.size())
  659. return;
  660. beginInsertRows(QModelIndex(), itemIdx, itemIdx + subItems.size() - 1);
  661. for (int i = 0; i < subItems.size(); i++)
  662. items.insert(i + itemIdx, subItems[i]);
  663. endInsertRows();
  664. st->UpdateWidgets();
  665. }
  666. void SourceTreeModel::CollapseGroup(obs_sceneitem_t *item)
  667. {
  668. int startIdx = -1;
  669. int endIdx = -1;
  670. obs_scene_t *scene = obs_sceneitem_group_get_scene(item);
  671. for (int i = 0; i < items.size(); i++) {
  672. obs_scene_t *itemScene = obs_sceneitem_get_scene(items[i]);
  673. if (itemScene == scene) {
  674. if (startIdx == -1)
  675. startIdx = i;
  676. endIdx = i;
  677. }
  678. }
  679. if (startIdx == -1)
  680. return;
  681. beginRemoveRows(QModelIndex(), startIdx, endIdx);
  682. items.remove(startIdx, endIdx - startIdx + 1);
  683. endRemoveRows();
  684. }
  685. void SourceTreeModel::UpdateGroupState(bool update)
  686. {
  687. bool nowHasGroups = false;
  688. for (auto &item : items) {
  689. if (obs_sceneitem_is_group(item)) {
  690. nowHasGroups = true;
  691. break;
  692. }
  693. }
  694. if (nowHasGroups != hasGroups) {
  695. hasGroups = nowHasGroups;
  696. if (update) {
  697. st->UpdateWidgets(true);
  698. }
  699. }
  700. }
  701. /* ========================================================================= */
  702. SourceTree::SourceTree(QWidget *parent_) : QListView(parent_)
  703. {
  704. SourceTreeModel *stm_ = new SourceTreeModel(this);
  705. setModel(stm_);
  706. setStyleSheet(QString(
  707. "*[bgColor=\"1\"]{background-color:rgba(255,68,68,33%);}" \
  708. "*[bgColor=\"2\"]{background-color:rgba(255,255,68,33%);}" \
  709. "*[bgColor=\"3\"]{background-color:rgba(68,255,68,33%);}" \
  710. "*[bgColor=\"4\"]{background-color:rgba(68,255,255,33%);}" \
  711. "*[bgColor=\"5\"]{background-color:rgba(68,68,255,33%);}" \
  712. "*[bgColor=\"6\"]{background-color:rgba(255,68,255,33%);}" \
  713. "*[bgColor=\"7\"]{background-color:rgba(68,68,68,33%);}" \
  714. "*[bgColor=\"8\"]{background-color:rgba(255,255,255,33%);}"));
  715. }
  716. void SourceTree::ResetWidgets()
  717. {
  718. OBSScene scene = GetCurrentScene();
  719. SourceTreeModel *stm = GetStm();
  720. stm->UpdateGroupState(false);
  721. for (int i = 0; i < stm->items.count(); i++) {
  722. QModelIndex index = stm->createIndex(i, 0, nullptr);
  723. setIndexWidget(index, new SourceTreeItem(this, stm->items[i]));
  724. }
  725. }
  726. void SourceTree::UpdateWidget(const QModelIndex &idx, obs_sceneitem_t *item)
  727. {
  728. setIndexWidget(idx, new SourceTreeItem(this, item));
  729. }
  730. void SourceTree::UpdateWidgets(bool force)
  731. {
  732. SourceTreeModel *stm = GetStm();
  733. for (int i = 0; i < stm->items.size(); i++) {
  734. obs_sceneitem_t *item = stm->items[i];
  735. SourceTreeItem *widget = GetItemWidget(i);
  736. if (!widget) {
  737. UpdateWidget(stm->createIndex(i, 0), item);
  738. } else {
  739. widget->Update(force);
  740. }
  741. }
  742. }
  743. void SourceTree::SelectItem(obs_sceneitem_t *sceneitem, bool select)
  744. {
  745. SourceTreeModel *stm = GetStm();
  746. int i = 0;
  747. for (; i < stm->items.count(); i++) {
  748. if (stm->items[i] == sceneitem)
  749. break;
  750. }
  751. if (i == stm->items.count())
  752. return;
  753. QModelIndex index = stm->createIndex(i, 0);
  754. if (index.isValid())
  755. selectionModel()->select(index, select
  756. ? QItemSelectionModel::Select
  757. : QItemSelectionModel::Deselect);
  758. }
  759. Q_DECLARE_METATYPE(OBSSceneItem);
  760. void SourceTree::mouseDoubleClickEvent(QMouseEvent *event)
  761. {
  762. if (event->button() == Qt::LeftButton)
  763. QListView::mouseDoubleClickEvent(event);
  764. }
  765. void SourceTree::dropEvent(QDropEvent *event)
  766. {
  767. if (event->source() != this) {
  768. QListView::dropEvent(event);
  769. return;
  770. }
  771. OBSScene scene = GetCurrentScene();
  772. SourceTreeModel *stm = GetStm();
  773. auto &items = stm->items;
  774. QModelIndexList indices = selectedIndexes();
  775. DropIndicatorPosition indicator = dropIndicatorPosition();
  776. int row = indexAt(event->pos()).row();
  777. bool emptyDrop = row == -1;
  778. if (emptyDrop) {
  779. if (!items.size()) {
  780. QListView::dropEvent(event);
  781. return;
  782. }
  783. row = items.size() - 1;
  784. indicator = QAbstractItemView::BelowItem;
  785. }
  786. /* --------------------------------------- */
  787. /* store destination group if moving to a */
  788. /* group */
  789. obs_sceneitem_t *dropItem = items[row]; /* item being dropped on */
  790. bool itemIsGroup = obs_sceneitem_is_group(dropItem);
  791. obs_sceneitem_t *dropGroup = itemIsGroup
  792. ? dropItem
  793. : obs_sceneitem_get_group(scene, dropItem);
  794. /* not a group if moving above the group */
  795. if (indicator == QAbstractItemView::AboveItem && itemIsGroup)
  796. dropGroup = nullptr;
  797. if (emptyDrop)
  798. dropGroup = nullptr;
  799. /* --------------------------------------- */
  800. /* remember to remove list items if */
  801. /* dropping on collapsed group */
  802. bool dropOnCollapsed = false;
  803. if (dropGroup) {
  804. obs_data_t *data = obs_sceneitem_get_private_settings(dropGroup);
  805. dropOnCollapsed = obs_data_get_bool(data, "collapsed");
  806. obs_data_release(data);
  807. }
  808. if (indicator == QAbstractItemView::BelowItem ||
  809. indicator == QAbstractItemView::OnItem)
  810. row++;
  811. if (row < 0 || row > stm->items.count()) {
  812. QListView::dropEvent(event);
  813. return;
  814. }
  815. /* --------------------------------------- */
  816. /* determine if any base group is selected */
  817. bool hasGroups = false;
  818. for (int i = 0; i < indices.size(); i++) {
  819. obs_sceneitem_t *item = items[indices[i].row()];
  820. if (obs_sceneitem_is_group(item)) {
  821. hasGroups = true;
  822. break;
  823. }
  824. }
  825. /* --------------------------------------- */
  826. /* if dropping a group, detect if it's */
  827. /* below another group */
  828. obs_sceneitem_t *itemBelow = row == stm->items.count()
  829. ? nullptr
  830. : stm->items[row];
  831. if (hasGroups) {
  832. if (!itemBelow ||
  833. obs_sceneitem_get_group(scene, itemBelow) != dropGroup) {
  834. indicator = QAbstractItemView::BelowItem;
  835. dropGroup = nullptr;
  836. dropOnCollapsed = false;
  837. }
  838. }
  839. /* --------------------------------------- */
  840. /* if dropping groups on other groups, */
  841. /* disregard as invalid drag/drop */
  842. if (dropGroup && hasGroups) {
  843. QListView::dropEvent(event);
  844. return;
  845. }
  846. /* --------------------------------------- */
  847. /* if selection includes base group items, */
  848. /* include all group sub-items and treat */
  849. /* them all as one */
  850. if (hasGroups) {
  851. /* remove sub-items if selected */
  852. for (int i = indices.size() - 1; i >= 0; i--) {
  853. obs_sceneitem_t *item = items[indices[i].row()];
  854. obs_scene_t *itemScene = obs_sceneitem_get_scene(item);
  855. if (itemScene != scene) {
  856. indices.removeAt(i);
  857. }
  858. }
  859. /* add all sub-items of selected groups */
  860. for (int i = indices.size() - 1; i >= 0; i--) {
  861. obs_sceneitem_t *item = items[indices[i].row()];
  862. if (obs_sceneitem_is_group(item)) {
  863. for (int j = items.size() - 1; j >= 0; j--) {
  864. obs_sceneitem_t *subitem = items[j];
  865. obs_sceneitem_t *subitemGroup =
  866. obs_sceneitem_get_group(scene,
  867. subitem);
  868. if (subitemGroup == item) {
  869. QModelIndex idx =
  870. stm->createIndex(j, 0);
  871. indices.insert(i + 1, idx);
  872. }
  873. }
  874. }
  875. }
  876. }
  877. /* --------------------------------------- */
  878. /* build persistent indices */
  879. QList<QPersistentModelIndex> persistentIndices;
  880. persistentIndices.reserve(indices.count());
  881. for (QModelIndex &index : indices)
  882. persistentIndices.append(index);
  883. std::sort(persistentIndices.begin(), persistentIndices.end());
  884. /* --------------------------------------- */
  885. /* move all items to destination index */
  886. int r = row;
  887. for (auto &persistentIdx : persistentIndices) {
  888. int from = persistentIdx.row();
  889. int to = r;
  890. int itemTo = to;
  891. if (itemTo > from)
  892. itemTo--;
  893. if (itemTo != from) {
  894. stm->beginMoveRows(QModelIndex(), from, from,
  895. QModelIndex(), to);
  896. MoveItem(items, from, itemTo);
  897. stm->endMoveRows();
  898. }
  899. r = persistentIdx.row() + 1;
  900. }
  901. std::sort(persistentIndices.begin(), persistentIndices.end());
  902. int firstIdx = persistentIndices.front().row();
  903. int lastIdx = persistentIndices.back().row();
  904. /* --------------------------------------- */
  905. /* reorder scene items in back-end */
  906. QVector<struct obs_sceneitem_order_info> orderList;
  907. obs_sceneitem_t *lastGroup = nullptr;
  908. int insertCollapsedIdx = 0;
  909. auto insertCollapsed = [&] (obs_sceneitem_t *item)
  910. {
  911. struct obs_sceneitem_order_info info;
  912. info.group = lastGroup;
  913. info.item = item;
  914. orderList.insert(insertCollapsedIdx++, info);
  915. };
  916. using insertCollapsed_t = decltype(insertCollapsed);
  917. auto preInsertCollapsed = [] (obs_scene_t *, obs_sceneitem_t *item,
  918. void *param)
  919. {
  920. (*reinterpret_cast<insertCollapsed_t *>(param))(item);
  921. return true;
  922. };
  923. auto insertLastGroup = [&] ()
  924. {
  925. obs_data_t *data = obs_sceneitem_get_private_settings(lastGroup);
  926. bool collapsed = obs_data_get_bool(data, "collapsed");
  927. obs_data_release(data);
  928. if (collapsed) {
  929. insertCollapsedIdx = 0;
  930. obs_sceneitem_group_enum_items(
  931. lastGroup,
  932. preInsertCollapsed,
  933. &insertCollapsed);
  934. }
  935. struct obs_sceneitem_order_info info;
  936. info.group = nullptr;
  937. info.item = lastGroup;
  938. orderList.insert(0, info);
  939. };
  940. auto updateScene = [&] ()
  941. {
  942. struct obs_sceneitem_order_info info;
  943. for (int i = 0; i < items.size(); i++) {
  944. obs_sceneitem_t *item = items[i];
  945. obs_sceneitem_t *group;
  946. if (obs_sceneitem_is_group(item)) {
  947. if (lastGroup) {
  948. insertLastGroup();
  949. }
  950. lastGroup = item;
  951. continue;
  952. }
  953. if (!hasGroups && i >= firstIdx && i <= lastIdx)
  954. group = dropGroup;
  955. else
  956. group = obs_sceneitem_get_group(scene, item);
  957. if (lastGroup && lastGroup != group) {
  958. insertLastGroup();
  959. }
  960. lastGroup = group;
  961. info.group = group;
  962. info.item = item;
  963. orderList.insert(0, info);
  964. }
  965. if (lastGroup) {
  966. insertLastGroup();
  967. }
  968. obs_scene_reorder_items2(scene,
  969. orderList.data(), orderList.size());
  970. };
  971. using updateScene_t = decltype(updateScene);
  972. auto preUpdateScene = [] (void *data, obs_scene_t *)
  973. {
  974. (*reinterpret_cast<updateScene_t *>(data))();
  975. };
  976. ignoreReorder = true;
  977. obs_scene_atomic_update(scene, preUpdateScene, &updateScene);
  978. ignoreReorder = false;
  979. /* --------------------------------------- */
  980. /* remove items if dropped in to collapsed */
  981. /* group */
  982. if (dropOnCollapsed) {
  983. stm->beginRemoveRows(QModelIndex(), firstIdx, lastIdx);
  984. items.remove(firstIdx, lastIdx - firstIdx + 1);
  985. stm->endRemoveRows();
  986. }
  987. /* --------------------------------------- */
  988. /* update widgets and accept event */
  989. UpdateWidgets(true);
  990. event->accept();
  991. event->setDropAction(Qt::CopyAction);
  992. QListView::dropEvent(event);
  993. }
  994. void SourceTree::selectionChanged(
  995. const QItemSelection &selected,
  996. const QItemSelection &deselected)
  997. {
  998. {
  999. SignalBlocker sourcesSignalBlocker(this);
  1000. SourceTreeModel *stm = GetStm();
  1001. QModelIndexList selectedIdxs = selected.indexes();
  1002. QModelIndexList deselectedIdxs = deselected.indexes();
  1003. for (int i = 0; i < selectedIdxs.count(); i++) {
  1004. int idx = selectedIdxs[i].row();
  1005. obs_sceneitem_select(stm->items[idx], true);
  1006. }
  1007. for (int i = 0; i < deselectedIdxs.count(); i++) {
  1008. int idx = deselectedIdxs[i].row();
  1009. obs_sceneitem_select(stm->items[idx], false);
  1010. }
  1011. }
  1012. QListView::selectionChanged(selected, deselected);
  1013. }
  1014. void SourceTree::Edit(int row)
  1015. {
  1016. SourceTreeModel *stm = GetStm();
  1017. if (row < 0 || row >= stm->items.count())
  1018. return;
  1019. QWidget *widget = indexWidget(stm->createIndex(row, 0));
  1020. SourceTreeItem *itemWidget = reinterpret_cast<SourceTreeItem *>(widget);
  1021. itemWidget->EnterEditMode();
  1022. edit(stm->createIndex(row, 0));
  1023. }
  1024. bool SourceTree::MultipleBaseSelected() const
  1025. {
  1026. SourceTreeModel *stm = GetStm();
  1027. QModelIndexList selectedIndices = selectedIndexes();
  1028. OBSScene scene = GetCurrentScene();
  1029. if (selectedIndices.size() < 1) {
  1030. return false;
  1031. }
  1032. for (auto &idx : selectedIndices) {
  1033. obs_sceneitem_t *item = stm->items[idx.row()];
  1034. if (obs_sceneitem_is_group(item)) {
  1035. return false;
  1036. }
  1037. obs_scene *itemScene = obs_sceneitem_get_scene(item);
  1038. if (itemScene != scene) {
  1039. return false;
  1040. }
  1041. }
  1042. return true;
  1043. }
  1044. bool SourceTree::GroupsSelected() const
  1045. {
  1046. SourceTreeModel *stm = GetStm();
  1047. QModelIndexList selectedIndices = selectedIndexes();
  1048. OBSScene scene = GetCurrentScene();
  1049. if (selectedIndices.size() < 1) {
  1050. return false;
  1051. }
  1052. for (auto &idx : selectedIndices) {
  1053. obs_sceneitem_t *item = stm->items[idx.row()];
  1054. if (!obs_sceneitem_is_group(item)) {
  1055. return false;
  1056. }
  1057. }
  1058. return true;
  1059. }
  1060. bool SourceTree::GroupedItemsSelected() const
  1061. {
  1062. SourceTreeModel *stm = GetStm();
  1063. QModelIndexList selectedIndices = selectedIndexes();
  1064. OBSScene scene = GetCurrentScene();
  1065. if (!selectedIndices.size()) {
  1066. return false;
  1067. }
  1068. for (auto &idx : selectedIndices) {
  1069. obs_sceneitem_t *item = stm->items[idx.row()];
  1070. obs_scene *itemScene = obs_sceneitem_get_scene(item);
  1071. if (itemScene != scene) {
  1072. return true;
  1073. }
  1074. }
  1075. return false;
  1076. }
  1077. void SourceTree::Remove(OBSSceneItem item)
  1078. {
  1079. OBSBasic *main = reinterpret_cast<OBSBasic*>(App()->GetMainWindow());
  1080. GetStm()->Remove(item);
  1081. main->SaveProject();
  1082. if (!main->SavingDisabled()) {
  1083. obs_scene_t *scene = obs_sceneitem_get_scene(item);
  1084. obs_source_t *sceneSource = obs_scene_get_source(scene);
  1085. obs_source_t *itemSource = obs_sceneitem_get_source(item);
  1086. blog(LOG_INFO, "User Removed source '%s' (%s) from scene '%s'",
  1087. obs_source_get_name(itemSource),
  1088. obs_source_get_id(itemSource),
  1089. obs_source_get_name(sceneSource));
  1090. }
  1091. }
  1092. void SourceTree::GroupSelectedItems()
  1093. {
  1094. QModelIndexList indices = selectedIndexes();
  1095. std::sort(indices.begin(), indices.end());
  1096. GetStm()->GroupSelectedItems(indices);
  1097. }
  1098. void SourceTree::UngroupSelectedGroups()
  1099. {
  1100. QModelIndexList indices = selectedIndexes();
  1101. GetStm()->UngroupSelectedGroups(indices);
  1102. }
  1103. void SourceTree::AddGroup()
  1104. {
  1105. GetStm()->AddGroup();
  1106. }