SourceTreeItem.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  1. #include "SourceTreeItem.hpp"
  2. #include <components/OBSSourceLabel.hpp>
  3. #include <widgets/OBSBasic.hpp>
  4. #include <qt-wrappers.hpp>
  5. #include <QCheckBox>
  6. #include <QLineEdit>
  7. #include <QPainter>
  8. #include "moc_SourceTreeItem.cpp"
  9. static inline OBSScene GetCurrentScene()
  10. {
  11. OBSBasic *main = OBSBasic::Get();
  12. return main->GetCurrentScene();
  13. }
  14. SourceTreeItem::SourceTreeItem(SourceTree *tree_, OBSSceneItem sceneitem_) : tree(tree_), sceneitem(sceneitem_)
  15. {
  16. setAttribute(Qt::WA_TranslucentBackground);
  17. setMouseTracking(true);
  18. obs_source_t *source = obs_sceneitem_get_source(sceneitem);
  19. const char *name = obs_source_get_name(source);
  20. OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneitem);
  21. int preset = obs_data_get_int(privData, "color-preset");
  22. if (preset == 1) {
  23. const char *color = obs_data_get_string(privData, "color");
  24. std::string col = "background: ";
  25. col += color;
  26. setStyleSheet(col.c_str());
  27. } else if (preset > 1) {
  28. setStyleSheet("");
  29. setProperty("bgColor", preset - 1);
  30. } else {
  31. setStyleSheet("background: none");
  32. }
  33. OBSBasic *main = OBSBasic::Get();
  34. const char *id = obs_source_get_id(source);
  35. bool sourceVisible = obs_sceneitem_visible(sceneitem);
  36. if (tree->iconsVisible) {
  37. QIcon icon;
  38. if (strcmp(id, "scene") == 0)
  39. icon = main->GetSceneIcon();
  40. else if (strcmp(id, "group") == 0)
  41. icon = main->GetGroupIcon();
  42. else
  43. icon = main->GetSourceIcon(id);
  44. QPixmap pixmap = icon.pixmap(QSize(16, 16));
  45. iconLabel = new QLabel();
  46. iconLabel->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
  47. iconLabel->setPixmap(pixmap);
  48. iconLabel->setEnabled(sourceVisible);
  49. iconLabel->setStyleSheet("background: none");
  50. iconLabel->setProperty("class", "source-icon");
  51. }
  52. vis = new QCheckBox();
  53. vis->setProperty("class", "checkbox-icon indicator-visibility");
  54. vis->setChecked(sourceVisible);
  55. vis->setAccessibleName(QTStr("Basic.Main.Sources.Visibility"));
  56. vis->setAccessibleDescription(QTStr("Basic.Main.Sources.VisibilityDescription").arg(name));
  57. vis->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
  58. lock = new QCheckBox();
  59. lock->setProperty("class", "checkbox-icon indicator-lock");
  60. lock->setChecked(obs_sceneitem_locked(sceneitem));
  61. lock->setAccessibleName(QTStr("Basic.Main.Sources.Lock"));
  62. lock->setAccessibleDescription(QTStr("Basic.Main.Sources.LockDescription").arg(name));
  63. lock->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
  64. label = new OBSSourceLabel(source);
  65. label->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
  66. label->setAttribute(Qt::WA_TranslucentBackground);
  67. label->setEnabled(sourceVisible);
  68. #ifdef __APPLE__
  69. vis->setAttribute(Qt::WA_LayoutUsesWidgetRect);
  70. lock->setAttribute(Qt::WA_LayoutUsesWidgetRect);
  71. #endif
  72. boxLayout = new QHBoxLayout();
  73. boxLayout->setContentsMargins(0, 0, 0, 0);
  74. boxLayout->setSpacing(0);
  75. if (iconLabel) {
  76. boxLayout->addWidget(iconLabel);
  77. boxLayout->addSpacing(2);
  78. }
  79. boxLayout->addWidget(label);
  80. boxLayout->addWidget(vis);
  81. boxLayout->addWidget(lock);
  82. Update(false);
  83. setLayout(boxLayout);
  84. /* --------------------------------------------------------- */
  85. auto setItemVisible = [this](bool val) {
  86. obs_scene_t *scene = obs_sceneitem_get_scene(sceneitem);
  87. obs_source_t *scenesource = obs_scene_get_source(scene);
  88. int64_t id = obs_sceneitem_get_id(sceneitem);
  89. const char *name = obs_source_get_name(scenesource);
  90. const char *uuid = obs_source_get_uuid(scenesource);
  91. obs_source_t *source = obs_sceneitem_get_source(sceneitem);
  92. auto undo_redo = [](const std::string &uuid, int64_t id, bool val) {
  93. OBSSourceAutoRelease s = obs_get_source_by_uuid(uuid.c_str());
  94. obs_scene_t *sc = obs_group_or_scene_from_source(s);
  95. obs_sceneitem_t *si = obs_scene_find_sceneitem_by_id(sc, id);
  96. if (si)
  97. obs_sceneitem_set_visible(si, val);
  98. };
  99. QString str = QTStr(val ? "Undo.ShowSceneItem" : "Undo.HideSceneItem");
  100. OBSBasic *main = OBSBasic::Get();
  101. main->undo_s.add_action(str.arg(obs_source_get_name(source), name),
  102. std::bind(undo_redo, std::placeholders::_1, id, !val),
  103. std::bind(undo_redo, std::placeholders::_1, id, val), uuid, uuid);
  104. QSignalBlocker sourcesSignalBlocker(this);
  105. obs_sceneitem_set_visible(sceneitem, val);
  106. };
  107. auto setItemLocked = [this](bool checked) {
  108. QSignalBlocker sourcesSignalBlocker(this);
  109. obs_sceneitem_set_locked(sceneitem, checked);
  110. };
  111. connect(vis, &QAbstractButton::clicked, setItemVisible);
  112. connect(lock, &QAbstractButton::clicked, setItemLocked);
  113. }
  114. void SourceTreeItem::paintEvent(QPaintEvent *event)
  115. {
  116. QStyleOption opt;
  117. opt.initFrom(this);
  118. QPainter p(this);
  119. style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
  120. QWidget::paintEvent(event);
  121. }
  122. void SourceTreeItem::DisconnectSignals()
  123. {
  124. sigs.clear();
  125. }
  126. void SourceTreeItem::Clear()
  127. {
  128. DisconnectSignals();
  129. sceneitem = nullptr;
  130. }
  131. void SourceTreeItem::ReconnectSignals()
  132. {
  133. if (!sceneitem)
  134. return;
  135. DisconnectSignals();
  136. /* --------------------------------------------------------- */
  137. auto removeItem = [](void *data, calldata_t *cd) {
  138. SourceTreeItem *this_ = static_cast<SourceTreeItem *>(data);
  139. obs_sceneitem_t *curItem = (obs_sceneitem_t *)calldata_ptr(cd, "item");
  140. obs_scene_t *curScene = (obs_scene_t *)calldata_ptr(cd, "scene");
  141. if (curItem == this_->sceneitem) {
  142. QMetaObject::invokeMethod(this_->tree, "Remove", Q_ARG(OBSSceneItem, curItem),
  143. Q_ARG(OBSScene, curScene));
  144. curItem = nullptr;
  145. }
  146. if (!curItem)
  147. QMetaObject::invokeMethod(this_, "Clear");
  148. };
  149. auto itemVisible = [](void *data, calldata_t *cd) {
  150. SourceTreeItem *this_ = static_cast<SourceTreeItem *>(data);
  151. obs_sceneitem_t *curItem = (obs_sceneitem_t *)calldata_ptr(cd, "item");
  152. bool visible = calldata_bool(cd, "visible");
  153. if (curItem == this_->sceneitem)
  154. QMetaObject::invokeMethod(this_, "VisibilityChanged", Q_ARG(bool, visible));
  155. };
  156. auto itemLocked = [](void *data, calldata_t *cd) {
  157. SourceTreeItem *this_ = static_cast<SourceTreeItem *>(data);
  158. obs_sceneitem_t *curItem = (obs_sceneitem_t *)calldata_ptr(cd, "item");
  159. bool locked = calldata_bool(cd, "locked");
  160. if (curItem == this_->sceneitem)
  161. QMetaObject::invokeMethod(this_, "LockedChanged", Q_ARG(bool, locked));
  162. };
  163. auto itemSelect = [](void *data, calldata_t *cd) {
  164. SourceTreeItem *this_ = static_cast<SourceTreeItem *>(data);
  165. obs_sceneitem_t *curItem = (obs_sceneitem_t *)calldata_ptr(cd, "item");
  166. if (curItem == this_->sceneitem)
  167. QMetaObject::invokeMethod(this_, "Select");
  168. };
  169. auto itemDeselect = [](void *data, calldata_t *cd) {
  170. SourceTreeItem *this_ = static_cast<SourceTreeItem *>(data);
  171. obs_sceneitem_t *curItem = (obs_sceneitem_t *)calldata_ptr(cd, "item");
  172. if (curItem == this_->sceneitem)
  173. QMetaObject::invokeMethod(this_, "Deselect");
  174. };
  175. auto reorderGroup = [](void *data, calldata_t *) {
  176. SourceTreeItem *this_ = static_cast<SourceTreeItem *>(data);
  177. QMetaObject::invokeMethod(this_->tree, "ReorderItems");
  178. };
  179. obs_scene_t *scene = obs_sceneitem_get_scene(sceneitem);
  180. obs_source_t *sceneSource = obs_scene_get_source(scene);
  181. signal_handler_t *signal = obs_source_get_signal_handler(sceneSource);
  182. sigs.emplace_back(signal, "remove", removeItem, this);
  183. sigs.emplace_back(signal, "item_remove", removeItem, this);
  184. sigs.emplace_back(signal, "item_visible", itemVisible, this);
  185. sigs.emplace_back(signal, "item_locked", itemLocked, this);
  186. sigs.emplace_back(signal, "item_select", itemSelect, this);
  187. sigs.emplace_back(signal, "item_deselect", itemDeselect, this);
  188. if (obs_sceneitem_is_group(sceneitem)) {
  189. obs_source_t *source = obs_sceneitem_get_source(sceneitem);
  190. signal = obs_source_get_signal_handler(source);
  191. sigs.emplace_back(signal, "reorder", reorderGroup, this);
  192. }
  193. /* --------------------------------------------------------- */
  194. auto removeSource = [](void *data, calldata_t *) {
  195. SourceTreeItem *this_ = static_cast<SourceTreeItem *>(data);
  196. this_->DisconnectSignals();
  197. this_->sceneitem = nullptr;
  198. QMetaObject::invokeMethod(this_->tree, "RefreshItems");
  199. };
  200. obs_source_t *source = obs_sceneitem_get_source(sceneitem);
  201. signal = obs_source_get_signal_handler(source);
  202. sigs.emplace_back(signal, "remove", removeSource, this);
  203. }
  204. void SourceTreeItem::mouseDoubleClickEvent(QMouseEvent *event)
  205. {
  206. QWidget::mouseDoubleClickEvent(event);
  207. if (expand) {
  208. expand->setChecked(!expand->isChecked());
  209. } else {
  210. obs_source_t *source = obs_sceneitem_get_source(sceneitem);
  211. OBSBasic *main = OBSBasic::Get();
  212. if (obs_source_configurable(source)) {
  213. main->CreatePropertiesWindow(source);
  214. }
  215. }
  216. }
  217. void SourceTreeItem::enterEvent(QEnterEvent *event)
  218. {
  219. QWidget::enterEvent(event);
  220. OBSBasicPreview *preview = OBSBasicPreview::Get();
  221. std::lock_guard<std::mutex> lock(preview->selectMutex);
  222. preview->hoveredPreviewItems.clear();
  223. preview->hoveredPreviewItems.push_back(sceneitem);
  224. }
  225. void SourceTreeItem::leaveEvent(QEvent *event)
  226. {
  227. QWidget::leaveEvent(event);
  228. OBSBasicPreview *preview = OBSBasicPreview::Get();
  229. std::lock_guard<std::mutex> lock(preview->selectMutex);
  230. preview->hoveredPreviewItems.clear();
  231. }
  232. bool SourceTreeItem::IsEditing()
  233. {
  234. return editor != nullptr;
  235. }
  236. void SourceTreeItem::EnterEditMode()
  237. {
  238. setFocusPolicy(Qt::StrongFocus);
  239. int index = boxLayout->indexOf(label);
  240. boxLayout->removeWidget(label);
  241. editor = new QLineEdit(label->text());
  242. editor->setStyleSheet("background: none");
  243. editor->selectAll();
  244. editor->installEventFilter(this);
  245. boxLayout->insertWidget(index, editor);
  246. setFocusProxy(editor);
  247. }
  248. void SourceTreeItem::ExitEditMode(bool save)
  249. {
  250. ExitEditModeInternal(save);
  251. if (tree->undoSceneData) {
  252. OBSBasic *main = OBSBasic::Get();
  253. main->undo_s.pop_disabled();
  254. OBSData redoSceneData = main->BackupScene(GetCurrentScene());
  255. QString text = QTStr("Undo.GroupItems").arg(newName.c_str());
  256. main->CreateSceneUndoRedoAction(text, tree->undoSceneData, redoSceneData);
  257. tree->undoSceneData = nullptr;
  258. }
  259. }
  260. void SourceTreeItem::ExitEditModeInternal(bool save)
  261. {
  262. if (!editor) {
  263. return;
  264. }
  265. OBSBasic *main = OBSBasic::Get();
  266. OBSScene scene = main->GetCurrentScene();
  267. newName = QT_TO_UTF8(editor->text());
  268. setFocusProxy(nullptr);
  269. int index = boxLayout->indexOf(editor);
  270. boxLayout->removeWidget(editor);
  271. delete editor;
  272. editor = nullptr;
  273. setFocusPolicy(Qt::NoFocus);
  274. boxLayout->insertWidget(index, label);
  275. setFocus();
  276. /* ----------------------------------------- */
  277. /* check for empty string */
  278. if (!save)
  279. return;
  280. if (newName.empty()) {
  281. OBSMessageBox::information(main, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text"));
  282. return;
  283. }
  284. /* ----------------------------------------- */
  285. /* Check for same name */
  286. obs_source_t *source = obs_sceneitem_get_source(sceneitem);
  287. if (newName == obs_source_get_name(source))
  288. return;
  289. /* ----------------------------------------- */
  290. /* check for existing source */
  291. OBSSourceAutoRelease existingSource = obs_get_source_by_name(newName.c_str());
  292. bool exists = !!existingSource;
  293. if (exists) {
  294. OBSMessageBox::information(main, QTStr("NameExists.Title"), QTStr("NameExists.Text"));
  295. return;
  296. }
  297. /* ----------------------------------------- */
  298. /* rename */
  299. QSignalBlocker sourcesSignalBlocker(this);
  300. std::string prevName(obs_source_get_name(source));
  301. std::string scene_uuid = obs_source_get_uuid(main->GetCurrentSceneSource());
  302. auto undo = [scene_uuid, prevName, main](const std::string &data) {
  303. OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str());
  304. obs_source_set_name(source, prevName.c_str());
  305. OBSSourceAutoRelease scene_source = obs_get_source_by_uuid(scene_uuid.c_str());
  306. main->SetCurrentScene(scene_source.Get(), true);
  307. };
  308. std::string editedName = newName;
  309. auto redo = [scene_uuid, main, editedName](const std::string &data) {
  310. OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str());
  311. obs_source_set_name(source, editedName.c_str());
  312. OBSSourceAutoRelease scene_source = obs_get_source_by_uuid(scene_uuid.c_str());
  313. main->SetCurrentScene(scene_source.Get(), true);
  314. };
  315. const char *uuid = obs_source_get_uuid(source);
  316. main->undo_s.add_action(QTStr("Undo.Rename").arg(newName.c_str()), undo, redo, uuid, uuid);
  317. obs_source_set_name(source, newName.c_str());
  318. }
  319. bool SourceTreeItem::eventFilter(QObject *object, QEvent *event)
  320. {
  321. if (editor != object)
  322. return false;
  323. if (LineEditCanceled(event)) {
  324. QMetaObject::invokeMethod(this, "ExitEditMode", Qt::QueuedConnection, Q_ARG(bool, false));
  325. return true;
  326. }
  327. if (LineEditChanged(event)) {
  328. QMetaObject::invokeMethod(this, "ExitEditMode", Qt::QueuedConnection, Q_ARG(bool, true));
  329. return true;
  330. }
  331. return false;
  332. }
  333. void SourceTreeItem::VisibilityChanged(bool visible)
  334. {
  335. if (iconLabel) {
  336. iconLabel->setEnabled(visible);
  337. }
  338. label->setEnabled(visible);
  339. vis->setChecked(visible);
  340. }
  341. void SourceTreeItem::LockedChanged(bool locked)
  342. {
  343. lock->setChecked(locked);
  344. OBSBasic::Get()->UpdateEditMenu();
  345. }
  346. void SourceTreeItem::Update(bool force)
  347. {
  348. OBSScene scene = GetCurrentScene();
  349. obs_scene_t *itemScene = obs_sceneitem_get_scene(sceneitem);
  350. Type newType;
  351. /* ------------------------------------------------- */
  352. /* if it's a group item, insert group checkbox */
  353. if (obs_sceneitem_is_group(sceneitem)) {
  354. newType = Type::Group;
  355. /* ------------------------------------------------- */
  356. /* if it's a group sub-item */
  357. } else if (itemScene != scene) {
  358. newType = Type::SubItem;
  359. /* ------------------------------------------------- */
  360. /* if it's a regular item */
  361. } else {
  362. newType = Type::Item;
  363. }
  364. /* ------------------------------------------------- */
  365. if (!force && newType == type) {
  366. return;
  367. }
  368. /* ------------------------------------------------- */
  369. ReconnectSignals();
  370. if (spacer) {
  371. boxLayout->removeItem(spacer);
  372. delete spacer;
  373. spacer = nullptr;
  374. }
  375. if (type == Type::Group) {
  376. boxLayout->removeWidget(expand);
  377. expand->deleteLater();
  378. expand = nullptr;
  379. }
  380. type = newType;
  381. if (type == Type::SubItem) {
  382. spacer = new QSpacerItem(16, 1);
  383. boxLayout->insertItem(0, spacer);
  384. } else if (type == Type::Group) {
  385. expand = new QCheckBox();
  386. expand->setProperty("class", "checkbox-icon indicator-expand");
  387. #ifdef __APPLE__
  388. expand->setAttribute(Qt::WA_LayoutUsesWidgetRect);
  389. #endif
  390. boxLayout->insertWidget(0, expand);
  391. OBSDataAutoRelease data = obs_sceneitem_get_private_settings(sceneitem);
  392. expand->blockSignals(true);
  393. expand->setChecked(obs_data_get_bool(data, "collapsed"));
  394. expand->blockSignals(false);
  395. connect(expand, &QPushButton::toggled, this, &SourceTreeItem::ExpandClicked);
  396. } else {
  397. spacer = new QSpacerItem(3, 1);
  398. boxLayout->insertItem(0, spacer);
  399. }
  400. }
  401. void SourceTreeItem::ExpandClicked(bool checked)
  402. {
  403. OBSDataAutoRelease data = obs_sceneitem_get_private_settings(sceneitem);
  404. obs_data_set_bool(data, "collapsed", checked);
  405. if (!checked)
  406. tree->GetStm()->ExpandGroup(sceneitem);
  407. else
  408. tree->GetStm()->CollapseGroup(sceneitem);
  409. }
  410. void SourceTreeItem::Select()
  411. {
  412. tree->SelectItem(sceneitem, true);
  413. OBSBasic::Get()->UpdateContextBarDeferred();
  414. OBSBasic::Get()->UpdateEditMenu();
  415. }
  416. void SourceTreeItem::Deselect()
  417. {
  418. tree->SelectItem(sceneitem, false);
  419. OBSBasic::Get()->UpdateContextBarDeferred();
  420. OBSBasic::Get()->UpdateEditMenu();
  421. }