/
Team8
/
kittycad
Обзор
Документация
Войти
/
Team8
/
kittycad
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/gui/model_tree.cpp
493 строки
14 KB
levovix
progress: FlangeCoupling template
08 янв 2026, 01:06
08 янв 2026, 01:06
d0bbf0b
Код
Авторство
О чём код?
#include "model_tree.h" #include <QHeaderView> #include <QShortcut> #include <QStyledItemDelegate> #include <QApplication> #include <QMimeData> #include "data/assembly/assembly.h" #include "data/assembly/solid_clone.h" #include "data/document.h" #include "data/c3dobject.h" #include "data/sketch/sketch.h" #include "data/sketch/contours.h" #include "gui/strings.h" #include "gui/document_view.h" #include "gui/edits/contour_select.h" #include "gui/utils.h" using namespace std::chrono_literals; Q_DECLARE_METATYPE(std::weak_ptr<data::C3dObject>) ModelTreeItems::ModelTreeItems(ref<data::Document> document, QObject* parent) : QAbstractItemModel(parent) , m_document(document) {} ModelTreeItems::~ModelTreeItems() {} ref<ISerializable> ModelTreeItems::at(QModelIndex const& index) const { if (!index.isValid()) { return m_document; } auto parentObj = parentAt(index); auto i = index.row(); if (auto ent = std::dynamic_pointer_cast<data::Document>(parentObj)) { if (i < 0 || i >= ent->objects.size()) { return nullptr; } return ent->objects[i]; } else if (auto ent = std::dynamic_pointer_cast<data::C3dObject>(parentObj)) { if (i < 0 || i >= ent->childCount()) { return nullptr; } return ent->childAt(i); } return nullptr; } ref<ISerializable> ModelTreeItems::parentAt(QModelIndex const& index) const { if (!index.isValid()) { return m_document; } assert(index.internalPointer() != nullptr); return ((ISerializable*)(index.internalPointer()))->shared_from_this(); } bool ModelTreeItems::removeObjectAt(QModelIndex const& index) { auto obj = at(index); ptr<ISerializable> objRef = obj->shared_from_this(); // чтобы не удалился пока обновляется if (auto ent = std::dynamic_pointer_cast<data::C3dObject>(obj)) { data::C3dObject::destroy(ent, 2); if (!ent->document.expired()) { ent->document.lock()->documentView->documentUpdated(); } emit layoutChanged(); return true; } return false; } void ModelTreeItems::onChanged(data::C3dObject* sender) { // todo: обновлять более точечно emit layoutChanged(); } QVariant ModelTreeItems::data(const QModelIndex &index, int role) const { auto obj = at(index); // todo: придумать более универсальный способ представления свойств объектов if (auto ent = std::dynamic_pointer_cast<data::C3dObject>(obj)) { switch (index.column()) { case 0: { // [чекбокс видимости] имя switch (role) { case Qt::DisplayRole: { return toQString(ent->name()); } break; case Qt::EditRole: { return toQString(ent->name()); } break; case Qt::CheckStateRole: { return ent->visible ? Qt::Checked : Qt::Unchecked; } break; case Qt::DecorationRole: { return qIconFromResources(ent->iconName()); } break; case Qt::UserRole: { // сохраняем указатель как double, ВНЕ ПРИЛОЖЕНИЯ НЕ ИСПОЛЬЗОВАТЬ return QVariant::fromValue(std::bit_cast<double>(obj.get())); } break; default: { return {}; } } } break; default: { return {}; } break; } } return {}; } Qt::ItemFlags ModelTreeItems::flags(const QModelIndex &index) const { auto obj = at(index); if (auto ent = std::dynamic_pointer_cast<data::C3dObject>(obj)) { switch (index.column()) { case 0: { return ( Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEditable ); } break; default: { return QAbstractItemModel::flags(index); } break; } } return QAbstractItemModel::flags(index); } QVariant ModelTreeItems::headerData(int section, Qt::Orientation orientation, int role) const { return {}; } QModelIndex ModelTreeItems::index(int row, int column, const QModelIndex &parent) const { auto parentObj = at(parent); if (auto ent = std::dynamic_pointer_cast<data::Document>(parentObj)) { if (row < 0 || row >= ent->objects.size()) { return {}; } return createIndex(row, column, parentObj.get()); } else if (auto ent = std::dynamic_pointer_cast<data::C3dObject>(parentObj)) { if (row < 0 || row >= ent->childCount()) { return {}; } return createIndex(row, column, parentObj.get()); } return {}; } QModelIndex ModelTreeItems::parent(const QModelIndex &index) const { auto obj = parentAt(index); if (auto ent = std::dynamic_pointer_cast<data::C3dObject>(obj)) { auto parent = ent->parent(); if (parent == nullptr) { goto parent_is_document; } auto index = parent->childIndex(ent); if (index == -1) { goto parent_is_document; } return createIndex(index, 0, parent.get()); } if (false) { parent_is_document: auto it = std::ranges::find( m_document->objects, obj.get(), [](auto x) { return x.get(); } ); if (it == m_document->objects.end()) { return {}; } return this->index(it - m_document->objects.begin(), 0, {}); } return {}; } int ModelTreeItems::rowCount(const QModelIndex &parent) const { auto parentObj = at(parent); if (auto ent = std::dynamic_pointer_cast<data::Document>(parentObj)) { return ent->objects.size(); } else if (auto ent = std::dynamic_pointer_cast<data::C3dObject>(parentObj)) { return ent->childCount(); } return 0; } int ModelTreeItems::columnCount(const QModelIndex &parent) const { return 1; // ([чекбокс видимости] имя) } bool ModelTreeItems::setData(const QModelIndex &index, const QVariant &value, int role) { auto obj = at(index); if (auto ent = std::dynamic_pointer_cast<data::C3dObject>(obj)) { switch (index.column()) { case 0: { // [чекбокс видимости] имя switch (role) { case Qt::EditRole: { ent->setName(toStdString(value.toString())); return true; } break; case Qt::CheckStateRole: { ent->setVisible(value.toBool()); emit dataChanged(index, index, {Qt::CheckStateRole}); return true; } break; default: { return false; } } } break; default: { return false; } break; } } return false; } bool ModelTreeItems::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent ) { QByteArray encoded = data->data("application/x-raw-pointer"); auto dropObj = *reinterpret_cast<data::C3dObject**>(encoded.data()); if (dropObj == nullptr) { return false; } debugPrint(dropObj->typeNameHumanReadable()); auto obj = at(parent); // todo: придумать более универсальный способ представления свойств объектов if (auto ent = std::dynamic_pointer_cast<data::Sketch>(obj)) { if (auto dropEnt = dynamic_cast<data::SketchObject*>(dropObj)) { // todo: C3dObject: reparent, childAt(), removeChild, parent, destroy // пока костыли if (!dropEnt->sketch.expired() && dropEnt->sketch.lock().get() == ent.get()) { return true; } ent->objects.push_back(std::static_pointer_cast<data::SketchObject>(dropEnt->shared_from_this())); dropEnt->usedBy.push_back(std::static_pointer_cast<data::Sketch>(ent->shared_from_this())); if (!dropEnt->sketch.expired()) { auto skc = dropEnt->sketch.lock(); skc->objects.erase(std::ranges::find( skc->objects, dropEnt, [](auto const& x){ return x.get(); } )); dropEnt->usedBy.erase(std::ranges::find( dropEnt->usedBy, skc.get(), [](auto const& x){ return x.expired() ? nullptr : x.lock().get(); } )); skc->markPropertyChanged(); } dropEnt->sketch = std::static_pointer_cast<data::Sketch>(ent->shared_from_this()); dropEnt->markPropertyChanged(); emit layoutChanged(); return true; } } return false; } bool ModelTreeItems::canDropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent ) const { if (!data->formats().contains("application/x-raw-pointer")) { return false; } auto obj = at(parent); // todo: придумать более универсальный способ представления свойств объектов if (auto ent = std::dynamic_pointer_cast<data::Sketch>(obj)) { return true; } if (auto ent = std::dynamic_pointer_cast<data::Assembly>(obj)) { return true; } return false; } Qt::DropActions ModelTreeItems::supportedDropActions() const { return Qt::DropAction::MoveAction; } Qt::DropActions ModelTreeItems::supportedDragActions() const { return Qt::DropAction::MoveAction; } QMimeData * ModelTreeItems::mimeData(const QModelIndexList &indexes) const { for (auto index : indexes) { auto obj = at(index); auto mime = new QMimeData(); mime->setData("application/x-raw-pointer", QByteArray((char*)&obj, sizeof(intptr_t))); return mime; } return nullptr; } #if 0 class CustomCheckboxDelegate : public QStyledItemDelegate { public: explicit CustomCheckboxDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent) {} void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override { QStyleOptionViewItem opt = option; initStyleOption(&opt, index); if (index.column() == 0 && index.data(Qt::CheckStateRole).isValid()) { QStyleOptionButton checkboxOption; checkboxOption.rect = option.rect; checkboxOption.state = option.state; checkboxOption.state |= (index.data(Qt::CheckStateRole).toInt() == Qt::Checked) ? QStyle::State_On : QStyle::State_Off; QApplication::style()->drawControl(QStyle::CE_CheckBox, &checkboxOption, painter, opt.widget); checkboxOption.rect = option.rect.adjusted(16, 0, 16, 0); QApplication::style()->drawItemText( painter, checkboxOption.rect, 0, QApplication::palette(), true, index.data(Qt::DisplayRole).toString()); } else { QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter, opt.widget); } // drawDisplay(painter, opt, opt.rect, index.data(Qt::DisplayRole).toString()); } }; #endif ModelTree::ModelTree(QWidget* parent) : QTreeView(parent) { m_lastClickTime = std::chrono::system_clock::now() - 1s; initUi(); } ModelTree::~ModelTree() {} void ModelTree::initUi() { header()->hide(); setSelectionMode(ModelTree::SingleSelection); setDragEnabled(true); setAcceptDrops(true); setDropIndicatorShown(true); setDefaultDropAction(Qt::MoveAction); setDragDropMode(QAbstractItemView::InternalMove); { QShortcut* keybinding = new QShortcut(QKeySequence("Delete"), this); connect(keybinding, &QShortcut::activated, this, [this]() { if (!hasFocus() || m_model == nullptr) { return; } auto indices = selectedIndexes(); if (indices.size() == 0) { return; } m_model->removeObjectAt(indices[0]); // todo: удаление по нескольким индексам selectionModel()->clearSelection(); }); } #if 0 // todo CustomCheckboxDelegate *delegate = new CustomCheckboxDelegate(this); setItemDelegate(delegate); #else setStyleSheet(R"_( QTreeView::indicator:unchecked { image: url(":/resources/icons/view-hidden.svg"); background-color: transparent; } QTreeView::indicator:checked { image: url(":/resources/icons/view-visible.svg"); background-color: transparent; color: #ff0000; } // QTreeView::indicator::after { // position: absolute; // top: 0; // left: 0; // width: 24px; // height: 24px; // background-color: rgba(0, 0, 0, 0.5); // } )_"); #endif } void ModelTree::setDocument(ref<data::Document> v) { m_document = v; if (m_model != nullptr) { setModel(nullptr); delete m_model; m_model = nullptr; } if (m_document == nullptr) { return; } m_model = new ModelTreeItems(m_document, this); setModel(m_model); setSelectionModel(new QItemSelectionModel(m_model)); connect( selectionModel(), &QItemSelectionModel::currentChanged, this, [this](QModelIndex const& current, QModelIndex const& previous) { on_objectSelected(current); }, Qt::QueuedConnection ); // todo: не считать нажатие на глазик у объекта (переключение видимости) за выбор объекта connect( this, &ModelTree::pressed, this, [this](QModelIndex const& index) { auto time = std::chrono::system_clock::now(); #if 0 // todo: сделать так чтобы двойной клик работал нормально if (time - m_lastClickTime < 400ms) { // двойной клик auto obj = m_model->at(index); if (auto ent = dynamic_cast<data::C3dObject*>(obj)) { if (ent->tool != nullptr && ent->tool == m_document->documentView->m_toolForSelectedObject) { ent->deleteTool(); m_document->documentView->setActiveTool( ent->getOrCreateTool(m_document->activeToolPalette) ); } } } else { on_objectSelected(index); } #else on_objectSelected(index); #endif m_lastClickTime = time; } ); } void ModelTree::on_objectChanged(data::C3dObject* sender) { m_model->onChanged(sender); } void ModelTree::on_objectSelected(QModelIndex const& index) { auto obj = m_model->at(index); if (auto ent = std::dynamic_pointer_cast<data::Sketch>(obj)) { ent->document.lock()->setActiveSketch(ent); } if (auto ent = std::dynamic_pointer_cast<data::Assembly>(obj)) { ent->document.lock()->setActiveAssembly(ent); } if (auto ent = std::dynamic_pointer_cast<data::C3dObject>(obj)) { if ( m_document->documentView->m_activeInteractiveEntry == nullptr || !(dynamic_cast<ContourSelect*>(m_document->documentView->m_activeInteractiveEntry)) ) { if (ent->tool == nullptr || ent->tool != m_document->documentView->m_activeTool) { m_document->documentView->setToolForSelectedObject( ent->getOrCreateTool(m_document->toolForSelectedObjectPalette) ); } } } }