#include "SourceTreeModel.hpp" #include #include #include "moc_SourceTreeModel.cpp" static inline OBSScene GetCurrentScene() { OBSBasic *main = OBSBasic::Get(); return main->GetCurrentScene(); } void SourceTreeModel::OBSFrontendEvent(enum obs_frontend_event event, void *ptr) { SourceTreeModel *stm = static_cast(ptr); switch (event) { case OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED: stm->SceneChanged(); break; case OBS_FRONTEND_EVENT_EXIT: stm->Clear(); obs_frontend_remove_event_callback(OBSFrontendEvent, stm); break; case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP: stm->Clear(); break; default: break; } } void SourceTreeModel::Clear() { beginResetModel(); items.clear(); endResetModel(); hasGroups = false; } static bool enumItem(obs_scene_t *, obs_sceneitem_t *item, void *ptr) { QVector &items = *static_cast *>(ptr); obs_source_t *src = obs_sceneitem_get_source(item); if (obs_source_removed(src)) { return true; } if (obs_sceneitem_is_group(item)) { OBSDataAutoRelease data = obs_sceneitem_get_private_settings(item); bool collapse = obs_data_get_bool(data, "collapsed"); if (!collapse) { obs_scene_t *scene = obs_sceneitem_group_get_scene(item); obs_scene_enum_items(scene, enumItem, &items); } } items.insert(0, item); return true; } void SourceTreeModel::SceneChanged() { OBSScene scene = GetCurrentScene(); beginResetModel(); items.clear(); obs_scene_enum_items(scene, enumItem, &items); endResetModel(); UpdateGroupState(false); st->ResetWidgets(); for (int i = 0; i < items.count(); i++) { bool select = obs_sceneitem_selected(items[i]); QModelIndex index = createIndex(i, 0); st->selectionModel()->select(index, select ? QItemSelectionModel::Select : QItemSelectionModel::Deselect); } } /* moves a scene item index (blame linux distros for using older Qt builds) */ static inline void MoveItem(QVector &items, int oldIdx, int newIdx) { OBSSceneItem item = items[oldIdx]; items.remove(oldIdx); items.insert(newIdx, item); } /* reorders list optimally with model reorder funcs */ void SourceTreeModel::ReorderItems() { OBSScene scene = GetCurrentScene(); QVector newitems; obs_scene_enum_items(scene, enumItem, &newitems); /* if item list has changed size, do full reset */ if (newitems.count() != items.count()) { SceneChanged(); return; } for (;;) { int idx1Old = 0; int idx1New = 0; int count; int i; /* find first starting changed item index */ for (i = 0; i < newitems.count(); i++) { obs_sceneitem_t *oldItem = items[i]; obs_sceneitem_t *newItem = newitems[i]; if (oldItem != newItem) { idx1Old = i; break; } } /* if everything is the same, break */ if (i == newitems.count()) { break; } /* find new starting index */ for (i = idx1Old + 1; i < newitems.count(); i++) { obs_sceneitem_t *oldItem = items[idx1Old]; obs_sceneitem_t *newItem = newitems[i]; if (oldItem == newItem) { idx1New = i; break; } } /* if item could not be found, do full reset */ if (i == newitems.count()) { SceneChanged(); return; } /* get move count */ for (count = 1; (idx1New + count) < newitems.count(); count++) { int oldIdx = idx1Old + count; int newIdx = idx1New + count; obs_sceneitem_t *oldItem = items[oldIdx]; obs_sceneitem_t *newItem = newitems[newIdx]; if (oldItem != newItem) { break; } } /* move items */ beginMoveRows(QModelIndex(), idx1Old, idx1Old + count - 1, QModelIndex(), idx1New + count); for (i = 0; i < count; i++) { int to = idx1New + count; if (to > idx1Old) to--; MoveItem(items, idx1Old, to); } endMoveRows(); } } void SourceTreeModel::Add(obs_sceneitem_t *item) { if (obs_sceneitem_is_group(item)) { SceneChanged(); } else { beginInsertRows(QModelIndex(), 0, 0); items.insert(0, item); endInsertRows(); st->UpdateWidget(createIndex(0, 0, nullptr), item); } } void SourceTreeModel::Remove(obs_sceneitem_t *item) { int idx = -1; for (int i = 0; i < items.count(); i++) { if (items[i] == item) { idx = i; break; } } if (idx == -1) return; int startIdx = idx; int endIdx = idx; bool is_group = obs_sceneitem_is_group(item); if (is_group) { obs_scene_t *scene = obs_sceneitem_group_get_scene(item); for (int i = endIdx + 1; i < items.count(); i++) { obs_sceneitem_t *subitem = items[i]; obs_scene_t *subscene = obs_sceneitem_get_scene(subitem); if (subscene == scene) endIdx = i; else break; } } beginRemoveRows(QModelIndex(), startIdx, endIdx); items.remove(idx, endIdx - startIdx + 1); endRemoveRows(); if (is_group) UpdateGroupState(true); OBSBasic::Get()->UpdateContextBarDeferred(); } OBSSceneItem SourceTreeModel::Get(int idx) { if (idx == -1 || idx >= items.count()) return OBSSceneItem(); return items[idx]; } SourceTreeModel::SourceTreeModel(SourceTree *st_) : QAbstractListModel(st_), st(st_) { obs_frontend_add_event_callback(OBSFrontendEvent, this); } int SourceTreeModel::rowCount(const QModelIndex &parent) const { return parent.isValid() ? 0 : items.count(); } QVariant SourceTreeModel::data(const QModelIndex &index, int role) const { if (role == Qt::AccessibleTextRole) { OBSSceneItem item = items[index.row()]; obs_source_t *source = obs_sceneitem_get_source(item); return QVariant(QT_UTF8(obs_source_get_name(source))); } return QVariant(); } Qt::ItemFlags SourceTreeModel::flags(const QModelIndex &index) const { if (!index.isValid()) return QAbstractListModel::flags(index) | Qt::ItemIsDropEnabled; obs_sceneitem_t *item = items[index.row()]; bool is_group = obs_sceneitem_is_group(item); return QAbstractListModel::flags(index) | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | (is_group ? Qt::ItemIsDropEnabled : Qt::NoItemFlags); } Qt::DropActions SourceTreeModel::supportedDropActions() const { return QAbstractItemModel::supportedDropActions() | Qt::MoveAction; } QString SourceTreeModel::GetNewGroupName() { OBSScene scene = GetCurrentScene(); QString name = QTStr("Group"); int i = 2; for (;;) { OBSSourceAutoRelease group = obs_get_source_by_name(QT_TO_UTF8(name)); if (!group) break; name = QTStr("Basic.Main.Group").arg(QString::number(i++)); } return name; } void SourceTreeModel::AddGroup() { QString name = GetNewGroupName(); obs_sceneitem_t *group = obs_scene_add_group(GetCurrentScene(), QT_TO_UTF8(name)); if (!group) return; beginInsertRows(QModelIndex(), 0, 0); items.insert(0, group); endInsertRows(); st->UpdateWidget(createIndex(0, 0, nullptr), group); UpdateGroupState(true); QMetaObject::invokeMethod(st, "Edit", Qt::QueuedConnection, Q_ARG(int, 0)); } void SourceTreeModel::GroupSelectedItems(QModelIndexList &indices) { if (indices.count() == 0) return; OBSBasic *main = OBSBasic::Get(); OBSScene scene = GetCurrentScene(); QString name = GetNewGroupName(); QVector item_order; for (int i = indices.count() - 1; i >= 0; i--) { obs_sceneitem_t *item = items[indices[i].row()]; item_order << item; } st->undoSceneData = main->BackupScene(scene); obs_sceneitem_t *item = obs_scene_insert_group(scene, QT_TO_UTF8(name), item_order.data(), item_order.size()); if (!item) { st->undoSceneData = nullptr; return; } main->undo_s.push_disabled(); for (obs_sceneitem_t *item : item_order) obs_sceneitem_select(item, false); hasGroups = true; st->UpdateWidgets(true); obs_sceneitem_select(item, true); /* ----------------------------------------------------------------- */ /* obs_scene_insert_group triggers a full refresh of scene items via */ /* the item_add signal. No need to insert a row, just edit the one */ /* that's created automatically. */ int newIdx = indices[0].row(); QMetaObject::invokeMethod(st, "NewGroupEdit", Qt::QueuedConnection, Q_ARG(int, newIdx)); } void SourceTreeModel::UngroupSelectedGroups(QModelIndexList &indices) { OBSBasic *main = OBSBasic::Get(); if (indices.count() == 0) return; OBSScene scene = main->GetCurrentScene(); OBSData undoData = main->BackupScene(scene); for (int i = indices.count() - 1; i >= 0; i--) { obs_sceneitem_t *item = items[indices[i].row()]; obs_sceneitem_group_ungroup(item); } SceneChanged(); OBSData redoData = main->BackupScene(scene); main->CreateSceneUndoRedoAction(QTStr("Basic.Main.Ungroup"), undoData, redoData); } void SourceTreeModel::ExpandGroup(obs_sceneitem_t *item) { int itemIdx = items.indexOf(item); if (itemIdx == -1) return; itemIdx++; obs_scene_t *scene = obs_sceneitem_group_get_scene(item); QVector subItems; obs_scene_enum_items(scene, enumItem, &subItems); if (!subItems.size()) return; beginInsertRows(QModelIndex(), itemIdx, itemIdx + subItems.size() - 1); for (int i = 0; i < subItems.size(); i++) items.insert(i + itemIdx, subItems[i]); endInsertRows(); st->UpdateWidgets(); } void SourceTreeModel::CollapseGroup(obs_sceneitem_t *item) { int startIdx = -1; int endIdx = -1; obs_scene_t *scene = obs_sceneitem_group_get_scene(item); for (int i = 0; i < items.size(); i++) { obs_scene_t *itemScene = obs_sceneitem_get_scene(items[i]); if (itemScene == scene) { if (startIdx == -1) startIdx = i; endIdx = i; } } if (startIdx == -1) return; beginRemoveRows(QModelIndex(), startIdx, endIdx); items.remove(startIdx, endIdx - startIdx + 1); endRemoveRows(); } void SourceTreeModel::UpdateGroupState(bool update) { bool nowHasGroups = false; for (auto &item : items) { if (obs_sceneitem_is_group(item)) { nowHasGroups = true; break; } } if (nowHasGroups != hasGroups) { hasGroups = nowHasGroups; if (update) { st->UpdateWidgets(true); } } }