#include "ContextModel.h" #include #include #include #include #include "Core/Object.h" #include #include #include #include #include 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 (auto* obj : m_rootContext->GetObjects()) { uLib::Object::connect(obj, &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(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(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 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(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(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(index.internalPointer()); if (dynamic_cast(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(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(parent.internalPointer()); targetCtx = dynamic_cast(parentObj); } if (!targetCtx) return false; QByteArray encodedData = data->data("application/x-ulib-object-ptr"); QDataStream stream(&encodedData, QIODevice::ReadOnly); std::vector objectsToMove; while (!stream.atEnd()) { qlonglong ptrVal; stream >> ptrVal; objectsToMove.push_back(reinterpret_cast(ptrVal)); } if (objectsToMove.empty()) return false; // Helper to find and remove from current parent std::function findAndRemoveRecursive = [&findAndRemoveRecursive](uLib::Object* current, uLib::Object* target) { if (auto ctx = current->GetChildren()) { ctx->RemoveObject(target); for (auto* obj : ctx->GetObjects()) { findAndRemoveRecursive(obj, 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 isDescendant = [&isDescendant](uLib::Object* root, uLib::Object* target) -> bool { if (auto ctx = root->GetChildren()) { for (auto* child : ctx->GetObjects()) { if (child == target) return true; if (isDescendant(child, 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(index.internalPointer()); obj->SetInstanceName(value.toString().toStdString()); emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole}); return true; } return false; }