435 lines
9.7 KiB
C++
435 lines
9.7 KiB
C++
#include "SourceTreeModel.hpp"
|
|
|
|
#include <widgets/OBSBasic.hpp>
|
|
|
|
#include <qt-wrappers.hpp>
|
|
|
|
#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<SourceTreeModel *>(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<OBSSceneItem> &items = *static_cast<QVector<OBSSceneItem> *>(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<OBSSceneItem> &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<OBSSceneItem> 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<obs_sceneitem_t *> 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<OBSSceneItem> 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);
|
|
}
|
|
}
|
|
}
|