Effectively reverting 4e97b1bb30fa79215f7162f36066336732b59f28, this removes a hack that added additional spacing as scrollbars would go over content due to a Qt bug. This bug appears to be fixed, as the described behavior is no longer observed. As such, the additional spacing is no longer needed (and only looks out of place).
541 lines
15 KiB
C++
541 lines
15 KiB
C++
#include "SourceTreeItem.hpp"
|
|
|
|
#include <components/OBSSourceLabel.hpp>
|
|
#include <widgets/OBSBasic.hpp>
|
|
|
|
#include <qt-wrappers.hpp>
|
|
|
|
#include <QCheckBox>
|
|
#include <QLineEdit>
|
|
#include <QPainter>
|
|
|
|
#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->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));
|
|
|
|
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));
|
|
|
|
label = new OBSSourceLabel(source);
|
|
label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
|
label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
|
label->setAttribute(Qt::WA_TranslucentBackground);
|
|
label->setEnabled(sourceVisible);
|
|
|
|
#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_ = reinterpret_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_ = reinterpret_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_ = reinterpret_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_ = reinterpret_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_ = reinterpret_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_ = reinterpret_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_ = reinterpret_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)) {
|
|
main->CreatePropertiesWindow(source);
|
|
}
|
|
}
|
|
}
|
|
|
|
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");
|
|
#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();
|
|
}
|