source-tree.cpp 32 KB

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