From 0a651782babe2036e7e4ae04b0cbd7916f4b9391 Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Mon, 26 Feb 2024 15:27:52 +0100 Subject: [PATCH] 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 );