diff --git a/CMakeLists.txt b/CMakeLists.txt index 62cb793..c7c5f32 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,170 +1,172 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.0) PROJECT(kquickview) IF(POLICY CMP0063) CMAKE_POLICY(SET CMP0063 NEW) ENDIF(POLICY CMP0063) FIND_PACKAGE(ECM 1.1.0 REQUIRED NO_MODULE) LIST(APPEND CMAKE_MODULE_PATH "${ECM_MODULE_PATH}") OPTION(BUILD_SHARED_LIBS "" OFF ) INCLUDE(ECMInstallIcons) INCLUDE(ECMOptionalAddSubdirectory) INCLUDE(KDEInstallDirs) INCLUDE(KDECMakeSettings) INCLUDE(KDECompilerSettings) if(NOT BUILD_SHARED_LIBS) set(STATIC_LIBRARY 1) else() set(STATIC_LIBRARY 0) endif() SET(CMAKE_AUTOMOC ON) SET(CMAKE_AUTORCC ON) SET(CMAKE_CXX_STANDARD 14) # if(STATIC_LIBRARY) add_definitions(-DQT_PLUGIN) add_definitions(-DQT_STATICPLUGIN=1) # else(STATIC_LIBRARY) # if (BUILD_TESTING) # add_subdirectory(autotests) # endif() # endif(STATIC_LIBRARY) FIND_PACKAGE(Qt5 CONFIG REQUIRED Core Gui Quick QuickControls2 ) add_definitions(-isystem ${Qt5Core_PRIVATE_INCLUDE_DIRS}) if("${CMAKE_BUILD_TYPE}" MATCHES "DEBUG") add_definitions(-DENABLE_EXTRA_VALIDATION=1) endif() SET(GENERIC_LIB_VERSION "1.0.0") #File to compile SET( kquickview_LIB_SRCS # Adapters src/adapters/abstractitemadapter.cpp src/adapters/decorationadapter.cpp src/adapters/modeladapter.cpp src/adapters/scrollbaradapter.cpp src/adapters/selectionadapter.cpp src/adapters/geometryadapter.cpp # Building blocks src/flickablescrollbar.cpp src/plugin.cpp src/proxies/sizehintproxymodel.cpp src/singlemodelviewbase.cpp src/viewbase.cpp src/viewport.cpp src/contextadapterfactory.cpp + src/qmodelindexwatcher.cpp # Views src/views/comboboxview.cpp src/views/flickable.cpp src/views/hierarchyview.cpp src/views/listview.cpp src/views/treeview.cpp src/views/indexview.cpp # State trackers src/private/statetracker/index_p.cpp src/private/statetracker/geometry_p.cpp src/private/statetracker/proximity_p.cpp src/private/statetracker/model_p.cpp src/private/statetracker/modelitem_p.cpp src/private/statetracker/selection_p.cpp src/private/statetracker/content_p.cpp src/private/runtimetests_p.cpp src/private/indexmetadata_p.cpp src/private/geostrategyselector_p.cpp # Geometry strategies src/strategies/justintime.cpp src/strategies/proxy.cpp src/strategies/role.cpp src/strategies/delegate.cpp src/strategies/uniform.cpp src/strategies/aheadoftime.cpp ) set(AUTOMOC_MOC_OPTIONS -Muri=org.kde.playground.kquickview) add_library(kquickview STATIC ${kquickview_LIB_SRCS} ) target_link_libraries( kquickview Qt5::Core Qt5::Gui Qt5::Quick Qt5::QuickControls2 ) SET( kquickview_LIB_HDRS adapters/abstractitemadapter.h adapters/contextadapter.h adapters/decorationadapter.h adapters/modeladapter.h adapters/scrollbaradapter.h adapters/selectionadapter.h adapters/geometryadapter.h extensions/contextextension.h flickablescrollbar.h plugin.h proxies/sizehintproxymodel.h singlemodelviewbase.h contextadapterfactory.h + qmodelindexwatcher.h viewbase.h views/comboboxview.h views/flickable.h views/indexview.h views/hierarchyview.h views/listview.h views/treeview.h viewport.h # Geometry strategies strategies/justintime.h strategies/proxy.h strategies/role.h strategies/delegate.h strategies/aheadoftime.h strategies/uniform.h ) # Create include file aliases foreach(header ${kquickview_LIB_HDRS}) file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/src/${header} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/api/KQuickView/ ) endforeach() target_include_directories(kquickview PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src;${CMAKE_CURRENT_BINARY_DIR}/api" ) set_target_properties(kquickview PROPERTIES INCLUDE_DIRECTORIES "${CMAKE_CURRENT_BINARY_DIR}/api;${CMAKE_CURRENT_SOURCE_DIR}/src" ) set_target_properties(kquickview PROPERTIES PUBLIC_HEADER "${CMAKE_CURRENT_BINARY_DIR}/api;${CMAKE_CURRENT_SOURCE_DIR}/src" ) add_subdirectory(tests) include_directories(${CMAKE_CURRENT_BINARY_DIR}/api/) diff --git a/src/plugin.cpp b/src/plugin.cpp index c4ca51f..0d0bd5e 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -1,64 +1,66 @@ /*************************************************************************** * Copyright (C) 2017 by Emmanuel Lepage Vallee * * Author : Emmanuel Lepage Vallee * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * **************************************************************************/ #include "plugin.h" #include #include "adapters/decorationadapter.h" #include "adapters/scrollbaradapter.h" #include "adapters/geometryadapter.h" #include "views/hierarchyview.h" #include "views/listview.h" #include "views/treeview.h" +#include "qmodelindexwatcher.h" #include "views/comboboxview.h" #include "views/indexview.h" #include "flickablescrollbar.h" #include "proxies/sizehintproxymodel.h" // Strategies #include "strategies/justintime.h" #include "strategies/role.h" #include "strategies/proxy.h" void KQuickView::registerTypes(const char *uri) { Q_ASSERT(uri == QLatin1String("org.kde.playground.kquickview")); qmlRegisterType(uri, 1, 0, "HierarchyView"); qmlRegisterType(uri, 1, 0, "TreeView"); qmlRegisterType(uri, 1, 0, "ListView"); qmlRegisterType(uri, 1, 0, "IndexView"); qmlRegisterType(uri, 1, 0, "ScrollBarAdapter"); qmlRegisterType(uri, 1, 0, "GeometryAdapter"); qmlRegisterType(uri, 1,0, "DecorationAdapter"); qmlRegisterType(uri, 1, 0, "ComboBoxView"); qmlRegisterType(uri, 1, 0, "FlickableScrollBar"); + qmlRegisterType(uri, 1, 0, "QModelIndexWatcher"); qmlRegisterType(uri, 1, 0, "SizeHintProxyModel"); qmlRegisterUncreatableType(uri, 1, 0, "ListViewSections", ""); auto suri = QString(QString(uri) + QString(".Strategies")).toLatin1(); qmlRegisterType(suri, 1, 0, "JustInTime"); qmlRegisterType(suri, 1, 0, "Role"); } void KQuickView::initializeEngine(QQmlEngine *engine, const char *uri) { Q_UNUSED(engine) Q_UNUSED(uri) } diff --git a/src/qmodelindexwatcher.cpp b/src/qmodelindexwatcher.cpp new file mode 100644 index 0000000..09e909c --- /dev/null +++ b/src/qmodelindexwatcher.cpp @@ -0,0 +1,153 @@ +/*************************************************************************** + * Copyright (C) 2018 by Emmanuel Lepage Vallee * + * Author : Emmanuel Lepage Vallee * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + **************************************************************************/ +#include "qmodelindexwatcher.h" + +// Qt +#include +#include + +// KQuickItemViews +#include +#include + +class QModelIndexWatcherPrivate : public QObject +{ + Q_OBJECT +public: + QAbstractItemModel *m_pModel {nullptr}; + QPersistentModelIndex m_Index { }; + + QModelIndexWatcher *q_ptr; + +public Q_SLOTS: + void slotDismiss(); + void slotDataChanged(const QModelIndex &tl, const QModelIndex &br, const QVector &roles); + void slotRemoved(const QModelIndex &parent, int first, int last); + void slotRowsMoved(const QModelIndex &p, int start, int end, + const QModelIndex &dest, int row); +}; + +QModelIndexWatcher::QModelIndexWatcher(QObject *parent) : QObject(parent), + d_ptr(new QModelIndexWatcherPrivate()) +{ + d_ptr->q_ptr = this; +} + +QModelIndexWatcher::~QModelIndexWatcher() +{ + delete d_ptr; +} + +QModelIndex QModelIndexWatcher::modelIndex() const +{ + return d_ptr->m_Index; +} + +QAbstractItemModel *QModelIndexWatcher::model() const +{ + return d_ptr->m_pModel; +} + +bool QModelIndexWatcher::isValid() const +{ + return d_ptr->m_Index.isValid(); +} + +void QModelIndexWatcher::setModelIndex(const QModelIndex &index) +{ + if (index == d_ptr->m_Index) + return; + + // Disconnect old models + if (d_ptr->m_pModel && d_ptr->m_pModel != index.model()) + d_ptr->slotDismiss(); + + + if (d_ptr->m_pModel != index.model()) { + + d_ptr->m_pModel = const_cast(index.model()); + + connect(d_ptr->m_pModel, &QAbstractItemModel::destroyed, + d_ptr, &QModelIndexWatcherPrivate::slotDismiss); + connect(d_ptr->m_pModel, &QAbstractItemModel::dataChanged, + d_ptr, &QModelIndexWatcherPrivate::slotDataChanged); + connect(d_ptr->m_pModel, &QAbstractItemModel::rowsAboutToBeRemoved, + d_ptr, &QModelIndexWatcherPrivate::slotRemoved); + connect(d_ptr->m_pModel, &QAbstractItemModel::rowsMoved, + d_ptr, &QModelIndexWatcherPrivate::slotRowsMoved); + } + + d_ptr->m_Index = index; + + emit indexChanged(); + emit validChanged(); +} + +void QModelIndexWatcherPrivate::slotDismiss() +{ + disconnect(m_pModel, &QAbstractItemModel::destroyed, + this, &QModelIndexWatcherPrivate::slotDismiss); + disconnect(m_pModel, &QAbstractItemModel::dataChanged, + this, &QModelIndexWatcherPrivate::slotDataChanged); + disconnect(m_pModel, &QAbstractItemModel::rowsAboutToBeRemoved, + this, &QModelIndexWatcherPrivate::slotRemoved); + disconnect(m_pModel, &QAbstractItemModel::rowsMoved, + this, &QModelIndexWatcherPrivate::slotRowsMoved); + + m_pModel = nullptr; + m_Index = QModelIndex(); + emit q_ptr->indexChanged(); + emit q_ptr->removed(); +} + +void QModelIndexWatcherPrivate::slotDataChanged(const QModelIndex &tl, const QModelIndex &br, const QVector &roles) +{ + if (tl.parent() != m_Index.parent()) + return; + + if (tl.row() > m_Index.row() || br.row() < m_Index.row()) + return; + + if (tl.column() > m_Index.column() || br.column() < m_Index.column()) + return; + + emit q_ptr->dataChanged(roles); +} + +void QModelIndexWatcherPrivate::slotRemoved(const QModelIndex &parent, int first, int last) +{ + if (parent != m_Index.parent() || !(m_Index.row() >= first && m_Index.row() <= last)) + return; + + emit q_ptr->removed(); + emit q_ptr->validChanged(); + + slotDismiss(); +} + +void QModelIndexWatcherPrivate::slotRowsMoved(const QModelIndex &p, int start, int end, const QModelIndex &dest, int row) +{ + Q_UNUSED(p) + Q_UNUSED(start) + Q_UNUSED(end) + Q_UNUSED(dest) + Q_UNUSED(row) + //TODO +} + +#include diff --git a/src/views/indexview.h b/src/qmodelindexwatcher.h similarity index 61% copy from src/views/indexview.h copy to src/qmodelindexwatcher.h index 5a829a1..2859b55 100644 --- a/src/views/indexview.h +++ b/src/qmodelindexwatcher.h @@ -1,62 +1,57 @@ /*************************************************************************** * Copyright (C) 2018 by Emmanuel Lepage Vallee * * Author : Emmanuel Lepage Vallee * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * **************************************************************************/ #pragma once -#include -#include +#include +#include -class IndexViewPrivate; +class QAbstractItemModel; +class QModelIndexWatcherPrivate; /** - * This view has a single delegate instance and display data for a single - * QModelIndex. - * - * This is useful for mobile application pages to get more information out of - * a list item. It allows for more compact list while avoiding the boilerplate - * of having a non-model component. - * - * It can also be used to create the equivalent of "editor widgets" from the - * QtWidgets era. - * - * The IndexView will take the implicit width and height of the component - * unless it is resized or in a managed layout, where it will resize the - * delegate. + * This class allows to get events on a QModelIndex from QML. */ -class Q_DECL_EXPORT IndexView : public QQuickItem +class QModelIndexWatcher : public QObject { Q_OBJECT public: - explicit IndexView(QQuickItem *parent = nullptr); - virtual ~IndexView(); + explicit QModelIndexWatcher(QObject *parent = nullptr); + virtual ~QModelIndexWatcher(); - Q_PROPERTY(QQmlComponent* delegate READ delegate WRITE setDelegate NOTIFY delegateChanged) + Q_PROPERTY(bool valid READ isValid NOTIFY validChanged) Q_PROPERTY(QModelIndex modelIndex READ modelIndex WRITE setModelIndex NOTIFY indexChanged) - - virtual void setDelegate(QQmlComponent* delegate); - QQmlComponent* delegate() const; + Q_PROPERTY(QAbstractItemModel* model READ model NOTIFY validChanged) QModelIndex modelIndex() const; void setModelIndex(const QModelIndex &index); + QAbstractItemModel *model() const; + + bool isValid() const; + Q_SIGNALS: - void delegateChanged(QQmlComponent* delegate); + void removed(); + void moved(); + void dataChanged(const QVector& roles); + void validChanged(); void indexChanged(); private: - IndexViewPrivate *d_ptr; + QModelIndexWatcherPrivate *d_ptr; + Q_DECLARE_PRIVATE(QModelIndexWatcher) }; diff --git a/src/views/indexview.cpp b/src/views/indexview.cpp index f01faec..73bea79 100644 --- a/src/views/indexview.cpp +++ b/src/views/indexview.cpp @@ -1,201 +1,207 @@ /*************************************************************************** * Copyright (C) 2018 by Emmanuel Lepage Vallee * * Author : Emmanuel Lepage Vallee * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * **************************************************************************/ #include "indexview.h" // Qt #include #include // KQuickItemViews #include #include class IndexViewPrivate : public QObject { Q_OBJECT public: QQmlComponent *m_pComponent {nullptr}; QAbstractItemModel *m_pModel {nullptr}; ContextAdapter *m_pCTX {nullptr}; ContextAdapterFactory *m_pFactory {nullptr}; QQuickItem *m_pItem {nullptr}; QPersistentModelIndex m_Index { }; // Helper void initDelegate(); IndexView *q_ptr; public Q_SLOTS: void slotDismiss(); void slotDataChanged(const QModelIndex &tl, const QModelIndex &br, const QVector &roles); void slotRemoved(const QModelIndex &parent, int first, int last); }; IndexView::IndexView(QQuickItem *parent) : QQuickItem(parent), d_ptr(new IndexViewPrivate()) { d_ptr->q_ptr = this; } IndexView::~IndexView() { if (d_ptr->m_pItem) delete d_ptr->m_pItem; if (d_ptr->m_pCTX) delete d_ptr->m_pCTX; if (d_ptr->m_pFactory) delete d_ptr->m_pFactory; delete d_ptr; } void IndexView::setDelegate(QQmlComponent* delegate) { if (delegate == d_ptr->m_pComponent) return; if (d_ptr->m_pItem) delete d_ptr->m_pItem; d_ptr->m_pComponent = delegate; emit delegateChanged(delegate); d_ptr->initDelegate(); } QQmlComponent* IndexView::delegate() const { return d_ptr->m_pComponent; } QModelIndex IndexView::modelIndex() const { return d_ptr->m_Index; } +QAbstractItemModel *IndexView::model() const +{ + return d_ptr->m_pModel; +} + void IndexView::setModelIndex(const QModelIndex &index) { if (index == d_ptr->m_Index) return; // Disconnect old models if (d_ptr->m_pModel && d_ptr->m_pModel != index.model()) d_ptr->slotDismiss(); if (d_ptr->m_pModel != index.model()) { d_ptr->m_pModel = const_cast(index.model()); if (!d_ptr->m_pFactory) d_ptr->m_pFactory = new ContextAdapterFactory(); d_ptr->m_pFactory->setModel(d_ptr->m_pModel); const auto ctx = QQmlEngine::contextForObject(this); if (d_ptr->m_pItem) { delete d_ptr->m_pItem; d_ptr->m_pItem = nullptr; } d_ptr->m_pCTX = d_ptr->m_pFactory->createAdapter(ctx); Q_ASSERT(d_ptr->m_pCTX->context()->parentContext() == ctx); connect(d_ptr->m_pModel, &QAbstractItemModel::destroyed, d_ptr, &IndexViewPrivate::slotDismiss); connect(d_ptr->m_pModel, &QAbstractItemModel::dataChanged, d_ptr, &IndexViewPrivate::slotDataChanged); connect(d_ptr->m_pModel, &QAbstractItemModel::rowsAboutToBeRemoved, d_ptr, &IndexViewPrivate::slotRemoved); } d_ptr->m_Index = index; d_ptr->m_pCTX->setModelIndex(index); d_ptr->initDelegate(); + emit indexChanged(); } void IndexViewPrivate::slotDismiss() { if (m_pItem) { delete m_pItem; m_pItem = nullptr; } disconnect(m_pModel, &QAbstractItemModel::destroyed, this, &IndexViewPrivate::slotDismiss); disconnect(m_pModel, &QAbstractItemModel::dataChanged, this, &IndexViewPrivate::slotDataChanged); disconnect(m_pModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &IndexViewPrivate::slotRemoved); if (m_pCTX) { delete m_pCTX; m_pCTX = nullptr; } m_pModel = nullptr; m_Index = QModelIndex(); + emit q_ptr->indexChanged(); } void IndexViewPrivate::slotDataChanged(const QModelIndex &tl, const QModelIndex &br, const QVector &roles) { if (tl.parent() != m_Index.parent()) return; if (tl.row() > m_Index.row() || br.row() < m_Index.row()) return; if (tl.column() > m_Index.column() || br.column() < m_Index.column()) return; m_pCTX->updateRoles(roles); } void IndexViewPrivate::slotRemoved(const QModelIndex &parent, int first, int last) { if (parent != m_Index.parent() || !(m_Index.row() >= first && m_Index.row() <= last)) return; slotDismiss(); } void IndexViewPrivate::initDelegate() { if (m_pItem || (!m_pComponent) || !m_Index.isValid()) return; m_pItem = qobject_cast(m_pComponent->create(m_pCTX->context())); // It will happen when the QML itself is invalid if (!m_pItem) return; const auto ctx = QQmlEngine::contextForObject(q_ptr); Q_ASSERT(ctx); ctx->engine()->setObjectOwnership(m_pItem, QQmlEngine::CppOwnership); m_pItem->setParentItem(q_ptr); - volatile auto cc = QQmlEngine::contextForObject(m_pItem); } #include diff --git a/src/views/indexview.h b/src/views/indexview.h index 5a829a1..e431929 100644 --- a/src/views/indexview.h +++ b/src/views/indexview.h @@ -1,62 +1,68 @@ /*************************************************************************** * Copyright (C) 2018 by Emmanuel Lepage Vallee * * Author : Emmanuel Lepage Vallee * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * **************************************************************************/ #pragma once #include #include +class QAbstractItemModel; + class IndexViewPrivate; /** * This view has a single delegate instance and display data for a single * QModelIndex. * * This is useful for mobile application pages to get more information out of * a list item. It allows for more compact list while avoiding the boilerplate * of having a non-model component. * * It can also be used to create the equivalent of "editor widgets" from the * QtWidgets era. * * The IndexView will take the implicit width and height of the component * unless it is resized or in a managed layout, where it will resize the * delegate. */ class Q_DECL_EXPORT IndexView : public QQuickItem { Q_OBJECT public: explicit IndexView(QQuickItem *parent = nullptr); virtual ~IndexView(); Q_PROPERTY(QQmlComponent* delegate READ delegate WRITE setDelegate NOTIFY delegateChanged) Q_PROPERTY(QModelIndex modelIndex READ modelIndex WRITE setModelIndex NOTIFY indexChanged) + Q_PROPERTY(QAbstractItemModel* model READ model NOTIFY indexChanged) virtual void setDelegate(QQmlComponent* delegate); QQmlComponent* delegate() const; + QAbstractItemModel *model() const; + QModelIndex modelIndex() const; void setModelIndex(const QModelIndex &index); Q_SIGNALS: void delegateChanged(QQmlComponent* delegate); void indexChanged(); private: IndexViewPrivate *d_ptr; + Q_DECLARE_PRIVATE(IndexView) };