From 6ea21266388c17ce2832a6d24c97a4df71ad8251 Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Sun, 25 Feb 2024 13:21:59 +0100 Subject: [PATCH 01/11] Squashed commit of the following: Merge: f614117d f0d49d9e Author: Uwe Rathmann Date: Sun Feb 25 09:11:12 2024 +0100 Merge branch 'master' of https://github.com/ovenpasta/qskinny into models ( Author: Aldo Nicolas Bruno ) --- cmake/QskFindMacros.cmake | 4 +- playground/CMakeLists.txt | 4 + playground/models/CMakeLists.txt | 8 ++ playground/models/QskModelObjectBinder.cpp | 157 +++++++++++++++++++++ playground/models/QskModelObjectBinder.h | 32 +++++ playground/models/Window.cpp | 82 +++++++++++ playground/models/Window.h | 22 +++ playground/models/main.cpp | 35 +++++ 8 files changed, 342 insertions(+), 2 deletions(-) create mode 100644 playground/models/CMakeLists.txt create mode 100644 playground/models/QskModelObjectBinder.cpp create mode 100644 playground/models/QskModelObjectBinder.h create mode 100644 playground/models/Window.cpp create mode 100644 playground/models/Window.h create mode 100644 playground/models/main.cpp diff --git a/cmake/QskFindMacros.cmake b/cmake/QskFindMacros.cmake index f2791254..e2b01e7f 100644 --- a/cmake/QskFindMacros.cmake +++ b/cmake/QskFindMacros.cmake @@ -54,11 +54,11 @@ macro(qsk_setup_Qt) # C++, but QSkinny itself does not need the WebEngine at all. if (QT_VERSION_MAJOR VERSION_LESS 6) - find_package(Qt${QT_VERSION_MAJOR} QUIET OPTIONAL_COMPONENTS WebEngine) + find_package(Qt${QT_VERSION_MAJOR} QUIET OPTIONAL_COMPONENTS WebEngine Sql) set( Qt5WebEngineQuick_FOUND ${Qt5WebEngine_FOUND} ) else() find_package(Qt${QT_VERSION_MAJOR} QUIET - OPTIONAL_COMPONENTS WebEngineCore WebEngineQuick) + OPTIONAL_COMPONENTS WebEngineCore WebEngineQuick Sql) endif() if( NOT Qt${QT_VERSION_MAJOR}WebEngineQuick_FOUND) diff --git a/playground/CMakeLists.txt b/playground/CMakeLists.txt index 2bc146fa..7ba9e0b5 100644 --- a/playground/CMakeLists.txt +++ b/playground/CMakeLists.txt @@ -32,3 +32,7 @@ endif() if(TARGET Qt::QuickWidgets) add_subdirectory(grids) endif() + +if(TARGET Qt::Sql) + add_subdirectory(models) +endif() diff --git a/playground/models/CMakeLists.txt b/playground/models/CMakeLists.txt new file mode 100644 index 00000000..541f5992 --- /dev/null +++ b/playground/models/CMakeLists.txt @@ -0,0 +1,8 @@ +############################################################################ +# QSkinny - Copyright (C) The authors +# SPDX-License-Identifier: BSD-3-Clause +############################################################################ + +qsk_add_example(models QskModelObjectBinder.cpp QskModelObjectBinder.h Window.h Window.cpp main.cpp) + +target_link_libraries(models PRIVATE Qt::Sql) \ No newline at end of file diff --git a/playground/models/QskModelObjectBinder.cpp b/playground/models/QskModelObjectBinder.cpp new file mode 100644 index 00000000..13beeb28 --- /dev/null +++ b/playground/models/QskModelObjectBinder.cpp @@ -0,0 +1,157 @@ +// +// Created by aldo on 27/01/21. +// + +#include "QskModelObjectBinder.h" +#include +#include +#include + +class QskModelObjectBinder::PrivateData +{ +public: + PrivateData( ) + { + } + + struct Item { + Item() :valid(false), column(-1) {}; + Item(QObject *obj, int column, const QByteArray& property) + : valid (true), object( obj ), column( column ), property( property ) + { + }; + bool valid; + QObject* object; + int column; + QByteArray property; + }; + + void updateObjectProperty(Item & x) + { + Q_ASSERT( x.object != nullptr ); + Q_ASSERT( ! x.property.isEmpty() ); + x.object->setProperty(x.property, + m->index(currentRowIndex.row(),x.column).data(Qt::EditRole)); + } + + void updateAllObjects() + { + for (auto it = items.begin(); it != items.end(); ++it) { + updateObjectProperty(it.value()); + } + } + + void updateModelField(Item& x) + { + Q_ASSERT( x.valid ); + Q_ASSERT( x.object != nullptr ); + auto prop =x.object->property( x.property ); + Q_ASSERT( prop.isValid() ); + + m->setData(m->index(currentRowIndex.row(),x.column),prop); + } + + void updateModelFields() + { + for (auto it = items.begin(); it != items.end(); ++it) + { + updateModelField( it.value() ); + } + } + +public: + QMap items; + QAbstractItemModel* m; + QPersistentModelIndex currentRowIndex; +}; + +QskModelObjectBinder::QskModelObjectBinder(QObject* parent) +: QObject( parent ) +{ + m_data=std::make_unique( ); +} + +QskModelObjectBinder::QskModelObjectBinder(QAbstractItemModel *model, QObject* parent) +: QObject( parent ) +{ + m_data=std::make_unique(); + bindModel(model); +} + + +void QskModelObjectBinder::bindObject(QObject *obj, int column, const QByteArray &propertyName) +{ + Q_ASSERT( obj != nullptr ); + + if(obj == nullptr) + return; + + QMetaProperty prop; + auto meta = obj->metaObject(); + if (propertyName.isEmpty()) + { + prop = meta->userProperty(); + }else + { + auto idx = meta->indexOfProperty( propertyName ); + if(idx>=0) + { + prop=meta->property(idx); + } + } + Q_ASSERT(! prop.isValid()); + + PrivateData::Item item(obj,column,prop.name()); + m_data->items[obj]=item; +} + +void QskModelObjectBinder::unbindObject(QObject *obj) +{ + m_data->items.remove(obj); +} + +void QskModelObjectBinder::bindModel(QAbstractItemModel *model) +{ + m_data->m=model; + m_data->currentRowIndex=QModelIndex(); + if(model) { + connect(m_data->m,&QAbstractItemModel::dataChanged,this, + &QskModelObjectBinder::modelDataChanged); + } +} + +void QskModelObjectBinder::modelDataChanged(const QModelIndex & topLeft, const QModelIndex & bottomRight, const QVector &) +{ + const int row = m_data->currentRowIndex.row(); + for (auto it = m_data->items.begin(); it != m_data->items.end(); ++it) { + auto & x = it.value(); + const int col = x.column; + if( qBound( topLeft.row(),row,bottomRight.row()) == row + && qBound( topLeft.column(), col, bottomRight.column()) == col) { + m_data->updateObjectProperty(x); + } + } +} + +void QskModelObjectBinder::selectRow( int row ) +{ + Q_ASSERT( m_data->m != nullptr ); + + if ( !m_data->m ) + return; + + m_data->currentRowIndex = m_data->m->index( row, 0 ); + m_data->updateAllObjects(); +} + +int QskModelObjectBinder::currentRow() const +{ + return m_data->currentRowIndex.row(); +} + +void QskModelObjectBinder::updateModel() +{ + m_data->updateModelFields(); +} + +#include "moc_QskModelObjectBinder.cpp" diff --git a/playground/models/QskModelObjectBinder.h b/playground/models/QskModelObjectBinder.h new file mode 100644 index 00000000..2f94a984 --- /dev/null +++ b/playground/models/QskModelObjectBinder.h @@ -0,0 +1,32 @@ +#ifndef QSKDATACONTROLMAPPER_H +#define QSKDATACONTROLMAPPER_H + +#include + +class QskModelObjectBinder : public QObject { + Q_OBJECT + +public: + QskModelObjectBinder(QObject *parent=nullptr); + QskModelObjectBinder(QAbstractItemModel* model, QObject *parent=nullptr); + + void bindObject(QObject*, int column, const QByteArray &propertyName=""); + void unbindObject(QObject*o); + + void bindModel(QAbstractItemModel* model); + + void selectRow(int row); + [[nodiscard]] int currentRow() const; + + void updateModel(); + +private Q_SLOTS: + void modelDataChanged(const QModelIndex & topLeft, const QModelIndex & bottomRight, const QVector &); + +private: + class PrivateData; + std::unique_ptr< PrivateData > m_data; +}; + + +#endif //QSKDATACONTROLMAPPER_H \ No newline at end of file diff --git a/playground/models/Window.cpp b/playground/models/Window.cpp new file mode 100644 index 00000000..23d3b665 --- /dev/null +++ b/playground/models/Window.cpp @@ -0,0 +1,82 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#include "Window.h" + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +Window::Window( ) + +{ + auto db = QSqlDatabase::addDatabase("QSQLITE"); + db.setDatabaseName(":memory:"); + db.open(); + QSqlQuery q(db); + q.exec("create table test(id integer,value text);"); + q.exec("insert into test (id,value) values (1,'HELLO');"); + q.exec("insert into test (id,value) values (2,'WORLD');"); + + auto table = new QSqlTableModel (nullptr,db); + table->setTable( "test" ); + table->select(); + + auto txt = new QskTextInput(); + auto spin = new QskSpinBox(); + + auto mapper = new QskModelObjectBinder(); + mapper->bindModel(table); + /* + Not needed if we set USER true for the corresponding Q_PROPERTY in QskBoundedInput and QskTextInput + maybe could be done also for other controls + mapper->bindControl(spin, 0,"value"); + mapper->bindControl(txt, 1, "text"); + */ + mapper->bindObject(spin, 0); // needs USER=true for value in QskBoundedInput + mapper->bindObject(txt, 1); // needs USER=true for text in QskTextInput + + // this loads the record from the first row and updates the controls data. + mapper->selectRow(0); + + auto v = new QskLinearBox(Qt::Vertical); + v->addSpacer( 0,100 ); + v->addItem( spin ); + v->addItem( txt ); + addItem(v); + auto h = new QskLinearBox(Qt::Horizontal); + auto prev = new QskPushButton("<",h); + auto next = new QskPushButton(">",h); + connect(prev,&QskPushButton::clicked,[=]() { mapper->selectRow( 0 ); }); + connect(next,&QskPushButton::clicked,[=]() { mapper->selectRow( 1 ); }); + v->addItem(h); + + auto save = new QskPushButton("Save Data to Model",v); + connect(save,&QskPushButton::clicked,[=]() { + // this will update the current record with the data from the SpinBox and TextInput + mapper->updateModel(); + // just for illustration we print out the record + auto r = table->record(mapper->currentRow()); + qDebug() << r; + }); + auto set0 = new QskPushButton("Set Model field to 0",v); + connect(set0,&QskPushButton::clicked,[=]() { + // this should trigger the binder and update the spinbox + table->setData( table->index(mapper->currentRow(),0),0 ); + }); + v->addSpacer( 0,100 ); +} + + +#include "moc_Window.cpp" diff --git a/playground/models/Window.h b/playground/models/Window.h new file mode 100644 index 00000000..2ededbbc --- /dev/null +++ b/playground/models/Window.h @@ -0,0 +1,22 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#pragma once + +#include +#include + +class QskDialogButtonBox; +class QskLinearBox; + +class Window : public QskWindow +{ + Q_OBJECT + + public: + Window(); + + +}; diff --git a/playground/models/main.cpp b/playground/models/main.cpp new file mode 100644 index 00000000..5b0476bb --- /dev/null +++ b/playground/models/main.cpp @@ -0,0 +1,35 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#include "Window.h" + +#include + +#include +#include + +#include + +int main( int argc, char* argv[] ) +{ +#ifdef ITEM_STATISTICS + QskObjectCounter counter( true ); +#endif + + QGuiApplication app( argc, argv ); + + SkinnyShortcut::enable( SkinnyShortcut::AllShortcuts ); + + Window window; + window.resize( 600, 400 ); + + auto focusIndicator = new QskFocusIndicator(); + focusIndicator->setObjectName( "FocusIndicator" ); + + window.addItem( focusIndicator ); + window.show(); + + return app.exec(); +} From 59afa42c84d0a1ef0f46364867862038008f643c Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Sun, 25 Feb 2024 13:28:11 +0100 Subject: [PATCH 02/11] QskModelObjectBinder modified and moved to src/controls --- playground/models/CMakeLists.txt | 5 +- playground/models/QskModelObjectBinder.cpp | 157 ------------------- playground/models/QskModelObjectBinder.h | 32 ---- playground/models/Window.cpp | 46 +++--- playground/models/Window.h | 6 - src/CMakeLists.txt | 2 + src/controls/QskModelObjectBinder.cpp | 174 +++++++++++++++++++++ src/controls/QskModelObjectBinder.h | 42 +++++ 8 files changed, 240 insertions(+), 224 deletions(-) delete mode 100644 playground/models/QskModelObjectBinder.cpp delete mode 100644 playground/models/QskModelObjectBinder.h create mode 100644 src/controls/QskModelObjectBinder.cpp create mode 100644 src/controls/QskModelObjectBinder.h diff --git a/playground/models/CMakeLists.txt b/playground/models/CMakeLists.txt index 541f5992..c8b277e0 100644 --- a/playground/models/CMakeLists.txt +++ b/playground/models/CMakeLists.txt @@ -3,6 +3,5 @@ # SPDX-License-Identifier: BSD-3-Clause ############################################################################ -qsk_add_example(models QskModelObjectBinder.cpp QskModelObjectBinder.h Window.h Window.cpp main.cpp) - -target_link_libraries(models PRIVATE Qt::Sql) \ No newline at end of file +qsk_add_example(models Window.h Window.cpp main.cpp) +target_link_libraries(models PRIVATE Qt::Sql) diff --git a/playground/models/QskModelObjectBinder.cpp b/playground/models/QskModelObjectBinder.cpp deleted file mode 100644 index 13beeb28..00000000 --- a/playground/models/QskModelObjectBinder.cpp +++ /dev/null @@ -1,157 +0,0 @@ -// -// Created by aldo on 27/01/21. -// - -#include "QskModelObjectBinder.h" -#include -#include -#include - -class QskModelObjectBinder::PrivateData -{ -public: - PrivateData( ) - { - } - - struct Item { - Item() :valid(false), column(-1) {}; - Item(QObject *obj, int column, const QByteArray& property) - : valid (true), object( obj ), column( column ), property( property ) - { - }; - bool valid; - QObject* object; - int column; - QByteArray property; - }; - - void updateObjectProperty(Item & x) - { - Q_ASSERT( x.object != nullptr ); - Q_ASSERT( ! x.property.isEmpty() ); - x.object->setProperty(x.property, - m->index(currentRowIndex.row(),x.column).data(Qt::EditRole)); - } - - void updateAllObjects() - { - for (auto it = items.begin(); it != items.end(); ++it) { - updateObjectProperty(it.value()); - } - } - - void updateModelField(Item& x) - { - Q_ASSERT( x.valid ); - Q_ASSERT( x.object != nullptr ); - auto prop =x.object->property( x.property ); - Q_ASSERT( prop.isValid() ); - - m->setData(m->index(currentRowIndex.row(),x.column),prop); - } - - void updateModelFields() - { - for (auto it = items.begin(); it != items.end(); ++it) - { - updateModelField( it.value() ); - } - } - -public: - QMap items; - QAbstractItemModel* m; - QPersistentModelIndex currentRowIndex; -}; - -QskModelObjectBinder::QskModelObjectBinder(QObject* parent) -: QObject( parent ) -{ - m_data=std::make_unique( ); -} - -QskModelObjectBinder::QskModelObjectBinder(QAbstractItemModel *model, QObject* parent) -: QObject( parent ) -{ - m_data=std::make_unique(); - bindModel(model); -} - - -void QskModelObjectBinder::bindObject(QObject *obj, int column, const QByteArray &propertyName) -{ - Q_ASSERT( obj != nullptr ); - - if(obj == nullptr) - return; - - QMetaProperty prop; - auto meta = obj->metaObject(); - if (propertyName.isEmpty()) - { - prop = meta->userProperty(); - }else - { - auto idx = meta->indexOfProperty( propertyName ); - if(idx>=0) - { - prop=meta->property(idx); - } - } - Q_ASSERT(! prop.isValid()); - - PrivateData::Item item(obj,column,prop.name()); - m_data->items[obj]=item; -} - -void QskModelObjectBinder::unbindObject(QObject *obj) -{ - m_data->items.remove(obj); -} - -void QskModelObjectBinder::bindModel(QAbstractItemModel *model) -{ - m_data->m=model; - m_data->currentRowIndex=QModelIndex(); - if(model) { - connect(m_data->m,&QAbstractItemModel::dataChanged,this, - &QskModelObjectBinder::modelDataChanged); - } -} - -void QskModelObjectBinder::modelDataChanged(const QModelIndex & topLeft, const QModelIndex & bottomRight, const QVector &) -{ - const int row = m_data->currentRowIndex.row(); - for (auto it = m_data->items.begin(); it != m_data->items.end(); ++it) { - auto & x = it.value(); - const int col = x.column; - if( qBound( topLeft.row(),row,bottomRight.row()) == row - && qBound( topLeft.column(), col, bottomRight.column()) == col) { - m_data->updateObjectProperty(x); - } - } -} - -void QskModelObjectBinder::selectRow( int row ) -{ - Q_ASSERT( m_data->m != nullptr ); - - if ( !m_data->m ) - return; - - m_data->currentRowIndex = m_data->m->index( row, 0 ); - m_data->updateAllObjects(); -} - -int QskModelObjectBinder::currentRow() const -{ - return m_data->currentRowIndex.row(); -} - -void QskModelObjectBinder::updateModel() -{ - m_data->updateModelFields(); -} - -#include "moc_QskModelObjectBinder.cpp" diff --git a/playground/models/QskModelObjectBinder.h b/playground/models/QskModelObjectBinder.h deleted file mode 100644 index 2f94a984..00000000 --- a/playground/models/QskModelObjectBinder.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef QSKDATACONTROLMAPPER_H -#define QSKDATACONTROLMAPPER_H - -#include - -class QskModelObjectBinder : public QObject { - Q_OBJECT - -public: - QskModelObjectBinder(QObject *parent=nullptr); - QskModelObjectBinder(QAbstractItemModel* model, QObject *parent=nullptr); - - void bindObject(QObject*, int column, const QByteArray &propertyName=""); - void unbindObject(QObject*o); - - void bindModel(QAbstractItemModel* model); - - void selectRow(int row); - [[nodiscard]] int currentRow() const; - - void updateModel(); - -private Q_SLOTS: - void modelDataChanged(const QModelIndex & topLeft, const QModelIndex & bottomRight, const QVector &); - -private: - class PrivateData; - std::unique_ptr< PrivateData > m_data; -}; - - -#endif //QSKDATACONTROLMAPPER_H \ No newline at end of file diff --git a/playground/models/Window.cpp b/playground/models/Window.cpp index 23d3b665..76c2f27b 100644 --- a/playground/models/Window.cpp +++ b/playground/models/Window.cpp @@ -5,7 +5,6 @@ #include "Window.h" -#include #include #include #include @@ -18,18 +17,18 @@ #include #include -Window::Window( ) - +Window::Window() { - auto db = QSqlDatabase::addDatabase("QSQLITE"); - db.setDatabaseName(":memory:"); + auto db = QSqlDatabase::addDatabase( "QSQLITE" ); + db.setDatabaseName( ":memory:" ); db.open(); - QSqlQuery q(db); - q.exec("create table test(id integer,value text);"); - q.exec("insert into test (id,value) values (1,'HELLO');"); - q.exec("insert into test (id,value) values (2,'WORLD');"); - auto table = new QSqlTableModel (nullptr,db); + QSqlQuery q(db); + q.exec( "create table test(id integer,value text);" ); + q.exec( "insert into test (id,value) values (1,'HELLO');" ); + q.exec( "insert into test (id,value) values (2,'WORLD');" ); + + auto table = new QSqlTableModel( nullptr, db ); table->setTable( "test" ); table->select(); @@ -37,46 +36,41 @@ Window::Window( ) auto spin = new QskSpinBox(); auto mapper = new QskModelObjectBinder(); - mapper->bindModel(table); - /* - Not needed if we set USER true for the corresponding Q_PROPERTY in QskBoundedInput and QskTextInput - maybe could be done also for other controls - mapper->bindControl(spin, 0,"value"); - mapper->bindControl(txt, 1, "text"); - */ - mapper->bindObject(spin, 0); // needs USER=true for value in QskBoundedInput - mapper->bindObject(txt, 1); // needs USER=true for text in QskTextInput + mapper->bindModel( table ); + + mapper->bindObject( spin, 0 ); + mapper->bindObject( txt, 1 ); // this loads the record from the first row and updates the controls data. - mapper->selectRow(0); + mapper->setCurrentRow(0); auto v = new QskLinearBox(Qt::Vertical); v->addSpacer( 0,100 ); v->addItem( spin ); v->addItem( txt ); addItem(v); + auto h = new QskLinearBox(Qt::Horizontal); auto prev = new QskPushButton("<",h); auto next = new QskPushButton(">",h); - connect(prev,&QskPushButton::clicked,[=]() { mapper->selectRow( 0 ); }); - connect(next,&QskPushButton::clicked,[=]() { mapper->selectRow( 1 ); }); + connect(prev,&QskPushButton::clicked,[ = ]() { mapper->setCurrentRow( 0 ); }); + connect(next,&QskPushButton::clicked,[ = ]() { mapper->setCurrentRow( 1 ); }); v->addItem(h); auto save = new QskPushButton("Save Data to Model",v); - connect(save,&QskPushButton::clicked,[=]() { + connect(save,&QskPushButton::clicked,[ = ]() { // this will update the current record with the data from the SpinBox and TextInput mapper->updateModel(); // just for illustration we print out the record - auto r = table->record(mapper->currentRow()); + auto r = table->record(mapper->currentRow() ); qDebug() << r; }); auto set0 = new QskPushButton("Set Model field to 0",v); - connect(set0,&QskPushButton::clicked,[=]() { + connect(set0,&QskPushButton::clicked,[ = ]() { // this should trigger the binder and update the spinbox table->setData( table->index(mapper->currentRow(),0),0 ); }); v->addSpacer( 0,100 ); } - #include "moc_Window.cpp" diff --git a/playground/models/Window.h b/playground/models/Window.h index 2ededbbc..816d6f4f 100644 --- a/playground/models/Window.h +++ b/playground/models/Window.h @@ -5,18 +5,12 @@ #pragma once -#include #include -class QskDialogButtonBox; -class QskLinearBox; - class Window : public QskWindow { Q_OBJECT public: Window(); - - }; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index de2dd310..02d6957b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -226,6 +226,7 @@ list(APPEND HEADERS controls/QskListViewSkinlet.h controls/QskMenu.h controls/QskMenuSkinlet.h + controls/QskModelObjectBinder.h controls/QskObjectTree.h controls/QskPageIndicator.h controls/QskPageIndicatorSkinlet.h @@ -332,6 +333,7 @@ list(APPEND SOURCES controls/QskListViewSkinlet.cpp controls/QskMenuSkinlet.cpp controls/QskMenu.cpp + controls/QskModelObjectBinder.cpp controls/QskObjectTree.cpp controls/QskPageIndicator.cpp controls/QskPageIndicatorSkinlet.cpp diff --git a/src/controls/QskModelObjectBinder.cpp b/src/controls/QskModelObjectBinder.cpp new file mode 100644 index 00000000..1683386b --- /dev/null +++ b/src/controls/QskModelObjectBinder.cpp @@ -0,0 +1,174 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#include "QskModelObjectBinder.h" + +#include +#include +#include +#include + +static inline QMetaProperty qskMetaProperty( + const QMetaObject* metaObject, const QByteArray& name ) +{ + if ( name.isEmpty() ) + return metaObject->userProperty(); + + const auto idx = metaObject->indexOfProperty( name ); + return ( idx >= 0 ) ? metaObject->property( idx ) : QMetaProperty(); +} + +namespace +{ + struct Binding + { + Binding( int column, const QMetaProperty& property ) + : property( property ) + , column( column ) + { + } + + QMetaProperty property; + int column; + }; +} + +class QskModelObjectBinder::PrivateData +{ + public: + void updateProperties(const QModelIndex& topLeft, + const QModelIndex& bottomRight, const QVector< int >& roles ) + { + if ( !roles.contains( Qt::EditRole ) ) + return; + + const int row = currentRowIndex.row(); + + if ( topLeft.row() <= row && row <= bottomRight.row() ) + { + for ( auto it = bindings.begin(); it != bindings.end(); ++it ) + { + const int col = it.value().column; + + if( topLeft.column() <= col && col <= bottomRight.column() ) + updateObjectProperty( it.key(), it.value() ); + } + } + } + + void updateObjectProperty( QObject* object, const Binding& binding ) + { + const auto index = model->index( currentRowIndex.row(), binding.column ); + binding.property.write( object, index.data( Qt::EditRole ) ); + } + + public: + QMap< QObject*, Binding > bindings; + QPointer< QAbstractItemModel > model; + QPersistentModelIndex currentRowIndex; +}; + +QskModelObjectBinder::QskModelObjectBinder( QObject* parent ) + : QObject( parent ) + , m_data( new PrivateData() ) +{ +} + +QskModelObjectBinder::QskModelObjectBinder( QAbstractItemModel* model, QObject* parent ) + : QskModelObjectBinder( parent ) +{ + bindModel( model ); +} + +void QskModelObjectBinder::bindObject( + QObject* object, int column, const QByteArray& propertyName ) +{ + if( object == nullptr ) + return; + + // does not work with dynamic properties ... + + const auto metaProperty = qskMetaProperty( object->metaObject(), propertyName ); + Q_ASSERT( metaProperty.isValid() ); + + if ( metaProperty.isValid() ) + { + m_data->bindings.insert( object, { column, metaProperty } ); + connect( object, &QObject::destroyed, + this, &QskModelObjectBinder::unbindObject ); + } +} + +void QskModelObjectBinder::unbindObject( QObject* object ) +{ + auto it = m_data->bindings.find( object ); + if ( it != m_data->bindings.end() ) + { + disconnect( object, &QObject::destroyed, + this, &QskModelObjectBinder::unbindObject ); + + m_data->bindings.erase( it ); + } +} + +void QskModelObjectBinder::bindModel( QAbstractItemModel* model ) +{ + if ( m_data->model ) + disconnect( m_data->model, nullptr, this, nullptr ); + + m_data->model = model; + m_data->currentRowIndex = QModelIndex(); + + if( model ) + { + auto updateProperties = [this]( + const QModelIndex& topLeft, const QModelIndex& bottomRight, + const QVector< int >& roles ) + { + m_data->updateProperties( topLeft, bottomRight, roles ); + }; + + connect( m_data->model, &QAbstractItemModel::dataChanged, + this, updateProperties ); + } +} + +void QskModelObjectBinder::setCurrentRow( int row ) +{ + Q_ASSERT( m_data->model != nullptr ); + + if ( m_data->model == nullptr ) + return; + + m_data->currentRowIndex = m_data->model->index( row, 0 ); + + for ( auto it = m_data->bindings.begin(); it != m_data->bindings.end(); ++it ) + m_data->updateObjectProperty( it.key(), it.value() ); +} + +int QskModelObjectBinder::currentRow() const +{ + return m_data->currentRowIndex.row(); +} + +void QskModelObjectBinder::updateModel() +{ + if ( auto model = m_data->model ) + { + const auto& bindings = m_data->bindings; + + for ( auto it = bindings.begin(); it != bindings.end(); ++it ) + { + const auto value = it.value().property.read( it.key() ); + + const auto row = m_data->currentRowIndex.row(); + const auto index = model->index( row, it.value().column ); + + model->setData( index, value ); + } + } +} + +#include "moc_QskModelObjectBinder.cpp" diff --git a/src/controls/QskModelObjectBinder.h b/src/controls/QskModelObjectBinder.h new file mode 100644 index 00000000..7cd3dc80 --- /dev/null +++ b/src/controls/QskModelObjectBinder.h @@ -0,0 +1,42 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#ifndef QSK_MODEL_OBJECT_BINDER_H +#define QSK_MODEL_OBJECT_BINDER_H + +#include "QskGlobal.h" + +#include +#include + +#include + +class QAbstractItemModel; + +class QSK_EXPORT QskModelObjectBinder : public QObject +{ + Q_OBJECT + + public: + QskModelObjectBinder( QObject* parent = nullptr ); + QskModelObjectBinder( QAbstractItemModel*, QObject* parent = nullptr ); + + void bindObject( QObject*, int column, + const QByteArray& propertyName = QByteArray() ); + + void unbindObject( QObject* ); + void bindModel( QAbstractItemModel* ); + + void setCurrentRow( int row ); + [[nodiscard]] int currentRow() const; + + void updateModel(); + + private: + class PrivateData; + std::unique_ptr< PrivateData > m_data; +}; + +#endif From 1a5dc6d358dc0a7fc250239cf4453e4268695f8e Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Sun, 25 Feb 2024 13:55:40 +0100 Subject: [PATCH 03/11] Qt5 incompatibility fixed --- src/controls/QskModelObjectBinder.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/controls/QskModelObjectBinder.cpp b/src/controls/QskModelObjectBinder.cpp index 1683386b..b33d51a5 100644 --- a/src/controls/QskModelObjectBinder.cpp +++ b/src/controls/QskModelObjectBinder.cpp @@ -8,7 +8,6 @@ #include #include #include -#include static inline QMetaProperty qskMetaProperty( const QMetaObject* metaObject, const QByteArray& name ) From 8faa6a8cad743d9f671c6a16bb9f7181292552a9 Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Mon, 26 Feb 2024 10:52:52 +0100 Subject: [PATCH 04/11] bugs fixed, QskModelObjectBinder improved --- playground/models/Window.cpp | 117 +++++++++++++++----------- playground/models/Window.h | 2 - src/controls/QskModelObjectBinder.cpp | 115 ++++++++++++++++++++----- src/controls/QskModelObjectBinder.h | 21 ++++- 4 files changed, 178 insertions(+), 77 deletions(-) diff --git a/playground/models/Window.cpp b/playground/models/Window.cpp index 76c2f27b..352dc538 100644 --- a/playground/models/Window.cpp +++ b/playground/models/Window.cpp @@ -17,60 +17,77 @@ #include #include -Window::Window() +namespace { - auto db = QSqlDatabase::addDatabase( "QSQLITE" ); - db.setDatabaseName( ":memory:" ); - db.open(); + class Model : public QSqlTableModel + { + public: + Model( QObject *parent = nullptr ) + : QSqlTableModel( parent, QSqlDatabase::addDatabase( "QSQLITE" ) ) + { + auto db = database(); - QSqlQuery q(db); - q.exec( "create table test(id integer,value text);" ); - q.exec( "insert into test (id,value) values (1,'HELLO');" ); - q.exec( "insert into test (id,value) values (2,'WORLD');" ); + db.setDatabaseName( ":memory:" ); + db.open(); - auto table = new QSqlTableModel( nullptr, db ); - table->setTable( "test" ); - table->select(); + QSqlQuery query( db ); + query.exec( "create table test(id integer,value text);" ); + query.exec( "insert into test (id,value) values (1,'HELLO');" ); + query.exec( "insert into test (id,value) values (2,'WORLD');" ); - auto txt = new QskTextInput(); - auto spin = new QskSpinBox(); + setTable( "test" ); + select(); + } - auto mapper = new QskModelObjectBinder(); - mapper->bindModel( table ); - - mapper->bindObject( spin, 0 ); - mapper->bindObject( txt, 1 ); - - // this loads the record from the first row and updates the controls data. - mapper->setCurrentRow(0); - - auto v = new QskLinearBox(Qt::Vertical); - v->addSpacer( 0,100 ); - v->addItem( spin ); - v->addItem( txt ); - addItem(v); - - auto h = new QskLinearBox(Qt::Horizontal); - auto prev = new QskPushButton("<",h); - auto next = new QskPushButton(">",h); - connect(prev,&QskPushButton::clicked,[ = ]() { mapper->setCurrentRow( 0 ); }); - connect(next,&QskPushButton::clicked,[ = ]() { mapper->setCurrentRow( 1 ); }); - v->addItem(h); - - auto save = new QskPushButton("Save Data to Model",v); - connect(save,&QskPushButton::clicked,[ = ]() { - // this will update the current record with the data from the SpinBox and TextInput - mapper->updateModel(); - // just for illustration we print out the record - auto r = table->record(mapper->currentRow() ); - qDebug() << r; - }); - auto set0 = new QskPushButton("Set Model field to 0",v); - connect(set0,&QskPushButton::clicked,[ = ]() { - // this should trigger the binder and update the spinbox - table->setData( table->index(mapper->currentRow(),0),0 ); - }); - v->addSpacer( 0,100 ); + void setValue( int row, const QVariant &value ) + { + setData( index( row, 0 ), value ); + } + }; } -#include "moc_Window.cpp" +Window::Window() +{ + auto model = new Model( this ); + + auto textInput = new QskTextInput(); + auto spinBox = new QskSpinBox(); + + auto mapper = new QskModelObjectBinder( model, this ); + + mapper->bindObject( spinBox, 0 ); + mapper->bindObject( textInput, 1 ); + + auto vBox = new QskLinearBox( Qt::Vertical ); + vBox->addSpacer( 0, 100 ); + vBox->addItem( spinBox ); + vBox->addItem( textInput ); + + addItem( vBox ); + + auto hBox = new QskLinearBox( Qt::Horizontal ); + + { + auto prev = new QskPushButton( "<", hBox ); + auto next = new QskPushButton( ">", hBox); + + connect(prev, &QskPushButton::clicked, + [ mapper ]() { mapper->setCurrentRow( 0 ); }); + + connect( next, &QskPushButton::clicked, + [ mapper ]() { mapper->setCurrentRow( 1 ); }); + + vBox->addItem( hBox ); + } + + // update the current record with the data from the SpinBox and TextInput + auto save = new QskPushButton( "Save Data to Model", vBox ); + connect( save, &QskPushButton::clicked, + [=]() { mapper->submit(); qDebug() << model->record( mapper->currentRow() ); }); + + // trigger the binder and update the spinbox + auto set0 = new QskPushButton( "Set Model field to 0", vBox ); + connect( set0, &QskPushButton::clicked, + [=]() { model->setValue( mapper->currentRow(), 0 ); } ); + vBox->addSpacer( 0,100 ); +} diff --git a/playground/models/Window.h b/playground/models/Window.h index 816d6f4f..112f59c2 100644 --- a/playground/models/Window.h +++ b/playground/models/Window.h @@ -9,8 +9,6 @@ class Window : public QskWindow { - Q_OBJECT - public: Window(); }; diff --git a/src/controls/QskModelObjectBinder.cpp b/src/controls/QskModelObjectBinder.cpp index b33d51a5..b344eef5 100644 --- a/src/controls/QskModelObjectBinder.cpp +++ b/src/controls/QskModelObjectBinder.cpp @@ -19,6 +19,21 @@ static inline QMetaProperty qskMetaProperty( return ( idx >= 0 ) ? metaObject->property( idx ) : QMetaProperty(); } +static void qskEnableConnections( QObject* object, + QskModelObjectBinder* binder, bool on ) +{ + if ( on ) + { + QObject::connect( object, &QObject::destroyed, + binder, &QskModelObjectBinder::unbindObject ); + } + else + { + QObject::disconnect( object, &QObject::destroyed, + binder, &QskModelObjectBinder::unbindObject ); + } +} + namespace { struct Binding @@ -37,17 +52,29 @@ namespace class QskModelObjectBinder::PrivateData { public: - void updateProperties(const QModelIndex& topLeft, + void updateProperties() + { + if ( model && currentRowIndex.isValid() ) + { + for ( auto it = bindings.constBegin(); it != bindings.constEnd(); ++it ) + updateObjectProperty( it.key(), it.value() ); + } + } + + void updateProperties( const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector< int >& roles ) { - if ( !roles.contains( Qt::EditRole ) ) + if ( !( model && currentRowIndex.isValid() ) ) + return; + + if ( !roles.isEmpty() && roles.contains( Qt::EditRole ) ) return; const int row = currentRowIndex.row(); if ( topLeft.row() <= row && row <= bottomRight.row() ) { - for ( auto it = bindings.begin(); it != bindings.end(); ++it ) + for ( auto it = bindings.constBegin(); it != bindings.constEnd(); ++it ) { const int col = it.value().column; @@ -78,7 +105,7 @@ QskModelObjectBinder::QskModelObjectBinder( QObject* parent ) QskModelObjectBinder::QskModelObjectBinder( QAbstractItemModel* model, QObject* parent ) : QskModelObjectBinder( parent ) { - bindModel( model ); + setModel( model ); } void QskModelObjectBinder::bindObject( @@ -94,9 +121,13 @@ void QskModelObjectBinder::bindObject( if ( metaProperty.isValid() ) { - m_data->bindings.insert( object, { column, metaProperty } ); - connect( object, &QObject::destroyed, - this, &QskModelObjectBinder::unbindObject ); + const Binding binding = { column, metaProperty }; + + m_data->bindings.insert( object, binding ); + qskEnableConnections( object, this, true ); + + if ( m_data->model && m_data->currentRowIndex.isValid() ) + m_data->updateObjectProperty( object, binding ); } } @@ -105,20 +136,37 @@ void QskModelObjectBinder::unbindObject( QObject* object ) auto it = m_data->bindings.find( object ); if ( it != m_data->bindings.end() ) { - disconnect( object, &QObject::destroyed, - this, &QskModelObjectBinder::unbindObject ); - + qskEnableConnections( object, this, false ); m_data->bindings.erase( it ); } } -void QskModelObjectBinder::bindModel( QAbstractItemModel* model ) +void QskModelObjectBinder::clearBindings() { + auto& bindings = m_data->bindings; + + for ( auto it = bindings.constBegin(); it != bindings.constEnd(); ++it ) + qskEnableConnections( it.key(), this, false ); + + bindings.clear(); +} + +void QskModelObjectBinder::setModel( QAbstractItemModel* model ) +{ + if ( model == m_data->model ) + return; + if ( m_data->model ) - disconnect( m_data->model, nullptr, this, nullptr ); + { + disconnect( m_data->model, &QAbstractItemModel::dataChanged, this, nullptr ); + + m_data->model = nullptr; + m_data->currentRowIndex = QModelIndex(); + + clearBindings(); + } m_data->model = model; - m_data->currentRowIndex = QModelIndex(); if( model ) { @@ -126,25 +174,45 @@ void QskModelObjectBinder::bindModel( QAbstractItemModel* model ) const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector< int >& roles ) { - m_data->updateProperties( topLeft, bottomRight, roles ); + m_data->updateProperties( topLeft, bottomRight, roles ); }; connect( m_data->model, &QAbstractItemModel::dataChanged, this, updateProperties ); + + connect( m_data->model, &QObject::destroyed, + this, &QskModelObjectBinder::clearBindings ); + + setCurrentRow( 0 ); } } +const QAbstractItemModel* QskModelObjectBinder::model() const +{ + return m_data->model; +} + +QAbstractItemModel* QskModelObjectBinder::model() +{ + return m_data->model; +} + void QskModelObjectBinder::setCurrentRow( int row ) { - Q_ASSERT( m_data->model != nullptr ); + auto model = m_data->model.data(); + auto& bindings = m_data->bindings; - if ( m_data->model == nullptr ) - return; + Q_ASSERT( model != nullptr ); - m_data->currentRowIndex = m_data->model->index( row, 0 ); + if ( model && row >= 0 && row < model->rowCount() ) + { + m_data->currentRowIndex = model->index( row, 0 ); - for ( auto it = m_data->bindings.begin(); it != m_data->bindings.end(); ++it ) - m_data->updateObjectProperty( it.key(), it.value() ); + for ( auto it = bindings.constBegin(); it != bindings.constEnd(); ++it ) + m_data->updateObjectProperty( it.key(), it.value() ); + + Q_EMIT currentRowChanged( row ); + } } int QskModelObjectBinder::currentRow() const @@ -152,7 +220,7 @@ int QskModelObjectBinder::currentRow() const return m_data->currentRowIndex.row(); } -void QskModelObjectBinder::updateModel() +void QskModelObjectBinder::submit() { if ( auto model = m_data->model ) { @@ -170,4 +238,9 @@ void QskModelObjectBinder::updateModel() } } +void QskModelObjectBinder::revert() +{ + m_data->updateProperties(); +} + #include "moc_QskModelObjectBinder.cpp" diff --git a/src/controls/QskModelObjectBinder.h b/src/controls/QskModelObjectBinder.h index 7cd3dc80..a7338cfe 100644 --- a/src/controls/QskModelObjectBinder.h +++ b/src/controls/QskModelObjectBinder.h @@ -19,20 +19,33 @@ class QSK_EXPORT QskModelObjectBinder : public QObject { Q_OBJECT + Q_PROPERTY( int currentRow READ currentRow + WRITE setCurrentRow NOTIFY currentRowChanged ) + public: QskModelObjectBinder( QObject* parent = nullptr ); QskModelObjectBinder( QAbstractItemModel*, QObject* parent = nullptr ); + void setModel( QAbstractItemModel* ); + + const QAbstractItemModel* model() const; + QAbstractItemModel* model(); + + void setCurrentRow( int row ); + int currentRow() const; + void bindObject( QObject*, int column, const QByteArray& propertyName = QByteArray() ); void unbindObject( QObject* ); - void bindModel( QAbstractItemModel* ); - void setCurrentRow( int row ); - [[nodiscard]] int currentRow() const; + Q_SIGNALS: + void currentRowChanged( int ); - void updateModel(); + public Q_SLOTS: + void submit(); + void revert(); + void clearBindings(); private: class PrivateData; From 60c7442fd0c5218fbafe1221da088d4ae896558d Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Mon, 26 Feb 2024 11:37:59 +0100 Subject: [PATCH 05/11] role check fixed --- src/controls/QskModelObjectBinder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controls/QskModelObjectBinder.cpp b/src/controls/QskModelObjectBinder.cpp index b344eef5..c3dc910e 100644 --- a/src/controls/QskModelObjectBinder.cpp +++ b/src/controls/QskModelObjectBinder.cpp @@ -67,7 +67,7 @@ class QskModelObjectBinder::PrivateData if ( !( model && currentRowIndex.isValid() ) ) return; - if ( !roles.isEmpty() && roles.contains( Qt::EditRole ) ) + if ( !( roles.isEmpty() || roles.contains( Qt::EditRole ) ) ) return; const int row = currentRowIndex.row(); From e2486f914d38664f6de9af5f17ce73f4da41f0d9 Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Mon, 26 Feb 2024 11:39:16 +0100 Subject: [PATCH 06/11] QSql dependency removed ( using a QStandardItemModel instead ) --- cmake/QskFindMacros.cmake | 4 +-- playground/CMakeLists.txt | 5 +-- playground/models/CMakeLists.txt | 2 +- playground/models/Window.cpp | 57 +++++++++++++++++--------------- 4 files changed, 34 insertions(+), 34 deletions(-) diff --git a/cmake/QskFindMacros.cmake b/cmake/QskFindMacros.cmake index e2b01e7f..f2791254 100644 --- a/cmake/QskFindMacros.cmake +++ b/cmake/QskFindMacros.cmake @@ -54,11 +54,11 @@ macro(qsk_setup_Qt) # C++, but QSkinny itself does not need the WebEngine at all. if (QT_VERSION_MAJOR VERSION_LESS 6) - find_package(Qt${QT_VERSION_MAJOR} QUIET OPTIONAL_COMPONENTS WebEngine Sql) + find_package(Qt${QT_VERSION_MAJOR} QUIET OPTIONAL_COMPONENTS WebEngine) set( Qt5WebEngineQuick_FOUND ${Qt5WebEngine_FOUND} ) else() find_package(Qt${QT_VERSION_MAJOR} QUIET - OPTIONAL_COMPONENTS WebEngineCore WebEngineQuick Sql) + OPTIONAL_COMPONENTS WebEngineCore WebEngineQuick) endif() if( NOT Qt${QT_VERSION_MAJOR}WebEngineQuick_FOUND) diff --git a/playground/CMakeLists.txt b/playground/CMakeLists.txt index 7ba9e0b5..d3f2b0c7 100644 --- a/playground/CMakeLists.txt +++ b/playground/CMakeLists.txt @@ -8,6 +8,7 @@ add_subdirectory(shadows) add_subdirectory(shapes) add_subdirectory(charts) add_subdirectory(plots) +add_subdirectory(models) if (BUILD_INPUTCONTEXT) add_subdirectory(inputpanel) @@ -32,7 +33,3 @@ endif() if(TARGET Qt::QuickWidgets) add_subdirectory(grids) endif() - -if(TARGET Qt::Sql) - add_subdirectory(models) -endif() diff --git a/playground/models/CMakeLists.txt b/playground/models/CMakeLists.txt index c8b277e0..da9a430e 100644 --- a/playground/models/CMakeLists.txt +++ b/playground/models/CMakeLists.txt @@ -4,4 +4,4 @@ ############################################################################ qsk_add_example(models Window.h Window.cpp main.cpp) -target_link_libraries(models PRIVATE Qt::Sql) +target_link_libraries(models) diff --git a/playground/models/Window.cpp b/playground/models/Window.cpp index 352dc538..69e6f623 100644 --- a/playground/models/Window.cpp +++ b/playground/models/Window.cpp @@ -9,39 +9,41 @@ #include #include #include - #include -#include -#include -#include -#include +#include namespace { - class Model : public QSqlTableModel + class Model : public QStandardItemModel { public: - Model( QObject *parent = nullptr ) - : QSqlTableModel( parent, QSqlDatabase::addDatabase( "QSQLITE" ) ) + Model( QObject* parent = nullptr ) + : QStandardItemModel( 2, 2, parent ) { - auto db = database(); + setValue( 0, 0, 1 ); + setValue( 0, 1, "HELLO" ); - db.setDatabaseName( ":memory:" ); - db.open(); - - QSqlQuery query( db ); - query.exec( "create table test(id integer,value text);" ); - query.exec( "insert into test (id,value) values (1,'HELLO');" ); - query.exec( "insert into test (id,value) values (2,'WORLD');" ); - - setTable( "test" ); - select(); + setValue( 1, 0, 2 ); + setValue( 1, 1, "WORLD" ); } - void setValue( int row, const QVariant &value ) + void setValue( int row, int col, const QVariant& value ) { - setData( index( row, 0 ), value ); + setData( index( row, col ), value, Qt::EditRole ); + } + + void dump() + { + qDebug() << "Model"; + for ( int row = 0; row < rowCount(); row++ ) + { + for ( int col = 0; col < columnCount(); col++ ) + { + qDebug() << '\t' << row << col + << data( index( row, col ), Qt::EditRole ); + } + } } }; } @@ -72,10 +74,10 @@ Window::Window() auto next = new QskPushButton( ">", hBox); connect(prev, &QskPushButton::clicked, - [ mapper ]() { mapper->setCurrentRow( 0 ); }); + [ mapper ]() { mapper->setCurrentRow( 0 ); } ); connect( next, &QskPushButton::clicked, - [ mapper ]() { mapper->setCurrentRow( 1 ); }); + [ mapper ]() { mapper->setCurrentRow( 1 ); } ); vBox->addItem( hBox ); } @@ -83,11 +85,12 @@ Window::Window() // update the current record with the data from the SpinBox and TextInput auto save = new QskPushButton( "Save Data to Model", vBox ); connect( save, &QskPushButton::clicked, - [=]() { mapper->submit(); qDebug() << model->record( mapper->currentRow() ); }); + [ = ]() { mapper->submit(); model->dump(); } ); // trigger the binder and update the spinbox - auto set0 = new QskPushButton( "Set Model field to 0", vBox ); + auto set0 = new QskPushButton( "Set Model field to 42", vBox ); connect( set0, &QskPushButton::clicked, - [=]() { model->setValue( mapper->currentRow(), 0 ); } ); - vBox->addSpacer( 0,100 ); + [ = ]() { model->setValue( mapper->currentRow(), 0, 42 ); } ); + + vBox->addSpacer( 0, 100 ); } From 7b4db3afc14301af8278daa559ce0d551e4d0e33 Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Mon, 26 Feb 2024 12:16:56 +0100 Subject: [PATCH 07/11] wip --- playground/models/Window.cpp | 138 +++++++++++++++++++++-------------- 1 file changed, 85 insertions(+), 53 deletions(-) diff --git a/playground/models/Window.cpp b/playground/models/Window.cpp index 69e6f623..da39c508 100644 --- a/playground/models/Window.cpp +++ b/playground/models/Window.cpp @@ -21,19 +21,14 @@ namespace Model( QObject* parent = nullptr ) : QStandardItemModel( 2, 2, parent ) { - setValue( 0, 0, 1 ); - setValue( 0, 1, "HELLO" ); + initValue( 0, 0, 1 ); + initValue( 0, 1, "HELLO" ); - setValue( 1, 0, 2 ); - setValue( 1, 1, "WORLD" ); + initValue( 1, 0, 2 ); + initValue( 1, 1, "WORLD" ); } - void setValue( int row, int col, const QVariant& value ) - { - setData( index( row, col ), value, Qt::EditRole ); - } - - void dump() + void dump() const { qDebug() << "Model"; for ( int row = 0; row < rowCount(); row++ ) @@ -45,52 +40,89 @@ namespace } } } + + private: + void initValue( int row, int col, const QVariant& value ) + { + setData( index( row, col ), value, Qt::EditRole ); + } + + }; + + class View : public QskLinearBox + { + public: + View( QQuickItem* parent = nullptr ) + : QskLinearBox( Qt::Vertical, parent ) + { + setPanel( true ); + + auto model = new Model( this ); + + auto textInput = new QskTextInput(); + auto spinBox = new QskSpinBox( -100.0, +100.0, 1.0 ); + + m_binder = new QskModelObjectBinder( model, this ); + m_binder->bindObject( spinBox, 0 ); + m_binder->bindObject( textInput, 1 ); + + auto hBox = new QskLinearBox( Qt::Horizontal ); + hBox->setSection( QskAspect::Header ); + hBox->setSizePolicy( Qt::Vertical, QskSizePolicy::Fixed ); + + { + auto rowButton = new QskPushButton( "Toggle Row", hBox ); + auto counterButton = new QskPushButton( "Invert Counter", hBox ); + auto submitButton = new QskPushButton( "Submit Changes", hBox ); + + connect( rowButton, &QskPushButton::clicked, + this, &View::toogleRow ); + + connect( counterButton, &QskPushButton::clicked, + this, &View::invertCounter ); + + connect( submitButton, &QskPushButton::clicked, + this, &View::updateModel ); + + hBox->addStretch( 1 ); + } + + addItem( hBox ); + addSpacer( 5 ); + addItem( spinBox ); + addItem( textInput ); + addStretch( 1 ); + } + + private: + void toogleRow() + { + m_binder->setCurrentRow( m_binder->currentRow() == 0 ? 1 : 0 ); + } + + void updateModel() + { + m_binder->submit(); + + const auto model = dynamic_cast< const Model* >( m_binder->model() ); + model->dump(); + } + + void invertCounter() + { + auto model = m_binder->model(); + + const auto index = model->index( m_binder->currentRow(), 0 ); + + auto value = model->data( index, Qt::EditRole ); + model->setData( index, -value.toDouble(), Qt::EditRole ); + } + + QskModelObjectBinder* m_binder; }; } Window::Window() { - auto model = new Model( this ); - - auto textInput = new QskTextInput(); - auto spinBox = new QskSpinBox(); - - auto mapper = new QskModelObjectBinder( model, this ); - - mapper->bindObject( spinBox, 0 ); - mapper->bindObject( textInput, 1 ); - - auto vBox = new QskLinearBox( Qt::Vertical ); - vBox->addSpacer( 0, 100 ); - vBox->addItem( spinBox ); - vBox->addItem( textInput ); - - addItem( vBox ); - - auto hBox = new QskLinearBox( Qt::Horizontal ); - - { - auto prev = new QskPushButton( "<", hBox ); - auto next = new QskPushButton( ">", hBox); - - connect(prev, &QskPushButton::clicked, - [ mapper ]() { mapper->setCurrentRow( 0 ); } ); - - connect( next, &QskPushButton::clicked, - [ mapper ]() { mapper->setCurrentRow( 1 ); } ); - - vBox->addItem( hBox ); - } - - // update the current record with the data from the SpinBox and TextInput - auto save = new QskPushButton( "Save Data to Model", vBox ); - connect( save, &QskPushButton::clicked, - [ = ]() { mapper->submit(); model->dump(); } ); - - // trigger the binder and update the spinbox - auto set0 = new QskPushButton( "Set Model field to 42", vBox ); - connect( set0, &QskPushButton::clicked, - [ = ]() { model->setValue( mapper->currentRow(), 0, 42 ); } ); - - vBox->addSpacer( 0, 100 ); + addItem( new View() ); } From 0a651782babe2036e7e4ae04b0cbd7916f4b9391 Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Mon, 26 Feb 2024 15:27:52 +0100 Subject: [PATCH 08/11] QskModelObjectBinder API improved, models example polished --- playground/models/CMakeLists.txt | 2 +- playground/models/MainView.cpp | 215 +++++++++++++++++++++ playground/models/{Window.h => MainView.h} | 13 +- playground/models/Window.cpp | 128 ------------ playground/models/main.cpp | 8 +- src/controls/QskModelObjectBinder.cpp | 26 ++- src/controls/QskModelObjectBinder.h | 4 + 7 files changed, 259 insertions(+), 137 deletions(-) create mode 100644 playground/models/MainView.cpp rename playground/models/{Window.h => MainView.h} (58%) delete mode 100644 playground/models/Window.cpp diff --git a/playground/models/CMakeLists.txt b/playground/models/CMakeLists.txt index da9a430e..c9e3347a 100644 --- a/playground/models/CMakeLists.txt +++ b/playground/models/CMakeLists.txt @@ -3,5 +3,5 @@ # SPDX-License-Identifier: BSD-3-Clause ############################################################################ -qsk_add_example(models Window.h Window.cpp main.cpp) +qsk_add_example(models MainView.h MainView.cpp main.cpp) target_link_libraries(models) diff --git a/playground/models/MainView.cpp b/playground/models/MainView.cpp new file mode 100644 index 00000000..1c97c205 --- /dev/null +++ b/playground/models/MainView.cpp @@ -0,0 +1,215 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#include "MainView.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace +{ + class TitleLabel : public QskTextLabel + { + public: + TitleLabel( const QString& text, QQuickItem* parent = nullptr ) + : QskTextLabel( text, parent ) + { + setFontRole( QskFontRole::Title ); + } + }; + + class SpinBox : public QskSpinBox + { + public: + SpinBox( QQuickItem* parent = nullptr ) + : QskSpinBox( -100.0, 100.0, 1.0, parent ) + { + initSizePolicy( QskSizePolicy::Fixed, QskSizePolicy::Fixed ); + } + }; + + class Header : public QskLinearBox + { + Q_OBJECT + + public: + Header( QQuickItem* parent = nullptr ) + : QskLinearBox( Qt::Horizontal, parent ) + { + initSizePolicy( QskSizePolicy::Ignored, QskSizePolicy::Fixed ); + + setPaddingHint( QskBox::Panel, 5 ); + + setPanel( true ); + + auto rowButton = new QskPushButton( "Toggle Row" ); + auto submitButton = new QskPushButton( "Submit Changes" ); + + connect( rowButton, &QskPushButton::clicked, + this, &Header::rowClicked ); + + connect( submitButton, &QskPushButton::clicked, + this, &Header::submitClicked ); + + addItem( rowButton ); + addItem( submitButton ); + addStretch( 1 ); + } + + Q_SIGNALS: + void rowClicked(); + void submitClicked(); + }; + + class Model : public QStandardItemModel + { + public: + Model( QObject* parent = nullptr ) + : QStandardItemModel( 2, 2, parent ) + { + initValue( 0, 0, 1.0 ); + initValue( 0, 1, "HELLO" ); + + initValue( 1, 0, 2.0 ); + initValue( 1, 1, "WORLD" ); + } + + private: + void initValue( int row, int col, const QVariant& value ) + { + setData( index( row, col ), value, Qt::EditRole ); + } + }; + + class DisplayBox : public QskLinearBox + { + public: + DisplayBox( QQuickItem* parent = nullptr ) + : QskLinearBox( Qt::Horizontal, parent ) + { + setMargins( 10, 0, 10, 0 ); + initSizePolicy( QskSizePolicy::MinimumExpanding, QskSizePolicy::Fixed ); + + addItem( new SpinBox() ); + addItem( new QskTextInput() ); + } + }; + + class ModelBox : public QskLinearBox + { + public: + ModelBox( QAbstractItemModel* model ) + : QskLinearBox( Qt::Horizontal, model->columnCount() ) + , m_model( model ) + { + setMargins( 10, 0, 10, 0 ); + setExtraSpacingAt( Qt::BottomEdge ); + + for ( int row = 0; row < model->rowCount(); row++ ) + { + for ( int col = 0; col < model->columnCount(); col++ ) + { + const auto value = model->data( + model->index( row, col ), Qt::EditRole ); + + if ( value.userType() == QVariant::Double ) + { + auto spinBox = new SpinBox( this ); + connect( spinBox, &QskSpinBox::valueChanged, + this, &ModelBox::updateModel ); + } + else + { + auto textInput = new QskTextInput( this ); + connect( textInput, &QskTextInput::textChanged, + this, &ModelBox::updateModel ); + } + } + } + + updateDisplay(); + + connect( m_model, &Model::dataChanged, this, &ModelBox::updateDisplay ); + } + + private: + void updateModel() + { + if ( auto item = qobject_cast< const QQuickItem* >( sender() ) ) + { + const int index = indexOf( item ); + + const auto modelIndex = m_model->index( + index / dimension(), index % dimension() ); + + const auto property = item->metaObject()->userProperty(); + m_model->setData( modelIndex, property.read( item ), Qt::EditRole ); + } + } + + void updateDisplay() const + { + for ( int row = 0; row < m_model->rowCount(); row++ ) + { + for ( int col = 0; col < m_model->columnCount(); col++ ) + { + const auto index = m_model->index( row, col ); + + if ( auto item = itemAtIndex( row * dimension() + col ) ) + { + const auto property = item->metaObject()->userProperty(); + property.write( item, m_model->data( index, Qt::EditRole ) ); + } + } + } + } + + QPointer< QAbstractItemModel > m_model; + }; +} + +MainView::MainView( QQuickItem* parent ) + : QskMainView( parent ) +{ + m_binder = new QskModelObjectBinder( new Model( this ), this ); + + auto header = new Header(); + + auto displayBox = new DisplayBox(); + for ( int i = 0; i < displayBox->elementCount(); i++ ) + m_binder->bindObject( displayBox->itemAtIndex( i ), i ); + + auto box = new QskLinearBox( Qt::Vertical ); + + box->addItem( new TitleLabel( "Editor:" ) ); + box->addItem( displayBox ); + box->addItem( new QskSeparator() ); + box->addItem( new TitleLabel( "Model:" ) ); + box->addItem( new ModelBox( m_binder->model() ) ); + + setHeader( header ); + setBody( box ); + + connect( header, &Header::rowClicked, + this, &MainView::toogleRow ); + + connect( header, &Header::submitClicked, + m_binder, &QskModelObjectBinder::submit ); +} + +void MainView::toogleRow() +{ + m_binder->setCurrentRow( m_binder->currentRow() == 0 ? 1 : 0 ); +} + +#include "MainView.moc" diff --git a/playground/models/Window.h b/playground/models/MainView.h similarity index 58% rename from playground/models/Window.h rename to playground/models/MainView.h index 112f59c2..7f80c80a 100644 --- a/playground/models/Window.h +++ b/playground/models/MainView.h @@ -5,10 +5,17 @@ #pragma once -#include +#include -class Window : public QskWindow +class QskModelObjectBinder; + +class MainView : public QskMainView { public: - Window(); + MainView( QQuickItem* = nullptr ); + + private: + void toogleRow(); + + QskModelObjectBinder* m_binder; }; diff --git a/playground/models/Window.cpp b/playground/models/Window.cpp deleted file mode 100644 index da39c508..00000000 --- a/playground/models/Window.cpp +++ /dev/null @@ -1,128 +0,0 @@ -/****************************************************************************** - * QSkinny - Copyright (C) The authors - * SPDX-License-Identifier: BSD-3-Clause - *****************************************************************************/ - -#include "Window.h" - -#include -#include -#include -#include -#include - -#include - -namespace -{ - class Model : public QStandardItemModel - { - public: - Model( QObject* parent = nullptr ) - : QStandardItemModel( 2, 2, parent ) - { - initValue( 0, 0, 1 ); - initValue( 0, 1, "HELLO" ); - - initValue( 1, 0, 2 ); - initValue( 1, 1, "WORLD" ); - } - - void dump() const - { - qDebug() << "Model"; - for ( int row = 0; row < rowCount(); row++ ) - { - for ( int col = 0; col < columnCount(); col++ ) - { - qDebug() << '\t' << row << col - << data( index( row, col ), Qt::EditRole ); - } - } - } - - private: - void initValue( int row, int col, const QVariant& value ) - { - setData( index( row, col ), value, Qt::EditRole ); - } - - }; - - class View : public QskLinearBox - { - public: - View( QQuickItem* parent = nullptr ) - : QskLinearBox( Qt::Vertical, parent ) - { - setPanel( true ); - - auto model = new Model( this ); - - auto textInput = new QskTextInput(); - auto spinBox = new QskSpinBox( -100.0, +100.0, 1.0 ); - - m_binder = new QskModelObjectBinder( model, this ); - m_binder->bindObject( spinBox, 0 ); - m_binder->bindObject( textInput, 1 ); - - auto hBox = new QskLinearBox( Qt::Horizontal ); - hBox->setSection( QskAspect::Header ); - hBox->setSizePolicy( Qt::Vertical, QskSizePolicy::Fixed ); - - { - auto rowButton = new QskPushButton( "Toggle Row", hBox ); - auto counterButton = new QskPushButton( "Invert Counter", hBox ); - auto submitButton = new QskPushButton( "Submit Changes", hBox ); - - connect( rowButton, &QskPushButton::clicked, - this, &View::toogleRow ); - - connect( counterButton, &QskPushButton::clicked, - this, &View::invertCounter ); - - connect( submitButton, &QskPushButton::clicked, - this, &View::updateModel ); - - hBox->addStretch( 1 ); - } - - addItem( hBox ); - addSpacer( 5 ); - addItem( spinBox ); - addItem( textInput ); - addStretch( 1 ); - } - - private: - void toogleRow() - { - m_binder->setCurrentRow( m_binder->currentRow() == 0 ? 1 : 0 ); - } - - void updateModel() - { - m_binder->submit(); - - const auto model = dynamic_cast< const Model* >( m_binder->model() ); - model->dump(); - } - - void invertCounter() - { - auto model = m_binder->model(); - - const auto index = model->index( m_binder->currentRow(), 0 ); - - auto value = model->data( index, Qt::EditRole ); - model->setData( index, -value.toDouble(), Qt::EditRole ); - } - - QskModelObjectBinder* m_binder; - }; -} - -Window::Window() -{ - addItem( new View() ); -} diff --git a/playground/models/main.cpp b/playground/models/main.cpp index 5b0476bb..27f25d63 100644 --- a/playground/models/main.cpp +++ b/playground/models/main.cpp @@ -3,12 +3,13 @@ * SPDX-License-Identifier: BSD-3-Clause *****************************************************************************/ -#include "Window.h" +#include "MainView.h" #include -#include #include +#include +#include #include @@ -22,13 +23,14 @@ int main( int argc, char* argv[] ) SkinnyShortcut::enable( SkinnyShortcut::AllShortcuts ); - Window window; + QskWindow window; window.resize( 600, 400 ); auto focusIndicator = new QskFocusIndicator(); focusIndicator->setObjectName( "FocusIndicator" ); window.addItem( focusIndicator ); + window.addItem( new MainView() ); window.show(); return app.exec(); diff --git a/src/controls/QskModelObjectBinder.cpp b/src/controls/QskModelObjectBinder.cpp index c3dc910e..74d37e78 100644 --- a/src/controls/QskModelObjectBinder.cpp +++ b/src/controls/QskModelObjectBinder.cpp @@ -133,8 +133,8 @@ void QskModelObjectBinder::bindObject( void QskModelObjectBinder::unbindObject( QObject* object ) { - auto it = m_data->bindings.find( object ); - if ( it != m_data->bindings.end() ) + auto it = m_data->bindings.constFind( object ); + if ( it != m_data->bindings.constEnd() ) { qskEnableConnections( object, this, false ); m_data->bindings.erase( it ); @@ -151,6 +151,28 @@ void QskModelObjectBinder::clearBindings() bindings.clear(); } +QObjectList QskModelObjectBinder::boundObjects() const +{ + auto& bindings = m_data->bindings; + + QObjectList objects; + objects.reserve( bindings.count() ); + + for ( auto it = bindings.constBegin(); it != bindings.constEnd(); ++it ) + objects += it.key(); + + return objects; +} + +QMetaProperty QskModelObjectBinder::boundProperty( const QObject* object ) const +{ + auto it = m_data->bindings.constFind( const_cast< QObject* >( object ) ); + if ( it != m_data->bindings.constEnd() ) + return it.value().property; + + return QMetaProperty(); +} + void QskModelObjectBinder::setModel( QAbstractItemModel* model ) { if ( model == m_data->model ) diff --git a/src/controls/QskModelObjectBinder.h b/src/controls/QskModelObjectBinder.h index a7338cfe..57bf3f20 100644 --- a/src/controls/QskModelObjectBinder.h +++ b/src/controls/QskModelObjectBinder.h @@ -14,6 +14,7 @@ #include class QAbstractItemModel; +class QMetaProperty; class QSK_EXPORT QskModelObjectBinder : public QObject { @@ -39,6 +40,9 @@ class QSK_EXPORT QskModelObjectBinder : public QObject void unbindObject( QObject* ); + QMetaProperty boundProperty( const QObject* ) const; + QObjectList boundObjects() const; + Q_SIGNALS: void currentRowChanged( int ); From 6a547e4698772956db792f319fef51aa607f6b9a Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Mon, 26 Feb 2024 15:49:06 +0100 Subject: [PATCH 09/11] Qt5 incompatibility fixed --- src/controls/QskModelObjectBinder.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/controls/QskModelObjectBinder.cpp b/src/controls/QskModelObjectBinder.cpp index 74d37e78..c177f3aa 100644 --- a/src/controls/QskModelObjectBinder.cpp +++ b/src/controls/QskModelObjectBinder.cpp @@ -133,11 +133,13 @@ void QskModelObjectBinder::bindObject( void QskModelObjectBinder::unbindObject( QObject* object ) { - auto it = m_data->bindings.constFind( object ); - if ( it != m_data->bindings.constEnd() ) + auto& bindings = m_data->bindings; + + auto it = bindings.find( object ); + if ( it != bindings.end() ) { qskEnableConnections( object, this, false ); - m_data->bindings.erase( it ); + bindings.erase( it ); } } From a888d9c5a8bdca868a24401d72f906337d0a41ea Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Wed, 11 Sep 2024 18:49:17 +0200 Subject: [PATCH 10/11] dummy implementation of the destructor in C++ file to avoid compiler errors because of not knowing PrivateData otherwise --- src/controls/QskModelObjectBinder.cpp | 4 ++++ src/controls/QskModelObjectBinder.h | 2 ++ 2 files changed, 6 insertions(+) diff --git a/src/controls/QskModelObjectBinder.cpp b/src/controls/QskModelObjectBinder.cpp index c177f3aa..bc978809 100644 --- a/src/controls/QskModelObjectBinder.cpp +++ b/src/controls/QskModelObjectBinder.cpp @@ -108,6 +108,10 @@ QskModelObjectBinder::QskModelObjectBinder( QAbstractItemModel* model, QObject* setModel( model ); } +QskModelObjectBinder::~QskModelObjectBinder() +{ +} + void QskModelObjectBinder::bindObject( QObject* object, int column, const QByteArray& propertyName ) { diff --git a/src/controls/QskModelObjectBinder.h b/src/controls/QskModelObjectBinder.h index 57bf3f20..4133a17b 100644 --- a/src/controls/QskModelObjectBinder.h +++ b/src/controls/QskModelObjectBinder.h @@ -27,6 +27,8 @@ class QSK_EXPORT QskModelObjectBinder : public QObject QskModelObjectBinder( QObject* parent = nullptr ); QskModelObjectBinder( QAbstractItemModel*, QObject* parent = nullptr ); + ~QskModelObjectBinder() override; + void setModel( QAbstractItemModel* ); const QAbstractItemModel* model() const; From 04e3378abbb2942bf84ba436e4595f87e740b066 Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Tue, 10 Dec 2024 14:29:36 +0100 Subject: [PATCH 11/11] QskTextInput -> QskTextField --- playground/models/MainView.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/playground/models/MainView.cpp b/playground/models/MainView.cpp index 1c97c205..f53e2386 100644 --- a/playground/models/MainView.cpp +++ b/playground/models/MainView.cpp @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include #include #include @@ -101,7 +101,7 @@ namespace initSizePolicy( QskSizePolicy::MinimumExpanding, QskSizePolicy::Fixed ); addItem( new SpinBox() ); - addItem( new QskTextInput() ); + addItem( new QskTextField() ); } }; @@ -130,8 +130,8 @@ namespace } else { - auto textInput = new QskTextInput( this ); - connect( textInput, &QskTextInput::textChanged, + auto textField = new QskTextField( this ); + connect( textField, &QskTextField::textChanged, this, &ModelBox::updateModel ); } }