Files
uLib/app/gcompose/src/ContextModel.cpp

277 lines
9.5 KiB
C++

#include "ContextModel.h"
#include <QString>
#include <typeinfo>
#include <cxxabi.h>
#include <functional>
#include "Core/Object.h"
#include <QMimeData>
#include <QDataStream>
#include <QIODevice>
#include <vector>
#include <algorithm>
ContextModel::ContextModel(QObject* parent)
: QAbstractItemModel(parent), m_rootContext(nullptr) {}
ContextModel::~ContextModel() {}
void ContextModel::setContext(uLib::ObjectsContext* context) {
m_isReseting = true;
beginResetModel();
m_rootContext = context;
if (m_rootContext) {
auto refresh = [this]() {
if (this->m_isReseting) return;
this->m_isReseting = true;
this->beginResetModel();
this->endResetModel();
this->m_isReseting = false;
};
uLib::Object::connect(m_rootContext, &uLib::Object::Updated, refresh);
uLib::Object::connect(m_rootContext, &uLib::ObjectsContext::ObjectAdded, [this, refresh](uLib::Object* obj) {
uLib::Object::connect(obj, &uLib::Object::Updated, refresh);
refresh();
});
uLib::Object::connect(m_rootContext, &uLib::ObjectsContext::ObjectRemoved, [this, refresh](uLib::Object* obj) {
refresh();
});
// Connect existing objects
for (const auto& obj : m_rootContext->GetObjects()) {
uLib::Object::connect(obj.get(), &uLib::Object::Updated, refresh);
}
}
endResetModel();
m_isReseting = false;
}
QModelIndex ContextModel::index(int row, int column, const QModelIndex& parent) const {
if (!hasIndex(row, column, parent) || !m_rootContext) {
return QModelIndex();
}
if (!parent.isValid()) {
if (row < m_rootContext->GetCount()) {
return createIndex(row, column, m_rootContext->GetObject(row));
}
} else {
uLib::Object* parentObj = static_cast<uLib::Object*>(parent.internalPointer());
uLib::ObjectsContext* parentCtx = parentObj->GetChildren();
if (parentCtx && row < (int)parentCtx->GetCount()) {
return createIndex(row, column, parentCtx->GetObject(row));
}
}
return QModelIndex();
}
QModelIndex ContextModel::parent(const QModelIndex& child) const {
if (!child.isValid() || !m_rootContext) {
return QModelIndex();
}
uLib::Object* childObj = static_cast<uLib::Object*>(child.internalPointer());
// Finding the parent of childObj is O(N) since there is no parent pointer.
// We just do a recursive search starting from root context.
std::function<uLib::Object*(uLib::Object*, uLib::Object*)> findParent =
[&findParent](uLib::Object* current, uLib::Object* target) -> uLib::Object* {
uLib::ObjectsContext* ctx = current->GetChildren();
if (ctx) {
for (const auto& obj : ctx->GetObjects()) {
if (obj == target) return current;
if (auto p = findParent(obj, target)) return p;
}
}
return nullptr;
};
uLib::Object* parentObj = findParent(m_rootContext, childObj);
if (!parentObj || parentObj == m_rootContext) {
return QModelIndex(); // Root items have invalid parent index
}
// Now need to find the row of parentObj in its own parent Context.
uLib::Object* grandParentObj = findParent(m_rootContext, parentObj);
uLib::ObjectsContext* grandParentCtx = grandParentObj ? grandParentObj->GetChildren() : m_rootContext;
int row = -1;
for (size_t i = 0; i < grandParentCtx->GetCount(); ++i) {
if (grandParentCtx->GetObject(i) == parentObj) {
row = (int)i;
break;
}
}
if (row != -1) {
return createIndex(row, 0, parentObj);
}
return QModelIndex();
}
int ContextModel::rowCount(const QModelIndex& parent) const {
if (!m_rootContext) return 0;
if (!parent.isValid()) {
return m_rootContext->GetCount();
}
uLib::Object* parentObj = static_cast<uLib::Object*>(parent.internalPointer());
if (auto parentCtx = parentObj->GetChildren()) {
return (int)parentCtx->GetCount();
}
return 0; // leaf node
}
int ContextModel::columnCount(const QModelIndex& parent) const {
return 1;
}
static QString getDemangledName(const std::type_info& info) {
int status = -4;
char* demangled = abi::__cxa_demangle(info.name(), nullptr, nullptr, &status);
QString res = (status == 0 && demangled) ? QString::fromUtf8(demangled) : QString::fromUtf8(info.name());
if (demangled) free(demangled);
// Remove namespaces
int lastColon = res.lastIndexOf("::");
if (lastColon != -1) {
res = res.mid(lastColon + 2);
}
// Remove "class " prefix if any
if (res.startsWith("class ")) res = res.mid(6);
return res;
}
QVariant ContextModel::data(const QModelIndex& index, int role) const {
if (!index.isValid()) return QVariant();
uLib::Object* obj = static_cast<uLib::Object*>(index.internalPointer());
if (role == Qt::DisplayRole) {
QString typeName = getDemangledName(typeid(*obj));
std::string instName = obj->GetInstanceName();
if (instName.empty()) return typeName;
return QString("%1 (%2)").arg(QString::fromStdString(instName)).arg(typeName);
}
if (role == Qt::EditRole) {
return QString::fromStdString(obj->GetInstanceName());
}
return QVariant();
}
QVariant ContextModel::headerData(int section, Qt::Orientation orientation, int role) const {
if (orientation == Qt::Horizontal && role == Qt::DisplayRole && section == 0) {
return "Object Context";
}
return QVariant();
}
Qt::ItemFlags ContextModel::flags(const QModelIndex& index) const {
if (!index.isValid()) return m_rootContext ? Qt::ItemIsDropEnabled : Qt::NoItemFlags;
Qt::ItemFlags f = Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled;
uLib::Object* obj = static_cast<uLib::Object*>(index.internalPointer());
if (dynamic_cast<uLib::ObjectsContext*>(obj)) {
f |= Qt::ItemIsDropEnabled;
}
return f;
}
Qt::DropActions ContextModel::supportedDropActions() const {
return Qt::MoveAction;
}
QStringList ContextModel::mimeTypes() const {
return {"application/x-ulib-object-ptr"};
}
QMimeData* ContextModel::mimeData(const QModelIndexList& indexes) const {
QMimeData* mimeData = new QMimeData();
QByteArray encodedData;
QDataStream stream(&encodedData, QIODevice::WriteOnly);
for (const auto& idx : indexes) {
if (idx.isValid() && idx.column() == 0) {
void* ptr = idx.internalPointer();
stream << reinterpret_cast<qlonglong>(ptr);
}
}
mimeData->setData("application/x-ulib-object-ptr", encodedData);
return mimeData;
}
bool ContextModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) {
if (action != Qt::MoveAction || !data->hasFormat("application/x-ulib-object-ptr")) return false;
uLib::ObjectsContext* targetCtx = m_rootContext;
if (parent.isValid()) {
uLib::Object* parentObj = static_cast<uLib::Object*>(parent.internalPointer());
targetCtx = dynamic_cast<uLib::ObjectsContext*>(parentObj);
}
if (!targetCtx) return false;
QByteArray encodedData = data->data("application/x-ulib-object-ptr");
QDataStream stream(&encodedData, QIODevice::ReadOnly);
std::vector<uLib::Object*> objectsToMove;
while (!stream.atEnd()) {
qlonglong ptrVal;
stream >> ptrVal;
objectsToMove.push_back(reinterpret_cast<uLib::Object*>(ptrVal));
}
if (objectsToMove.empty()) return false;
// Helper to find and remove from current parent
std::function<void(uLib::Object*, uLib::Object*)> findAndRemoveRecursive =
[&findAndRemoveRecursive](uLib::Object* current, uLib::Object* target) {
if (auto ctx = current->GetChildren()) {
ctx->RemoveObject(target);
for (const auto& obj : ctx->GetObjects()) {
findAndRemoveRecursive(obj.get(), target);
}
}
};
m_isReseting = true;
beginResetModel();
for (auto* obj : objectsToMove) {
// Don't drop onto itself or its descendants
bool invalid = (obj == targetCtx || obj == (uLib::Object*)targetCtx);
if (!invalid) {
// check if targetCtx is descendant of obj
std::function<bool(uLib::Object*, uLib::Object*)> isDescendant =
[&isDescendant](uLib::Object* root, uLib::Object* target) -> bool {
if (auto ctx = root->GetChildren()) {
for (const auto& child : ctx->GetObjects()) {
if (child.get() == target) return true;
if (isDescendant(child.get(), target)) return true;
}
}
return false;
};
if (isDescendant(obj, (uLib::Object*)targetCtx)) invalid = true;
}
if (!invalid) {
findAndRemoveRecursive(m_rootContext, obj);
targetCtx->AddObject(obj);
}
}
endResetModel();
m_isReseting = false;
return true;
}
bool ContextModel::setData(const QModelIndex& index, const QVariant& value, int role) {
if (index.isValid() && role == Qt::EditRole) {
uLib::Object* obj = static_cast<uLib::Object*>(index.internalPointer());
obj->SetInstanceName(value.toString().toStdString());
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
}
return false;
}