├── doc ├── .gitignore ├── screen-gantt.png └── implement-qabstractrangemodel.md ├── lib ├── .gitignore ├── qmlgantt.pro ├── qganttglobal.h ├── qganttmodelitem.cpp ├── qganttmodelitem.h ├── qabstractrangemodel.cpp ├── qganttmodel.h ├── qrangeview.h ├── qabstractrangemodel.h ├── qganttmodel.cpp └── qrangeview.cpp ├── sample ├── .gitignore ├── src │ ├── .gitignore │ ├── src.pri │ ├── qganttdata.cpp │ ├── qganttdata.h │ ├── qganttmodellist.h │ ├── main.cpp │ └── qganttmodellist.cpp ├── resource │ ├── .gitignore │ ├── img │ │ ├── help-add-item.png │ │ ├── help-edit-item.png │ │ ├── help-edit-row.png │ │ ├── help-move-item.png │ │ └── help-stretch-item.png │ ├── qml.qrc │ ├── GanttEditWindow.qml │ ├── GanttHelpWindow.qml │ ├── main.qml │ └── GanttLine.qml ├── deployment.pri └── sample.pro ├── tests ├── .gitignore ├── tests.pro ├── main.cpp ├── qganttmodeltest.h ├── qviewportbenchmarktest.h ├── qtestrunner.h ├── qtestrunner.cpp ├── qrangeviewtest.h ├── qganttmodeltest.cpp ├── qviewportbenchmarktest.cpp └── qrangeviewtest.cpp ├── .gitignore ├── LICENSE.LGPLv3 ├── qmlgantt.pro ├── appveyor.yml ├── .travis.yml └── README.md /doc/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sample/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sample/src/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | qmlgantt.pro.user -------------------------------------------------------------------------------- /sample/resource/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /LICENSE.LGPLv3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinusv/qml-gantt/HEAD/LICENSE.LGPLv3 -------------------------------------------------------------------------------- /doc/screen-gantt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinusv/qml-gantt/HEAD/doc/screen-gantt.png -------------------------------------------------------------------------------- /sample/resource/img/help-add-item.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinusv/qml-gantt/HEAD/sample/resource/img/help-add-item.png -------------------------------------------------------------------------------- /sample/resource/img/help-edit-item.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinusv/qml-gantt/HEAD/sample/resource/img/help-edit-item.png -------------------------------------------------------------------------------- /sample/resource/img/help-edit-row.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinusv/qml-gantt/HEAD/sample/resource/img/help-edit-row.png -------------------------------------------------------------------------------- /sample/resource/img/help-move-item.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinusv/qml-gantt/HEAD/sample/resource/img/help-move-item.png -------------------------------------------------------------------------------- /sample/resource/img/help-stretch-item.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dinusv/qml-gantt/HEAD/sample/resource/img/help-stretch-item.png -------------------------------------------------------------------------------- /qmlgantt.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | CONFIG += ordered 3 | 4 | SUBDIRS += $$PWD/lib/qmlgantt.pro 5 | SUBDIRS += $$PWD/sample/sample.pro 6 | SUBDIRS += $$PWD/tests/tests.pro 7 | -------------------------------------------------------------------------------- /sample/src/src.pri: -------------------------------------------------------------------------------- 1 | INCLUDEPATH += $$PWD 2 | 3 | SOURCES += \ 4 | $$PWD/main.cpp \ 5 | $$PWD/qganttdata.cpp \ 6 | $$PWD/qganttmodellist.cpp 7 | 8 | HEADERS += \ 9 | $$PWD/qganttdata.h \ 10 | $$PWD/qganttmodellist.h 11 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | os: unstable 2 | image: Visual Studio 2017 3 | 4 | platform: 5 | - x64 6 | 7 | install: 8 | - SET QTDIR=C:\Qt\5.13.2\mingw73_32 9 | - set PATH=%PATH%;%QTDIR%\bin;C:\Qt\Tools\mingw730_32 10 | build_script: 11 | - qmake -r qmlgantt.pro 12 | - mingw32-make 13 | -------------------------------------------------------------------------------- /sample/resource/qml.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | main.qml 4 | GanttEditWindow.qml 5 | GanttLine.qml 6 | GanttHelpWindow.qml 7 | img/help-add-item.png 8 | img/help-edit-item.png 9 | img/help-edit-row.png 10 | img/help-move-item.png 11 | img/help-stretch-item.png 12 | 13 | 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | 3 | os: 4 | - linux 5 | 6 | sudo: required 7 | dist: bionic 8 | 9 | addons: 10 | apt: 11 | sources: 12 | - ubuntu-toolchain-r-test 13 | packages: 14 | - libdc1394-22-dev 15 | - libgl1-mesa-dev 16 | 17 | before_install: 18 | - sudo add-apt-repository ppa:beineri/opt-qt-5.14.0-bionic -y 19 | - sudo apt-get update -qq 20 | install: 21 | - sudo apt-get install -qq qt514base qt514declarative qt514quickcontrols 22 | script: 23 | - source /opt/qt514/bin/qt514-env.sh 24 | - qmake -r 25 | - make 26 | - export LD_LIBRARY_PATH=$(pwd)/build 27 | - make check 28 | -------------------------------------------------------------------------------- /sample/deployment.pri: -------------------------------------------------------------------------------- 1 | android-no-sdk { 2 | target.path = /data/user/qt 3 | export(target.path) 4 | INSTALLS += target 5 | } else:android { 6 | x86 { 7 | target.path = /libs/x86 8 | } else: armeabi-v7a { 9 | target.path = /libs/armeabi-v7a 10 | } else { 11 | target.path = /libs/armeabi 12 | } 13 | export(target.path) 14 | INSTALLS += target 15 | } else:unix { 16 | isEmpty(target.path) { 17 | qnx { 18 | target.path = /tmp/$${TARGET}/bin 19 | } else { 20 | target.path = /opt/$${TARGET}/bin 21 | } 22 | export(target.path) 23 | } 24 | INSTALLS += target 25 | } 26 | 27 | export(INSTALLS) 28 | -------------------------------------------------------------------------------- /sample/sample.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | TARGET = sample 3 | 4 | QT += qml quick widgets 5 | 6 | RESOURCES += $$PWD/resource/qml.qrc 7 | 8 | # Additional import path used to resolve QML modules in Qt Creator's code model 9 | QML_IMPORT_PATH = 10 | 11 | 12 | # Configure Dependencies 13 | 14 | INCLUDEPATH += $$PWD/../lib 15 | DEPENDPATH += $$PWD/../lib 16 | 17 | win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../lib/release/ -lqmlgantt 18 | else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../lib/debug/ -lqmlgantt 19 | else:unix: LIBS += -L$$OUT_PWD/../build/ -lqmlgantt 20 | 21 | include($$PWD/src/src.pri) 22 | 23 | # Default rules for deployment. 24 | include($$PWD/deployment.pri) 25 | -------------------------------------------------------------------------------- /tests/tests.pro: -------------------------------------------------------------------------------- 1 | 2 | TARGET = test_qmlgantt 3 | TEMPLATE = app 4 | QT += qml quick testlib 5 | CONFIG += console testcase 6 | 7 | # Configure Dependencies 8 | 9 | INCLUDEPATH += $$PWD/../lib 10 | DEPENDPATH += $$PWD/../lib 11 | 12 | win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../lib/release/ -lqmlgantt 13 | else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../lib/debug/ -lqmlgantt 14 | else:unix: LIBS += -L$$OUT_PWD/../build/ -lqmlgantt 15 | 16 | HEADERS += \ 17 | qtestrunner.h \ 18 | qganttmodeltest.h \ 19 | qrangeviewtest.h \ 20 | qviewportbenchmarktest.h 21 | 22 | SOURCES += \ 23 | main.cpp \ 24 | qtestrunner.cpp \ 25 | qganttmodeltest.cpp \ 26 | qrangeviewtest.cpp \ 27 | qviewportbenchmarktest.cpp 28 | 29 | -------------------------------------------------------------------------------- /lib/qmlgantt.pro: -------------------------------------------------------------------------------- 1 | # Config 2 | 3 | TEMPLATE = lib 4 | TARGET = qmlgantt 5 | QT += qml quick 6 | CONFIG += qt 7 | 8 | DEFINES += Q_GANTT 9 | DEFINES += Q_GANTT_LIB 10 | 11 | # Destination 12 | 13 | win32:CONFIG(debug, debug|release): DLLDESTDIR = $$quote($$OUT_PWD/../build/debug) 14 | else:win32:CONFIG(release, debug|release): DLLDESTDIR = $$quote($$OUT_PWD/../build/release) 15 | else:unix: TARGET = $$quote($$OUT_PWD/../build/qmlgantt) 16 | 17 | # Files 18 | 19 | INCLUDEPATH += $$PWD 20 | 21 | HEADERS += \ 22 | $$PWD/qganttglobal.h \ 23 | $$PWD/qrangeview.h \ 24 | $$PWD/qabstractrangemodel.h \ 25 | $$PWD/qganttmodel.h \ 26 | $$PWD/qganttmodelitem.h 27 | 28 | SOURCES += \ 29 | $$PWD/qrangeview.cpp \ 30 | $$PWD/qabstractrangemodel.cpp \ 31 | $$PWD/qganttmodel.cpp \ 32 | $$PWD/qganttmodelitem.cpp 33 | -------------------------------------------------------------------------------- /sample/src/qganttdata.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2015-2016 Dinu SV. 4 | ** (contact: mail@dinusv.com) 5 | ** This file is part of QML Gantt library. 6 | ** 7 | ** GNU Lesser General Public License Usage 8 | ** This file may be used under the terms of the GNU Lesser 9 | ** General Public License version 3 as published by the Free Software 10 | ** Foundation and appearing in the file LICENSE.LGPLv3 included in the 11 | ** packaging of this file. Please review the following information to 12 | ** ensure the GNU Lesser General Public License version 3 requirements 13 | ** will be met: https://www.gnu.org/licenses/lgpl.html. 14 | ** 15 | ****************************************************************************/ 16 | 17 | #include "qganttdata.h" 18 | 19 | const QColor initColors[4] = {QColor("#bb4e4c"), QColor("#218c8d"), QColor("#4f83bd"), QColor("#e39541")}; 20 | 21 | QGanttData::QGanttData(QObject *parent) 22 | : QObject(parent) 23 | , m_color(initColors[rand() % 4]){ 24 | } 25 | 26 | QGanttData::~QGanttData(){ 27 | } 28 | 29 | -------------------------------------------------------------------------------- /tests/main.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2015-2016 Dinu SV. 4 | ** (contact: mail@dinusv.com) 5 | ** This file is part of QML Gantt library. 6 | ** 7 | ** GNU Lesser General Public License Usage 8 | ** This file may be used under the terms of the GNU Lesser 9 | ** General Public License version 3 as published by the Free Software 10 | ** Foundation and appearing in the file LICENSE.LGPLv3 included in the 11 | ** packaging of this file. Please review the following information to 12 | ** ensure the GNU Lesser General Public License version 3 requirements 13 | ** will be met: https://www.gnu.org/licenses/lgpl.html. 14 | ** 15 | ****************************************************************************/ 16 | 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | #include "qtestrunner.h" 23 | #include "qganttmodeltest.h" 24 | #include "qrangeviewtest.h" 25 | #include "qviewportbenchmarktest.h" 26 | 27 | int main(int argc, char *argv[]){ 28 | 29 | QCoreApplication app(argc, argv); 30 | app.setAttribute(Qt::AA_Use96Dpi, true); 31 | 32 | return QTestRunner::runTests(argc, argv); 33 | } 34 | -------------------------------------------------------------------------------- /lib/qganttglobal.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2015-2016 Dinu SV. 4 | ** (contact: mail@dinusv.com) 5 | ** This file is part of QML Gantt library. 6 | ** 7 | ** GNU Lesser General Public License Usage 8 | ** This file may be used under the terms of the GNU Lesser 9 | ** General Public License version 3 as published by the Free Software 10 | ** Foundation and appearing in the file LICENSE.LGPLv3 included in the 11 | ** packaging of this file. Please review the following information to 12 | ** ensure the GNU Lesser General Public License version 3 requirements 13 | ** will be met: https://www.gnu.org/licenses/lgpl.html. 14 | ** 15 | ****************************************************************************/ 16 | 17 | #ifndef QGANTTGLOBAL_H 18 | #define QGANTTGLOBAL_H 19 | 20 | #include 21 | 22 | #define QML_GANTT_VERSION_MAJOR 0 23 | #define QML_GANTT_VERSION_MINOR 1 24 | #define QML_GANTT_VERSION_PATCH 0 25 | 26 | #ifndef Q_GANTT_STATIC 27 | # ifdef Q_GANTT_LIB 28 | # define Q_GANTT_EXPORT Q_DECL_EXPORT 29 | # else 30 | # define Q_GANTT_EXPORT Q_DECL_IMPORT 31 | # endif 32 | #else 33 | # define Q_GANTT_EXPORT 34 | #endif 35 | 36 | #endif // QGANTTGLOBAL_H 37 | -------------------------------------------------------------------------------- /lib/qganttmodelitem.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2015-2016 Dinu SV. 4 | ** (contact: mail@dinusv.com) 5 | ** This file is part of QML Gantt library. 6 | ** 7 | ** GNU Lesser General Public License Usage 8 | ** This file may be used under the terms of the GNU Lesser 9 | ** General Public License version 3 as published by the Free Software 10 | ** Foundation and appearing in the file LICENSE.LGPLv3 included in the 11 | ** packaging of this file. Please review the following information to 12 | ** ensure the GNU Lesser General Public License version 3 requirements 13 | ** will be met: https://www.gnu.org/licenses/lgpl.html. 14 | ** 15 | ****************************************************************************/ 16 | 17 | #include "qganttmodelitem.h" 18 | #include 19 | 20 | QGanttModelItem::QGanttModelItem(QObject *parent) 21 | : QObject(parent) 22 | , m_position(0) 23 | , m_length(0) 24 | { 25 | } 26 | 27 | QGanttModelItem::QGanttModelItem(qint64 position, qint64 length, QObject* parent) 28 | : QObject(parent) 29 | , m_position(position) 30 | , m_length(length) 31 | { 32 | } 33 | 34 | QGanttModelItem::~QGanttModelItem(){ 35 | if ( !m_data.isNull() && m_data.canConvert()) 36 | delete m_data.value(); 37 | } 38 | -------------------------------------------------------------------------------- /tests/qganttmodeltest.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2015-2016 Dinu SV. 4 | ** (contact: mail@dinusv.com) 5 | ** This file is part of QML Gantt library. 6 | ** 7 | ** GNU Lesser General Public License Usage 8 | ** This file may be used under the terms of the GNU Lesser 9 | ** General Public License version 3 as published by the Free Software 10 | ** Foundation and appearing in the file LICENSE.LGPLv3 included in the 11 | ** packaging of this file. Please review the following information to 12 | ** ensure the GNU Lesser General Public License version 3 requirements 13 | ** will be met: https://www.gnu.org/licenses/lgpl.html. 14 | ** 15 | ****************************************************************************/ 16 | 17 | #ifndef QGANTTMODELTEST_HPP 18 | #define QGANTTMODELTEST_HPP 19 | 20 | #include 21 | #include "qtestrunner.h" 22 | 23 | class QGanttModelTest : public QObject{ 24 | 25 | Q_OBJECT 26 | Q_TEST_RUNNER_SUITE 27 | 28 | public: 29 | explicit QGanttModelTest(QObject *parent = 0); 30 | ~QGanttModelTest(); 31 | 32 | private slots: 33 | void initTestCase(); 34 | void descendingTest(); 35 | void alternateTest(); 36 | void sameLengthTest(); 37 | void sameLengthTest2(); 38 | 39 | }; 40 | 41 | #endif // QGANTTMODELTEST_HPP 42 | -------------------------------------------------------------------------------- /tests/qviewportbenchmarktest.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2015-2016 Dinu SV. 4 | ** (contact: mail@dinusv.com) 5 | ** This file is part of QML Gantt library. 6 | ** 7 | ** GNU Lesser General Public License Usage 8 | ** This file may be used under the terms of the GNU Lesser 9 | ** General Public License version 3 as published by the Free Software 10 | ** Foundation and appearing in the file LICENSE.LGPLv3 included in the 11 | ** packaging of this file. Please review the following information to 12 | ** ensure the GNU Lesser General Public License version 3 requirements 13 | ** will be met: https://www.gnu.org/licenses/lgpl.html. 14 | ** 15 | ****************************************************************************/ 16 | 17 | #ifndef QVIEWPORTBENCHMARKTEST_HPP 18 | #define QVIEWPORTBENCHMARKTEST_HPP 19 | 20 | #include 21 | #include "qtestrunner.h" 22 | 23 | class QViewportBenchmarkTest : public QObject{ 24 | 25 | Q_OBJECT 26 | Q_TEST_RUNNER_SUITE 27 | 28 | public: 29 | static const int CONTENT_WIDTH = 20000; 30 | static const int VIEWPORT_WIDTH = 2000; 31 | static const int ITEM_WIDTH = 1; 32 | static const int SCROLL_ITERATIONS = 6000; 33 | 34 | public: 35 | QViewportBenchmarkTest(QObject* parent = 0); 36 | ~QViewportBenchmarkTest(); 37 | 38 | private slots: 39 | void initTestCase(); 40 | void minimalDelegateInsertionTest(); 41 | void minimalDelegateViewTest(); 42 | void complexDelegateInsertionTest(); 43 | void complexDelegateViewTest(); 44 | 45 | }; 46 | 47 | #endif // QVIEWPORTBENCHMARKTEST_HPP 48 | -------------------------------------------------------------------------------- /tests/qtestrunner.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2015-2016 Dinu SV. 4 | ** (contact: mail@dinusv.com) 5 | ** This file is part of QML Gantt library. 6 | ** 7 | ** GNU Lesser General Public License Usage 8 | ** This file may be used under the terms of the GNU Lesser 9 | ** General Public License version 3 as published by the Free Software 10 | ** Foundation and appearing in the file LICENSE.LGPLv3 included in the 11 | ** packaging of this file. Please review the following information to 12 | ** ensure the GNU Lesser General Public License version 3 requirements 13 | ** will be met: https://www.gnu.org/licenses/lgpl.html. 14 | ** 15 | ****************************************************************************/ 16 | 17 | #ifndef QTESTRUNNER_HPP 18 | #define QTESTRUNNER_HPP 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | class QTestRunner{ 25 | 26 | public: 27 | static int registerTest(QObject* test); 28 | static int runTests(int argc, char *argv[]); 29 | static int runTest(int index, int argc, char* argv[]); 30 | static int totalRegisteredTests(); 31 | static void cleanupTests(); 32 | 33 | private: 34 | static QList >* m_tests; 35 | }; 36 | 37 | inline int QTestRunner::totalRegisteredTests(){ 38 | return m_tests->size(); 39 | } 40 | 41 | #define Q_TEST_RUNNER_SUITE \ 42 | public:\ 43 | static const int testIndex; 44 | 45 | #define Q_TEST_RUNNER_REGISTER(className) \ 46 | const int className::testIndex = QTestRunner::registerTest(new className) 47 | 48 | 49 | #endif // QTESTRUNNER_HPP 50 | -------------------------------------------------------------------------------- /tests/qtestrunner.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2015-2016 Dinu SV. 4 | ** (contact: mail@dinusv.com) 5 | ** This file is part of QML Gantt library. 6 | ** 7 | ** GNU Lesser General Public License Usage 8 | ** This file may be used under the terms of the GNU Lesser 9 | ** General Public License version 3 as published by the Free Software 10 | ** Foundation and appearing in the file LICENSE.LGPLv3 included in the 11 | ** packaging of this file. Please review the following information to 12 | ** ensure the GNU Lesser General Public License version 3 requirements 13 | ** will be met: https://www.gnu.org/licenses/lgpl.html. 14 | ** 15 | ****************************************************************************/ 16 | 17 | #include "qtestrunner.h" 18 | #include 19 | 20 | QList >* QTestRunner::m_tests = 0; 21 | 22 | int QTestRunner::registerTest(QObject* test){ 23 | if ( !m_tests ){ 24 | m_tests = new QList >; 25 | atexit(&cleanupTests); 26 | } 27 | m_tests->append(QSharedPointer(test)); 28 | return 0; 29 | } 30 | 31 | int QTestRunner::runTests(int argc, char *argv[]){ 32 | int code = 0; 33 | for ( QList >::iterator it = m_tests->begin(); it != m_tests->end(); ++it ){ 34 | code += QTest::qExec(it->data(), argc, argv); 35 | } 36 | return code; 37 | } 38 | 39 | int QTestRunner::runTest(int index, int argc, char* argv[]){ 40 | if ( index > m_tests->size() ) 41 | return -1; 42 | return QTest::qExec((*m_tests)[index].data(), argc, argv); 43 | } 44 | 45 | void QTestRunner::cleanupTests(){ 46 | delete m_tests; 47 | } 48 | -------------------------------------------------------------------------------- /tests/qrangeviewtest.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2015-2016 Dinu SV. 4 | ** (contact: mail@dinusv.com) 5 | ** This file is part of QML Gantt library. 6 | ** 7 | ** GNU Lesser General Public License Usage 8 | ** This file may be used under the terms of the GNU Lesser 9 | ** General Public License version 3 as published by the Free Software 10 | ** Foundation and appearing in the file LICENSE.LGPLv3 included in the 11 | ** packaging of this file. Please review the following information to 12 | ** ensure the GNU Lesser General Public License version 3 requirements 13 | ** will be met: https://www.gnu.org/licenses/lgpl.html. 14 | ** 15 | ****************************************************************************/ 16 | 17 | #ifndef QRANGEVIEWTEST_H 18 | #define QRANGEVIEWTEST_H 19 | 20 | #include 21 | #include "qtestrunner.h" 22 | 23 | class QQmlEngine; 24 | class QQmlContext; 25 | class QQmlComponent; 26 | class QQuickItem; 27 | class QRangeView; 28 | class QRangeViewTest : public QObject{ 29 | 30 | Q_OBJECT 31 | Q_TEST_RUNNER_SUITE 32 | 33 | public: 34 | explicit QRangeViewTest(QObject *parent = 0); 35 | ~QRangeViewTest(); 36 | 37 | private slots: 38 | void initTestCase(); 39 | void cleanupTestCase(); 40 | void lookupTest(); 41 | void edgeLookupTest(); 42 | void modelItemChangeTest(); 43 | void modelItemPositionChangeTest(); 44 | void modelItemLengthChangeTest(); 45 | void modelItemDataChangeTest(); 46 | 47 | private: 48 | QQmlEngine* m_engine; 49 | QQmlContext* m_context; 50 | QQmlComponent* m_viewDelegate; 51 | QQuickItem* m_container; 52 | 53 | private: 54 | QRangeView* createView(); 55 | QVariant getModelData(QRangeView* view, qint64 startPos, qint64 endPos, int role); 56 | }; 57 | 58 | #endif // QRANGEVIEWTEST_H 59 | -------------------------------------------------------------------------------- /sample/src/qganttdata.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2015-2016 Dinu SV. 4 | ** (contact: mail@dinusv.com) 5 | ** This file is part of QML Gantt library. 6 | ** 7 | ** GNU Lesser General Public License Usage 8 | ** This file may be used under the terms of the GNU Lesser 9 | ** General Public License version 3 as published by the Free Software 10 | ** Foundation and appearing in the file LICENSE.LGPLv3 included in the 11 | ** packaging of this file. Please review the following information to 12 | ** ensure the GNU Lesser General Public License version 3 requirements 13 | ** will be met: https://www.gnu.org/licenses/lgpl.html. 14 | ** 15 | ****************************************************************************/ 16 | 17 | #ifndef QGANTTDATA_H 18 | #define QGANTTDATA_H 19 | 20 | #include 21 | #include 22 | 23 | class QGanttData : public QObject{ 24 | 25 | Q_OBJECT 26 | Q_PROPERTY(QString label READ label WRITE setLabel NOTIFY labelChanged) 27 | Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) 28 | 29 | public: 30 | explicit QGanttData(QObject *parent = 0); 31 | ~QGanttData(); 32 | 33 | QString label() const; 34 | QColor color() const; 35 | 36 | signals: 37 | void labelChanged(QString arg); 38 | void colorChanged(QColor arg); 39 | 40 | public slots: 41 | void setLabel(QString arg); 42 | void setColor(QColor arg); 43 | 44 | private: 45 | QString m_label; 46 | QColor m_color; 47 | }; 48 | 49 | inline QString QGanttData::label() const{ 50 | return m_label; 51 | } 52 | 53 | inline QColor QGanttData::color() const{ 54 | return m_color; 55 | } 56 | 57 | inline void QGanttData::setLabel(QString arg){ 58 | if (m_label == arg) 59 | return; 60 | 61 | m_label = arg; 62 | emit labelChanged(arg); 63 | } 64 | 65 | inline void QGanttData::setColor(QColor arg){ 66 | if (m_color == arg) 67 | return; 68 | 69 | m_color = arg; 70 | emit colorChanged(arg); 71 | } 72 | 73 | #endif // QGANTTDATA_H 74 | -------------------------------------------------------------------------------- /sample/src/qganttmodellist.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2015-2016 Dinu SV. 4 | ** (contact: mail@dinusv.com) 5 | ** This file is part of QML Gantt library. 6 | ** 7 | ** GNU Lesser General Public License Usage 8 | ** This file may be used under the terms of the GNU Lesser 9 | ** General Public License version 3 as published by the Free Software 10 | ** Foundation and appearing in the file LICENSE.LGPLv3 included in the 11 | ** packaging of this file. Please review the following information to 12 | ** ensure the GNU Lesser General Public License version 3 requirements 13 | ** will be met: https://www.gnu.org/licenses/lgpl.html. 14 | ** 15 | ****************************************************************************/ 16 | 17 | #ifndef QGANTTMODELLIST_H 18 | #define QGANTTMODELLIST_H 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | class QGanttModel; 25 | class QGanttModelContainer; 26 | class QGanttModelList : public QAbstractListModel{ 27 | 28 | Q_OBJECT 29 | Q_PROPERTY(qint64 contentWidth READ contentWidth NOTIFY contentWidthChanged) 30 | 31 | public: 32 | enum Roles{ 33 | Name = Qt::UserRole + 1, 34 | GanttModel 35 | }; 36 | 37 | public: 38 | QGanttModelList(QObject *parent = 0); 39 | QGanttModelList(qint64 contentWidth, QObject* parent = 0); 40 | ~QGanttModelList(); 41 | 42 | QVariant data(const QModelIndex &index, int role) const; 43 | bool setData(const QModelIndex &index, const QVariant &value, int role); 44 | int rowCount(const QModelIndex &parent) const; 45 | virtual QHash roleNames() const; 46 | 47 | void clearAll(); 48 | 49 | void appendModel(QGanttModel* model); 50 | 51 | bool insertRows(int row, int count, const QModelIndex &parent); 52 | bool removeRows(int row, int count, const QModelIndex &parent); 53 | 54 | qint64 contentWidth() const; 55 | void setContentWidth(qint64 arg); 56 | 57 | signals: 58 | void contentWidthChanged(qint64 arg); 59 | 60 | private: 61 | QList m_items; 62 | QHash m_roles; 63 | 64 | qint64 m_contentWidth; 65 | }; 66 | 67 | inline int QGanttModelList::rowCount(const QModelIndex &) const{ 68 | return m_items.size(); 69 | } 70 | 71 | inline qint64 QGanttModelList::contentWidth() const{ 72 | return m_contentWidth; 73 | } 74 | 75 | inline void QGanttModelList::setContentWidth(qint64 arg){ 76 | if (m_contentWidth == arg) 77 | return; 78 | m_contentWidth = arg; 79 | emit contentWidthChanged(arg); 80 | } 81 | 82 | #endif // QGANTTMODELLIST_H 83 | -------------------------------------------------------------------------------- /lib/qganttmodelitem.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2015-2016 Dinu SV. 4 | ** (contact: mail@dinusv.com) 5 | ** This file is part of QML Gantt library. 6 | ** 7 | ** GNU Lesser General Public License Usage 8 | ** This file may be used under the terms of the GNU Lesser 9 | ** General Public License version 3 as published by the Free Software 10 | ** Foundation and appearing in the file LICENSE.LGPLv3 included in the 11 | ** packaging of this file. Please review the following information to 12 | ** ensure the GNU Lesser General Public License version 3 requirements 13 | ** will be met: https://www.gnu.org/licenses/lgpl.html. 14 | ** 15 | ****************************************************************************/ 16 | 17 | #ifndef QGANTTMODELITEM_H 18 | #define QGANTTMODELITEM_H 19 | 20 | #include "qganttglobal.h" 21 | #include 22 | #include 23 | 24 | class Q_GANTT_EXPORT QGanttModelItem : public QObject{ 25 | 26 | Q_OBJECT 27 | Q_PROPERTY(qint64 position READ position NOTIFY positionChanged) 28 | Q_PROPERTY(qint64 length READ length NOTIFY lengthChanged) 29 | Q_PROPERTY(QVariant data READ data NOTIFY dataChanged) 30 | 31 | public: 32 | explicit QGanttModelItem(QObject *parent = 0); 33 | QGanttModelItem(qint64 position, qint64 length, QObject* parent = 0); 34 | virtual ~QGanttModelItem(); 35 | 36 | qint64 position() const; 37 | qint64 length() const; 38 | const QVariant& data() const; 39 | 40 | void setPosition(qint64 arg); 41 | void setLength(qint64 arg); 42 | void setData(const QVariant& data); 43 | 44 | signals: 45 | void positionChanged(); 46 | void lengthChanged(); 47 | void dataChanged(); 48 | 49 | private: 50 | qint64 m_position; 51 | qint64 m_length; 52 | QVariant m_data; 53 | }; 54 | 55 | inline qint64 QGanttModelItem::position() const{ 56 | return m_position; 57 | } 58 | 59 | inline qint64 QGanttModelItem::length() const{ 60 | return m_length; 61 | } 62 | 63 | inline void QGanttModelItem::setPosition(qint64 arg){ 64 | if (m_position == arg) 65 | return; 66 | 67 | m_position = arg; 68 | emit positionChanged(); 69 | } 70 | 71 | inline void QGanttModelItem::setLength(qint64 arg){ 72 | if (m_length == arg) 73 | return; 74 | 75 | m_length = arg; 76 | emit lengthChanged(); 77 | } 78 | 79 | inline const QVariant& QGanttModelItem::data() const{ 80 | return m_data; 81 | } 82 | 83 | inline void QGanttModelItem::setData(const QVariant &data){ 84 | if ( data != m_data ){ 85 | m_data = data; 86 | emit dataChanged(); 87 | } 88 | } 89 | 90 | #endif // QGANTTMODELITEM_H 91 | -------------------------------------------------------------------------------- /sample/src/main.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2015-2016 Dinu SV. 4 | ** (contact: mail@dinusv.com) 5 | ** This file is part of QML Gantt library. 6 | ** 7 | ** GNU Lesser General Public License Usage 8 | ** This file may be used under the terms of the GNU Lesser 9 | ** General Public License version 3 as published by the Free Software 10 | ** Foundation and appearing in the file LICENSE.LGPLv3 included in the 11 | ** packaging of this file. Please review the following information to 12 | ** ensure the GNU Lesser General Public License version 3 requirements 13 | ** will be met: https://www.gnu.org/licenses/lgpl.html. 14 | ** 15 | ****************************************************************************/ 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | #include "qrangeview.h" 22 | #include "qganttmodel.h" 23 | #include "qganttmodelitem.h" 24 | #include "qganttmodellist.h" 25 | #include "qganttdata.h" 26 | 27 | static int CONFIGURATION_MODEL_SIZE = 20000; 28 | 29 | int randBetween(int min, int max){ 30 | return rand() % (max - min + 1) + min; 31 | } 32 | 33 | QVariant createModelData(){ 34 | return QVariant::fromValue(new QGanttData); 35 | } 36 | 37 | 38 | int main(int argc, char *argv[]) 39 | { 40 | QApplication app(argc, argv); 41 | 42 | qmlRegisterUncreatableType( 43 | "Gantt", 1, 0, "AbstractRangeModel", "AbstractRangeModel is of abstract type."); 44 | qmlRegisterType( "Gantt", 1, 0, "RangeView"); 45 | qmlRegisterType( "Gantt", 1, 0, "GanttModel"); 46 | qmlRegisterType("Gantt", 1, 0, "GanttModelItem"); 47 | qmlRegisterType( "Gantt", 1, 0, "GanttData"); 48 | qmlRegisterType("Gantt", 1, 0, "GanttModelList"); 49 | 50 | QGanttModelList modelList(CONFIGURATION_MODEL_SIZE); 51 | 52 | for( int i = 0; i < 100; ++i ){ 53 | QGanttModel* m = new QGanttModel; 54 | m->setItemDataFactoryFunction(&createModelData); 55 | m->setContentWidth(CONFIGURATION_MODEL_SIZE); 56 | 57 | int pos = 0, length = 0; 58 | while ( pos < CONFIGURATION_MODEL_SIZE - 320 ){ 59 | pos += randBetween(40, 300); 60 | length = randBetween(40, 120); 61 | m->insertItem(pos, length); 62 | pos += length; 63 | } 64 | 65 | modelList.appendModel(m); 66 | } 67 | 68 | QQmlApplicationEngine engine; 69 | engine.rootContext()->setContextProperty("ganttModelList", &modelList); 70 | engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); 71 | 72 | return app.exec(); 73 | } 74 | -------------------------------------------------------------------------------- /lib/qabstractrangemodel.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2015-2016 Dinu SV. 4 | ** (contact: mail@dinusv.com) 5 | ** This file is part of QML Gantt library. 6 | ** 7 | ** GNU Lesser General Public License Usage 8 | ** This file may be used under the terms of the GNU Lesser 9 | ** General Public License version 3 as published by the Free Software 10 | ** Foundation and appearing in the file LICENSE.LGPLv3 included in the 11 | ** packaging of this file. Please review the following information to 12 | ** ensure the GNU Lesser General Public License version 3 requirements 13 | ** will be met: https://www.gnu.org/licenses/lgpl.html. 14 | ** 15 | ****************************************************************************/ 16 | 17 | #include "qabstractrangemodel.h" 18 | 19 | // QAbstractRangeModelPrivate 20 | // ---------------------------------------------------------------------------- 21 | 22 | class QAbstractRangeModelPrivate{ 23 | public: 24 | class QRangeModelChange{ 25 | public: 26 | qint64 startPosition; 27 | qint64 endPosition; 28 | }; 29 | 30 | public: 31 | QAbstractRangeModelPrivate() : lastChange(0){} 32 | 33 | QRangeModelChange* change(qint64 startPosition, qint64 endPosition); 34 | QRangeModelChange* lastChange; 35 | }; 36 | 37 | 38 | QAbstractRangeModelPrivate::QRangeModelChange* QAbstractRangeModelPrivate::change( 39 | qint64 startPosition, 40 | qint64 endPosition) 41 | { 42 | QRangeModelChange* change = new QRangeModelChange; 43 | change->startPosition = startPosition; 44 | change->endPosition = endPosition; 45 | return change; 46 | } 47 | 48 | 49 | // QAbstractRangeModel 50 | // ---------------------------------------------------------------------------- 51 | 52 | QAbstractRangeModel::QAbstractRangeModel(QObject* parent) 53 | : QObject(parent) 54 | , d_ptr(new QAbstractRangeModelPrivate) 55 | { 56 | } 57 | 58 | QAbstractRangeModel::~QAbstractRangeModel(){ 59 | delete d_ptr; 60 | } 61 | 62 | void QAbstractRangeModel::beginDataChange(qint64 startPosition, qint64 endPosition){ 63 | Q_D(QAbstractRangeModel); 64 | if ( d->lastChange != 0 ) 65 | delete d->lastChange; 66 | d->lastChange = d->change(startPosition, endPosition); 67 | emit itemsAboutToBeChanged(startPosition, endPosition); 68 | } 69 | 70 | void QAbstractRangeModel::endDataChange(){ 71 | Q_D(QAbstractRangeModel); 72 | if ( d->lastChange != 0 ){ 73 | emit itemsChanged(d->lastChange->startPosition, d->lastChange->endPosition); 74 | delete d->lastChange; 75 | d->lastChange = 0; 76 | } 77 | } 78 | 79 | void QAbstractRangeModel::insertItem(qint64, qint64){} 80 | 81 | void QAbstractRangeModel::removeItem(qint64, qint64, qint64){} 82 | -------------------------------------------------------------------------------- /lib/qganttmodel.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2015-2016 Dinu SV. 4 | ** (contact: mail@dinusv.com) 5 | ** This file is part of QML Gantt library. 6 | ** 7 | ** GNU Lesser General Public License Usage 8 | ** This file may be used under the terms of the GNU Lesser 9 | ** General Public License version 3 as published by the Free Software 10 | ** Foundation and appearing in the file LICENSE.LGPLv3 included in the 11 | ** packaging of this file. Please review the following information to 12 | ** ensure the GNU Lesser General Public License version 3 requirements 13 | ** will be met: https://www.gnu.org/licenses/lgpl.html. 14 | ** 15 | ****************************************************************************/ 16 | 17 | #ifndef QGANTTMODEL_H 18 | #define QGANTTMODEL_H 19 | 20 | #include "qganttglobal.h" 21 | #include "qabstractrangemodel.h" 22 | 23 | // QGanttModelIterator 24 | // ------------------- 25 | 26 | class QGanttModelItem; 27 | class Q_GANTT_EXPORT QGanttModelIterator : public QAbstractRangeModelIterator{ 28 | 29 | public: 30 | QGanttModelIterator( 31 | QList::iterator it, 32 | QList::iterator endIt, 33 | qint64 endPosition) 34 | : m_endIt(endIt) 35 | , m_currentIt(it) 36 | , m_endPosition(endPosition) 37 | {} 38 | 39 | bool isEnd() const; 40 | void nextItem(); 41 | 42 | QVariant data(int role = 0); 43 | 44 | private: 45 | QList::iterator m_endIt; 46 | QList::iterator m_currentIt; 47 | qint64 m_endPosition; 48 | 49 | }; 50 | 51 | // QGanttModel 52 | // ----------- 53 | 54 | class QGanttModelPrivate; 55 | class Q_GANTT_EXPORT QGanttModel : public QAbstractRangeModel{ 56 | 57 | Q_OBJECT 58 | 59 | public: 60 | typedef QVariant (*ItemDataFactoryFunction)(); 61 | 62 | enum Role{ 63 | modelData = QAbstractRangeModel::LengthRole + 1 64 | }; 65 | 66 | public: 67 | explicit QGanttModel(QObject *parent = 0); 68 | ~QGanttModel(); 69 | 70 | QAbstractRangeModelIterator* dataBetween(qint64 startPosition, qint64 endPosition); 71 | 72 | void setItemPosition(qint64 itemPosition, qint64 itemLength, int positionIndex, qint64 itemNewPosition); 73 | void setItemLength(qint64 itemPosition, qint64 itemLength, int positionIndex, qint64 newLength); 74 | void setItemData(qint64 itemPosition, qint64 itemLength, int positionIndex, int role, const QVariant& value); 75 | 76 | QHash roleNames() const; 77 | 78 | int insertItem(QGanttModelItem* item); 79 | 80 | void setItemDataFactoryFunction(ItemDataFactoryFunction fp); 81 | 82 | public slots: 83 | void insertItem(qint64 position, qint64 length); 84 | void removeItem(qint64 position, qint64 length, qint64 relativeIndex); 85 | 86 | private: 87 | QGanttModel(const QGanttModel&); 88 | QGanttModel& operator = (const QGanttModel&); 89 | 90 | QGanttModelPrivate* const d_ptr; 91 | Q_DECLARE_PRIVATE(QGanttModel) 92 | 93 | }; 94 | 95 | #endif // QGANTTMODEL_H 96 | -------------------------------------------------------------------------------- /sample/src/qganttmodellist.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2015-2016 Dinu SV. 4 | ** (contact: mail@dinusv.com) 5 | ** This file is part of QML Gantt library. 6 | ** 7 | ** GNU Lesser General Public License Usage 8 | ** This file may be used under the terms of the GNU Lesser 9 | ** General Public License version 3 as published by the Free Software 10 | ** Foundation and appearing in the file LICENSE.LGPLv3 included in the 11 | ** packaging of this file. Please review the following information to 12 | ** ensure the GNU Lesser General Public License version 3 requirements 13 | ** will be met: https://www.gnu.org/licenses/lgpl.html. 14 | ** 15 | ****************************************************************************/ 16 | 17 | #include "qganttmodellist.h" 18 | #include "qganttmodel.h" 19 | 20 | #include 21 | class QGanttModelContainer{ 22 | public: 23 | ~QGanttModelContainer(){ delete model; } 24 | 25 | QString name; 26 | QGanttModel* model; 27 | }; 28 | 29 | 30 | QGanttModelList::QGanttModelList(QObject* parent) 31 | : QAbstractListModel(parent) 32 | { 33 | } 34 | 35 | QGanttModelList::QGanttModelList(qint64 size, QObject *parent) 36 | : QAbstractListModel(parent) 37 | , m_contentWidth(size) 38 | { 39 | } 40 | 41 | QGanttModelList::~QGanttModelList(){ 42 | clearAll(); 43 | } 44 | 45 | QVariant QGanttModelList::data(const QModelIndex &index, int role) const{ 46 | if ( index.row() >= m_items.size() ) 47 | return QVariant(); 48 | if ( role == QGanttModelList::Name ) 49 | return m_items[index.row()]->name; 50 | else if ( role == QGanttModelList::GanttModel ) 51 | return QVariant::fromValue(m_items[index.row()]->model); 52 | else 53 | return QVariant(); 54 | } 55 | 56 | bool QGanttModelList::setData(const QModelIndex &index, const QVariant &value, int role){ 57 | if ( index.row() >= m_items.size() ) 58 | return false; 59 | if ( role == QGanttModelList::Name ){ 60 | m_items[index.row()]->name = value.toString(); 61 | emit dataChanged(index, index, QVector() << role); 62 | return true; 63 | } else 64 | return false; 65 | } 66 | 67 | QHash QGanttModelList::roleNames() const{ 68 | QHash rolenames; 69 | rolenames[Name] = "name"; 70 | rolenames[GanttModel] = "ganttModel"; 71 | return rolenames; 72 | } 73 | 74 | void QGanttModelList::clearAll(){ 75 | for ( QList::Iterator it = m_items.begin(); it != m_items.end(); ++it ) 76 | delete *it; 77 | m_items.clear(); 78 | } 79 | 80 | void QGanttModelList::appendModel(QGanttModel *model){ 81 | QGanttModelContainer* item = new QGanttModelContainer; 82 | item->model = model; 83 | item->name = "Gantt Row #" + QString::number(m_items.size()); 84 | m_items.append(item); 85 | } 86 | 87 | bool QGanttModelList::insertRows(int row, int count, const QModelIndex &parent){ 88 | beginInsertRows(parent, row, row + count - 1); 89 | for ( int i = row; i < row + count - 1; ++i ){ 90 | QGanttModelContainer* item = new QGanttModelContainer; 91 | item->model = new QGanttModel; 92 | item->model->setContentWidth(m_contentWidth); 93 | item->name = "Gantt Row"; 94 | m_items.insert(i, item); 95 | } 96 | endInsertRows(); 97 | return true; 98 | } 99 | 100 | bool QGanttModelList::removeRows(int row, int count, const QModelIndex &parent){ 101 | beginRemoveRows(parent, row, row + count - 1); 102 | for ( int i = row + count - 1; i <= row; --i ) 103 | m_items.removeAt(i); 104 | endRemoveRows(); 105 | return true; 106 | } 107 | 108 | -------------------------------------------------------------------------------- /lib/qrangeview.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2015-2016 Dinu SV. 4 | ** (contact: mail@dinusv.com) 5 | ** This file is part of QML Gantt library. 6 | ** 7 | ** GNU Lesser General Public License Usage 8 | ** This file may be used under the terms of the GNU Lesser 9 | ** General Public License version 3 as published by the Free Software 10 | ** Foundation and appearing in the file LICENSE.LGPLv3 included in the 11 | ** packaging of this file. Please review the following information to 12 | ** ensure the GNU Lesser General Public License version 3 requirements 13 | ** will be met: https://www.gnu.org/licenses/lgpl.html. 14 | ** 15 | ****************************************************************************/ 16 | 17 | #ifndef QRANGEVIEW_H 18 | #define QRANGEVIEW_H 19 | 20 | #include "qganttglobal.h" 21 | #include "qabstractrangemodel.h" 22 | 23 | #include 24 | #include 25 | 26 | class QRangeViewPrivate; 27 | class Q_GANTT_EXPORT QRangeView : public QQuickItem{ 28 | 29 | Q_OBJECT 30 | Q_PROPERTY(QQmlComponent* delegate READ delegate WRITE setDelegate NOTIFY delegateChanged) 31 | Q_PROPERTY(QAbstractRangeModel* model READ model WRITE setModel NOTIFY modelChanged) 32 | Q_PROPERTY(qint64 viewportX READ viewportX WRITE setViewportX NOTIFY viewportXChanged) 33 | Q_PROPERTY(qint64 viewportWidth READ viewportWidth WRITE setViewportWidth NOTIFY viewportWidthChanged) 34 | 35 | public: 36 | explicit QRangeView(QQuickItem *parent = 0); 37 | ~QRangeView(); 38 | 39 | QQmlComponent* delegate() const; 40 | void setDelegate(QQmlComponent* delegate); 41 | 42 | qint64 viewportWidth() const; 43 | qint64 viewportX() const; 44 | 45 | QAbstractRangeModel* model() const; 46 | 47 | signals: 48 | void delegateChanged(); 49 | void viewportWidthChanged(qint64 arg); 50 | void viewportXChanged(qint64 arg); 51 | void modelChanged(QAbstractRangeModel* arg); 52 | 53 | public slots: 54 | void setViewportWidth(qint64 arg); 55 | void setViewportX(qint64 arg); 56 | void setModel(QAbstractRangeModel* arg); 57 | 58 | void modelWidthChanged(); 59 | void modelitemCoordinatesChanged( 60 | qint64 oldPosition, qint64 oldLength, int relativeIndex, 61 | QAbstractRangeModelIterator* modelIt, int newRelativeIndex); 62 | void modelItemDataChanged(QAbstractRangeModelIterator* modelIt, int relativeIndex, QList roles); 63 | void modelItemsAboutToBeChanged(qint64 beginPosition, qint64 endPosition); 64 | void modelItemsChanged(qint64 beginPosition, qint64 endPosition); 65 | 66 | void setPositionViaDelegate(QQuickItem* delegate, qint64 position); 67 | void setLengthViaDelegate(QQuickItem* delegate, qint64 length); 68 | void setDataViaDelegate(QQuickItem* delegate, const QString& role, const QVariant& value); 69 | void removeItemViaDelegate(QQuickItem* delegate); 70 | 71 | protected: 72 | void componentComplete(); 73 | 74 | private: 75 | QRangeView(const QRangeView&); 76 | QRangeView& operator = (const QRangeView&); 77 | 78 | QRangeViewPrivate* const d_ptr; 79 | Q_DECLARE_PRIVATE(QRangeView) 80 | 81 | void regenerateNewContent(); 82 | 83 | int m_viewportWidth; 84 | int m_viewportX; 85 | }; 86 | 87 | inline qint64 QRangeView::viewportWidth() const{ 88 | return m_viewportWidth; 89 | } 90 | 91 | inline qint64 QRangeView::viewportX() const{ 92 | return m_viewportX; 93 | } 94 | 95 | inline void QRangeView::setViewportWidth(qint64 arg){ 96 | if (m_viewportWidth == arg) 97 | return; 98 | 99 | m_viewportWidth = arg; 100 | regenerateNewContent(); 101 | emit viewportWidthChanged(arg); 102 | } 103 | 104 | inline void QRangeView::setViewportX(qint64 arg){ 105 | if (m_viewportX == arg) 106 | return; 107 | 108 | m_viewportX = arg; 109 | regenerateNewContent(); 110 | emit viewportXChanged(arg); 111 | } 112 | 113 | 114 | #endif // QRANGEVIEW_H 115 | -------------------------------------------------------------------------------- /lib/qabstractrangemodel.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2015-2016 Dinu SV. 4 | ** (contact: mail@dinusv.com) 5 | ** This file is part of QML Gantt library. 6 | ** 7 | ** GNU Lesser General Public License Usage 8 | ** This file may be used under the terms of the GNU Lesser 9 | ** General Public License version 3 as published by the Free Software 10 | ** Foundation and appearing in the file LICENSE.LGPLv3 included in the 11 | ** packaging of this file. Please review the following information to 12 | ** ensure the GNU Lesser General Public License version 3 requirements 13 | ** will be met: https://www.gnu.org/licenses/lgpl.html. 14 | ** 15 | ****************************************************************************/ 16 | 17 | #ifndef QABSTRACTRANGEMODEL_H 18 | #define QABSTRACTRANGEMODEL_H 19 | 20 | #include "qganttglobal.h" 21 | #include 22 | 23 | // QAbstractRangeModelIterator 24 | // -------------------------------- 25 | 26 | class Q_GANTT_EXPORT QAbstractRangeModelIterator{ 27 | 28 | public: 29 | virtual ~QAbstractRangeModelIterator(){} 30 | 31 | virtual bool isEnd() const = 0; 32 | virtual void nextItem() = 0; 33 | virtual QVariant data(int role = 0) = 0; 34 | 35 | }; 36 | 37 | 38 | // QAbstractRangeModel 39 | // ------------------------ 40 | 41 | class QAbstractRangeModelPrivate; 42 | class Q_GANTT_EXPORT QAbstractRangeModel : public QObject{ 43 | 44 | Q_OBJECT 45 | Q_PROPERTY(qint64 contentWidth READ contentWidth NOTIFY contentWidthChanged) 46 | 47 | public: 48 | enum Role{ 49 | PositionRole = 0, 50 | LengthRole 51 | }; 52 | 53 | public: 54 | explicit QAbstractRangeModel(QObject* parent = 0); 55 | virtual ~QAbstractRangeModel(); 56 | 57 | qint64 contentWidth() const; 58 | void setContentWidth(qint64 contentWidth); 59 | 60 | virtual QAbstractRangeModelIterator* dataBetween(qint64 startPosition, qint64 endPosition) = 0; 61 | 62 | virtual void setItemPosition( 63 | qint64 itemPosition, 64 | qint64 itemLength, 65 | int relativeIndex, 66 | qint64 itemNewPosition 67 | ) = 0; 68 | virtual void setItemLength( 69 | qint64 itemPosition, 70 | qint64 itemLength, 71 | int relativeIndex, 72 | qint64 newLength 73 | ) = 0; 74 | virtual void setItemData( 75 | qint64 itemPosition, 76 | qint64 itemLength, 77 | int relativeIndex, 78 | int role, 79 | const QVariant& value 80 | ) = 0; 81 | virtual QHash roleNames() const = 0; 82 | 83 | protected: 84 | void beginDataChange(qint64 startPosition, qint64 endPosition); 85 | void endDataChange(); 86 | 87 | signals: 88 | void contentWidthChanged(); 89 | void itemsAboutToBeChanged(qint64 startPosition, qint64 endPosition); 90 | void itemsChanged(qint64 startPosition, qint64 endPosition); 91 | void itemCoordinatesChanged( 92 | qint64 oldPosition, qint64 oldLength, int relativeIndex, 93 | QAbstractRangeModelIterator* modelIt, int newRelativeIndex); 94 | void itemsDataChanged(QAbstractRangeModelIterator*, int relativeIndex, QList roles = QList() ); 95 | 96 | public slots: 97 | virtual void insertItem(qint64 position, qint64 length); 98 | virtual void removeItem(qint64 position, qint64 length, qint64 relativeIndex); 99 | 100 | private: 101 | QAbstractRangeModel(const QAbstractRangeModel&); 102 | QAbstractRangeModel& operator = (const QAbstractRangeModel&); 103 | 104 | QAbstractRangeModelPrivate* const d_ptr; 105 | Q_DECLARE_PRIVATE(QAbstractRangeModel) 106 | 107 | qint64 m_contentWidth; 108 | }; 109 | 110 | 111 | inline qint64 QAbstractRangeModel::contentWidth() const{ 112 | return m_contentWidth; 113 | } 114 | 115 | inline void QAbstractRangeModel::setContentWidth(qint64 contentWidth){ 116 | if ( m_contentWidth != contentWidth ){ 117 | m_contentWidth = contentWidth; 118 | emit contentWidthChanged(); 119 | } 120 | } 121 | 122 | #endif // QABSTRACTRANGEMODEL_H 123 | -------------------------------------------------------------------------------- /tests/qganttmodeltest.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2015-2016 Dinu SV. 4 | ** (contact: mail@dinusv.com) 5 | ** This file is part of QML Gantt library. 6 | ** 7 | ** GNU Lesser General Public License Usage 8 | ** This file may be used under the terms of the GNU Lesser 9 | ** General Public License version 3 as published by the Free Software 10 | ** Foundation and appearing in the file LICENSE.LGPLv3 included in the 11 | ** packaging of this file. Please review the following information to 12 | ** ensure the GNU Lesser General Public License version 3 requirements 13 | ** will be met: https://www.gnu.org/licenses/lgpl.html. 14 | ** 15 | ****************************************************************************/ 16 | 17 | #include "qganttmodeltest.h" 18 | #include "qganttmodel.h" 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | 25 | Q_TEST_RUNNER_REGISTER(QGanttModelTest); 26 | 27 | class QGanttModelTestHelper{ 28 | 29 | public: 30 | typedef QPair Range; 31 | typedef QList RangeList; 32 | 33 | public: 34 | static bool compare(QGanttModel& model, const RangeList& ranges); 35 | 36 | }; 37 | 38 | QGanttModelTest::QGanttModelTest(QObject *parent) : QObject(parent){ 39 | 40 | } 41 | 42 | QGanttModelTest::~QGanttModelTest(){ 43 | 44 | } 45 | 46 | void QGanttModelTest::initTestCase(){ 47 | } 48 | 49 | void QGanttModelTest::descendingTest(){ 50 | QGanttModel model; 51 | model.setContentWidth(100); 52 | model.insertItem(60, 10); 53 | model.insertItem(50, 10); 54 | model.insertItem(40, 10); 55 | model.insertItem(30, 10); 56 | QCOMPARE(QGanttModelTestHelper::compare( 57 | model, 58 | QGanttModelTestHelper::RangeList() << 59 | QGanttModelTestHelper::Range(30, 10) << 60 | QGanttModelTestHelper::Range(40, 10) << 61 | QGanttModelTestHelper::Range(50, 10) << 62 | QGanttModelTestHelper::Range(60, 10) 63 | ), true); 64 | } 65 | 66 | void QGanttModelTest::alternateTest(){ 67 | QGanttModel model; 68 | model.setContentWidth(100); 69 | model.insertItem(10, 10); 70 | model.insertItem(5, 10); 71 | model.insertItem(20, 10); 72 | model.insertItem(4, 10); 73 | QCOMPARE(QGanttModelTestHelper::compare( 74 | model, 75 | QGanttModelTestHelper::RangeList() << 76 | QGanttModelTestHelper::Range(4, 10) << 77 | QGanttModelTestHelper::Range(5, 10) << 78 | QGanttModelTestHelper::Range(10, 10) << 79 | QGanttModelTestHelper::Range(20, 10) 80 | ), true); 81 | } 82 | 83 | void QGanttModelTest::sameLengthTest(){ 84 | QGanttModel model; 85 | model.setContentWidth(100); 86 | model.insertItem(10, 10); 87 | model.insertItem(10, 10); 88 | model.insertItem(10, 12); 89 | model.insertItem(10, 11); 90 | QCOMPARE(QGanttModelTestHelper::compare( 91 | model, 92 | QGanttModelTestHelper::RangeList() << 93 | QGanttModelTestHelper::Range(10, 10) << 94 | QGanttModelTestHelper::Range(10, 10) << 95 | QGanttModelTestHelper::Range(10, 11) << 96 | QGanttModelTestHelper::Range(10, 12) 97 | ), true); 98 | } 99 | 100 | void QGanttModelTest::sameLengthTest2(){ 101 | QGanttModel model; 102 | model.setContentWidth(100); 103 | model.insertItem(10, 10); 104 | model.insertItem(10, 10); 105 | model.insertItem(9, 11); 106 | model.insertItem(9, 11); 107 | model.insertItem(9, 10); 108 | model.insertItem(10, 12); 109 | model.insertItem(10, 10); 110 | model.insertItem(10, 11); 111 | model.insertItem(14, 2); 112 | model.insertItem(14, 1); 113 | QCOMPARE(QGanttModelTestHelper::compare( 114 | model, 115 | QGanttModelTestHelper::RangeList() << 116 | QGanttModelTestHelper::Range(9, 10) << 117 | QGanttModelTestHelper::Range(9, 11) << 118 | QGanttModelTestHelper::Range(9, 11) << 119 | QGanttModelTestHelper::Range(10, 10) << 120 | QGanttModelTestHelper::Range(10, 10) << 121 | QGanttModelTestHelper::Range(10, 10) << 122 | QGanttModelTestHelper::Range(10, 11) << 123 | QGanttModelTestHelper::Range(10, 12) << 124 | QGanttModelTestHelper::Range(14, 1) << 125 | QGanttModelTestHelper::Range(14, 2) 126 | ), true); 127 | } 128 | 129 | bool QGanttModelTestHelper::compare(QGanttModel& model, const QGanttModelTestHelper::RangeList& ranges){ 130 | QScopedPointer modelIt(model.dataBetween(0, model.contentWidth())); 131 | QGanttModelTestHelper::RangeList::const_iterator it = ranges.begin(); 132 | while ( !modelIt->isEnd() ){ 133 | if ( it == ranges.end() ) 134 | return false; 135 | qint64 position = modelIt->data(QAbstractRangeModel::PositionRole).toLongLong(); 136 | qint64 length = modelIt->data(QAbstractRangeModel::LengthRole).toLongLong(); 137 | if ( position != it->first || length != it->second ) 138 | return false; 139 | ++it; 140 | modelIt->nextItem(); 141 | } 142 | return true; 143 | } 144 | -------------------------------------------------------------------------------- /sample/resource/GanttEditWindow.qml: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2015-2016 Dinu SV. 4 | ** (contact: mail@dinusv.com) 5 | ** This file is part of QML Gantt library. 6 | ** 7 | ** GNU Lesser General Public License Usage 8 | ** This file may be used under the terms of the GNU Lesser 9 | ** General Public License version 3 as published by the Free Software 10 | ** Foundation and appearing in the file LICENSE.LGPLv3 included in the 11 | ** packaging of this file. Please review the following information to 12 | ** ensure the GNU Lesser General Public License version 3 requirements 13 | ** will be met: https://www.gnu.org/licenses/lgpl.html. 14 | ** 15 | ****************************************************************************/ 16 | 17 | import QtQuick 2.3 18 | import QtQuick.Window 2.1 19 | import QtQuick.Controls 1.2 20 | import QtQuick.Controls.Styles 1.2 21 | import QtQuick.Dialogs 1.2 22 | import Gantt 1.0 23 | 24 | Window{ 25 | id: root 26 | title: "Edit Gantt Item" 27 | modality: Qt.ApplicationModal 28 | 29 | visible: false 30 | minimumWidth: 300 31 | minimumHeight: 200 32 | width: 400 33 | height: 200 34 | 35 | property variant ganttItem : null 36 | 37 | signal update(string color, string data) 38 | signal canceled() 39 | 40 | Rectangle{ 41 | color: "#ddd" 42 | anchors.fill: parent 43 | focus: true 44 | 45 | Keys.onEscapePressed: { 46 | labelInput.text = "" 47 | root.close() 48 | root.canceled() 49 | } 50 | 51 | TextArea{ 52 | id: colorInput 53 | anchors.top: parent.top 54 | anchors.topMargin: 12 55 | anchors.left: parent.left 56 | anchors.right: parent.right 57 | anchors.leftMargin: 12 58 | anchors.rightMargin: 12 59 | height: 25 60 | clip: true 61 | text: root.ganttItem ? root.ganttItem.color : "" 62 | selectByMouse: true 63 | font.pixelSize: 12 64 | font.family: "Courier New" 65 | style: TextAreaStyle { 66 | textColor: "#333" 67 | selectionColor: "#eee" 68 | selectedTextColor: "#333" 69 | backgroundColor: "#bbb" 70 | } 71 | } 72 | 73 | TextArea{ 74 | id: labelInput 75 | anchors.top: parent.top 76 | anchors.topMargin: 40 77 | anchors.left: parent.left 78 | anchors.right: parent.right 79 | anchors.leftMargin: 12 80 | anchors.rightMargin: 12 81 | height: parent.height - 100 82 | clip: true 83 | text: root.ganttItem ? root.ganttItem.label : "" 84 | selectByMouse: true 85 | font.pixelSize: 12 86 | font.family: "Courier New" 87 | style: TextAreaStyle { 88 | textColor: "#333" 89 | selectionColor: "#eee" 90 | selectedTextColor: "#333" 91 | backgroundColor: "#bbb" 92 | } 93 | } 94 | 95 | Rectangle{ 96 | id: updateButton 97 | anchors.left: parent.left 98 | anchors.leftMargin: 10 99 | anchors.bottom: parent.bottom 100 | anchors.bottomMargin: 16 101 | color: createButtonArea.containsMouse ? "#334499" : "#334466" 102 | width: 100 103 | height: 30 104 | Text{ 105 | anchors.centerIn: parent 106 | text: "Update" 107 | color: "#fff" 108 | font.pixelSize: 12 109 | font.family: "Arial, Helvetica, sans-serif" 110 | } 111 | MouseArea{ 112 | id: createButtonArea 113 | anchors.fill: parent 114 | hoverEnabled: true 115 | onClicked: { 116 | colorInput.select(0, 0) 117 | labelInput.select(0, 0) 118 | if ( ganttItem ){ 119 | ganttItem.label = labelInput.text 120 | ganttItem.color = colorInput.text 121 | } 122 | root.close() 123 | root.update(colorInput.text, labelInput.text) 124 | root.ganttItem = null 125 | } 126 | } 127 | } 128 | 129 | Rectangle{ 130 | id: cancelButton 131 | anchors.right: parent.right 132 | anchors.rightMargin: 10 133 | anchors.bottom: parent.bottom 134 | anchors.bottomMargin: 16 135 | color: cancelButtonArea.containsMouse ? "#b14433" : "#884433" 136 | width: 100 137 | height: 30 138 | Text{ 139 | anchors.centerIn: parent 140 | text: "Cancel" 141 | color: "#fff" 142 | font.pixelSize: 12 143 | font.family: "Arial, Helvetica, sans-serif" 144 | } 145 | MouseArea{ 146 | id: cancelButtonArea 147 | anchors.fill: parent 148 | hoverEnabled: true 149 | onClicked: { 150 | ganttItem = null 151 | root.close() 152 | root.canceled() 153 | } 154 | } 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QML Gantt library 2 | 3 | | Linux | Windows | 4 | |:-------------:|:----------:| 5 | | [![Build Status](https://travis-ci.org/dinusv/qml-gantt.svg?branch=master)](https://travis-ci.org/dinusv/qml-gantt) | [![Build status](https://ci.appveyor.com/api/projects/status/dmstdr0ukobutitm/branch/master?svg=true)](https://ci.appveyor.com/project/dinusv/qml-gantt/branch/master) | 6 | 7 | 8 | 9 | An efficient implementation of a gantt chart library for qml. The library provides a custom View element 10 | which handles lazy loading of items according to its viewport. Items are loaded from a model which can 11 | easily be implemented according to custom specifications. 12 | 13 | * **Version**: 0.1.0 14 | * **License**: LPGL 15 | * **Library requirements**: Qt5 or higher 16 | * **Sample project requirements**: Qt5.3 or higher 17 | 18 | A fully editable gantt chart element is provided in the associated sample project: 19 | 20 | ![Gantt sample screen shot](/doc/screen-gantt.png) 21 | 22 | ## Usage 23 | 24 | The project can be used as an externally linked library or by including the source files directly. (The first option would be the preferred one). 25 | 26 | ### Compiling the library 27 | 28 | * Download the source code 29 | * Run the following command within the project directory to generate the library: 30 | 31 | ``` 32 | cd build 33 | qmake -r .. 34 | make (or nmake) 35 | ``` 36 | 37 | * The library will be available within the build directory. You can run the associated gantt example project from the sample directory: 38 | 39 | ``` 40 | sample/./sample 41 | ``` 42 | 43 | * Or you can link to the library from your own project and implement the model data: 44 | 45 | ``` 46 | INCLUDEPATH += $$GANTTLIBPATH/include 47 | 48 | LIBS += -L$$GANTTLIBPATH/lib -lqmlgantt 49 | ``` 50 | 51 | ### Manually including the source files 52 | 53 | * Download the source code 54 | * Include the following files into your project: 55 | * qganttglobal 56 | * qabstractrangemodel.h 57 | * qabstractrangemodel.cpp 58 | * qrangeview.h 59 | * qrangeview.cpp 60 | * qganttmodel.h 61 | * qganttmodel.cpp 62 | * qganttmodelitem.h 63 | * qganttmodelitem.cpp 64 | * Implement the model data and you're ready to go 65 | 66 | 67 | ## Implementing the model data 68 | 69 | There are two ways to add custom data through the model: 70 | 71 | * Adding a ```QGanntModel``` item (the easy way) 72 | * Implementing your own model by extending the ```QAbstractRangeModel``` (the custom way and also the bit more difficult one) 73 | 74 | ### Adding a ```QGanntModel``` data item 75 | 76 | The `QGanttModel` class is an already implemented version of the ```QAbstractRangeModel``` class, therefore provides all the methods 77 | required to communicate with the ```RangeView``` element. It's child items contain 3 properites: the **position** and **length**, 78 | which are used by the model to keep items sorted for easy access, and the **data** property, which is a variant type customizable by 79 | the user. It can contain any qml value type, or a reference to a QObject subclass, which can further extend the number of properties 80 | available. 81 | 82 | In the associated sample subproject, I've included the ```QGanttData``` class, which adds a color and label property to each gantt item. 83 | The ```QGanttModel``` class is assigned a ```QGanttData``` object whenever a new ```QGanttModelItem``` is created and inserted in the model. This 84 | is done internally by the model by calling a *configured* data factory function whenever an insertion is triggered. The function is configured 85 | by using the ```QGanttModelItem::setItemDataFactoryFunction``` setter. The factory function is defined in the *sample/src/main.cpp* file. 86 | 87 | To assign a different data item: 88 | 89 | ```cpp 90 | class CustomData : public QObject{ 91 | 92 | Q_OBJECT 93 | Q_PROPERTY(QString property1...) 94 | ... 95 | 96 | } 97 | ``` 98 | 99 | we register the type to qml: 100 | 101 | ```cpp 102 | qmlRegisterType("Gantt", 1, 0, "CustomData"); 103 | ``` 104 | 105 | 106 | then define the item factory function: 107 | 108 | ```cpp 109 | QVariant createModelData(){ 110 | return QVariant::fromValue(new CustomData); 111 | } 112 | ``` 113 | 114 | and register the function to our model: 115 | 116 | ```cpp 117 | QGanttModel* model = new QanttModel; 118 | model->setItemDataFactoryFunction(&createModelData); 119 | ``` 120 | 121 | then, whenever we invoke a new insertion, a ```CustomData``` type object will be set in the data field: 122 | 123 | ```cpp 124 | model->insertItem(10, 10); 125 | ``` 126 | 127 | Which, in our delegate, can be accessed through the ```modelData``` property: 128 | 129 | ```qml 130 | RangeView{ 131 | ... 132 | delegate: Component{ 133 | Text{ text: modelData.property1 } 134 | } 135 | } 136 | ``` 137 | 138 | The properties are set through simple assignments: 139 | 140 | ```qml 141 | RangeView{ 142 | ... 143 | delegate: Component{ 144 | Rectangle{ 145 | width: 200; height: 40; 146 | Text{ text: modelData.property1 } 147 | MouseArea{ anchors.fill: parent; onClicked: modelData.property1 = "new value"; } 148 | } 149 | } 150 | } 151 | ``` 152 | 153 | 154 | The *sample/resource/GanttLine.qml* file provides a full example on assignments and other item handling. 155 | 156 | ### Implementing the ```QAbstractRangeModel``` 157 | 158 | The QAbstractRangeModel and QAbstractRangeModelIterator provide the implementation 159 | requirements used by the QRangeView to work with the model data. The section is treated separately 160 | [here](doc/implement-qabstractrangemodel.md). 161 | -------------------------------------------------------------------------------- /sample/resource/GanttHelpWindow.qml: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2015-2016 Dinu SV. 4 | ** (contact: mail@dinusv.com) 5 | ** This file is part of QML Gantt library. 6 | ** 7 | ** GNU Lesser General Public License Usage 8 | ** This file may be used under the terms of the GNU Lesser 9 | ** General Public License version 3 as published by the Free Software 10 | ** Foundation and appearing in the file LICENSE.LGPLv3 included in the 11 | ** packaging of this file. Please review the following information to 12 | ** ensure the GNU Lesser General Public License version 3 requirements 13 | ** will be met: https://www.gnu.org/licenses/lgpl.html. 14 | ** 15 | ****************************************************************************/ 16 | 17 | import QtQuick 2.3 18 | import QtQuick.Window 2.1 19 | import QtQuick.Controls 1.2 20 | import QtQuick.Controls.Styles 1.2 21 | import QtQuick.Dialogs 1.2 22 | import Gantt 1.0 23 | 24 | Window { 25 | id: root 26 | title: "Gantt Interactions" 27 | modality: Qt.ApplicationModal 28 | 29 | visible: false 30 | minimumWidth: 300 31 | minimumHeight: 200 32 | width: 400 33 | height: 200 34 | 35 | ScrollView { 36 | anchors.fill: parent 37 | 38 | Rectangle { 39 | anchors.top: parent.top 40 | anchors.topMargin: 10 41 | anchors.left: parent.left 42 | anchors.leftMargin: 10 43 | width: root.width - 30 44 | color: 'transparent' 45 | height: helpGrid.height + 20 46 | 47 | Grid { 48 | id: helpGrid 49 | columns: 2 50 | spacing: 10 51 | width: root.width - 30 52 | 53 | Image { 54 | width: 100 55 | height: 80 56 | source: "/img/help-add-item.png" 57 | } 58 | 59 | Rectangle { 60 | width: root.width - 140 61 | height: 80 62 | Text { 63 | anchors.verticalCenter: parent.verticalCenter 64 | width: root.width - 140 65 | text: "To add a new item to a row, double click in the empty area inside the row." 66 | color: "#333" 67 | wrapMode: Text.WordWrap 68 | font.family: "sans-serif" 69 | font.pixelSize: 12 70 | } 71 | } 72 | 73 | Image { 74 | width: 100 75 | height: 80 76 | source: "/img/help-move-item.png" 77 | } 78 | 79 | Rectangle { 80 | width: root.width - 140 81 | height: 80 82 | Text { 83 | anchors.verticalCenter: parent.verticalCenter 84 | width: root.width - 140 85 | text: "To move an item, click inside the item and drag while holding the mouse button pressed." 86 | color: "#333" 87 | wrapMode: Text.WordWrap 88 | font.family: "sans-serif" 89 | font.pixelSize: 12 90 | } 91 | } 92 | 93 | Image { 94 | width: 100 95 | height: 80 96 | source: "/img/help-stretch-item.png" 97 | } 98 | 99 | Rectangle { 100 | width: root.width - 140 101 | height: 80 102 | Text { 103 | anchors.verticalCenter: parent.verticalCenter 104 | width: parent.width 105 | text: "Stretch the item by dragging its edges." 106 | color: "#333" 107 | wrapMode: Text.WordWrap 108 | font.family: "sans-serif" 109 | font.pixelSize: 12 110 | } 111 | } 112 | 113 | Image { 114 | width: 100 115 | height: 80 116 | source: "/img/help-edit-item.png" 117 | } 118 | 119 | Rectangle { 120 | width: root.width - 140 121 | height: 80 122 | Text { 123 | anchors.verticalCenter: parent.verticalCenter 124 | width: root.width - 140 125 | text: "Double click an item to edit its color and label." 126 | color: "#333" 127 | wrapMode: Text.WordWrap 128 | font.family: "sans-serif" 129 | font.pixelSize: 12 130 | } 131 | } 132 | 133 | Image { 134 | width: 100 135 | height: 80 136 | source: "/img/help-edit-row.png" 137 | } 138 | 139 | Rectangle { 140 | width: root.width - 140 141 | height: 80 142 | Text { 143 | anchors.verticalCenter: parent.verticalCenter 144 | width: root.width - 140 145 | text: "Double click a row label to edit its name, press return once you're done." 146 | color: "#333" 147 | wrapMode: Text.WordWrap 148 | font.family: "sans-serif" 149 | font.pixelSize: 12 150 | } 151 | } 152 | } 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /sample/resource/main.qml: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2015-2016 Dinu SV. 4 | ** (contact: mail@dinusv.com) 5 | ** This file is part of QML Gantt library. 6 | ** 7 | ** GNU Lesser General Public License Usage 8 | ** This file may be used under the terms of the GNU Lesser 9 | ** General Public License version 3 as published by the Free Software 10 | ** Foundation and appearing in the file LICENSE.LGPLv3 included in the 11 | ** packaging of this file. Please review the following information to 12 | ** ensure the GNU Lesser General Public License version 3 requirements 13 | ** will be met: https://www.gnu.org/licenses/lgpl.html. 14 | ** 15 | ****************************************************************************/ 16 | 17 | import QtQuick 2.2 18 | import QtQuick.Controls 1.1 19 | import Gantt 1.0 20 | 21 | ApplicationWindow { 22 | visible: true 23 | width: 640 24 | height: 480 25 | title: qsTr("QML Gantt Sample") 26 | 27 | menuBar: MenuBar { 28 | Menu { 29 | title: qsTr("File") 30 | MenuItem { 31 | text: qsTr("Exit") 32 | onTriggered: Qt.quit(); 33 | } 34 | } 35 | Menu { 36 | title: qsTr("Help") 37 | MenuItem { 38 | text: qsTr("Usage...") 39 | onTriggered: ganttHelpWindow.show() 40 | } 41 | } 42 | } 43 | 44 | Rectangle{ 45 | height: parent.height - 14 46 | clip: true 47 | width: 150 48 | 49 | ListView{ 50 | id: ganttNameView 51 | 52 | anchors.fill: parent 53 | 54 | interactive: false 55 | contentY: ganttView.contentY 56 | 57 | model: ganttModelList 58 | delegate: Rectangle{ 59 | width: ganttNameView.width 60 | height: 40 61 | Rectangle{ 62 | color: "#ccc" 63 | width: parent.width 64 | height: 38 65 | Text{ 66 | anchors.verticalCenter: parent.verticalCenter 67 | anchors.left: parent.left 68 | anchors.leftMargin: 10 69 | text: name 70 | font.family: "Arial, Helvetica, open-sans" 71 | font.pixelSize: 13 72 | color: "#000" 73 | } 74 | MouseArea{ 75 | anchors.fill: parent 76 | onDoubleClicked: { 77 | trackEditBox.visible = true 78 | trackEdit.selectAll() 79 | trackEdit.forceActiveFocus() 80 | } 81 | } 82 | Rectangle{ 83 | id: trackEditBox 84 | anchors.verticalCenter: parent.verticalCenter 85 | anchors.fill: parent 86 | anchors.margins: 5 87 | visible: false 88 | color: "#aaa" 89 | border.color: "#6666cc" 90 | border.width: 1 91 | TextInput{ 92 | id: trackEdit 93 | anchors.verticalCenter: parent.verticalCenter 94 | anchors.left: parent.left 95 | anchors.leftMargin: 5 96 | width: parent.width - 10 97 | text: name 98 | color: "#000" 99 | font.pixelSize: 13 100 | font.bold: false 101 | font.family: "Arial, Helvetica, sans-serif" 102 | selectByMouse: true 103 | selectionColor: "#444499" 104 | onActiveFocusChanged: { 105 | if ( !activeFocus ){ 106 | name = text 107 | trackEditBox.visible = false 108 | } 109 | } 110 | Keys.onReturnPressed: { 111 | name = text 112 | event.accepted = true 113 | trackEditBox.visible = false 114 | } 115 | Keys.onEnterPressed: { 116 | name = text 117 | event.accepted = true 118 | trackEditBox.visible = false 119 | } 120 | 121 | Keys.onEscapePressed: { 122 | text = name 123 | event.accepted = true 124 | trackEditBox.visible = false 125 | } 126 | MouseArea{ 127 | anchors.fill: parent 128 | acceptedButtons: Qt.NoButton 129 | cursorShape: Qt.IBeamCursor 130 | } 131 | } 132 | } 133 | } 134 | } 135 | 136 | MouseArea{ 137 | anchors.fill: parent 138 | onWheel: { 139 | wheel.accepted = true 140 | var newContentY = ganttView.contentY -= wheel.angleDelta.y / 6 141 | if ( newContentY > ganttView.contentHeight - ganttView.height ) 142 | ganttView.contentY = ganttView.contentHeight - ganttView.height 143 | else if ( newContentY < 0 ) 144 | ganttView.contentY = 0 145 | else 146 | ganttView.contentY = newContentY 147 | } 148 | onClicked: mouse.accepted = false; 149 | onPressed: mouse.accepted = false; 150 | onReleased: mouse.accepted = false 151 | onDoubleClicked: mouse.accepted = false; 152 | onPositionChanged: mouse.accepted = false; 153 | onPressAndHold: mouse.accepted = false; 154 | } 155 | } 156 | } 157 | 158 | Rectangle{ 159 | height: parent.height 160 | width: parent.width - 152 161 | x: 152 162 | 163 | ScrollView{ 164 | id: mainScroll 165 | anchors.fill: parent 166 | 167 | ListView{ 168 | id: ganttView 169 | 170 | height: parent.height 171 | contentWidth: ganttModelList.contentWidth 172 | model: ganttModelList 173 | delegate: Rectangle{ 174 | height: 40 175 | width: ganttLine.width 176 | color: "#fff" 177 | 178 | Rectangle{ 179 | height: 38 180 | width: ganttLine.width 181 | color: "#ccc" 182 | GanttLine{ 183 | id: ganttLine 184 | color: "#ccc" 185 | height: 24 186 | anchors.centerIn: parent 187 | viewportX: ganttView.contentX / ganttLine.zoomScale 188 | viewportWidth: ganttView.width / ganttLine.zoomScale 189 | model: ganttModel 190 | onEditItem: { 191 | ganttEditWindow.ganttItem = item 192 | ganttEditWindow.visible = true 193 | } 194 | } 195 | } 196 | } 197 | } 198 | } 199 | } 200 | 201 | 202 | GanttEditWindow{ 203 | id: ganttEditWindow 204 | } 205 | GanttHelpWindow{ 206 | id: ganttHelpWindow 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /doc/implement-qabstractrangemodel.md: -------------------------------------------------------------------------------- 1 | # Implementing the QAbstractRangeModel 2 | 3 | To implement the QAbstractRangeModel, we must first understand how the QRangeView and QAbstractRangeModel 4 | communicate with each other. Since items are loaded according to their position, the QRangeView doesn't 5 | need to know the number of items within the model, nor their indexes. This means that the model can store 6 | items in linked-lists, trees or other container types as long as it can manage to find items between specific 7 | ranges. The boundaries of the model are configured by the ```contentLength``` property of the model. 8 | 9 | Since item indexes are not used, identification of items between the model and the view is done 10 | through an items position, length and relative index. The relative index is used in case 2 items with the 11 | same position and length are present, and refers to the order in which the item was received from the model. 12 | The following table provides an example: 13 | 14 | Position | Length | Relative Index 15 | -------- | ------ | -------------- 16 | 10 | 5 | 0 17 | 15 | 5 | 0 18 | 15 | 5 | 1 19 | 20 | 5 | 0 20 | 21 | The second and third items are differentiated by their relative index, which helps the view identify a specific 22 | item for the model. 23 | 24 | 25 | ## Read-only model 26 | 27 | For a **read-only model** the following methods are required: 28 | 29 | * the ```contentWidth``` **property** needs to be set so the view knows the width of the model: 30 | 31 | ```cpp 32 | CustomModel::CustomModel(QObject* parent = 0) 33 | : QAbstractRangeModel(parent) 34 | { 35 | setContentWidth(1000); 36 | } 37 | ``` 38 | 39 | * the ```roleNames()``` function is implemented to map item data. It doesn't include the item position and length 40 | properties, which are already defined in the QAbstractRangeModel. This functionality provides the same means of 41 | accessing data as the QAbstractItemModel: 42 | 43 | ```cpp 44 | // in our header file, we define the Role enum 45 | 46 | class CustomModel : public QAbstractRangeModel{ 47 | // ... 48 | public: 49 | // custom roles are next in line after the position and length role 50 | 51 | enum Role{ 52 | modelData = QAbstractRangeModel::LengthRole + 1 53 | }; 54 | // ... 55 | QHash roleNames() const; 56 | // ... 57 | } 58 | ``` 59 | 60 | ```cpp 61 | // Then we map our enum to property names 62 | 63 | QHash CustomModel::roleNames() const{ 64 | QHash roles; 65 | roles[QGanttModel::ModelData] = "modelData"; 66 | return roles; 67 | } 68 | ``` 69 | 70 | * ```dataBetween(startPosition, endPosition)``` - returns an iterable set of items to display between startPosition and endPosition 71 | Items need to be ordered in ascending order according to their position and length. The items are returned in the form 72 | of a ```QAbstractRangeModelIterator```. The iterator is called by the view and iterates through the given items, and provides the 73 | data for the view from the set ```roleNames()```. The iterator will be deleted by the view once it reaches the end of the model. 74 | 75 | ```cpp 76 | QAbstractRangeModelIterator* CustomModel::dataBetween(qint64 startPosition, qint64 endPosition){ 77 | // ItemIterator is used as an example, and will be used only internally by the CustomModelIterator to iterate items. This can be 78 | // an iterator for any container type defined by the user. 79 | ItemIterator start = itemIteratorFromPosition(startPosition); 80 | ItemIterator end = itemIteratorFromPosition(endPosition); 81 | return new CustomModelIterator(start, end); 82 | } 83 | ``` 84 | 85 | Where a ```CustomModelIterator``` implements ```isEnd()```, ```nextItem()``` and ```data(int role)``` from 86 | ```QAbstractRangeModelIterator``` : 87 | 88 | ```cpp 89 | class CustomModelIterator : public QAbstractRangeModelIterator{ 90 | CustomModelIterator(ItemIterator current, ItemIterator end) : m_current(current), m_end(end){} 91 | 92 | bool isEnd() const{ return m_start == m_end; } 93 | void nextItem(){ ++m_current; } 94 | 95 | QVariant data(int role = 0){ 96 | if ( role == CustomModel::PositionRole ) return m_current->position; 97 | else if ( role == CustomModel::LengthRole) return m_current->length; 98 | else if ( role == CustomModel::ModelData) return m_current->data; 99 | else return QVariant(); 100 | } 101 | 102 | private: 103 | ItemIterator m_start; 104 | ItemIterator m_end; 105 | } 106 | ``` 107 | 108 | 109 | ## Writable model 110 | 111 | To make the model **writable**, an extra set of methods requires implementation: 112 | 113 | * the ```setItemPosition()``` function is called when the view requests an item to change its position. The item is 114 | searched for within the model, assigned a new position, then the view is notified through the 115 | ```itemCoordinatesChanged()``` signal: 116 | 117 | ```cpp 118 | void CustomModel::setItemPosition(qint64 itemPosition, qint64 itemLength, int indexOffset, qint64 itemNewPosition){ 119 | ItemIterator itemIt = findItem(itemPosition, itemLength, indexOffset); 120 | itemIt->position = itemNewPosition; 121 | 122 | // update our container to have it sorted, here we simply place the item to its new index 123 | itemIt = sortItem(itemIt); 124 | 125 | CustomModelIterator* modelIt = new CustomModelIterator(itemIt, itemIt + 1); 126 | 127 | // I have considered sortItem to always position the item in front of items with the same position and length, 128 | // making its relativeIndex = 0 129 | int newRelativeIndex = 0; 130 | emit ItemCoordinatesChanged(itemPosition, itemLength, indexOffset, modelIt, newRelativeIndex); 131 | } 132 | ``` 133 | 134 | * ```setItemLength()``` behaves the same as ```setItemPosition()``` 135 | 136 | ```cpp 137 | void CustomModel::setItemLength(qint64 itemPosition, qint64 itemLength, int indexOffset, qint64 itemNewLength){ 138 | ItemIterator itemIt = findItem(itemPosition, itemLength, indexOffset); 139 | itemIt->length = itemNewLength; 140 | itemIt = sortItem(itemIt); 141 | CustomModelIterator* modelIt = new CustomModelIterator(itemIt, itemIt + 1); 142 | 143 | int newRelativeIndex = 0; 144 | emit ItemCoordinatesChanged(itemPosition, itemLength, indexOffset, modelIt, newRelativeIndex); 145 | } 146 | ``` 147 | 148 | * ```setItemData()``` sets custom data to the model from the given role names 149 | 150 | ```cpp 151 | void CustomModel::setItemData( 152 | qint64 itemPosition, 153 | qint64 itemLength, 154 | int indexOffset, 155 | int role, 156 | const QVariant& value) 157 | { 158 | ItemIterator itemIt = findItem(itemPosition, itemLength, indexOffset); 159 | if ( role == CustomModel::ModelData ){ 160 | CustomModelIterator* modelIt = new CustomModelIterator(itemIt, itemIt + 1); 161 | itemIt->setData(value.toString()); 162 | emit itemsDataChanged(modelIt, indexOffset, QList() << CustomModel::ModelData) 163 | } 164 | } 165 | ``` 166 | 167 | * ```insertItem(position, length)``` - inserts an item in the model at the specified position and length. 168 | The view is notified by calling ```beginDataChange(startPosition, endPosition)``` and 169 | ```endDataChange()``` methods: 170 | 171 | ```cpp 172 | void CustomModel::insertItem(qint64 position, qint64 length){ 173 | beginDataChange(position, position + length); 174 | insertItem(new CustomModelItem(position, length)); 175 | endDataChange(); 176 | } 177 | ``` 178 | 179 | * removeItem(position, length, relativeIndex) - remove the item at the specified coordinates. The view is notified 180 | through the same 2 methods as the insertion: 181 | 182 | ```cpp 183 | void CustomModel::removeItem(qint64 position, qint64 length, qint64 relativeIndex){ 184 | ItemIterator itemIt = findItem(position, length, relativeIndex); 185 | if (itemIt != NULL){ 186 | beginDataChange(position, position + length); 187 | removeItem(itemIt); 188 | endDataChange(); 189 | } 190 | } 191 | ``` 192 | 193 | * The ```beginDataChange(startPosition, endPosition)``` and ```endDataChange()``` are used whenever the 194 | model wants to notify the view over a set of items that have changed between those positions. Changes 195 | to the whole model can be notified by signalling a change between 0 and the ```contentWidth``` of the model: 196 | 197 | ```cpp 198 | void CustomModel::resetModel(){ 199 | beginDataChange(0, contentWidth()); 200 | clearData(); 201 | endDataChange(); 202 | } 203 | ``` 204 | 205 | ## Example 206 | 207 | The default ```QGanttModel``` provided in the library is an implementation of a ```QAbstractRangeModel```. 208 | -------------------------------------------------------------------------------- /tests/qviewportbenchmarktest.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2015-2016 Dinu SV. 4 | ** (contact: mail@dinusv.com) 5 | ** This file is part of QML Gantt library. 6 | ** 7 | ** GNU Lesser General Public License Usage 8 | ** This file may be used under the terms of the GNU Lesser 9 | ** General Public License version 3 as published by the Free Software 10 | ** Foundation and appearing in the file LICENSE.LGPLv3 included in the 11 | ** packaging of this file. Please review the following information to 12 | ** ensure the GNU Lesser General Public License version 3 requirements 13 | ** will be met: https://www.gnu.org/licenses/lgpl.html. 14 | ** 15 | ****************************************************************************/ 16 | 17 | #include "qviewportbenchmarktest.h" 18 | #include "qrangeview.h" 19 | #include "qganttmodel.h" 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | 31 | Q_TEST_RUNNER_REGISTER(QViewportBenchmarkTest); 32 | 33 | // QViewportBenchmarkTestData 34 | // -------------------------- 35 | 36 | class QViewportBenchmarkTestData{ 37 | public: 38 | QViewportBenchmarkTestData(const char* delegateData); 39 | ~QViewportBenchmarkTestData(); 40 | 41 | QQmlEngine* engine; 42 | QQmlContext* context; 43 | QQmlComponent* viewDelegate; 44 | QQuickItem* container; 45 | QGanttModel* model; 46 | QRangeView* view; 47 | 48 | static const char* MINIMAL_DELEGATE; 49 | static const char* COMPLEX_DELEGATE; 50 | 51 | }; 52 | 53 | const char* QViewportBenchmarkTestData::MINIMAL_DELEGATE = 54 | "import QtQuick 2.1\n" 55 | "Rectangle{\n" 56 | " x:position\n " 57 | " width:length\n" 58 | "}\n"; 59 | 60 | const char* QViewportBenchmarkTestData::COMPLEX_DELEGATE = 61 | "import QtQuick 2.2\n" 62 | "Rectangle{\n" 63 | " id: ganttDelegate\n" 64 | " width: length \n" 65 | " height: 20\n" 66 | " clip: true\n" 67 | " x: position * 1\n" 68 | " Rectangle{\n" 69 | " id: stretchLeft\n" 70 | " anchors.left: parent.left\n" 71 | " anchors.top: parent.top\n" 72 | " width : 2\n" 73 | " height : parent.height\n" 74 | " color: \"#fff\"\n" 75 | " MouseArea{\n" 76 | " anchors.fill: parent\n" 77 | " cursorShape: Qt.SizeHorCursor\n" 78 | " property int oldMouseX\n" 79 | " onPressed: { oldMouseX = mouseX }\n" 80 | " onPositionChanged: {\n" 81 | " if ( pressed ){\n" 82 | " var newWidth = ganttDelegate.width + oldMouseX - mouseX\n" 83 | " var newX = ganttDelegate.x + mouseX - oldMouseX\n" 84 | " var newLength = Math.round(newWidth / 1)\n" 85 | " if ( newLength > 0 && newX + newWidth < rangeView.width ){\n" 86 | " if ( newX > 0 ){\n" 87 | " ganttDelegate.width = newWidth\n" 88 | " ganttDelegate.x = newX\n" 89 | " } else {\n" 90 | " ganttDelegate.width = ganttDelegate.x + ganttDelegate.width\n" 91 | " ganttDelegate.x = 0\n" 92 | " }\n" 93 | " } else {\n" 94 | " ganttDelegate.x =\n" 95 | " ganttDelegate.x + ganttDelegate.width - (1 * 1)\n" 96 | " ganttDelegate.width = 1 * 1\n" 97 | " }\n" 98 | " }\n" 99 | " }\n" 100 | " }\n" 101 | " }\n" 102 | " Rectangle{\n" 103 | " id: centerMove\n" 104 | " anchors.left: parent.left\n" 105 | " anchors.leftMargin: 2\n" 106 | " anchors.right: parent.right\n" 107 | " anchors.rightMargin: 2\n" 108 | " width : parent.width\n" 109 | " height : parent.height\n" 110 | " color : parent.color\n" 111 | " MouseArea{\n" 112 | " anchors.fill: parent\n" 113 | " cursorShape: Qt.SizeAllCursor\n" 114 | " onPressed: { ganttDelegate.forceActiveFocus() }\n" 115 | " }\n" 116 | " }\n" 117 | " Rectangle{\n" 118 | " id: strechRight\n" 119 | " anchors.right: parent.right\n" 120 | " anchors.top: parent.top\n" 121 | " width : 2\n" 122 | " height : parent.height\n" 123 | " color: \"#fff\"\n" 124 | " MouseArea{\n" 125 | " anchors.fill: parent\n" 126 | " cursorShape: Qt.SizeHorCursor\n" 127 | " property int oldMouseX\n" 128 | " onPressed: oldMouseX = mouseX\n" 129 | " onPositionChanged: {\n" 130 | " if ( pressed ){\n" 131 | " var newWidth = ganttDelegate.width + (mouseX - oldMouseX)\n" 132 | " var newLength = Math.round(newWidth / 1)\n" 133 | " if ( newLength > 0 ){\n" 134 | " if ( position + newLength < root.ganttLength)\n" 135 | " ganttDelegate.width = newWidth\n" 136 | " else\n" 137 | " ganttDelegate.width = (root.ganttLength - position) * 1\n" 138 | " } else\n" 139 | " ganttDelegate.width = 1 * 1\n" 140 | " }\n" 141 | " }\n" 142 | " }\n" 143 | " }\n" 144 | "}\n"; 145 | 146 | QViewportBenchmarkTestData::QViewportBenchmarkTestData(const char* delegateData) 147 | : engine(new QQmlEngine) 148 | , context(new QQmlContext(engine)) 149 | , viewDelegate(new QQmlComponent(engine)) 150 | , container(new QQuickItem) 151 | , model(new QGanttModel) 152 | , view(new QRangeView(container)) 153 | { 154 | engine->setContextForObject(viewDelegate, context); 155 | viewDelegate->setData(delegateData, QUrl()); 156 | 157 | view->setDelegate(viewDelegate); 158 | view->setModel(model); 159 | } 160 | 161 | QViewportBenchmarkTestData::~QViewportBenchmarkTestData(){ 162 | delete container; 163 | delete viewDelegate; 164 | delete engine; 165 | delete context; 166 | delete model; 167 | } 168 | 169 | 170 | // QViewportBenchmarkTest 171 | // ---------------------- 172 | 173 | QViewportBenchmarkTest::QViewportBenchmarkTest(QObject* parent) : QObject(parent){ 174 | 175 | } 176 | 177 | QViewportBenchmarkTest::~QViewportBenchmarkTest(){ 178 | 179 | } 180 | 181 | void QViewportBenchmarkTest::initTestCase(){ 182 | } 183 | 184 | void QViewportBenchmarkTest::minimalDelegateInsertionTest(){ 185 | QViewportBenchmarkTestData data(QViewportBenchmarkTestData::MINIMAL_DELEGATE); 186 | data.model->setContentWidth(CONTENT_WIDTH); 187 | data.view->setViewportWidth(VIEWPORT_WIDTH); 188 | data.view->setViewportX(0); 189 | 190 | QBENCHMARK_ONCE{ 191 | for ( int i = 0; i < CONTENT_WIDTH - ITEM_WIDTH; i += ITEM_WIDTH + 1 ) 192 | data.model->insertItem(i, 1); 193 | } 194 | } 195 | 196 | void QViewportBenchmarkTest::minimalDelegateViewTest(){ 197 | QViewportBenchmarkTestData data(QViewportBenchmarkTestData::MINIMAL_DELEGATE); 198 | data.model->setContentWidth(CONTENT_WIDTH); 199 | data.view->setViewportWidth(VIEWPORT_WIDTH); 200 | data.view->setViewportX(0); 201 | for ( int i = 0; i < CONTENT_WIDTH - ITEM_WIDTH; i += ITEM_WIDTH + 1 ) 202 | data.model->insertItem(i, 1); 203 | 204 | QBENCHMARK_ONCE{ 205 | for ( int i = 0; i < SCROLL_ITERATIONS; ++i ) 206 | data.view->setViewportX(i); 207 | } 208 | } 209 | 210 | void QViewportBenchmarkTest::complexDelegateInsertionTest(){ 211 | QViewportBenchmarkTestData data(QViewportBenchmarkTestData::COMPLEX_DELEGATE); 212 | 213 | data.model->setContentWidth(CONTENT_WIDTH); 214 | data.view->setViewportWidth(VIEWPORT_WIDTH); 215 | data.view->setViewportX(0); 216 | 217 | QBENCHMARK_ONCE{ 218 | for ( int i = 0; i < CONTENT_WIDTH - ITEM_WIDTH; i += ITEM_WIDTH + 1 ) 219 | data.model->insertItem(i, 1); 220 | } 221 | } 222 | 223 | void QViewportBenchmarkTest::complexDelegateViewTest(){ 224 | QViewportBenchmarkTestData data(QViewportBenchmarkTestData::COMPLEX_DELEGATE); 225 | data.model->setContentWidth(CONTENT_WIDTH); 226 | data.view->setViewportWidth(VIEWPORT_WIDTH); 227 | data.view->setViewportX(0); 228 | for ( int i = 0; i < CONTENT_WIDTH - ITEM_WIDTH; i += ITEM_WIDTH + 1 ) 229 | data.model->insertItem(i, 1); 230 | 231 | QBENCHMARK_ONCE{ 232 | for ( int i = 0; i < SCROLL_ITERATIONS; ++i ) 233 | data.view->setViewportX(i); 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /tests/qrangeviewtest.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2015-2016 Dinu SV. 4 | ** (contact: mail@dinusv.com) 5 | ** This file is part of QML Gantt library. 6 | ** 7 | ** GNU Lesser General Public License Usage 8 | ** This file may be used under the terms of the GNU Lesser 9 | ** General Public License version 3 as published by the Free Software 10 | ** Foundation and appearing in the file LICENSE.LGPLv3 included in the 11 | ** packaging of this file. Please review the following information to 12 | ** ensure the GNU Lesser General Public License version 3 requirements 13 | ** will be met: https://www.gnu.org/licenses/lgpl.html. 14 | ** 15 | ****************************************************************************/ 16 | 17 | #include "qrangeviewtest.h" 18 | #include "qganttmodel.h" 19 | #include "qrangeview.h" 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | Q_TEST_RUNNER_REGISTER(QRangeViewTest); 29 | 30 | QRangeViewTest::QRangeViewTest(QObject *parent) 31 | : QObject(parent) 32 | , m_engine(0) 33 | , m_context(0) 34 | , m_viewDelegate(0) 35 | { 36 | } 37 | 38 | QRangeViewTest::~QRangeViewTest(){ 39 | } 40 | 41 | void QRangeViewTest::initTestCase(){ 42 | m_engine = new QQmlEngine; 43 | m_context = new QQmlContext(m_engine); 44 | m_viewDelegate = new QQmlComponent(m_engine); 45 | m_container = new QQuickItem; 46 | 47 | m_engine->setContextForObject(m_viewDelegate, m_context); 48 | m_viewDelegate->setData("import QtQuick 2.1\nRectangle{x:position; width:length}", QUrl()); 49 | } 50 | 51 | void QRangeViewTest::cleanupTestCase(){ 52 | delete m_container; 53 | delete m_viewDelegate; 54 | delete m_engine; 55 | delete m_context; 56 | } 57 | 58 | void QRangeViewTest::lookupTest(){ 59 | QRangeView* view = createView(); 60 | 61 | view->model()->setContentWidth(200); 62 | for ( int i = 10; i < 200; i += 10 ) 63 | view->model()->insertItem(i, 10); 64 | view->setViewportWidth(100); 65 | 66 | view->setViewportX(0); 67 | QCOMPARE(m_container->childItems().size(), 10); 68 | 69 | view->setViewportX(100); 70 | QCOMPARE(m_container->childItems().size(), 11); 71 | 72 | view->setViewportX(150); 73 | QCOMPARE(m_container->childItems().size(), 6); 74 | 75 | view->setViewportWidth(15); 76 | QCOMPARE(m_container->childItems().size(), 3); 77 | 78 | view->setViewportWidth(10); 79 | QCOMPARE(m_container->childItems().size(), 2); 80 | 81 | view->setViewportWidth(11); 82 | QCOMPARE(m_container->childItems().size(), 3); 83 | 84 | view->setViewportX(159); 85 | QCOMPARE(m_container->childItems().size(), 3); 86 | 87 | delete view->model(); 88 | delete view; 89 | } 90 | 91 | void QRangeViewTest::edgeLookupTest(){ 92 | QRangeView* view = createView(); 93 | 94 | view->model()->setContentWidth(200); 95 | view->setViewportWidth(100); 96 | 97 | view->model()->insertItem(10, 30); 98 | view->model()->insertItem(5, 40); 99 | view->model()->insertItem(10, 40); 100 | 101 | view->setViewportX(60); 102 | QCOMPARE(m_container->childItems().size(), 1); 103 | view->setViewportX(45); 104 | QCOMPARE(m_container->childItems().size(), 2); 105 | view->setViewportX(0); 106 | QCOMPARE(m_container->childItems().size(), 4); 107 | 108 | view->model()->insertItem(20, 10); 109 | QCOMPARE(m_container->childItems().size(), 5); 110 | 111 | // keep all items, due to the first segment (5, 40) that covers the rest 112 | view->setViewportX(40); 113 | QCOMPARE(m_container->childItems().size(), 5); 114 | 115 | delete view->model(); 116 | delete view; 117 | } 118 | 119 | void QRangeViewTest::modelItemChangeTest(){ 120 | QRangeView* view = createView(); 121 | 122 | view->model()->setContentWidth(200); 123 | for ( int i = 10; i < 200; i += 10 ) 124 | view->model()->insertItem(i, 10); 125 | 126 | view->setViewportWidth(100); 127 | view->setViewportX(0); 128 | QCOMPARE(m_container->childItems().size(), 10); 129 | 130 | view->model()->removeItem(10, 10, 0); 131 | QCOMPARE(m_container->childItems().size(), 9); 132 | 133 | view->model()->removeItem(20, 10, 0); 134 | view->model()->removeItem(30, 10, 0); 135 | view->model()->removeItem(40, 10, 0); 136 | view->model()->removeItem(50, 10, 0); 137 | QCOMPARE(m_container->childItems().size(), 5); 138 | 139 | view->model()->insertItem(33, 5); 140 | view->model()->insertItem(99, 10); 141 | view->model()->insertItem(99, 10); 142 | view->model()->insertItem(99, 10); 143 | view->model()->insertItem(98, 10); 144 | QCOMPARE(m_container->childItems().size(), 10); 145 | 146 | view->model()->removeItem(99, 10, 1); 147 | QCOMPARE(m_container->childItems().size(), 9); 148 | 149 | view->model()->removeItem(98, 10, 0); 150 | QCOMPARE(m_container->childItems().size(), 8); 151 | 152 | view->setViewportX(100); 153 | QCOMPARE(m_container->childItems().size(), 13); 154 | 155 | view->model()->insertItem(99, 1); 156 | QCOMPARE(m_container->childItems().size(), 13); 157 | 158 | delete view->model(); 159 | delete view; 160 | } 161 | 162 | void QRangeViewTest::modelItemPositionChangeTest(){ 163 | QRangeView* view = createView(); 164 | 165 | view->model()->setContentWidth(200); 166 | for ( int i = 10; i < 200; i += 10 ) 167 | view->model()->insertItem(i, 10); 168 | 169 | view->setViewportWidth(100); 170 | view->setViewportX(0); 171 | QCOMPARE(m_container->childItems().size(), 10); 172 | 173 | view->model()->setItemPosition(10, 10, 0, 100); 174 | QCOMPARE(m_container->childItems().size(), 9); 175 | 176 | view->model()->setItemPosition(150, 10, 0, 99); 177 | QCOMPARE(m_container->childItems().size(), 10); 178 | 179 | view->model()->insertItem(99, 10); 180 | view->model()->insertItem(99, 10); 181 | QCOMPARE(m_container->childItems().size(), 12); 182 | 183 | view->model()->setItemPosition(99, 10, 2, 101); 184 | QCOMPARE(m_container->childItems().size(), 11); 185 | 186 | view->model()->setItemPosition(101, 10, 0, 99); 187 | QCOMPARE(m_container->childItems().size(), 12); 188 | 189 | delete view->model(); 190 | delete view; 191 | } 192 | 193 | void QRangeViewTest::modelItemLengthChangeTest(){ 194 | QRangeView* view = createView(); 195 | 196 | view->model()->setContentWidth(200); 197 | for ( int i = 10; i < 200; i += 10 ) 198 | view->model()->insertItem(i, 10); 199 | 200 | view->setViewportWidth(100); 201 | view->setViewportX(15); 202 | QCOMPARE(m_container->childItems().size(), 12); 203 | 204 | view->model()->setItemLength(10, 10, 0, 5); 205 | QCOMPARE(m_container->childItems().size(), 11); 206 | 207 | view->model()->setItemLength(20, 10, 0, 5); 208 | QCOMPARE(m_container->childItems().size(), 11); 209 | 210 | view->model()->insertItem(5, 10); 211 | QCOMPARE(m_container->childItems().size(), 11); 212 | 213 | view->model()->setItemLength(5, 10, 0, 15); 214 | QCOMPARE(m_container->childItems().size(), 12); 215 | 216 | delete view->model(); 217 | delete view; 218 | } 219 | 220 | void QRangeViewTest::modelItemDataChangeTest(){ 221 | QRangeView* view = createView(); 222 | 223 | view->model()->setContentWidth(200); 224 | for ( int i = 10; i < 200; i += 10 ) 225 | view->model()->insertItem(i, 10); 226 | 227 | view->setViewportWidth(100); 228 | view->setViewportX(0); 229 | 230 | QCOMPARE(m_container->childItems().size(), 10); 231 | 232 | view->setDataViaDelegate(m_container->childItems()[2], "modelData", "200"); 233 | QCOMPARE(getModelData(view, 30, 31, QGanttModel::modelData), QVariant("200")); 234 | QCOMPARE(qmlContext(m_container->childItems()[2])->contextProperty("modelData"), QVariant("200")); 235 | 236 | view->setDataViaDelegate(m_container->childItems()[1], "modelData", "3300"); 237 | QCOMPARE(getModelData(view, 20, 21, QGanttModel::modelData), QVariant("3300")); 238 | QCOMPARE(qmlContext(m_container->childItems()[1])->contextProperty("modelData"), QVariant("3300")); 239 | 240 | view->setDataViaDelegate(m_container->childItems()[9], "modelData", "100"); 241 | QCOMPARE(getModelData(view, 100, 101, QGanttModel::modelData), QVariant("100")); 242 | QCOMPARE(qmlContext(m_container->childItems()[9])->contextProperty("modelData"), QVariant("100")); 243 | 244 | delete view->model(); 245 | delete view; 246 | } 247 | 248 | QRangeView *QRangeViewTest::createView(){ 249 | QRangeView* view = new QRangeView(m_container); 250 | view->setDelegate(m_viewDelegate); 251 | view->setModel(new QGanttModel); 252 | return view; 253 | } 254 | 255 | QVariant QRangeViewTest::getModelData(QRangeView* view, qint64 startPos, qint64 endPos, int role){ 256 | QAbstractRangeModelIterator* modelIt = view->model()->dataBetween(startPos, endPos); 257 | if ( modelIt->isEnd() ) 258 | return QVariant(); 259 | QVariant value = modelIt->data(role); 260 | delete modelIt; 261 | return value; 262 | } 263 | -------------------------------------------------------------------------------- /lib/qganttmodel.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2015-2016 Dinu SV. 4 | ** (contact: mail@dinusv.com) 5 | ** This file is part of QML Gantt library. 6 | ** 7 | ** GNU Lesser General Public License Usage 8 | ** This file may be used under the terms of the GNU Lesser 9 | ** General Public License version 3 as published by the Free Software 10 | ** Foundation and appearing in the file LICENSE.LGPLv3 included in the 11 | ** packaging of this file. Please review the following information to 12 | ** ensure the GNU Lesser General Public License version 3 requirements 13 | ** will be met: https://www.gnu.org/licenses/lgpl.html. 14 | ** 15 | ****************************************************************************/ 16 | 17 | #include "qganttmodel.h" 18 | #include "qganttmodelitem.h" 19 | #include 20 | 21 | // QGanttModelPrivate 22 | // ---------------------------------------------------------------------------- 23 | 24 | class QGanttModelPrivate{ 25 | public: 26 | QGanttModelPrivate() : lastChange(0), itemDataFactory(0){} 27 | ~QGanttModelPrivate(); 28 | 29 | class QGanttModelChange{ 30 | public: 31 | qint64 startPosition; 32 | qint64 endPosition; 33 | }; 34 | 35 | QList items; 36 | QGanttModelChange* lastChange; 37 | QGanttModelChange* change(qint64 startPosition, qint64 endPosition); 38 | 39 | QGanttModel::ItemDataFactoryFunction itemDataFactory; 40 | 41 | int searchFirstIndex(qint64 position); 42 | int searchFirstIndex(qint64 position, qint64 length); 43 | }; 44 | 45 | QGanttModelPrivate::~QGanttModelPrivate(){ 46 | for (QList::iterator it = items.begin(); it != items.end(); ++it ){ 47 | delete *it; 48 | } 49 | items.clear(); 50 | } 51 | 52 | QGanttModelPrivate::QGanttModelChange* QGanttModelPrivate::change(qint64 startPosition, qint64 endPosition){ 53 | QGanttModelChange* change = new QGanttModelChange; 54 | change->startPosition = startPosition; 55 | change->endPosition = endPosition; 56 | return change; 57 | } 58 | 59 | int QGanttModelPrivate::searchFirstIndex(qint64 position){ 60 | if ( items.size() == 0 ) 61 | return 0; 62 | if ( items.last()->position() < position ) 63 | items.size(); 64 | 65 | int first = 0, last = items.size() - 1, middle = (first + last) / 2; 66 | 67 | while( first <= last ){ 68 | if ( items[middle]->position() < position ){ 69 | first = middle + 1; 70 | } else { 71 | last = middle - 1; 72 | } 73 | 74 | middle = (first + last) / 2; 75 | } 76 | 77 | if ( items[middle]->position() < position ) 78 | return middle + 1; 79 | else 80 | return middle; 81 | } 82 | 83 | int QGanttModelPrivate::searchFirstIndex(qint64 position, qint64 length){ 84 | int index = searchFirstIndex(position); 85 | while ( index < items.size() ){ 86 | if ( items[index]->position() != position ) 87 | return items.size(); 88 | if ( items[index]->length() == length ) 89 | return index; 90 | ++index; 91 | } 92 | return items.size(); 93 | } 94 | 95 | // QGanttModel 96 | // ---------------------------------------------------------------------------- 97 | 98 | QGanttModel::QGanttModel(QObject *parent) 99 | : QAbstractRangeModel(parent) 100 | , d_ptr(new QGanttModelPrivate) 101 | { 102 | } 103 | 104 | QGanttModel::~QGanttModel(){ 105 | delete d_ptr; 106 | } 107 | 108 | QAbstractRangeModelIterator* QGanttModel::dataBetween(qint64 startPosition, qint64 endPosition){ 109 | Q_D(QGanttModel); 110 | 111 | int startIndex = d->searchFirstIndex(startPosition); 112 | while ( startIndex > 0 ){ 113 | --startIndex; 114 | if ( d->items[startIndex]->position() + d->items[startIndex]->length() < startPosition ){ 115 | ++startIndex; 116 | break; 117 | } 118 | } 119 | if ( startIndex < d->items.size() ){ 120 | return new QGanttModelIterator(d->items.begin() + startIndex, d->items.end(), endPosition); 121 | } 122 | return new QGanttModelIterator(d->items.end(), d->items.end(), endPosition); 123 | } 124 | 125 | void QGanttModel::setItemPosition(qint64 itemPosition, qint64 itemLength, int indexOffset, qint64 itemNewPosition){ 126 | Q_D(QGanttModel); 127 | 128 | int index = d->searchFirstIndex(itemPosition, itemLength) + indexOffset; 129 | if ( index >= d->items.size() ) 130 | return; 131 | 132 | QGanttModelItem* item = d->items[index]; 133 | if ( item->position() != itemPosition ) 134 | return; 135 | 136 | d->items.takeAt(index); 137 | item->setPosition(itemNewPosition); 138 | int newIndex = insertItem(item); 139 | 140 | QList::iterator itemIt = d->items.begin() + newIndex; 141 | QGanttModelIterator* modelIt = new QGanttModelIterator(itemIt, itemIt + 1, itemNewPosition + 1); 142 | emit itemCoordinatesChanged(itemPosition, itemLength, indexOffset, modelIt, 0); 143 | } 144 | 145 | void QGanttModel::setItemLength(qint64 itemPosition, qint64 itemLength, int indexOffset, qint64 newLength){ 146 | Q_D(QGanttModel); 147 | if ( itemLength == newLength ) 148 | return; 149 | 150 | int positionIndex = d->searchFirstIndex(itemPosition); 151 | int index = positionIndex; 152 | while ( index < d->items.size() ){ 153 | if ( d->items[index]->length() == itemLength ) 154 | break; 155 | ++index; 156 | } 157 | 158 | index += indexOffset; 159 | if ( index >= d->items.size() ) 160 | return; 161 | 162 | QGanttModelItem* item = d->items[index]; 163 | if ( item->position() != itemPosition ) 164 | return; 165 | 166 | while ( positionIndex < d->items.size() ){ 167 | if ( d->items[positionIndex]->position() == item->position() && d->items[positionIndex]->length() < newLength ) 168 | ++positionIndex; 169 | else 170 | break; 171 | } 172 | 173 | if ( index == positionIndex || index == positionIndex + 1 ){ 174 | item->setLength(newLength); 175 | 176 | QList::iterator itemIt = d->items.begin() + index; 177 | QGanttModelIterator* modelIt = new QGanttModelIterator(itemIt, itemIt + 1, itemPosition + 1); 178 | emit itemCoordinatesChanged(itemPosition, itemLength, indexOffset, modelIt, indexOffset); 179 | } else { 180 | d->items.takeAt(index); 181 | item->setLength(newLength); 182 | int newIndex = insertItem(item); 183 | 184 | QList::iterator itemIt = d->items.begin() + newIndex; 185 | QGanttModelIterator* modelIt = new QGanttModelIterator(itemIt, itemIt + 1, itemPosition + 1); 186 | emit itemCoordinatesChanged(itemPosition, itemLength, indexOffset, modelIt, 0); 187 | } 188 | 189 | } 190 | 191 | void QGanttModel::setItemData( 192 | qint64 itemPosition, 193 | qint64 itemLength, 194 | int positionIndex, 195 | int role, 196 | const QVariant& value) 197 | { 198 | Q_D(QGanttModel); 199 | int index = d->searchFirstIndex(itemPosition, itemLength) + positionIndex; 200 | if ( index >= d->items.size() ) 201 | return; 202 | 203 | QList::iterator itemIt = d->items.begin() + index; 204 | QGanttModelItem* item = *itemIt; 205 | QGanttModelIterator* modelIt = new QGanttModelIterator(itemIt, itemIt + 1, itemPosition + 1); 206 | 207 | if ( role == QGanttModel::modelData ){ 208 | item->setData(value.toString()); 209 | emit itemsDataChanged(modelIt, positionIndex, QList() << QGanttModel::modelData); 210 | } 211 | } 212 | 213 | QHash QGanttModel::roleNames() const{ 214 | QHash roles; 215 | roles[QGanttModel::modelData] = "modelData"; 216 | return roles; 217 | } 218 | 219 | int QGanttModel::insertItem(QGanttModelItem* item){ 220 | Q_D(QGanttModel); 221 | int index = d->searchFirstIndex(item->position()); 222 | while ( index < d->items.size() ){ 223 | if ( d->items[index]->position() == item->position() && d->items[index]->length() < item->length() ) 224 | ++index; 225 | else { 226 | break; 227 | } 228 | } 229 | d->items.insert(index, item); 230 | return index; 231 | } 232 | 233 | void QGanttModel::setItemDataFactoryFunction(QGanttModel::ItemDataFactoryFunction fp){ 234 | Q_D(QGanttModel); 235 | d->itemDataFactory = fp; 236 | } 237 | 238 | void QGanttModel::insertItem(qint64 position, qint64 length){ 239 | Q_D(QGanttModel); 240 | QGanttModelItem* item = new QGanttModelItem(position, length); 241 | if ( d->itemDataFactory ) 242 | item->setData(d->itemDataFactory()); 243 | 244 | beginDataChange(position, position + 1); 245 | insertItem(item); 246 | endDataChange(); 247 | } 248 | 249 | void QGanttModel::removeItem(qint64 position, qint64 length, qint64 relativeIndex){ 250 | Q_D(QGanttModel); 251 | int index = d->searchFirstIndex(position); 252 | while ( index < d->items.size() ){ 253 | if ( d->items[index]->position() != position ) 254 | return; 255 | 256 | if ( d->items[index]->length() == length && index + relativeIndex < d->items.size() ){ 257 | index += relativeIndex; 258 | if ( d->items[index]->position() == position && d->items[index]->length() == length ){ 259 | beginDataChange(position, position + 1); 260 | delete d->items[index]; 261 | d->items.removeAt(index); 262 | endDataChange(); 263 | return; 264 | } 265 | } 266 | ++index; 267 | } 268 | } 269 | 270 | bool QGanttModelIterator::isEnd() const{ 271 | if ( m_currentIt == m_endIt ) 272 | return true; 273 | if ( (*m_currentIt)->position() >= m_endPosition ) 274 | return true; 275 | return false; 276 | } 277 | 278 | void QGanttModelIterator::nextItem(){ 279 | if ( isEnd() ) 280 | return; 281 | ++m_currentIt; 282 | } 283 | 284 | QVariant QGanttModelIterator::data(int role){ 285 | if ( role == QGanttModel::PositionRole ) 286 | return (*m_currentIt)->position(); 287 | else if ( role == QGanttModel::LengthRole ) 288 | return (*m_currentIt)->length(); 289 | else if ( role == QGanttModel::modelData ) 290 | return (*m_currentIt)->data(); 291 | return QVariant(); 292 | } 293 | -------------------------------------------------------------------------------- /sample/resource/GanttLine.qml: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2015-2016 Dinu SV. 4 | ** (contact: mail@dinusv.com) 5 | ** This file is part of QML Gantt library. 6 | ** 7 | ** GNU Lesser General Public License Usage 8 | ** This file may be used under the terms of the GNU Lesser 9 | ** General Public License version 3 as published by the Free Software 10 | ** Foundation and appearing in the file LICENSE.LGPLv3 included in the 11 | ** packaging of this file. Please review the following information to 12 | ** ensure the GNU Lesser General Public License version 3 requirements 13 | ** will be met: https://www.gnu.org/licenses/lgpl.html. 14 | ** 15 | ****************************************************************************/ 16 | 17 | import QtQuick 2.2 18 | import QtQuick.Controls 1.1 19 | import Gantt 1.0 20 | 21 | Rectangle{ 22 | id: root 23 | 24 | signal editItem(variant item) 25 | 26 | property variant model: GanttModel{} 27 | 28 | property int zoomScale: 1 29 | property int ganttLength: ganttModel.contentWidth 30 | 31 | property alias viewportX: rangeView.viewportX 32 | property alias viewportWidth: rangeView.viewportWidth 33 | 34 | property color elementBorderColor: "#ccc" 35 | property color elementBorderFocusColor: "#fff" 36 | 37 | color: "#ccc" 38 | width: rangeView.width * zoomScale 39 | 40 | RangeView { 41 | id: rangeView 42 | 43 | model: ganttModel 44 | height: root.height 45 | 46 | MouseArea { 47 | anchors.fill: parent 48 | onDoubleClicked: { 49 | var itemPosition = mouse.x - 10 >= 0 ? mouse.x - 10 : 0 50 | if (itemPosition + 20 >= ganttModel.contentWidth) 51 | itemPosition = ganttModel.contentWidth - 20 52 | ganttModel.insertItem(itemPosition, 20) 53 | } 54 | } 55 | 56 | delegate: Component { 57 | 58 | Rectangle { 59 | id: ganttDelegate 60 | 61 | width: length * root.zoomScale 62 | height: rangeView.height 63 | 64 | clip: true 65 | color: modelData.color 66 | 67 | x: position * root.zoomScale 68 | 69 | Keys.onPressed: { 70 | if (event.key === Qt.Key_Delete) { 71 | event.accepted = true 72 | rangeView.removeItemViaDelegate(ganttDelegate) 73 | } else if (event.key === Qt.Key_Left) { 74 | 75 | if (event.modifiers & Qt.ShiftModifier) { 76 | var newLength = length - 1 77 | if (newLength > 0) { 78 | rangeView.setLengthViaDelegate(ganttDelegate, 79 | newLength) 80 | ganttDelegate.width = newLength * root.zoomScale 81 | } 82 | } else { 83 | var newPosition = position - 1 84 | if (newPosition >= 0) { 85 | rangeView.setPositionViaDelegate(ganttDelegate, 86 | newPosition) 87 | ganttDelegate.x = newPosition * root.zoomScale 88 | } 89 | } 90 | event.accepted = true 91 | } else if (event.key === Qt.Key_Right) { 92 | 93 | if (event.modifiers & Qt.ShiftModifier) { 94 | var newLength = length + 1 95 | if (position + length < ganttModel.contentWidth) { 96 | rangeView.setLengthViaDelegate(ganttDelegate, 97 | newLength) 98 | ganttDelegate.width = newLength * root.zoomScale 99 | } 100 | } else { 101 | var newPosition = position + 1 102 | if (position + length < ganttModel.contentWidth) { 103 | rangeView.setPositionViaDelegate(ganttDelegate, 104 | newPosition) 105 | ganttDelegate.x = newPosition * root.zoomScale 106 | } 107 | } 108 | 109 | event.accepted = true 110 | } 111 | } 112 | 113 | Rectangle { 114 | id: stretchLeft 115 | anchors.left: parent.left 116 | anchors.top: parent.top 117 | width: 2 118 | height: parent.height 119 | color: ganttDelegate.activeFocus ? root.elementBorderFocusColor : root.elementBorderColor 120 | 121 | MouseArea { 122 | anchors.fill: parent 123 | cursorShape: Qt.SizeHorCursor 124 | 125 | property int oldMouseX 126 | 127 | onPressed: { 128 | oldMouseX = mouseX 129 | } 130 | onPositionChanged: { 131 | if (pressed) { 132 | var newWidth = ganttDelegate.width + oldMouseX - mouseX 133 | var newX = ganttDelegate.x + mouseX - oldMouseX 134 | var newLength = Math.round( 135 | newWidth / root.zoomScale) 136 | if (newLength > 0 137 | && newX + newWidth < rangeView.width) { 138 | if (newX > 0) { 139 | ganttDelegate.width = newWidth 140 | ganttDelegate.x = newX 141 | } else { 142 | ganttDelegate.width = ganttDelegate.x + ganttDelegate.width 143 | ganttDelegate.x = 0 144 | } 145 | } else { 146 | ganttDelegate.x = ganttDelegate.x 147 | + ganttDelegate.width - (1 * root.zoomScale) 148 | ganttDelegate.width = 1 * root.zoomScale 149 | } 150 | } 151 | } 152 | 153 | onReleased: { 154 | rangeView.setLengthViaDelegate( 155 | ganttDelegate, Math.round( 156 | ganttDelegate.width / root.zoomScale)) 157 | rangeView.setPositionViaDelegate( 158 | ganttDelegate, Math.round( 159 | ganttDelegate.x / root.zoomScale)) 160 | ganttDelegate.width = length * root.zoomScale 161 | ganttDelegate.x = position * root.zoomScale 162 | } 163 | } 164 | } 165 | Rectangle { 166 | id: centerMove 167 | anchors.left: parent.left 168 | anchors.leftMargin: 2 169 | anchors.right: parent.right 170 | anchors.rightMargin: 2 171 | width: parent.width 172 | height: parent.height 173 | color: parent.color 174 | MouseArea { 175 | anchors.fill: parent 176 | cursorShape: Qt.SizeAllCursor 177 | drag.target: ganttDelegate 178 | drag.axis: Drag.XAxis 179 | drag.minimumX: 0 180 | drag.maximumX: rangeView.width - ganttDelegate.width 181 | onPressed: { 182 | ganttDelegate.forceActiveFocus() 183 | } 184 | onDoubleClicked: { 185 | if (mouse.modifiers & Qt.ControlModifier) { 186 | var leftDistance = root.currentFrame - position 187 | var rightDistance = root.currentFrame - (position + length) 188 | if (Math.abs(rightDistance) > Math.abs( 189 | leftDistance)) { 190 | length -= leftDistance 191 | position += leftDistance 192 | ganttDelegate.width = length * root.zoomScale 193 | ganttDelegate.x = position * root.zoomScale 194 | } else { 195 | length += rightDistance 196 | if (length < 1) 197 | length = 1 198 | ganttDelegate.width = length * root.zoomScale 199 | } 200 | } else { 201 | root.editItem(modelData) 202 | } 203 | } 204 | onReleased: { 205 | rangeView.setPositionViaDelegate( 206 | ganttDelegate, Math.round( 207 | ganttDelegate.x / root.zoomScale)) 208 | ganttDelegate.x = position * root.zoomScale 209 | } 210 | } 211 | } 212 | Rectangle { 213 | id: strechRight 214 | anchors.right: parent.right 215 | anchors.top: parent.top 216 | width: 2 217 | height: parent.height 218 | color: ganttDelegate.activeFocus ? root.elementBorderFocusColor : root.elementBorderColor 219 | 220 | MouseArea { 221 | anchors.fill: parent 222 | cursorShape: Qt.SizeHorCursor 223 | 224 | property int oldMouseX 225 | 226 | onPressed: oldMouseX = mouseX 227 | onPositionChanged: { 228 | if (pressed) { 229 | var newWidth = ganttDelegate.width + (mouseX - oldMouseX) 230 | var newLength = Math.round( 231 | newWidth / root.zoomScale) 232 | if (newLength > 0) { 233 | if (position + newLength < root.ganttLength) 234 | ganttDelegate.width = newWidth 235 | else 236 | ganttDelegate.width = (root.ganttLength - position) 237 | * root.zoomScale 238 | } else 239 | ganttDelegate.width = 1 * root.zoomScale 240 | } 241 | } 242 | onReleased: { 243 | rangeView.setLengthViaDelegate( 244 | ganttDelegate, Math.round( 245 | ganttDelegate.width / root.zoomScale)) 246 | ganttDelegate.width = length * root.zoomScale 247 | } 248 | } 249 | } 250 | 251 | Text { 252 | anchors.left: parent.left 253 | anchors.leftMargin: 5 254 | anchors.verticalCenter: parent.verticalCenter 255 | color: "#fff" 256 | text: modelData.label 257 | font.family: "sans-serif" 258 | font.pixelSize: 10 259 | } 260 | } 261 | } 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /lib/qrangeview.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2015-2016 Dinu SV. 4 | ** (contact: mail@dinusv.com) 5 | ** This file is part of QML Gantt library. 6 | ** 7 | ** GNU Lesser General Public License Usage 8 | ** This file may be used under the terms of the GNU Lesser 9 | ** General Public License version 3 as published by the Free Software 10 | ** Foundation and appearing in the file LICENSE.LGPLv3 included in the 11 | ** packaging of this file. Please review the following information to 12 | ** ensure the GNU Lesser General Public License version 3 requirements 13 | ** will be met: https://www.gnu.org/licenses/lgpl.html. 14 | ** 15 | ****************************************************************************/ 16 | 17 | #include "qrangeview.h" 18 | #include "qabstractrangemodel.h" 19 | #include 20 | 21 | #include 22 | 23 | // QRangeViewItem 24 | // ---------------------------------------------------------------------------- 25 | 26 | class QRangeViewItem{ 27 | public: 28 | QQmlContext* context; 29 | QQuickItem* delegate; 30 | qint64 position; 31 | qint64 length; 32 | 33 | ~QRangeViewItem(); 34 | }; 35 | 36 | QRangeViewItem::~QRangeViewItem(){ 37 | delegate->setParentItem(0); 38 | delegate->deleteLater(); 39 | context->deleteLater(); 40 | } 41 | 42 | // QRangeViewPrivate 43 | // ---------------------------------------------------------------------------- 44 | 45 | class QRangeViewPrivate{ 46 | 47 | public: 48 | QRangeViewPrivate(); 49 | ~QRangeViewPrivate(); 50 | 51 | // Fields 52 | // ------ 53 | 54 | QList items; 55 | 56 | int lastViewportWidth; 57 | int lastViewportX; 58 | 59 | QAbstractRangeModel* model; 60 | QQmlComponent* delegate; 61 | QHash modelRoles; 62 | 63 | // Methods 64 | // ------- 65 | 66 | int findItemFrom(qint64 position); 67 | int findItem(qint64 position, qint64 length); 68 | int findItemByDelegate(QQuickItem* delegate); 69 | 70 | int findRelativeIndex(int index); 71 | 72 | void clearItemsInInterval(qint64 positionBegin, qint64 positionEnd); 73 | void clearItems(); 74 | 75 | QString listItems(); 76 | }; 77 | 78 | QRangeViewPrivate::QRangeViewPrivate() 79 | : lastViewportWidth(0) 80 | , lastViewportX(0) 81 | , model(0) 82 | , delegate(0) 83 | { 84 | } 85 | 86 | QRangeViewPrivate::~QRangeViewPrivate(){ 87 | clearItems(); 88 | } 89 | 90 | int QRangeViewPrivate::findItemFrom(qint64 position){ 91 | if ( items.size() == 0 ) 92 | return 0; 93 | if ( items.last()->position < position ) 94 | return items.size(); 95 | 96 | int first = 0, last = items.size() - 1, middle = (first + last) / 2; 97 | 98 | while( first <= last ){ 99 | if ( items[middle]->position < position ){ 100 | first = middle + 1; 101 | } else { 102 | last = middle - 1; 103 | } 104 | 105 | middle = (first + last) / 2; 106 | } 107 | 108 | if ( items[middle]->position < position ) 109 | return middle + 1; 110 | else 111 | return middle; 112 | } 113 | 114 | int QRangeViewPrivate::findItem(qint64 position, qint64 length){ 115 | int index = findItemFrom(position); 116 | while ( index < items.size() ){ 117 | if ( items[index]->position != position ) 118 | return items.size(); 119 | if ( items[index]->length == length ) 120 | return index; 121 | ++index; 122 | } 123 | return items.size(); 124 | } 125 | 126 | int QRangeViewPrivate::findItemByDelegate(QQuickItem* delegate){ 127 | int index = findItemFrom(qmlContext(delegate)->contextProperty("position").toLongLong()); 128 | while( index != items.size() ){ 129 | if ( items[index]->delegate == delegate ) 130 | return index; 131 | ++index; 132 | } 133 | return index; 134 | } 135 | 136 | int QRangeViewPrivate::findRelativeIndex(int index){ 137 | if ( index >= items.size() ) 138 | return 0; 139 | 140 | QRangeViewItem* rangeItem = items[index]; 141 | int itemIndex = 0; 142 | while ( index > 0 ){ 143 | --index; 144 | if ( items[index]->position == rangeItem->position && items[index]->length == rangeItem->length) 145 | ++itemIndex; 146 | else 147 | break; 148 | } 149 | return itemIndex; 150 | } 151 | 152 | void QRangeViewPrivate::clearItemsInInterval(qint64 positionBegin, qint64 positionEnd){ 153 | int endIndex = findItemFrom(positionEnd); 154 | int index = endIndex; 155 | while ( index > 0 ){ 156 | --index; 157 | if ( items[index]->position + items[index]->length < positionBegin ){ 158 | ++index; 159 | break; 160 | } 161 | delete items[index]; 162 | } 163 | 164 | if (index != endIndex) 165 | items.erase(items.begin() + index, items.begin() + endIndex); 166 | } 167 | 168 | void QRangeViewPrivate::clearItems(){ 169 | for ( QList::iterator it = items.begin(); it != items.end(); ++it ) 170 | delete *it; 171 | items.clear(); 172 | } 173 | 174 | QString QRangeViewPrivate::listItems(){ 175 | QString base = ""; 176 | for ( int i = 0; i < items.size(); ++i ){ 177 | if ( i != 0 ) 178 | base += " "; 179 | base += "[" + QString::number(items[i]->position) + "," + 180 | QString::number(items[i]->position + items[i]->length) + "]"; 181 | } 182 | return base; 183 | } 184 | 185 | // QRangeView 186 | // ---------------------------------------------------------------------------- 187 | 188 | QRangeView::QRangeView(QQuickItem *parent) 189 | : QQuickItem(parent) 190 | , d_ptr(new QRangeViewPrivate) 191 | , m_viewportWidth(0) 192 | , m_viewportX(0) 193 | { 194 | setFlag(QQuickItem::ItemHasContents, true); 195 | } 196 | 197 | QRangeView::~QRangeView(){ 198 | delete d_ptr; 199 | } 200 | 201 | void QRangeView::modelWidthChanged(){ 202 | Q_D(QRangeView); 203 | setImplicitWidth(d->model->contentWidth()); 204 | } 205 | 206 | void QRangeView::modelitemCoordinatesChanged(qint64 oldPosition, qint64 oldLength, int relativeIndex, 207 | QAbstractRangeModelIterator *modelIt, int newRelativeIndex) 208 | { 209 | Q_D(QRangeView); 210 | 211 | QRangeViewItem* item = 0; 212 | 213 | if ( modelIt->isEnd() ){ 214 | qWarning("Empty model iterator sent by model."); 215 | return; 216 | } 217 | 218 | qint64 newPosition = modelIt->data(QAbstractRangeModel::PositionRole).toLongLong(); 219 | qint64 newLength = modelIt->data(QAbstractRangeModel::LengthRole).toLongLong(); 220 | 221 | if ( oldPosition < m_viewportX + m_viewportWidth && oldPosition + oldLength > m_viewportX ){ 222 | int index = d->findItem(oldPosition, oldLength) + relativeIndex; 223 | if ( index >= d->items.size() ){ 224 | qWarning("Failed to find item from given position: %lld, %lld", oldPosition, oldLength); 225 | return; 226 | } 227 | item = d->items.takeAt(index); 228 | } 229 | 230 | if ( newPosition >= m_viewportX + m_viewportWidth || newPosition + newLength <= m_viewportX ){ 231 | if (item) 232 | delete item; 233 | } else { 234 | if (!item){ 235 | item = new QRangeViewItem; 236 | item->context = new QQmlContext(d->delegate->creationContext(), this); 237 | item->context->setContextProperty("position", newPosition); 238 | item->context->setContextProperty("length", newLength); 239 | for(QHash::iterator rolesIt = d->modelRoles.begin(); rolesIt != d->modelRoles.end(); ++rolesIt){ 240 | item->context->setContextProperty(rolesIt.value(), modelIt->data(rolesIt.key())); 241 | } 242 | 243 | item->delegate = qobject_cast(d->delegate->create(item->context)); 244 | item->delegate->setParentItem(parentItem()); 245 | } 246 | 247 | item->position = newPosition; 248 | item->length = newLength; 249 | item->context->setContextProperty("position", newPosition); 250 | item->context->setContextProperty("length", newLength); 251 | 252 | int newIndex = d->findItemFrom(newPosition); 253 | while ( newIndex < d->items.size() ){ 254 | if ( d->items[newIndex]->position == newPosition && d->items[newIndex]->length < newLength ) 255 | ++newIndex; 256 | else { 257 | d->items.insert(newIndex + newRelativeIndex, item); 258 | return; 259 | } 260 | } 261 | d->items.insert(newIndex, item); 262 | } 263 | } 264 | 265 | void QRangeView::modelItemDataChanged(QAbstractRangeModelIterator* modelIt, int relativeIndex, QList roles){ 266 | Q_D(QRangeView); 267 | 268 | int itemAbsoluteIndex = d->items.size(); 269 | if ( !modelIt->isEnd() ){ 270 | qint64 position = modelIt->data(QAbstractRangeModel::PositionRole).toLongLong(); 271 | qint64 length = modelIt->data(QAbstractRangeModel::LengthRole).toLongLong(); 272 | if ( position >= m_viewportX + m_viewportWidth || position + length <= m_viewportX ) 273 | return; 274 | 275 | itemAbsoluteIndex = d->findItem(position, length) + relativeIndex; 276 | if ( itemAbsoluteIndex >= d->items.size() ){ 277 | qWarning("Failed to find item from given model."); 278 | return; 279 | } 280 | 281 | for ( int i = 0; i < roles.size(); ++i ){ 282 | d->items[itemAbsoluteIndex]->context->setContextProperty( 283 | d->modelRoles[roles[i]], modelIt->data(roles[i]) 284 | ); 285 | } 286 | } 287 | } 288 | 289 | void QRangeView::modelItemsAboutToBeChanged(qint64, qint64){} 290 | 291 | void QRangeView::modelItemsChanged(qint64 beginPosition, qint64 endPosition){ 292 | Q_D(QRangeView); 293 | 294 | if ( beginPosition >= m_viewportX + m_viewportWidth || endPosition <= m_viewportX ) 295 | return; 296 | 297 | d->clearItemsInInterval(beginPosition, endPosition); 298 | 299 | QAbstractRangeModelIterator* modelIt = d->model->dataBetween(beginPosition, endPosition); 300 | 301 | QList::iterator itemsIt = d->items.end(); 302 | if ( !modelIt->isEnd() ){ 303 | itemsIt = d->items.begin() + d->findItemFrom(modelIt->data(QAbstractRangeModel::PositionRole).toLongLong()); 304 | } 305 | 306 | while ( !modelIt->isEnd() ){ 307 | QRangeViewItem* viewItem = new QRangeViewItem; 308 | viewItem->position = modelIt->data(QAbstractRangeModel::PositionRole).toLongLong(); 309 | viewItem->length = modelIt->data(QAbstractRangeModel::LengthRole).toLongLong(); 310 | 311 | viewItem->context = new QQmlContext(d->delegate->creationContext(), this); 312 | viewItem->context->setContextProperty("position", viewItem->position); 313 | viewItem->context->setContextProperty("length", viewItem->length); 314 | 315 | for(QHash::iterator rolesIt = d->modelRoles.begin(); rolesIt != d->modelRoles.end(); ++rolesIt){ 316 | viewItem->context->setContextProperty(rolesIt.value(), modelIt->data(rolesIt.key())); 317 | } 318 | 319 | viewItem->delegate = qobject_cast(d->delegate->create(viewItem->context)); 320 | viewItem->delegate->setParentItem(parentItem()); 321 | 322 | itemsIt = d->items.insert(itemsIt, viewItem); 323 | ++itemsIt; 324 | 325 | modelIt->nextItem(); 326 | } 327 | 328 | delete modelIt; 329 | } 330 | 331 | void QRangeView::setPositionViaDelegate(QQuickItem* item, qint64 position){ 332 | Q_D(QRangeView); 333 | int index = d->findItemByDelegate(item); 334 | if ( index == d->items.size() ){ 335 | qWarning("Cannot find item by its position: %lld", static_cast(item->x())); 336 | return; 337 | } 338 | 339 | QRangeViewItem* rangeItem = d->items[index]; 340 | if ( rangeItem->position == position ) 341 | return; 342 | 343 | int relativeIndex = d->findRelativeIndex(index); 344 | 345 | d->model->setItemPosition(rangeItem->position, rangeItem->length, relativeIndex, position); 346 | } 347 | 348 | void QRangeView::setLengthViaDelegate(QQuickItem* item, qint64 newLength){ 349 | Q_D(QRangeView); 350 | int index = d->findItemByDelegate(item); 351 | if ( index == d->items.size() ){ 352 | qWarning("Cannot find item by its position: %lld", static_cast(item->x())); 353 | return; 354 | } 355 | 356 | QRangeViewItem* rangeItem = d->items[index]; 357 | if ( rangeItem->length == newLength ) 358 | return; 359 | 360 | int relativeIndex = d->findRelativeIndex(index); 361 | 362 | d->model->setItemLength(rangeItem->position, rangeItem->length, relativeIndex, newLength); 363 | 364 | } 365 | 366 | void QRangeView::setDataViaDelegate(QQuickItem* item, const QString& role, const QVariant& value){ 367 | Q_D(QRangeView); 368 | int index = d->findItemByDelegate(item); 369 | if ( index == d->items.size() ){ 370 | qWarning("Cannot find item by its position: %lld.", static_cast(item->x())); 371 | return; 372 | } 373 | 374 | QRangeViewItem* rangeItem = d->items[index]; 375 | int relativeIndex = d->findRelativeIndex(index); 376 | 377 | d->model->setItemData( 378 | rangeItem->position, 379 | rangeItem->length, 380 | relativeIndex, 381 | d->modelRoles.key(role.toLatin1()), 382 | value 383 | ); 384 | } 385 | 386 | void QRangeView::removeItemViaDelegate(QQuickItem *item){ 387 | Q_D(QRangeView); 388 | int index = d->findItemByDelegate(item); 389 | if ( index == d->items.size() ){ 390 | qWarning("Cannot find item by its position: %lld.", static_cast(item->x())); 391 | return; 392 | } 393 | 394 | QRangeViewItem* rangeItem = d->items[index]; 395 | int relativeIndex = d->findRelativeIndex(index); 396 | 397 | d->model->removeItem(rangeItem->position, rangeItem->length, relativeIndex); 398 | } 399 | 400 | void QRangeView::componentComplete(){ 401 | regenerateNewContent(); 402 | } 403 | 404 | void QRangeView::regenerateNewContent(){ 405 | Q_D(QRangeView); 406 | if ( !d->delegate || !d->model ) 407 | return; 408 | 409 | if ( m_viewportX > d->lastViewportX ){ // delete left 410 | QList::iterator it = d->items.begin(); 411 | while ( it != d->items.end() ){ 412 | if ( (*it)->position + (*it)->length > m_viewportX ){ 413 | if ( it != d->items.begin() ) 414 | d->items.erase(d->items.begin(), it); 415 | break; 416 | } 417 | delete *it; 418 | ++it; 419 | } 420 | if ( it == d->items.end() ) 421 | d->items.clear(); 422 | } else if ( m_viewportX < d->lastViewportX ){ // append left 423 | QAbstractRangeModelIterator *modelIt = d->model->dataBetween( 424 | m_viewportX, 425 | d->lastViewportX < m_viewportX + m_viewportWidth ? d->lastViewportX : m_viewportX + m_viewportWidth 426 | ); 427 | 428 | QList::iterator itemsIt = d->items.end(); 429 | if ( !modelIt->isEnd() ){ 430 | itemsIt = d->items.begin() + d->findItemFrom(modelIt->data(QAbstractRangeModel::PositionRole).toLongLong()); 431 | if ( itemsIt != d->items.begin() && itemsIt != d->items.end() ) 432 | --itemsIt; 433 | } 434 | 435 | while ( !modelIt->isEnd() ){ 436 | 437 | qint64 position = modelIt->data(QAbstractRangeModel::PositionRole).toLongLong(); 438 | qint64 length = modelIt->data(QAbstractRangeModel::LengthRole).toLongLong(); 439 | if ( position + length > d->lastViewportX && d->items.size() > 0 ){ 440 | if ( d->items.first()->position < position || 441 | ( d->items.first()->position == position && d->items.first()->length < length ) ) 442 | break; 443 | } 444 | 445 | QRangeViewItem* viewItem = new QRangeViewItem; 446 | viewItem->position = position; 447 | viewItem->length = length; 448 | 449 | viewItem->context = new QQmlContext(d->delegate->creationContext(), this); 450 | viewItem->context->setContextProperty("position", position); 451 | viewItem->context->setContextProperty("length", length); 452 | 453 | for(QHash::iterator rolesIt = d->modelRoles.begin(); rolesIt != d->modelRoles.end(); ++rolesIt){ 454 | viewItem->context->setContextProperty(rolesIt.value(), modelIt->data(rolesIt.key())); 455 | } 456 | 457 | viewItem->delegate = qobject_cast(d->delegate->create(viewItem->context)); 458 | viewItem->delegate->setParentItem(parentItem()); 459 | 460 | modelIt->nextItem(); 461 | 462 | itemsIt = d->items.insert(itemsIt, viewItem); 463 | ++itemsIt; 464 | } 465 | 466 | delete modelIt; 467 | } 468 | 469 | if ( m_viewportX + m_viewportWidth < d->lastViewportX + d->lastViewportWidth ){ // delete right 470 | QList::iterator it = d->items.end(); 471 | while ( it != d->items.begin() ){ 472 | --it; 473 | if ( (*it)->position < m_viewportX + m_viewportWidth ){ 474 | ++it; 475 | if ( it != d->items.end() ) 476 | d->items.erase(it, d->items.end()); 477 | break; 478 | } 479 | delete *it; 480 | } 481 | if ( it == d->items.begin() ) 482 | d->items.clear(); 483 | 484 | } else if ( m_viewportX + m_viewportWidth > d->lastViewportX + d->lastViewportWidth ){ // append right 485 | QAbstractRangeModelIterator* modelIt = d->model->dataBetween( 486 | m_viewportX > d->lastViewportX + d->lastViewportWidth ? m_viewportX : d->lastViewportX + d->lastViewportWidth, 487 | m_viewportX + m_viewportWidth 488 | ); 489 | 490 | while ( !modelIt->isEnd() ){ 491 | if ( modelIt->data(QAbstractRangeModel::PositionRole).toLongLong() >= d->lastViewportX + d->lastViewportWidth ) 492 | break; 493 | modelIt->nextItem(); 494 | } 495 | 496 | QList::iterator itemsIt = d->items.end(); 497 | if ( !modelIt->isEnd() ){ 498 | itemsIt = d->items.begin() + d->findItemFrom(modelIt->data(QAbstractRangeModel::PositionRole).toLongLong()); 499 | if ( itemsIt != d->items.begin() && itemsIt != d->items.end() ) 500 | --itemsIt; 501 | } 502 | 503 | while ( !modelIt->isEnd() ){ 504 | 505 | QRangeViewItem* viewItem = new QRangeViewItem; 506 | viewItem->position = modelIt->data(QAbstractRangeModel::PositionRole).toLongLong(); 507 | viewItem->length = modelIt->data(QAbstractRangeModel::LengthRole).toLongLong(); 508 | 509 | viewItem->context = new QQmlContext(d->delegate->creationContext(), this); 510 | viewItem->context->setContextProperty("position", viewItem->position); 511 | viewItem->context->setContextProperty("length", viewItem->length); 512 | 513 | for(QHash::iterator rolesIt = d->modelRoles.begin(); rolesIt != d->modelRoles.end(); ++rolesIt){ 514 | viewItem->context->setContextProperty(rolesIt.value(), modelIt->data(rolesIt.key())); 515 | } 516 | 517 | viewItem->delegate = qobject_cast(d->delegate->create(viewItem->context)); 518 | viewItem->delegate->setParentItem(parentItem()); 519 | 520 | modelIt->nextItem(); 521 | 522 | itemsIt = d->items.insert(itemsIt, viewItem); 523 | ++itemsIt; 524 | } 525 | 526 | delete modelIt; 527 | } 528 | 529 | d->lastViewportX = m_viewportX; 530 | d->lastViewportWidth = m_viewportWidth; 531 | } 532 | 533 | QQmlComponent*QRangeView::delegate() const{ 534 | const Q_D(QRangeView); 535 | return d->delegate; 536 | } 537 | 538 | void QRangeView::setDelegate(QQmlComponent* delegate){ 539 | Q_D(QRangeView); 540 | if ( d->delegate != delegate ){ 541 | d->delegate = delegate; 542 | emit delegateChanged(); 543 | regenerateNewContent(); 544 | update(); 545 | } 546 | } 547 | 548 | QAbstractRangeModel*QRangeView::model() const{ 549 | const Q_D(QRangeView); 550 | return d->model; 551 | } 552 | 553 | void QRangeView::setModel(QAbstractRangeModel* arg){ 554 | Q_D(QRangeView); 555 | if (d->model == arg) 556 | return; 557 | 558 | if (d->model) 559 | disconnect(d->model, 0, this, 0); 560 | 561 | d->model = arg; 562 | if ( d->model == 0 ) 563 | return; 564 | 565 | d->modelRoles = d->model->roleNames(); 566 | setImplicitWidth(d->model->contentWidth()); 567 | 568 | connect(d->model, SIGNAL(contentWidthChanged()), this, SLOT(modelWidthChanged())); 569 | connect(d->model, SIGNAL(itemCoordinatesChanged(qint64,qint64,int,QAbstractRangeModelIterator*,int)), 570 | this, SLOT(modelitemCoordinatesChanged(qint64,qint64,int,QAbstractRangeModelIterator*,int))); 571 | connect(d->model, SIGNAL(itemsDataChanged(QAbstractRangeModelIterator*,int, QList)), 572 | this, SLOT(modelItemDataChanged(QAbstractRangeModelIterator*,int, QList))); 573 | connect(d->model, SIGNAL(itemsAboutToBeChanged(qint64,qint64)), 574 | this, SLOT(modelItemsAboutToBeChanged(qint64,qint64))); 575 | connect(d->model, SIGNAL(itemsChanged(qint64,qint64)), 576 | this, SLOT(modelItemsChanged(qint64,qint64))); 577 | 578 | d->lastViewportX = 0; 579 | d->lastViewportWidth = 0; 580 | regenerateNewContent(); 581 | 582 | emit modelChanged(arg); 583 | } 584 | 585 | --------------------------------------------------------------------------------