1
0

SourceTreeItem.cpp 16 KB

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