source-tree.cpp 33 KB

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