├── qmldir ├── TreeListViewExample.png ├── README.md ├── example ├── rootcontroller.h ├── rootcontroller.cpp ├── example.cpp └── example.qml ├── sources ├── qml │ ├── ModelItem.qml │ ├── ListItem.qml │ └── TreeItem.qml ├── treeitem.h ├── treemodel.h ├── treeitem.cpp └── treemodel.cpp ├── .github └── workflows │ └── build_plugin.yml ├── tests └── tst_treemodeltest.cpp ├── .gitignore └── CMakeLists.txt /qmldir: -------------------------------------------------------------------------------- 1 | module com.palm1r.treecontrol 2 | plugin aqmltreeplugin 3 | -------------------------------------------------------------------------------- /TreeListViewExample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Palm1r/AQmlTreePlugin/HEAD/TreeListViewExample.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Another QML Tree Plugin 2 | [![Actions Status](https://github.com/Palm1r/AQmlTreePlugin/workflows/build_release_master/badge.svg)](https://github.com/Palm1r/AQmlTreePlugin/actions) 3 | 4 | Requirements: 5 | For last versions only Qt6.x 6 | 7 | Reworked into QML plugin for easy add to projects and added test 8 | 9 | ![Image alt](https://github.com/ArtifeksNN/QmlTreeViewExample/blob/master/TreeListViewExample.png) 10 | -------------------------------------------------------------------------------- /example/rootcontroller.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef ROOTCONTROLLER_H 3 | #define ROOTCONTROLLER_H 4 | 5 | #include "treemodel.h" 6 | #include 7 | 8 | class RootController : public QObject 9 | { 10 | Q_OBJECT 11 | Q_PROPERTY(TreeModel *treeModel READ treeModel NOTIFY treeModelChanged) 12 | 13 | public: 14 | explicit RootController(QObject *parent = nullptr); 15 | 16 | TreeModel *treeModel() const; 17 | // TO DO add new item for any parent 18 | Q_INVOKABLE void addItem(const QVariant &data); 19 | 20 | signals: 21 | void treeModelChanged(); 22 | 23 | private: 24 | TreeModel *m_treeModel; 25 | }; 26 | 27 | #endif // ROOTCONTROLLER_H 28 | -------------------------------------------------------------------------------- /sources/qml/ModelItem.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | 3 | Row { 4 | id: root 5 | 6 | property alias content: contentData.text 7 | 8 | signal selected 9 | 10 | spacing: 10 11 | 12 | Rectangle { 13 | anchors.verticalCenter: parent.verticalCenter 14 | width: 20 15 | height: 20 16 | color: "#3c85b5" 17 | 18 | MouseArea { 19 | anchors.fill: parent 20 | onClicked: root.selected() 21 | } 22 | } 23 | 24 | Text { 25 | id: contentData 26 | 27 | anchors.verticalCenter: parent.verticalCenter 28 | color: "#3c85b5" 29 | font.pixelSize: 20 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /example/rootcontroller.cpp: -------------------------------------------------------------------------------- 1 | #include "rootcontroller.h" 2 | 3 | RootController::RootController(QObject *parent) 4 | : QObject{parent} 5 | , m_treeModel(new TreeModel(this)) 6 | { 7 | m_treeModel->addTreeItem(nullptr, "Child"); 8 | auto child = m_treeModel->addTreeItem(nullptr, "Child"); 9 | m_treeModel->addTreeItem(child, "GrandChild"); 10 | m_treeModel->addTreeItem(child, "GrandChild"); 11 | m_treeModel->addTreeItem(child, "GrandChild"); 12 | auto grandChild = m_treeModel->addTreeItem(child, "GrandChild"); 13 | m_treeModel->addTreeItem(grandChild, "GrandGrandChild"); 14 | 15 | emit treeModelChanged(); 16 | } 17 | 18 | TreeModel *RootController::treeModel() const 19 | { 20 | return m_treeModel; 21 | } 22 | 23 | void RootController::addItem(const QVariant &data) 24 | { 25 | m_treeModel->addTreeItem(m_treeModel->rootItem(), data); 26 | 27 | emit treeModelChanged(); 28 | } 29 | -------------------------------------------------------------------------------- /sources/treeitem.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef TREEITEM_H 3 | #define TREEITEM_H 4 | 5 | #include "QVariant" 6 | #include 7 | #include 8 | 9 | class TreeItemData 10 | { 11 | bool isSelected; 12 | bool isOpened; 13 | }; 14 | 15 | class TreeItem : public std::enable_shared_from_this 16 | { 17 | public: 18 | using ItemWeekPtr = std::weak_ptr; 19 | using ItemPtr = std::shared_ptr; 20 | 21 | explicit TreeItem(ItemWeekPtr parent = ItemWeekPtr(), const QVariant &data = QVariant()); 22 | virtual ~TreeItem() = default; 23 | 24 | int index() const; 25 | int childCount() const; 26 | 27 | ItemPtr parent(); 28 | ItemPtr child(int index); 29 | 30 | int childIndexByItem(const std::shared_ptr &item) const; 31 | 32 | ItemPtr appendChild(const QVariant &data); 33 | void removeChild(int childIndex); 34 | 35 | QVariant data() const; 36 | 37 | private: 38 | ItemWeekPtr m_parent; 39 | QVariant m_data; 40 | std::vector m_childItems; 41 | }; 42 | #endif // TREEITEM_H 43 | -------------------------------------------------------------------------------- /.github/workflows/build_plugin.yml: -------------------------------------------------------------------------------- 1 | name: build_release_master 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | env: 10 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 11 | BUILD_TYPE: Release 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@v3 20 | 21 | - name: Install Qt 22 | uses: jurplel/install-qt-action@v3 23 | with: 24 | version: '6.5' 25 | host: 'linux' 26 | target: 'desktop' 27 | dir: '${{ github.workspace }}/Qt/' 28 | install-deps: 'true' 29 | set-env: 'true' 30 | 31 | - name: Configure CMake 32 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} 33 | 34 | - name: Build 35 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} 36 | 37 | - name: Test 38 | working-directory: ${{github.workspace}}/build 39 | run: ctest -C -V ${{env.BUILD_TYPE}} 40 | 41 | -------------------------------------------------------------------------------- /sources/treemodel.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef TREEMODEL_H 3 | #define TREEMODEL_H 4 | 5 | #include "treeitem.h" 6 | 7 | #include 8 | 9 | #include 10 | 11 | class TreeModel : public QAbstractItemModel 12 | { 13 | Q_OBJECT 14 | QML_ELEMENT 15 | public: 16 | explicit TreeModel(QObject *parent = nullptr); 17 | ~TreeModel() override = default; 18 | 19 | int rowCount(const QModelIndex &index) const override; 20 | int columnCount(const QModelIndex &) const override; 21 | QModelIndex index(const int row, const int column, const QModelIndex &parent) const override; 22 | QModelIndex parent(const QModelIndex &childIndex) const override; 23 | QVariant data(const QModelIndex &index, const int role = 0) const override; 24 | 25 | Q_INVOKABLE TreeItem::ItemPtr addTreeItem(TreeItem::ItemPtr parent, QVariant data); 26 | Q_INVOKABLE TreeItem::ItemPtr addTreeItem(const QModelIndex &parentIndex, const QVariant &data); 27 | Q_INVOKABLE void removeItemByIndex(TreeItem::ItemPtr parent, int index); 28 | Q_INVOKABLE void removeItemByIndex(const QModelIndex &index); 29 | Q_INVOKABLE TreeItem::ItemPtr rootItem() const; 30 | Q_INVOKABLE QModelIndex rootIndex(); 31 | 32 | private: 33 | TreeItem::ItemPtr m_rootItem; 34 | }; 35 | 36 | #endif // TREEMODEL_H 37 | -------------------------------------------------------------------------------- /sources/treeitem.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "treeitem.h" 3 | 4 | TreeItem::TreeItem(ItemWeekPtr parent, const QVariant &data) 5 | : m_parent(parent) 6 | , m_data(data) 7 | { 8 | m_childItems.reserve(4); 9 | } 10 | 11 | TreeItem::ItemPtr TreeItem::parent() 12 | { 13 | return m_parent.lock(); 14 | } 15 | 16 | int TreeItem::childCount() const 17 | { 18 | return m_childItems.size(); 19 | } 20 | 21 | TreeItem::ItemPtr TreeItem::child(int index) 22 | { 23 | return m_childItems.at(index); 24 | } 25 | 26 | int TreeItem::childIndexByItem(const std::shared_ptr &item) const 27 | { 28 | auto it = std::find(m_childItems.begin(), m_childItems.end(), item); 29 | if (it != m_childItems.end()) 30 | return std::distance(m_childItems.begin(), it); 31 | return -1; 32 | } 33 | 34 | int TreeItem::index() const 35 | { 36 | if (m_parent.expired()) 37 | return 0; 38 | 39 | return m_parent.lock()->childIndexByItem(TreeItem::shared_from_this()); 40 | } 41 | 42 | TreeItem::ItemPtr TreeItem::appendChild(const QVariant &data) 43 | { 44 | return m_childItems.emplace_back(std::make_shared(TreeItem::shared_from_this(), data)); 45 | } 46 | 47 | void TreeItem::removeChild(int childIndex) 48 | { 49 | auto child = m_childItems.begin() + childIndex; 50 | m_childItems.erase(child); 51 | } 52 | 53 | QVariant TreeItem::data() const 54 | { 55 | return m_data; 56 | } 57 | -------------------------------------------------------------------------------- /tests/tst_treemodeltest.cpp: -------------------------------------------------------------------------------- 1 | #include "treemodel.h" 2 | #include 3 | 4 | class TreeModelTest : public QObject 5 | { 6 | Q_OBJECT 7 | 8 | private slots: 9 | void initTestCase(); 10 | void cleanupTestCase(); 11 | void testRowCount(); 12 | void testColumnCount(); 13 | void testAddTreeItem(); 14 | void testRemoveItemByIndex(); 15 | void testRootItem(); 16 | 17 | private: 18 | TreeModel *model; 19 | }; 20 | 21 | void TreeModelTest::initTestCase() 22 | { 23 | model = new TreeModel(); 24 | } 25 | 26 | void TreeModelTest::cleanupTestCase() 27 | { 28 | delete model; 29 | } 30 | 31 | void TreeModelTest::testRowCount() 32 | { 33 | int rowCount = model->rowCount(QModelIndex()); 34 | QCOMPARE(rowCount, 0); 35 | } 36 | 37 | void TreeModelTest::testColumnCount() 38 | { 39 | int columnCount = model->columnCount(QModelIndex()); 40 | QCOMPARE(columnCount, 1); 41 | } 42 | 43 | void TreeModelTest::testAddTreeItem() 44 | { 45 | QVariant data("Test Data"); 46 | auto root = model->rootItem(); 47 | model->addTreeItem(root, data); 48 | QCOMPARE(model->rowCount(QModelIndex()), 1); 49 | } 50 | 51 | void TreeModelTest::testRemoveItemByIndex() 52 | { 53 | auto root = model->rootItem(); 54 | model->removeItemByIndex(root, 0); 55 | QCOMPARE(model->rowCount(QModelIndex()), 0); 56 | } 57 | 58 | void TreeModelTest::testRootItem() 59 | { 60 | auto root = model->rootItem(); 61 | QVERIFY(root != nullptr); 62 | } 63 | 64 | QTEST_MAIN(TreeModelTest) 65 | #include "tst_treemodeltest.moc" 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This file is used to ignore files which are generated 2 | 3 | # ---------------------------------------------------------------------------- 4 | 5 | 6 | 7 | *~ 8 | 9 | *.autosave 10 | 11 | *.a 12 | 13 | *.core 14 | 15 | *.moc 16 | 17 | *.o 18 | 19 | *.obj 20 | 21 | *.orig 22 | 23 | *.rej 24 | 25 | *.so 26 | 27 | *.so.* 28 | 29 | *_pch.h.cpp 30 | 31 | *_resource.rc 32 | 33 | *.qm 34 | 35 | .#* 36 | 37 | *.*# 38 | 39 | core 40 | 41 | !core/ 42 | 43 | tags 44 | 45 | .DS_Store 46 | 47 | .directory 48 | 49 | *.debug 50 | 51 | Makefile* 52 | 53 | *.prl 54 | 55 | *.app 56 | 57 | moc_*.cpp 58 | 59 | ui_*.h 60 | 61 | qrc_*.cpp 62 | 63 | Thumbs.db 64 | 65 | *.res 66 | 67 | *.rc 68 | 69 | /.qmake.cache 70 | 71 | /.qmake.stash 72 | 73 | /.txt.user 74 | 75 | 76 | 77 | # qtcreator generated files 78 | 79 | *.pro.user* 80 | 81 | CMakeLists.txt.user* 82 | 83 | 84 | 85 | # xemacs temporary files 86 | 87 | *.flc 88 | 89 | 90 | 91 | # Vim temporary files 92 | 93 | .*.swp 94 | 95 | 96 | 97 | # Visual Studio generated files 98 | 99 | *.ib_pdb_index 100 | 101 | *.idb 102 | 103 | *.ilk 104 | 105 | *.pdb 106 | 107 | *.sln 108 | 109 | *.suo 110 | 111 | *.vcproj 112 | 113 | *vcproj.*.*.user 114 | 115 | *.ncb 116 | 117 | *.sdf 118 | 119 | *.opensdf 120 | 121 | *.vcxproj 122 | 123 | *vcxproj.* 124 | 125 | 126 | 127 | # MinGW generated files 128 | 129 | *.Debug 130 | 131 | *.Release 132 | 133 | 134 | 135 | # Python byte code 136 | 137 | *.pyc 138 | 139 | 140 | 141 | # Binaries 142 | 143 | # -------- 144 | 145 | *.dll 146 | 147 | *.exe 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /sources/qml/ListItem.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import QtQuick.Controls 3 | 4 | Item { 5 | id: root 6 | 7 | property int itemLeftPadding: 30 8 | 9 | anchors.fill: parent 10 | 11 | Component.onCompleted: { 12 | fillData() 13 | } 14 | 15 | ListModel { 16 | id: listItemModel 17 | } 18 | 19 | ListView { 20 | id: listArea 21 | 22 | anchors.fill: parent 23 | spacing: 10 24 | boundsBehavior: Flickable.StopAtBounds 25 | ScrollBar.vertical: ScrollBar {} 26 | 27 | model: listItemModel 28 | delegate: Column { 29 | width: parent.width 30 | spacing: 10 31 | 32 | ModelItem { 33 | content: name 34 | } 35 | 36 | Rectangle { 37 | anchors { 38 | left: parent.left 39 | leftMargin: -100 40 | right: parent.right 41 | } 42 | 43 | width: parent.width 44 | height: 1 45 | color: "gray" 46 | opacity: 0.5 47 | } 48 | } 49 | } 50 | 51 | function fillData() { 52 | let rootIndex = _treemodel.rootIndex() 53 | 54 | for(var i = 0; i < _treemodel.rowCount(rootIndex); i++) { 55 | let index =_treemodel.index(i, 0, rootIndex) 56 | listItemModel.append({"name": _treemodel.data(index)}) 57 | fillFromBranch(index) 58 | } 59 | } 60 | 61 | function fillFromBranch(modelIndex) { 62 | let childCount = _treemodel.rowCount(modelIndex) 63 | 64 | if (childCount > 0) { 65 | for (var i = 0; i < childCount; i++) { 66 | let index =_treemodel.index(i, 0, modelIndex) 67 | listItemModel.append({"name": _treemodel.data(index)}) 68 | fillFromBranch(index) 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | project(aqmltreeplugin VERSION 0.1 LANGUAGES CXX) 4 | 5 | set(CMAKE_AUTOMOC ON) 6 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 | set(CMAKE_CXX_STANDARD 17) 8 | set(QT_QML_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) 9 | 10 | find_package(Qt6 6.5 COMPONENTS Quick REQUIRED) 11 | 12 | qt_add_library(aqmltreeplugin STATIC) 13 | qt_add_qml_module(aqmltreeplugin 14 | URI aqmltreeplugin 15 | VERSION 1.0 16 | QML_FILES 17 | sources/qml/TreeItem.qml 18 | sources/qml/ListItem.qml 19 | sources/qml/ModelItem.qml 20 | 21 | SOURCES 22 | sources/treeitem.h 23 | sources/treeitem.cpp 24 | sources/treemodel.h 25 | sources/treemodel.cpp 26 | ) 27 | 28 | set_target_properties(aqmltreeplugin PROPERTIES 29 | MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com 30 | MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} 31 | MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} 32 | MACOSX_BUNDLE TRUE 33 | WIN32_EXECUTABLE TRUE 34 | ) 35 | 36 | target_compile_definitions(aqmltreeplugin 37 | PRIVATE $<$,$>:QT_QML_DEBUG>) 38 | target_link_libraries(aqmltreeplugin 39 | PRIVATE Qt6::Quick) 40 | 41 | target_include_directories(aqmltreeplugin PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/sources) 42 | 43 | # Test 44 | find_package(Qt6 COMPONENTS Test REQUIRED) 45 | 46 | set(TEST_SOURCES 47 | tests/tst_treemodeltest.cpp 48 | ) 49 | enable_testing() 50 | add_executable(tests ${TEST_SOURCES}) 51 | target_link_libraries(tests PRIVATE Qt6::Test aqmltreepluginplugin) 52 | target_include_directories(tests PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/sources) 53 | add_test(NAME test COMMAND tests) 54 | 55 | # Example Project 56 | qt_add_executable(ExampleProject example/example.cpp) 57 | qt_add_qml_module(ExampleProject 58 | URI ExampleProjectApp 59 | VERSION 1.0 60 | QML_FILES example/example.qml 61 | SOURCES example/rootcontroller.h example/rootcontroller.cpp 62 | ) 63 | target_link_libraries(ExampleProject PRIVATE Qt6::Quick aqmltreepluginplugin) 64 | target_include_directories(ExampleProject PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/sources) 65 | target_compile_definitions(ExampleProject PRIVATE $<$,$>:QT_QML_DEBUG>) 66 | -------------------------------------------------------------------------------- /example/example.cpp: -------------------------------------------------------------------------------- 1 | #include "rootcontroller.h" 2 | 3 | #include 4 | #include 5 | #include 6 | Q_IMPORT_QML_PLUGIN(aqmltreepluginPlugin) 7 | 8 | int main(int argc, char *argv[]) 9 | { 10 | QGuiApplication app(argc, argv); 11 | 12 | QQmlApplicationEngine engine; 13 | // The first subfolder is the libraryName followed by the regular 14 | // folder structure: LibraryName/Subfolder 15 | const QUrl url(u"qrc:/ExampleProjectApp/example/example.qml"_qs); 16 | QObject::connect( 17 | &engine, 18 | &QQmlApplicationEngine::objectCreated, 19 | &app, 20 | [url](QObject *obj, const QUrl &objUrl) { 21 | if (!obj && url == objUrl) 22 | QCoreApplication::exit(-1); 23 | }, 24 | Qt::QueuedConnection); 25 | 26 | qmlRegisterSingletonType("Palm1r.treeviewProjects.RootController", 27 | 1, 28 | 0, 29 | "RootController", 30 | [](QQmlEngine *engine, QJSEngine *) { 31 | auto rootController = new RootController(engine); 32 | return rootController; 33 | }); 34 | engine.load(url); 35 | 36 | return app.exec(); 37 | //#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 38 | // QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); 39 | //#endif 40 | // QGuiApplication app(argc, argv); 41 | 42 | // QQmlApplicationEngine engine; 43 | // const QUrl url(QStringLiteral("qrc:/example.qml")); 44 | // QObject::connect( 45 | // &engine, 46 | // &QQmlApplicationEngine::objectCreated, 47 | // &app, 48 | // [url](QObject *obj, const QUrl &objUrl) { 49 | // if (!obj && url == objUrl) 50 | // QCoreApplication::exit(-1); 51 | // }, 52 | // Qt::QueuedConnection); 53 | // qmlRegisterSingletonType("Palm1r.treeviewProjects.RootController", 54 | // 1, 55 | // 0, 56 | // "RootController", 57 | // [](QQmlEngine *engine, QJSEngine *) { 58 | // auto rootController = new RootController(engine); 59 | // return rootController; 60 | // }); 61 | 62 | // engine.load(url); 63 | 64 | // return app.exec(); 65 | } 66 | -------------------------------------------------------------------------------- /example/example.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import QtQuick.Window 3 | import QtQuick.Controls 4 | import Palm1r.treeviewProjects.RootController 5 | import aqmltreeplugin 6 | import "./" 7 | 8 | Window { 9 | id: root 10 | 11 | visible: true 12 | width: 640 13 | height: 480 14 | title: qsTr("TreeView and ListView example") 15 | 16 | property var _treemodel: RootController.treeModel 17 | 18 | Row { 19 | id: buttonRow 20 | 21 | anchors.horizontalCenter: parent.horizontalCenter 22 | width: parent.width 23 | height: 60 24 | 25 | Button { 26 | width: parent.width / 2 27 | height: 40 28 | text: "Add item" 29 | font.pixelSize: 16 30 | 31 | onClicked: { 32 | var child = RootController.addItem("Child") 33 | } 34 | } 35 | 36 | Button { 37 | width: parent.width / 2 38 | height: 40 39 | text: "[WIP]//Remove item" 40 | font.pixelSize: 16 41 | opacity: 0.5 42 | enabled: false 43 | } 44 | } 45 | 46 | Row { 47 | id: views 48 | 49 | anchors { 50 | top: buttonRow.bottom 51 | bottom: parent.bottom 52 | } 53 | spacing: -1 54 | width: parent.width 55 | 56 | Rectangle { 57 | id: treePart 58 | 59 | width: parent.width / 2 60 | height: parent.height 61 | border { 62 | width: 1 63 | color: "gray" 64 | } 65 | clip: true 66 | 67 | Flickable { 68 | id: toolbarFlickable 69 | 70 | anchors.fill: parent 71 | contentHeight: tree.height 72 | contentWidth: parent.width 73 | boundsBehavior: Flickable.StopAtBounds 74 | ScrollBar.vertical: ScrollBar {} 75 | 76 | TreeItem { 77 | id: tree 78 | 79 | anchors { 80 | top: parent.top 81 | left: parent.left 82 | leftMargin: 5 83 | topMargin: 5 84 | } 85 | 86 | parentIndex: _treemodel.rootIndex() 87 | childCount: _treemodel.rowCount(parentIndex) 88 | itemLeftPadding: 0 89 | } 90 | } 91 | } 92 | 93 | Rectangle { 94 | id: listPart 95 | 96 | width: parent.width / 2 97 | height: parent.height 98 | border { 99 | width: 1 100 | color: "gray" 101 | } 102 | clip: true 103 | 104 | ListItem { 105 | anchors { 106 | top: parent.top 107 | left: parent.left 108 | leftMargin: 5 109 | topMargin: 5 110 | } 111 | 112 | itemLeftPadding: 0 113 | } 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /sources/qml/TreeItem.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import QtQuick.Layouts 3 | 4 | Item { 5 | id: root 6 | 7 | property var parentIndex 8 | property var childCount 9 | property int itemLeftPadding: 30 10 | 11 | implicitWidth: parent.width 12 | implicitHeight: childrenRect.height 13 | 14 | ColumnLayout { 15 | width: parent.width 16 | spacing: 10 17 | 18 | Repeater { 19 | model: childCount 20 | 21 | delegate: ColumnLayout { 22 | id: itemColumn 23 | 24 | property bool isSelected: false 25 | 26 | Layout.fillWidth: true 27 | Layout.leftMargin: itemLeftPadding 28 | 29 | spacing: 10 30 | 31 | QtObject { 32 | id: _d 33 | 34 | property var currentIndex: _treemodel.index(index, 0, parentIndex) 35 | property var currentData: _treemodel.data(currentIndex) 36 | property var itemChildCount: _treemodel.rowCount(currentIndex) 37 | property bool isOpen: false 38 | property bool isSelected: false 39 | } 40 | 41 | Row { 42 | spacing: 10 43 | width: parent.width 44 | 45 | MouseArea { 46 | anchors.verticalCenter: parent.verticalCenter 47 | width: 15 48 | height: 15 49 | onClicked: _d.isOpen = !_d.isOpen 50 | 51 | Rectangle { 52 | anchors.verticalCenter: parent.verticalCenter 53 | width: 15 54 | height: 3 55 | color: _d.isSelected ? "red" : "#3c85b5" 56 | opacity: _d.itemChildCount > 0 ? 1.0 : 0.0 57 | 58 | Rectangle { 59 | anchors.centerIn: parent 60 | width: 3 61 | height: 15 62 | color: "#3c85b5" 63 | visible: !_d.isOpen 64 | } 65 | } 66 | } 67 | 68 | ModelItem { 69 | content: _d.currentData 70 | onSelected: _treemodel.removeItemByIndex(_treemodel.index(index, 0, parentIndex)) //_d.isSelected = !_d.isSelected 71 | } 72 | 73 | } 74 | 75 | Rectangle { 76 | Layout.fillWidth: true 77 | Layout.leftMargin: -100 78 | 79 | height: 1 80 | color: "gray" 81 | opacity: 0.5 82 | } 83 | 84 | Loader { 85 | width: parent.width 86 | 87 | visible: _d.isOpen 88 | source: "TreeItem.qml" 89 | onLoaded: { 90 | item.parentIndex = _d.currentIndex 91 | item.childCount = _d.itemChildCount 92 | } 93 | } 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /sources/treemodel.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "treemodel.h" 3 | 4 | TreeModel::TreeModel(QObject *parent) 5 | : QAbstractItemModel(parent) 6 | , m_rootItem(std::make_shared()) 7 | {} 8 | 9 | int TreeModel::rowCount(const QModelIndex &parent) const 10 | { 11 | if (parent.isValid()) { 12 | TreeItem *p = static_cast(parent.internalPointer()); 13 | return p->childCount(); 14 | } 15 | return m_rootItem->childCount(); 16 | } 17 | 18 | int TreeModel::columnCount(const QModelIndex & /*parent*/) const 19 | { 20 | return 1; 21 | } 22 | 23 | QModelIndex TreeModel::index(const int row, const int column, const QModelIndex &parent) const 24 | { 25 | if (!hasIndex(row, column, parent)) 26 | return QModelIndex(); 27 | 28 | TreeItem *item = m_rootItem.get(); 29 | if (parent.isValid()) 30 | item = static_cast(parent.internalPointer()); 31 | 32 | auto child = item->child(row); 33 | if (child) 34 | return createIndex(row, column, child.get()); 35 | 36 | return QModelIndex(); 37 | } 38 | QModelIndex TreeModel::parent(const QModelIndex &index) const 39 | { 40 | if (!index.isValid()) 41 | return QModelIndex(); 42 | 43 | TreeItem *childItem = static_cast(index.internalPointer()); 44 | TreeItem *parentItem = childItem->parent().get(); 45 | 46 | if (parentItem == m_rootItem.get()) 47 | return QModelIndex(); 48 | 49 | return createIndex(parentItem->index(), 0, parentItem); 50 | } 51 | 52 | QVariant TreeModel::data(const QModelIndex &index, const int role) const 53 | { 54 | if (!index.isValid() || role != Qt::DisplayRole) { 55 | return QVariant(); 56 | } 57 | TreeItem *item = static_cast(index.internalPointer()); 58 | return item->data(); 59 | } 60 | 61 | TreeItem::ItemPtr TreeModel::addTreeItem(TreeItem::ItemPtr parent, QVariant data) 62 | { 63 | if (!parent) 64 | parent = m_rootItem; 65 | int indexToInsert = parent->childCount(); 66 | beginInsertRows(parent->parent() ? createIndex(parent->index(), 0, parent.get()) : QModelIndex(), 67 | indexToInsert, 68 | indexToInsert); 69 | auto newItem = parent->appendChild(data); 70 | endInsertRows(); 71 | 72 | return newItem; 73 | } 74 | 75 | TreeItem::ItemPtr TreeModel::addTreeItem(const QModelIndex &parentIndex, const QVariant &data) 76 | { 77 | TreeItem::ItemPtr parent; 78 | if (!parentIndex.isValid()) { 79 | parent = m_rootItem; 80 | } else { 81 | parent = static_cast(parentIndex.internalPointer())->shared_from_this(); 82 | } 83 | int indexToInsert = parent->childCount(); 84 | beginInsertRows(parentIndex, indexToInsert, indexToInsert); 85 | auto newItem = parent->appendChild(data); 86 | endInsertRows(); 87 | 88 | return newItem; 89 | } 90 | 91 | void TreeModel::removeItemByIndex(TreeItem::ItemPtr parent, int index) 92 | { 93 | if (parent->child(index)) { 94 | beginRemoveRows(parent->parent() ? createIndex(parent->index(), 0, parent.get()) 95 | : QModelIndex(), 96 | index, 97 | index); 98 | parent->removeChild(index); 99 | endRemoveRows(); 100 | } 101 | } 102 | 103 | void TreeModel::removeItemByIndex(const QModelIndex &index) 104 | { 105 | qDebug() << "index" << index << "count" << rowCount(index); 106 | if (!index.isValid()) { 107 | return; 108 | } 109 | 110 | TreeItem::ItemPtr item = static_cast(index.internalPointer())->shared_from_this(); 111 | TreeItem::ItemPtr parent = item->parent(); 112 | 113 | if (!parent) { 114 | return; 115 | } 116 | 117 | int rowIndex = index.row(); 118 | 119 | beginRemoveRows(parent->parent() ? createIndex(parent->index(), 0, parent.get()) : QModelIndex(), 120 | rowIndex, 121 | rowIndex); 122 | parent->removeChild(rowIndex); 123 | endRemoveRows(); 124 | } 125 | 126 | TreeItem::ItemPtr TreeModel::rootItem() const 127 | { 128 | return m_rootItem; 129 | } 130 | 131 | QModelIndex TreeModel::rootIndex() 132 | { 133 | return QModelIndex(); 134 | } 135 | --------------------------------------------------------------------------------