Compare commits

...

17 Commits

Author SHA1 Message Date
Uwe Rathmann 9040e006ad Merge branch 'master' into features/modelobjectbinder 2025-02-28 09:31:35 +01:00
Uwe Rathmann d9ef4f51ae Merge branch 'master' into features/modelobjectbinder 2025-02-06 10:28:04 +01:00
Uwe Rathmann 04e3378abb QskTextInput -> QskTextField 2024-12-10 14:29:36 +01:00
Uwe Rathmann 52686242ec Merge branch 'master' into features/modelobjectbinder 2024-12-10 14:10:57 +01:00
Uwe Rathmann c6fc72e07c Merge branch 'master' into features/modelobjectbinder 2024-12-02 10:16:42 +01:00
Uwe Rathmann 69eae90d3a Merge branch 'master' into features/modelobjectbinder 2024-09-17 17:43:06 +02:00
Uwe Rathmann a888d9c5a8 dummy implementation of the destructor in C++ file to avoid compiler
errors because of not knowing PrivateData otherwise
2024-09-11 18:49:17 +02:00
Uwe Rathmann cd8fd0d2b2 Merge branch 'master' into features/modelobjectbinder 2024-09-11 18:13:26 +02:00
Uwe Rathmann 6a547e4698 Qt5 incompatibility fixed 2024-02-26 15:49:06 +01:00
Uwe Rathmann 0a651782ba QskModelObjectBinder API improved, models example polished 2024-02-26 15:27:52 +01:00
Uwe Rathmann 7b4db3afc1 wip 2024-02-26 12:16:56 +01:00
Uwe Rathmann e2486f914d QSql dependency removed ( using a QStandardItemModel instead ) 2024-02-26 11:39:16 +01:00
Uwe Rathmann 60c7442fd0 role check fixed 2024-02-26 11:37:59 +01:00
Uwe Rathmann 8faa6a8cad bugs fixed, QskModelObjectBinder improved 2024-02-26 10:52:52 +01:00
Uwe Rathmann 1a5dc6d358 Qt5 incompatibility fixed 2024-02-25 13:55:40 +01:00
Uwe Rathmann 59afa42c84 QskModelObjectBinder modified and moved to src/controls 2024-02-25 13:28:11 +01:00
Uwe Rathmann 6ea2126638 Squashed commit of the following:
Merge: f614117d f0d49d9e
Author: Uwe Rathmann <Uwe.Rathmann@tigertal.de>
Date:   Sun Feb 25 09:11:12 2024 +0100

    Merge branch 'master' of https://github.com/ovenpasta/qskinny into models
    ( Author: Aldo Nicolas Bruno <ovenpasta@pizzahack.eu> )
2024-02-25 13:21:59 +01:00
8 changed files with 618 additions and 0 deletions

View File

@ -9,6 +9,7 @@ add_subdirectory(shadows)
add_subdirectory(shapes)
add_subdirectory(charts)
add_subdirectory(plots)
add_subdirectory(models)
if (BUILD_INPUTCONTEXT)
add_subdirectory(inputpanel)

View File

@ -0,0 +1,7 @@
############################################################################
# QSkinny - Copyright (C) The authors
# SPDX-License-Identifier: BSD-3-Clause
############################################################################
qsk_add_example(models MainView.h MainView.cpp main.cpp)
target_link_libraries(models)

View File

@ -0,0 +1,215 @@
/******************************************************************************
* QSkinny - Copyright (C) The authors
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
#include "MainView.h"
#include <QskLinearBox.h>
#include <QskPushButton.h>
#include <QskTextLabel.h>
#include <QskTextField.h>
#include <QskSpinBox.h>
#include <QskSeparator.h>
#include <QskFontRole.h>
#include <QskModelObjectBinder.h>
#include <QStandardItemModel>
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 QskTextField() );
}
};
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 textField = new QskTextField( this );
connect( textField, &QskTextField::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"

View File

@ -0,0 +1,21 @@
/******************************************************************************
* QSkinny - Copyright (C) The authors
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
#pragma once
#include <QskMainView.h>
class QskModelObjectBinder;
class MainView : public QskMainView
{
public:
MainView( QQuickItem* = nullptr );
private:
void toogleRow();
QskModelObjectBinder* m_binder;
};

View File

@ -0,0 +1,37 @@
/******************************************************************************
* QSkinny - Copyright (C) The authors
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
#include "MainView.h"
#include <SkinnyShortcut.h>
#include <QskObjectCounter.h>
#include <QskFocusIndicator.h>
#include <QskWindow.h>
#include <QGuiApplication>
int main( int argc, char* argv[] )
{
#ifdef ITEM_STATISTICS
QskObjectCounter counter( true );
#endif
QGuiApplication app( argc, argv );
SkinnyShortcut::enable( SkinnyShortcut::AllShortcuts );
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();
}

View File

@ -233,6 +233,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
@ -345,6 +346,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

View File

@ -0,0 +1,274 @@
/******************************************************************************
* QSkinny - Copyright (C) The authors
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
#include "QskModelObjectBinder.h"
#include <qmetaobject.h>
#include <qabstractitemmodel.h>
#include <qpointer.h>
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();
}
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
{
Binding( int column, const QMetaProperty& property )
: property( property )
, column( column )
{
}
QMetaProperty property;
int column;
};
}
class QskModelObjectBinder::PrivateData
{
public:
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 ( !( 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.constBegin(); it != bindings.constEnd(); ++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 )
{
setModel( model );
}
QskModelObjectBinder::~QskModelObjectBinder()
{
}
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() )
{
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 );
}
}
void QskModelObjectBinder::unbindObject( QObject* object )
{
auto& bindings = m_data->bindings;
auto it = bindings.find( object );
if ( it != bindings.end() )
{
qskEnableConnections( object, this, false );
bindings.erase( it );
}
}
void QskModelObjectBinder::clearBindings()
{
auto& bindings = m_data->bindings;
for ( auto it = bindings.constBegin(); it != bindings.constEnd(); ++it )
qskEnableConnections( it.key(), this, false );
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 )
return;
if ( m_data->model )
{
disconnect( m_data->model, &QAbstractItemModel::dataChanged, this, nullptr );
m_data->model = nullptr;
m_data->currentRowIndex = QModelIndex();
clearBindings();
}
m_data->model = model;
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 );
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 )
{
auto model = m_data->model.data();
auto& bindings = m_data->bindings;
Q_ASSERT( model != nullptr );
if ( model && row >= 0 && row < model->rowCount() )
{
m_data->currentRowIndex = model->index( row, 0 );
for ( auto it = bindings.constBegin(); it != bindings.constEnd(); ++it )
m_data->updateObjectProperty( it.key(), it.value() );
Q_EMIT currentRowChanged( row );
}
}
int QskModelObjectBinder::currentRow() const
{
return m_data->currentRowIndex.row();
}
void QskModelObjectBinder::submit()
{
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 );
}
}
}
void QskModelObjectBinder::revert()
{
m_data->updateProperties();
}
#include "moc_QskModelObjectBinder.cpp"

View File

@ -0,0 +1,61 @@
/******************************************************************************
* 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 <qobject.h>
#include <qbytearray.h>
#include <memory>
class QAbstractItemModel;
class QMetaProperty;
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 );
~QskModelObjectBinder() override;
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* );
QMetaProperty boundProperty( const QObject* ) const;
QObjectList boundObjects() const;
Q_SIGNALS:
void currentRowChanged( int );
public Q_SLOTS:
void submit();
void revert();
void clearBindings();
private:
class PrivateData;
std::unique_ptr< PrivateData > m_data;
};
#endif