SourceTreeItem.cpp 15 KB

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