From 59afa42c84d0a1ef0f46364867862038008f643c Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Sun, 25 Feb 2024 13:28:11 +0100 Subject: [PATCH] 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