123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575 |
- #include "SourceTreeItem.hpp"
- #include <components/OBSSourceLabel.hpp>
- #include <widgets/OBSBasic.hpp>
- #include <qt-wrappers.hpp>
- #include <QCheckBox>
- #include <QLineEdit>
- #include <QPainter>
- #include "plugin-manager/PluginManager.hpp"
- #ifdef _WIN32
- #define WIN32_LEAN_AND_MEAN
- #include <windows.h>
- #endif
- #include "moc_SourceTreeItem.cpp"
- static inline OBSScene GetCurrentScene()
- {
- OBSBasic *main = OBSBasic::Get();
- return main->GetCurrentScene();
- }
- SourceTreeItem::SourceTreeItem(SourceTree *tree_, OBSSceneItem sceneitem_) : tree(tree_), sceneitem(sceneitem_)
- {
- setAttribute(Qt::WA_TranslucentBackground);
- setMouseTracking(true);
- obs_source_t *source = obs_sceneitem_get_source(sceneitem);
- const char *name = obs_source_get_name(source);
- OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneitem);
- int preset = obs_data_get_int(privData, "color-preset");
- if (preset == 1) {
- const char *color = obs_data_get_string(privData, "color");
- std::string col = "background: ";
- col += color;
- setStyleSheet(col.c_str());
- } else if (preset > 1) {
- setStyleSheet("");
- setProperty("bgColor", preset - 1);
- } else {
- setStyleSheet("background: none");
- }
- OBSBasic *main = OBSBasic::Get();
- const char *id = obs_source_get_id(source);
- bool sourceVisible = obs_sceneitem_visible(sceneitem);
- if (tree->iconsVisible) {
- QIcon icon;
- if (strcmp(id, "scene") == 0)
- icon = main->GetSceneIcon();
- else if (strcmp(id, "group") == 0)
- icon = main->GetGroupIcon();
- else
- icon = main->GetSourceIcon(id);
- QPixmap pixmap = icon.pixmap(QSize(16, 16));
- iconLabel = new QLabel();
- iconLabel->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
- iconLabel->setPixmap(pixmap);
- iconLabel->setEnabled(sourceVisible);
- iconLabel->setStyleSheet("background: none");
- iconLabel->setProperty("class", "source-icon");
- }
- vis = new QCheckBox();
- vis->setProperty("class", "checkbox-icon indicator-visibility");
- vis->setChecked(sourceVisible);
- vis->setAccessibleName(QTStr("Basic.Main.Sources.Visibility"));
- vis->setAccessibleDescription(QTStr("Basic.Main.Sources.VisibilityDescription").arg(name));
- vis->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
- lock = new QCheckBox();
- lock->setProperty("class", "checkbox-icon indicator-lock");
- lock->setChecked(obs_sceneitem_locked(sceneitem));
- lock->setAccessibleName(QTStr("Basic.Main.Sources.Lock"));
- lock->setAccessibleDescription(QTStr("Basic.Main.Sources.LockDescription").arg(name));
- lock->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
- label = new OBSSourceLabel(source);
- label->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
- label->setAttribute(Qt::WA_TranslucentBackground);
- label->setEnabled(sourceVisible);
- const char *sourceId = obs_source_get_unversioned_id(source);
- switch (obs_source_load_state(sourceId)) {
- case OBS_MODULE_DISABLED:
- case OBS_MODULE_MISSING:
- label->setStyleSheet("QLabel {color: #CC0000;}");
- break;
- default:
- break;
- }
- #ifdef __APPLE__
- vis->setAttribute(Qt::WA_LayoutUsesWidgetRect);
- lock->setAttribute(Qt::WA_LayoutUsesWidgetRect);
- #endif
- boxLayout = new QHBoxLayout();
- boxLayout->setContentsMargins(0, 0, 0, 0);
- boxLayout->setSpacing(0);
- if (iconLabel) {
- boxLayout->addWidget(iconLabel);
- boxLayout->addSpacing(2);
- }
- boxLayout->addWidget(label);
- boxLayout->addWidget(vis);
- boxLayout->addWidget(lock);
- Update(false);
- setLayout(boxLayout);
- /* --------------------------------------------------------- */
- auto setItemVisible = [this](bool val) {
- obs_scene_t *scene = obs_sceneitem_get_scene(sceneitem);
- obs_source_t *scenesource = obs_scene_get_source(scene);
- int64_t id = obs_sceneitem_get_id(sceneitem);
- const char *name = obs_source_get_name(scenesource);
- const char *uuid = obs_source_get_uuid(scenesource);
- obs_source_t *source = obs_sceneitem_get_source(sceneitem);
- auto undo_redo = [](const std::string &uuid, int64_t id, bool val) {
- OBSSourceAutoRelease s = obs_get_source_by_uuid(uuid.c_str());
- obs_scene_t *sc = obs_group_or_scene_from_source(s);
- obs_sceneitem_t *si = obs_scene_find_sceneitem_by_id(sc, id);
- if (si)
- obs_sceneitem_set_visible(si, val);
- };
- QString str = QTStr(val ? "Undo.ShowSceneItem" : "Undo.HideSceneItem");
- OBSBasic *main = OBSBasic::Get();
- main->undo_s.add_action(str.arg(obs_source_get_name(source), name),
- std::bind(undo_redo, std::placeholders::_1, id, !val),
- std::bind(undo_redo, std::placeholders::_1, id, val), uuid, uuid);
- QSignalBlocker sourcesSignalBlocker(this);
- obs_sceneitem_set_visible(sceneitem, val);
- };
- auto setItemLocked = [this](bool checked) {
- QSignalBlocker sourcesSignalBlocker(this);
- obs_sceneitem_set_locked(sceneitem, checked);
- };
- connect(vis, &QAbstractButton::clicked, setItemVisible);
- connect(lock, &QAbstractButton::clicked, setItemLocked);
- }
- void SourceTreeItem::paintEvent(QPaintEvent *event)
- {
- QStyleOption opt;
- opt.initFrom(this);
- QPainter p(this);
- style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
- QWidget::paintEvent(event);
- }
- void SourceTreeItem::DisconnectSignals()
- {
- sigs.clear();
- }
- void SourceTreeItem::Clear()
- {
- DisconnectSignals();
- sceneitem = nullptr;
- }
- void SourceTreeItem::ReconnectSignals()
- {
- if (!sceneitem)
- return;
- DisconnectSignals();
- /* --------------------------------------------------------- */
- auto removeItem = [](void *data, calldata_t *cd) {
- SourceTreeItem *this_ = static_cast<SourceTreeItem *>(data);
- obs_sceneitem_t *curItem = (obs_sceneitem_t *)calldata_ptr(cd, "item");
- obs_scene_t *curScene = (obs_scene_t *)calldata_ptr(cd, "scene");
- if (curItem == this_->sceneitem) {
- QMetaObject::invokeMethod(this_->tree, "Remove", Q_ARG(OBSSceneItem, curItem),
- Q_ARG(OBSScene, curScene));
- curItem = nullptr;
- }
- if (!curItem)
- QMetaObject::invokeMethod(this_, "Clear");
- };
- auto itemVisible = [](void *data, calldata_t *cd) {
- SourceTreeItem *this_ = static_cast<SourceTreeItem *>(data);
- obs_sceneitem_t *curItem = (obs_sceneitem_t *)calldata_ptr(cd, "item");
- bool visible = calldata_bool(cd, "visible");
- if (curItem == this_->sceneitem)
- QMetaObject::invokeMethod(this_, "VisibilityChanged", Q_ARG(bool, visible));
- };
- auto itemLocked = [](void *data, calldata_t *cd) {
- SourceTreeItem *this_ = static_cast<SourceTreeItem *>(data);
- obs_sceneitem_t *curItem = (obs_sceneitem_t *)calldata_ptr(cd, "item");
- bool locked = calldata_bool(cd, "locked");
- if (curItem == this_->sceneitem)
- QMetaObject::invokeMethod(this_, "LockedChanged", Q_ARG(bool, locked));
- };
- auto itemSelect = [](void *data, calldata_t *cd) {
- SourceTreeItem *this_ = static_cast<SourceTreeItem *>(data);
- obs_sceneitem_t *curItem = (obs_sceneitem_t *)calldata_ptr(cd, "item");
- if (curItem == this_->sceneitem)
- QMetaObject::invokeMethod(this_, "Select");
- };
- auto itemDeselect = [](void *data, calldata_t *cd) {
- SourceTreeItem *this_ = static_cast<SourceTreeItem *>(data);
- obs_sceneitem_t *curItem = (obs_sceneitem_t *)calldata_ptr(cd, "item");
- if (curItem == this_->sceneitem)
- QMetaObject::invokeMethod(this_, "Deselect");
- };
- auto reorderGroup = [](void *data, calldata_t *) {
- SourceTreeItem *this_ = static_cast<SourceTreeItem *>(data);
- QMetaObject::invokeMethod(this_->tree, "ReorderItems");
- };
- obs_scene_t *scene = obs_sceneitem_get_scene(sceneitem);
- obs_source_t *sceneSource = obs_scene_get_source(scene);
- signal_handler_t *signal = obs_source_get_signal_handler(sceneSource);
- sigs.emplace_back(signal, "remove", removeItem, this);
- sigs.emplace_back(signal, "item_remove", removeItem, this);
- sigs.emplace_back(signal, "item_visible", itemVisible, this);
- sigs.emplace_back(signal, "item_locked", itemLocked, this);
- sigs.emplace_back(signal, "item_select", itemSelect, this);
- sigs.emplace_back(signal, "item_deselect", itemDeselect, this);
- if (obs_sceneitem_is_group(sceneitem)) {
- obs_source_t *source = obs_sceneitem_get_source(sceneitem);
- signal = obs_source_get_signal_handler(source);
- sigs.emplace_back(signal, "reorder", reorderGroup, this);
- }
- /* --------------------------------------------------------- */
- auto removeSource = [](void *data, calldata_t *) {
- SourceTreeItem *this_ = static_cast<SourceTreeItem *>(data);
- this_->DisconnectSignals();
- this_->sceneitem = nullptr;
- QMetaObject::invokeMethod(this_->tree, "RefreshItems");
- };
- obs_source_t *source = obs_sceneitem_get_source(sceneitem);
- signal = obs_source_get_signal_handler(source);
- sigs.emplace_back(signal, "remove", removeSource, this);
- }
- void SourceTreeItem::mouseDoubleClickEvent(QMouseEvent *event)
- {
- QWidget::mouseDoubleClickEvent(event);
- if (expand) {
- expand->setChecked(!expand->isChecked());
- } else {
- obs_source_t *source = obs_sceneitem_get_source(sceneitem);
- OBSBasic *main = OBSBasic::Get();
- if (obs_source_configurable(source)) {
- #if defined(_WIN32)
- /* This timer works around a bug introduced around Qt 6.8.3 that causes
- * the application to hang when double clicking the sources list and the
- * Windows setting 'Snap mouse to default button in dialog boxes' is enabled.
- */
- bool snapEnabled;
- SystemParametersInfo(SPI_GETSNAPTODEFBUTTON, 0, &snapEnabled, 0);
- if (snapEnabled) {
- QTimer::singleShot(200, this, [=]() { main->CreatePropertiesWindow(source); });
- } else {
- main->CreatePropertiesWindow(source);
- }
- #else
- main->CreatePropertiesWindow(source);
- #endif
- }
- }
- }
- void SourceTreeItem::enterEvent(QEnterEvent *event)
- {
- QWidget::enterEvent(event);
- OBSBasicPreview *preview = OBSBasicPreview::Get();
- std::lock_guard<std::mutex> lock(preview->selectMutex);
- preview->hoveredPreviewItems.clear();
- preview->hoveredPreviewItems.push_back(sceneitem);
- }
- void SourceTreeItem::leaveEvent(QEvent *event)
- {
- QWidget::leaveEvent(event);
- OBSBasicPreview *preview = OBSBasicPreview::Get();
- std::lock_guard<std::mutex> lock(preview->selectMutex);
- preview->hoveredPreviewItems.clear();
- }
- bool SourceTreeItem::IsEditing()
- {
- return editor != nullptr;
- }
- void SourceTreeItem::EnterEditMode()
- {
- setFocusPolicy(Qt::StrongFocus);
- int index = boxLayout->indexOf(label);
- boxLayout->removeWidget(label);
- editor = new QLineEdit(label->text());
- editor->setStyleSheet("background: none");
- editor->selectAll();
- editor->installEventFilter(this);
- boxLayout->insertWidget(index, editor);
- setFocusProxy(editor);
- }
- void SourceTreeItem::ExitEditMode(bool save)
- {
- ExitEditModeInternal(save);
- if (tree->undoSceneData) {
- OBSBasic *main = OBSBasic::Get();
- main->undo_s.pop_disabled();
- OBSData redoSceneData = main->BackupScene(GetCurrentScene());
- QString text = QTStr("Undo.GroupItems").arg(newName.c_str());
- main->CreateSceneUndoRedoAction(text, tree->undoSceneData, redoSceneData);
- tree->undoSceneData = nullptr;
- }
- }
- void SourceTreeItem::ExitEditModeInternal(bool save)
- {
- if (!editor) {
- return;
- }
- OBSBasic *main = OBSBasic::Get();
- OBSScene scene = main->GetCurrentScene();
- newName = QT_TO_UTF8(editor->text());
- setFocusProxy(nullptr);
- int index = boxLayout->indexOf(editor);
- boxLayout->removeWidget(editor);
- delete editor;
- editor = nullptr;
- setFocusPolicy(Qt::NoFocus);
- boxLayout->insertWidget(index, label);
- setFocus();
- /* ----------------------------------------- */
- /* check for empty string */
- if (!save)
- return;
- if (newName.empty()) {
- OBSMessageBox::information(main, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text"));
- return;
- }
- /* ----------------------------------------- */
- /* Check for same name */
- obs_source_t *source = obs_sceneitem_get_source(sceneitem);
- if (newName == obs_source_get_name(source))
- return;
- /* ----------------------------------------- */
- /* check for existing source */
- OBSSourceAutoRelease existingSource = obs_get_source_by_name(newName.c_str());
- bool exists = !!existingSource;
- if (exists) {
- OBSMessageBox::information(main, QTStr("NameExists.Title"), QTStr("NameExists.Text"));
- return;
- }
- /* ----------------------------------------- */
- /* rename */
- QSignalBlocker sourcesSignalBlocker(this);
- std::string prevName(obs_source_get_name(source));
- std::string scene_uuid = obs_source_get_uuid(main->GetCurrentSceneSource());
- auto undo = [scene_uuid, prevName, main](const std::string &data) {
- OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str());
- obs_source_set_name(source, prevName.c_str());
- OBSSourceAutoRelease scene_source = obs_get_source_by_uuid(scene_uuid.c_str());
- main->SetCurrentScene(scene_source.Get(), true);
- };
- std::string editedName = newName;
- auto redo = [scene_uuid, main, editedName](const std::string &data) {
- OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str());
- obs_source_set_name(source, editedName.c_str());
- OBSSourceAutoRelease scene_source = obs_get_source_by_uuid(scene_uuid.c_str());
- main->SetCurrentScene(scene_source.Get(), true);
- };
- const char *uuid = obs_source_get_uuid(source);
- main->undo_s.add_action(QTStr("Undo.Rename").arg(newName.c_str()), undo, redo, uuid, uuid);
- obs_source_set_name(source, newName.c_str());
- }
- bool SourceTreeItem::eventFilter(QObject *object, QEvent *event)
- {
- if (editor != object)
- return false;
- if (LineEditCanceled(event)) {
- QMetaObject::invokeMethod(this, "ExitEditMode", Qt::QueuedConnection, Q_ARG(bool, false));
- return true;
- }
- if (LineEditChanged(event)) {
- QMetaObject::invokeMethod(this, "ExitEditMode", Qt::QueuedConnection, Q_ARG(bool, true));
- return true;
- }
- return false;
- }
- void SourceTreeItem::VisibilityChanged(bool visible)
- {
- if (iconLabel) {
- iconLabel->setEnabled(visible);
- }
- label->setEnabled(visible);
- vis->setChecked(visible);
- }
- void SourceTreeItem::LockedChanged(bool locked)
- {
- lock->setChecked(locked);
- OBSBasic::Get()->UpdateEditMenu();
- }
- void SourceTreeItem::Update(bool force)
- {
- OBSScene scene = GetCurrentScene();
- obs_scene_t *itemScene = obs_sceneitem_get_scene(sceneitem);
- Type newType;
- /* ------------------------------------------------- */
- /* if it's a group item, insert group checkbox */
- if (obs_sceneitem_is_group(sceneitem)) {
- newType = Type::Group;
- /* ------------------------------------------------- */
- /* if it's a group sub-item */
- } else if (itemScene != scene) {
- newType = Type::SubItem;
- /* ------------------------------------------------- */
- /* if it's a regular item */
- } else {
- newType = Type::Item;
- }
- /* ------------------------------------------------- */
- if (!force && newType == type) {
- return;
- }
- /* ------------------------------------------------- */
- ReconnectSignals();
- if (spacer) {
- boxLayout->removeItem(spacer);
- delete spacer;
- spacer = nullptr;
- }
- if (type == Type::Group) {
- boxLayout->removeWidget(expand);
- expand->deleteLater();
- expand = nullptr;
- }
- type = newType;
- if (type == Type::SubItem) {
- spacer = new QSpacerItem(16, 1);
- boxLayout->insertItem(0, spacer);
- } else if (type == Type::Group) {
- expand = new QCheckBox();
- expand->setProperty("class", "checkbox-icon indicator-expand");
- expand->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
- #ifdef __APPLE__
- expand->setAttribute(Qt::WA_LayoutUsesWidgetRect);
- #endif
- boxLayout->insertWidget(0, expand);
- OBSDataAutoRelease data = obs_sceneitem_get_private_settings(sceneitem);
- expand->blockSignals(true);
- expand->setChecked(obs_data_get_bool(data, "collapsed"));
- expand->blockSignals(false);
- connect(expand, &QPushButton::toggled, this, &SourceTreeItem::ExpandClicked);
- } else {
- spacer = new QSpacerItem(3, 1);
- boxLayout->insertItem(0, spacer);
- }
- }
- void SourceTreeItem::ExpandClicked(bool checked)
- {
- OBSDataAutoRelease data = obs_sceneitem_get_private_settings(sceneitem);
- obs_data_set_bool(data, "collapsed", checked);
- if (!checked)
- tree->GetStm()->ExpandGroup(sceneitem);
- else
- tree->GetStm()->CollapseGroup(sceneitem);
- }
- void SourceTreeItem::Select()
- {
- tree->SelectItem(sceneitem, true);
- OBSBasic::Get()->UpdateContextBarDeferred();
- OBSBasic::Get()->UpdateEditMenu();
- }
- void SourceTreeItem::Deselect()
- {
- tree->SelectItem(sceneitem, false);
- OBSBasic::Get()->UpdateContextBarDeferred();
- OBSBasic::Get()->UpdateEditMenu();
- }
|