diff --git a/examples/gallery/button/ButtonPage.cpp b/examples/gallery/button/ButtonPage.cpp index d2e0ab3d..e6cb217d 100644 --- a/examples/gallery/button/ButtonPage.cpp +++ b/examples/gallery/button/ButtonPage.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -78,6 +79,24 @@ namespace } } }; + + class CheckButtonBox : public QskLinearBox + { + public: + CheckButtonBox( QQuickItem* parent = nullptr ) + : QskLinearBox( Qt::Horizontal, parent ) + { + setSpacing( 20 ); + setExtraSpacingAt( Qt::LeftEdge | Qt::RightEdge | Qt::BottomEdge ); + + for ( auto state : { Qt::Unchecked, Qt::PartiallyChecked, Qt::Checked } ) + { + auto button = new QskCheckBox( this ); + button->setTristate( true ); + button->setCheckState( state ); + } + } + }; } ButtonPage::ButtonPage( QQuickItem* parent ) @@ -92,4 +111,6 @@ void ButtonPage::populate() new ButtonBox( this ); new QskSeparator( Qt::Horizontal, this ); new SwitchButtonBox( this ); + new QskSeparator( Qt::Horizontal, this ); + new CheckButtonBox( this ); } diff --git a/examples/gallery/gallery.pro b/examples/gallery/gallery.pro index 2c1eedb3..c21ad9a3 100644 --- a/examples/gallery/gallery.pro +++ b/examples/gallery/gallery.pro @@ -7,15 +7,9 @@ SOURCES += \ label/LabelPage.cpp \ HEADERS += \ - slider/CustomSlider.h \ - slider/CustomSliderSkinlet.h \ - slider/OtherSlider.h \ slider/SliderPage.h SOURCES += \ - slider/CustomSlider.cpp \ - slider/CustomSliderSkinlet.cpp \ - slider/OtherSlider.cpp \ slider/SliderPage.cpp HEADERS += \ diff --git a/examples/gallery/slider/SliderPage.cpp b/examples/gallery/slider/SliderPage.cpp index 8e164a5c..733692d6 100644 --- a/examples/gallery/slider/SliderPage.cpp +++ b/examples/gallery/slider/SliderPage.cpp @@ -4,14 +4,27 @@ *****************************************************************************/ #include "SliderPage.h" -#include "CustomSlider.h" -#include "OtherSlider.h" +#include -#include -#include +namespace +{ + class Slider : public QskSlider + { + public: + Slider( Qt::Orientation orientation, QQuickItem* parent = nullptr ) + : QskSlider( orientation, parent ) + { + setBoundaries( 0, 1000 ); -SliderPage::SliderPage( QQuickItem* parentItem ) - : Page( Qt::Vertical, parentItem ) + setPageSize( 10 ); + setStepSize( 10 ); + setSnap( true ); + } + }; +} + +SliderPage::SliderPage( QQuickItem* parent ) + : Page( Qt::Horizontal, parent ) { setMargins( 10 ); setSpacing( 20 ); @@ -35,54 +48,6 @@ SliderPage::SliderPage( QQuickItem* parentItem ) void SliderPage::populate() { - { - auto slider = new QskSlider( this ); - - slider->setMinimum( 0 ); - slider->setMaximum( 1000 ); - slider->setPageSize( 10 ); - slider->setStepSize( 10 ); - slider->setSnap( true ); - } - - { - auto slider = new OtherSlider( this ); - - slider->setMinimum( 0 ); - slider->setMaximum( 10 ); - slider->setStepSize( 1 ); - } - - - auto hBox = new QskLinearBox( Qt::Horizontal, this ); - - { - auto slider = new QskSlider( Qt::Vertical, hBox ); - - slider->setMinimum( 0 ); - slider->setMaximum( 1000 ); - slider->setPageSize( 10 ); - slider->setStepSize( 10 ); - slider->setSnap( true ); - } - - { - auto slider = new OtherSlider( hBox ); - slider->setOrientation( Qt::Vertical ); - - slider->setMinimum( 0 ); - slider->setMaximum( 10 ); - slider->setStepSize( 1 ); - } - - { - auto slider = new CustomSlider( this ); - - slider->setMargins( QskMargins( 0, 15 ) ); - slider->setSnap( true ); - slider->setMinimum( 0 ); - slider->setMaximum( 2000 ); - slider->setStepSize( 10 ); - slider->setPageSize( 10 ); - } + ( void ) new Slider( Qt::Horizontal, this ); + ( void ) new Slider( Qt::Vertical, this ); } diff --git a/examples/gallery/slider/CustomSlider.cpp b/examples/tabview/CustomSlider.cpp similarity index 100% rename from examples/gallery/slider/CustomSlider.cpp rename to examples/tabview/CustomSlider.cpp diff --git a/examples/gallery/slider/CustomSlider.h b/examples/tabview/CustomSlider.h similarity index 100% rename from examples/gallery/slider/CustomSlider.h rename to examples/tabview/CustomSlider.h diff --git a/examples/gallery/slider/CustomSliderSkinlet.cpp b/examples/tabview/CustomSliderSkinlet.cpp similarity index 100% rename from examples/gallery/slider/CustomSliderSkinlet.cpp rename to examples/tabview/CustomSliderSkinlet.cpp diff --git a/examples/gallery/slider/CustomSliderSkinlet.h b/examples/tabview/CustomSliderSkinlet.h similarity index 100% rename from examples/gallery/slider/CustomSliderSkinlet.h rename to examples/tabview/CustomSliderSkinlet.h diff --git a/examples/gallery/slider/OtherSlider.cpp b/examples/tabview/OtherSlider.cpp similarity index 100% rename from examples/gallery/slider/OtherSlider.cpp rename to examples/tabview/OtherSlider.cpp diff --git a/examples/gallery/slider/OtherSlider.h b/examples/tabview/OtherSlider.h similarity index 100% rename from examples/gallery/slider/OtherSlider.h rename to examples/tabview/OtherSlider.h diff --git a/examples/tabview/main.cpp b/examples/tabview/main.cpp index 5fbd43dc..9c8bbda2 100644 --- a/examples/tabview/main.cpp +++ b/examples/tabview/main.cpp @@ -3,6 +3,9 @@ * This file may be used under the terms of the 3-clause BSD License *****************************************************************************/ +#include "CustomSlider.h" +#include "OtherSlider.h" + #include #include @@ -33,6 +36,36 @@ class Label : public QskTextLabel } }; +class SliderBox : public QskLinearBox +{ + public: + SliderBox( QQuickItem* parent = nullptr ) + : QskLinearBox( Qt::Vertical, parent ) + { + setMargins( 30 ); + setSpacing( 50 ); + setExtraSpacingAt( Qt::BottomEdge ); + + { + auto slider = new OtherSlider( this ); + + slider->setMinimum( 0 ); + slider->setMaximum( 10 ); + slider->setStepSize( 1 ); + } + + { + auto slider = new CustomSlider( this ); + + slider->setSnap( true ); + slider->setMinimum( 0 ); + slider->setMaximum( 2000 ); + slider->setStepSize( 10 ); + slider->setPageSize( 10 ); + } + } +}; + class TabView : public QskTabView { public: @@ -43,11 +76,20 @@ class TabView : public QskTabView { QString text; if ( i == 4 ) - text = QString( "Another Tab" ); + { + const auto text = QStringLiteral( "Another Tab" ); + addTab( text, new Label( text ) ); + } + else if ( i == 7 ) + { + addTab( "Sliders", new SliderBox() ); + } else - text = QString( "Tab %1" ).arg( i + 1 ); + { + const auto text = QString( "Tab %1" ).arg( i + 1 ); + addTab( text, new Label( text ) ); + } - addTab( text, new Label( text ) ); } buttonAt( 2 )->setEnabled( false ); diff --git a/examples/tabview/tabview.pro b/examples/tabview/tabview.pro index 0a7f08c9..dbef219c 100644 --- a/examples/tabview/tabview.pro +++ b/examples/tabview/tabview.pro @@ -1,4 +1,12 @@ CONFIG += qskexample +HEADERS += \ + CustomSlider.h \ + CustomSliderSkinlet.h \ + OtherSlider.h \ + SOURCES += \ + CustomSlider.cpp \ + CustomSliderSkinlet.cpp \ + OtherSlider.cpp \ main.cpp diff --git a/qskinny.pro b/qskinny.pro index 7a41eef1..91e2624a 100644 --- a/qskinny.pro +++ b/qskinny.pro @@ -7,6 +7,7 @@ SUBDIRS = \ qmlexport \ tools \ support \ + tests \ examples \ playground @@ -22,6 +23,7 @@ qmlexport.depends = src inputcontext.depends = src skins.depends = src tools.depends = src +tests.depends = src support support.depends = src skins examples.depends = tools support skins qmlexport playground.depends = tools support skins qmlexport diff --git a/skins/material/QskMaterialSkin.cpp b/skins/material/QskMaterialSkin.cpp index 2e88955f..ec55b47b 100644 --- a/skins/material/QskMaterialSkin.cpp +++ b/skins/material/QskMaterialSkin.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -64,6 +65,7 @@ namespace void setupControl(); void setupBox(); + void setupCheckBox(); void setupDialogButtonBox(); void setupDialogButton(); void setupFocusIndicator(); @@ -95,6 +97,7 @@ void Editor::setup() setupControl(); setupBox(); + setupCheckBox(); setupDialogButtonBox(); setupDialogButton(); setupFocusIndicator(); @@ -130,6 +133,24 @@ void Editor::setupControl() QskRgb::toTransparentF( m_pal.onBackground, 0.6 ) ); } +void Editor::setupCheckBox() +{ + using Q = QskCheckBox; + + const qreal radius = qskDpiScaled( 18 ); + + setMargin( QskCheckBox::Tick, QMarginsF( 3, 5, 3, 3 ) ); + setStrutSize( Q::Box, radius, radius ); + + setBoxShape( Q::Box, 2 ); + + setColor( Q::Box, m_pal.secondaryNoSaturation); + setColor( Q::Box | Q::Checked, m_pal.secondary ); + setGradient( Q::Box | Q::Checked | Q::Disabled, QskRgb::Grey ); + + setColor( Q::Tick, m_pal.primary ); +} + void Editor::setupBox() { using Q = QskBox; diff --git a/skins/squiek/QskSquiekSkin.cpp b/skins/squiek/QskSquiekSkin.cpp index da74a7d3..09ea727e 100644 --- a/skins/squiek/QskSquiekSkin.cpp +++ b/skins/squiek/QskSquiekSkin.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -132,6 +133,7 @@ namespace void setupControl(); void setupBox(); + void setupCheckBox(); void setupDialogButton(); void setupDialogButtonBox(); void setupFocusIndicator(); @@ -251,6 +253,7 @@ void Editor::setup() setupControl(); setupBox(); + setupCheckBox(); setupDialogButtonBox(); setupDialogButton(); setupFocusIndicator(); @@ -292,6 +295,23 @@ void Editor::setupBox() setPanel( QskBox::Panel, Plain ); } +void Editor::setupCheckBox() +{ + using Q = QskCheckBox; + + const qreal size = qskDpiScaled( 26 ); + + setMargin( Q::Tick, QskMargins( qskDpiScaled( 5 ) ) ); + + setStrutSize( Q::Box, QSizeF( size, size ) ); + setBoxShape( Q::Box, qskDpiScaled( 3 ) ); + setBoxBorderMetrics( Q::Box, qskDpiScaled( 1 ) ); + setBoxBorderColors( Q::Box, m_pal.darker125 ); + setColor( Q::Box, m_pal.lighter135 ); + setColor( Q::Box | Q::Checked, m_pal.highlighted ); + setColor( Q::Tick, m_pal.lighter135 ); +} + void Editor::setupPopup() { using A = QskAspect; diff --git a/src/controls/QskCheckBox.cpp b/src/controls/QskCheckBox.cpp new file mode 100644 index 00000000..5a5cdbee --- /dev/null +++ b/src/controls/QskCheckBox.cpp @@ -0,0 +1,185 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#include "QskCheckBox.h" +#include "QskAspect.h" + +#include + +QSK_SUBCONTROL( QskCheckBox, Box ) +QSK_SUBCONTROL( QskCheckBox, Tick ) + +QSK_SYSTEM_STATE( QskCheckBox, PartiallyChecked, QskAspect::LastUserState << 2 ) + +class QskCheckBox::PrivateData +{ + public: + PrivateData() + : checkState( Qt::Unchecked ) + , checkStateChanging( false ) + , toggleChanging( false ) + , tristate( false ) + { + } + + QSet< QskAbstractButton* > group; + + int groupItemsChecked = 0; + + int checkState : 2; + bool checkStateChanging : 1; + bool toggleChanging : 1; + bool tristate : 1; +}; + +QskCheckBox::QskCheckBox( QQuickItem* parent ) + : Inherited( parent ) + , m_data( new PrivateData() ) +{ + setAcceptHoverEvents( true ); + initSizePolicy( QskSizePolicy::Fixed, QskSizePolicy::Fixed ); + + connect( this, &QskCheckBox::checkedChanged, this, + [ this ]( bool on ) { setCheckStateInternal( on ? Qt::Checked : Qt::Unchecked ); } ); +} + +QskCheckBox::~QskCheckBox() +{ + Q_EMIT removeFromAllGroupsRequested(); +} + +bool QskCheckBox::isCheckable() const +{ + return true; +} + +Qt::CheckState QskCheckBox::checkState() const +{ + return static_cast< Qt::CheckState >( m_data->checkState ); +} + +void QskCheckBox::setCheckStateInternal( Qt::CheckState checkState ) +{ + if( m_data->checkStateChanging ) + return; + + setSkinStateFlag( PartiallyChecked, checkState == Qt::PartiallyChecked ); + + m_data->checkState = checkState; + Q_EMIT checkStateChanged( checkState ); +} + +void QskCheckBox::setCheckState( Qt::CheckState checkState ) +{ + if( checkState == m_data->checkState ) + return; + + m_data->checkStateChanging = true; + + if( checkState == Qt::PartiallyChecked ) + { + setChecked( true ); + setTristate( true ); + } + else + { + setChecked( checkState == Qt::Checked ); + } + + m_data->checkStateChanging = false; + + setCheckStateInternal( checkState ); +} + +bool QskCheckBox::isTristate() const +{ + return m_data->tristate; +} + +void QskCheckBox::setTristate( bool tristate ) +{ + if( m_data->tristate != tristate ) + { + m_data->tristate = tristate; + Q_EMIT tristateChanged( tristate ); + } +} + +void QskCheckBox::updated() +{ + if( m_data->toggleChanging ) + return; + + const auto& groupItemsChecked = m_data->groupItemsChecked; + + if( groupItemsChecked == m_data->group.size() ) + { + setCheckState( Qt::Checked ); + } + else if ( groupItemsChecked == 0 ) + { + setCheckState( Qt::Unchecked ); + } + else + { + setCheckState( Qt::PartiallyChecked ); + } +} + +void QskCheckBox::addToGroup( QskCheckBox* groupItem ) +{ + if( m_data->group.contains( groupItem ) ) + return; + + m_data->group.insert( groupItem ); + + if( groupItem->checkState() == Qt::Checked ) + m_data->groupItemsChecked++; + + updated(); + + connect( this, &QskCheckBox::checkStateChanged, + groupItem, [ this, groupItem ]( Qt::CheckState checkState ) + { + if( checkState == Qt::Checked ) + { + m_data->toggleChanging = true; + groupItem->setChecked( true ); + m_data->groupItemsChecked = m_data->group.size(); + m_data->toggleChanging = false; + } + else if ( checkState == Qt::Unchecked ) + { + m_data->toggleChanging = true; + groupItem->setChecked( false ); + m_data->groupItemsChecked = 0; + m_data->toggleChanging = false; + } + } ); + + connect( groupItem, &QskAbstractButton::toggled, + this, [ this, groupItem ]( bool toggled ) + { + auto& groupItemsChecked = m_data->groupItemsChecked; + groupItemsChecked += toggled ? 1 : -1; + updated(); + } ); + + connect( groupItem, &QskCheckBox::removeFromAllGroupsRequested, + this, [ this, groupItem ]( ) { removeFromGroup( groupItem ); } ); +} + +void QskCheckBox::removeFromGroup( QskCheckBox* groupItem ) +{ + if( !m_data->group.remove( groupItem ) ) + return; + + if( groupItem->checkState() == Qt::Checked ) + m_data->groupItemsChecked--; + + updated(); +} + +#include "moc_QskCheckBox.cpp" diff --git a/src/controls/QskCheckBox.h b/src/controls/QskCheckBox.h new file mode 100644 index 00000000..b1443c1d --- /dev/null +++ b/src/controls/QskCheckBox.h @@ -0,0 +1,54 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#ifndef QSK_CHECK_BOX_H +#define QSK_CHECK_BOX_H + +#include "QskAbstractButton.h" + +class QSK_EXPORT QskCheckBox : public QskAbstractButton +{ + Q_OBJECT + + Q_PROPERTY( Qt::CheckState checkState READ checkState + WRITE setCheckState NOTIFY checkStateChanged FINAL ) + + Q_PROPERTY( bool tristate READ isTristate + WRITE setTristate NOTIFY tristateChanged FINAL ) + + using Inherited = QskAbstractButton; + + public: + QSK_SUBCONTROLS( Box, Tick ) + QSK_STATES( PartiallyChecked ) + + QskCheckBox( QQuickItem* parent = nullptr ); + ~QskCheckBox() override; + + Qt::CheckState checkState() const; + bool isTristate() const; + bool isCheckable() const override final; + + void addToGroup( QskCheckBox* ); + void removeFromGroup( QskCheckBox* ); + + public Q_SLOTS: + void setCheckState( Qt::CheckState ); + void setTristate( bool triState = true ); + + Q_SIGNALS: + void checkStateChanged( Qt::CheckState ); + void tristateChanged( bool ); + void removeFromAllGroupsRequested(); + + private: + void setCheckStateInternal( Qt::CheckState ); + void updated(); + + class PrivateData; + std::unique_ptr< PrivateData > m_data; +}; + +#endif diff --git a/src/controls/QskCheckBoxSkinlet.cpp b/src/controls/QskCheckBoxSkinlet.cpp new file mode 100644 index 00000000..fd9f842f --- /dev/null +++ b/src/controls/QskCheckBoxSkinlet.cpp @@ -0,0 +1,166 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#include "QskCheckBoxSkinlet.h" +#include "QskCheckBox.h" + +#include +#include + +namespace +{ + class Tick : public QSGGeometryNode + { + public: + Tick( const QRectF& rect, const QColor& color ) + : m_target( rect ) + , geometry( QSGGeometry::defaultAttributes_Point2D(), 3 ) + { + geometry.setDrawingMode( QSGGeometry::DrawLineStrip ); + geometry.setLineWidth( 2 ); + setGeometry( &geometry ); + + material.setColor( color ); + setMaterial( &material ); + + markDirty( QSGNode::DirtyGeometry ); + } + + void setColor( const QColor& color ) { + material.setColor( color ); + markDirty( QSGNode::DirtyMaterial ); + } + + void makeTick() + { + const auto x = m_target.x(); + const auto y = m_target.y(); + + auto vertexData = geometry.vertexDataAsPoint2D(); + + vertexData[0].set( x, y + m_target.height() / 2 ); + vertexData[1].set( x + m_target.width() / 3, y + m_target.height() ); + vertexData[2].set( x + m_target.width(), y ); + + markDirty( QSGNode::DirtyGeometry ); + } + + void makePartially() + { + const auto x = m_target.x(); + const auto y = m_target.y(); + + auto vertexData = geometry.vertexDataAsPoint2D(); + + vertexData[0].set( x, y + m_target.height() / 2 ); + vertexData[1].set( x, y + m_target.height() / 2 ); + vertexData[2].set( x + m_target.width(), y + m_target.height() / 2 ); + + markDirty( QSGNode::DirtyGeometry ); + } + + void makeEmpty() + { + const auto x = m_target.x(); + const auto y = m_target.y(); + + auto vertexData = geometry.vertexDataAsPoint2D(); + + vertexData[0].set( x, y ); + vertexData[1].set( x, y ); + vertexData[2].set( x, y ); + + markDirty( QSGNode::DirtyGeometry ); + } + + private: + + const QRectF m_target; + + QSGFlatColorMaterial material; + QSGGeometry geometry; + }; +} + +QskCheckBoxSkinlet::QskCheckBoxSkinlet( QskSkin* skin ) + : QskSkinlet( skin ) +{ + setNodeRoles( { BoxRole, TickRole } ); +} + +QskCheckBoxSkinlet::~QskCheckBoxSkinlet() +{ +} + +QRectF QskCheckBoxSkinlet::subControlRect( + const QskSkinnable*, const QRectF& contentsRect, QskAspect::Subcontrol ) const +{ + return contentsRect; +} + +QSGNode* QskCheckBoxSkinlet::updateSubNode( + const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const +{ + switch( nodeRole ) + { + case BoxRole: + { + return updateBoxNode( skinnable, node, QskCheckBox::Box ); + } + + case TickRole: + { + auto checkBox = static_cast< const QskCheckBox* >( skinnable ); + return updateTickNode( checkBox, node ); + } + } + + return Inherited::updateSubNode( skinnable, nodeRole, node ); +} + +QSGNode* QskCheckBoxSkinlet::updateTickNode( + const QskCheckBox* checkBox, QSGNode* node ) const +{ + using Q = QskCheckBox; + + auto rect = checkBox->subControlRect( Q::Tick ); + rect = rect.marginsRemoved( checkBox->marginHint( Q::Tick ) ); + + auto tick = static_cast< Tick* >( node ); + if ( tick == nullptr ) + tick = new Tick( rect, checkBox->color( Q::Tick ) ); + + switch ( checkBox->checkState() ) + { + case Qt::Unchecked: + + tick->setColor( checkBox->color( Q::Tick ) ); + tick->makeEmpty(); + + break; + + case Qt::PartiallyChecked: + + tick->setColor( checkBox->color( Q::Tick | Q::PartiallyChecked ) ); + tick->makePartially(); + + break; + + case Qt::Checked: + + tick->setColor( checkBox->color( Q::Tick | Q::Checked ) ); + tick->makeTick(); + + break; + } + + return tick; +} + +QSizeF QskCheckBoxSkinlet::sizeHint( const QskSkinnable* skinnable, + Qt::SizeHint, const QSizeF& ) const +{ + return skinnable->strutSizeHint( QskCheckBox::Box ); +} diff --git a/src/controls/QskCheckBoxSkinlet.h b/src/controls/QskCheckBoxSkinlet.h new file mode 100644 index 00000000..d703600c --- /dev/null +++ b/src/controls/QskCheckBoxSkinlet.h @@ -0,0 +1,43 @@ +/****************************************************************************** + * QSkinny - Copyright (C) 2016 Uwe Rathmann + * This file may be used under the terms of the QSkinny License, Version 1.0 + *****************************************************************************/ + +#ifndef QSK_CHECK_BOX_SKINLET_H +#define QSK_CHECK_BOX_SKINLET_H + +#include "QskSkinlet.h" + +class QskCheckBox; + +class QSK_EXPORT QskCheckBoxSkinlet : public QskSkinlet +{ + Q_GADGET + + using Inherited = QskSkinlet; + + public: + enum NodeRole + { + BoxRole, + TickRole, + }; + + Q_INVOKABLE QskCheckBoxSkinlet( QskSkin* = nullptr ); + ~QskCheckBoxSkinlet() override; + + QRectF subControlRect( const QskSkinnable*, + const QRectF&, QskAspect::Subcontrol ) const override; + + QSizeF sizeHint( const QskSkinnable*, + Qt::SizeHint, const QSizeF& ) const override; + + protected: + QSGNode* updateSubNode( const QskSkinnable*, + quint8 nodeRole, QSGNode* ) const override; + + private: + QSGNode* updateTickNode( const QskCheckBox*, QSGNode* ) const; +}; + +#endif diff --git a/src/controls/QskSkin.cpp b/src/controls/QskSkin.cpp index 1f8b67db..4a8e20e5 100644 --- a/src/controls/QskSkin.cpp +++ b/src/controls/QskSkin.cpp @@ -29,6 +29,9 @@ QSK_QT_PRIVATE_END #include "QskBox.h" #include "QskBoxSkinlet.h" +#include "QskCheckBox.h" +#include "QskCheckBoxSkinlet.h" + #include "QskFocusIndicator.h" #include "QskFocusIndicatorSkinlet.h" @@ -143,6 +146,7 @@ QskSkin::QskSkin( QObject* parent ) declareSkinlet< QskControl, QskSkinlet >(); declareSkinlet< QskBox, QskBoxSkinlet >(); + declareSkinlet< QskCheckBox, QskCheckBoxSkinlet >(); declareSkinlet< QskFocusIndicator, QskFocusIndicatorSkinlet >(); declareSkinlet< QskGraphicLabel, QskGraphicLabelSkinlet >(); declareSkinlet< QskListView, QskListViewSkinlet >(); diff --git a/src/src.pro b/src/src.pro index 0c00a0ec..2f51cb2b 100644 --- a/src/src.pro +++ b/src/src.pro @@ -141,6 +141,8 @@ HEADERS += \ controls/QskBoundedValueInput.h \ controls/QskBox.h \ controls/QskBoxSkinlet.h \ + controls/QskCheckBox.h \ + controls/QskCheckBoxSkinlet.h \ controls/QskControl.h \ controls/QskControlPrivate.h \ controls/QskDirtyItemFilter.h \ @@ -221,6 +223,8 @@ SOURCES += \ controls/QskBoundedValueInput.cpp \ controls/QskBox.cpp \ controls/QskBoxSkinlet.cpp \ + controls/QskCheckBox.cpp \ + controls/QskCheckBoxSkinlet.cpp \ controls/QskControl.cpp \ controls/QskControlPrivate.cpp \ controls/QskDirtyItemFilter.cpp \ diff --git a/tests/checkboxes/checkboxes.pro b/tests/checkboxes/checkboxes.pro new file mode 100644 index 00000000..79376a0b --- /dev/null +++ b/tests/checkboxes/checkboxes.pro @@ -0,0 +1,11 @@ +CONFIG += qskexample + CONFIG += console + CONFIG += testcase + + QT += testlib + + HEADERS += \ + main.h + + SOURCES += \ + main.cpp diff --git a/tests/checkboxes/main.cpp b/tests/checkboxes/main.cpp new file mode 100644 index 00000000..711a9d4a --- /dev/null +++ b/tests/checkboxes/main.cpp @@ -0,0 +1,223 @@ +#include "main.h" + +#include + +void CheckBoxTests::init() +{ + root = new QskControl(); +} + +void CheckBoxTests::cleanup() +{ + delete root; +} + +void CheckBoxTests::checkbox() +{ + auto t = new QskCheckBox( root ); + + QVERIFY( t->isCheckable() ); +} + +void CheckBoxTests::click() +{ + auto t = new QskCheckBox( root ); + + QVERIFY( t->isChecked() == false ); + t->click(); + QVERIFY( t->isChecked() ); +} + +void CheckBoxTests::toggle() +{ + auto t = new QskCheckBox( root ); + + QVERIFY( t->isChecked() == false ); + t->toggle(); + QVERIFY( t->isChecked() ); + + t->toggle(); + QVERIFY( t->isChecked() == false ); +} + +void CheckBoxTests::tristate() +{ + auto t = new QskCheckBox( root ); + + QVERIFY( t->isChecked() == false ); + QVERIFY( t->isTristate() == false ); + + t->setCheckState( Qt::CheckState::PartiallyChecked ); + + QVERIFY( t->isChecked() == true ); + QVERIFY( t->isTristate() == true ); +} + +void CheckBoxTests::higherGroupUpdatesLower() +{ + auto t = new QskCheckBox( root ); + auto t1 = new QskCheckBox( root ); + auto t2 = new QskCheckBox( root ); + auto t3 = new QskCheckBox( root ); + + t->addToGroup( t1 ); + t->addToGroup( t2 ); + t->addToGroup( t3 ); + + QVERIFY( t->isChecked() == false ); + QVERIFY( t1->isChecked() == false ); + QVERIFY( t2->isChecked() == false ); + QVERIFY( t3->isChecked() == false ); + + t->setChecked( true ); + QVERIFY( t->isChecked() ); + QVERIFY( t1->isChecked() ); + QVERIFY( t2->isChecked() ); + QVERIFY( t3->isChecked() ); + + t->setChecked( false ); + QVERIFY( t->isChecked() == false ); + QVERIFY( t1->isChecked() == false ); + QVERIFY( t2->isChecked() == false ); + QVERIFY( t3->isChecked() == false ); + +} + +void CheckBoxTests::lowerGroupUpdatesHigher() +{ + auto t = new QskCheckBox( root ); + + auto t1 = new QskCheckBox( root ); + auto t2 = new QskCheckBox( root ); + + t->addToGroup( t1 ); + t->addToGroup( t2 ); + + t1->setChecked( true ); + QVERIFY( t->isChecked() ); + QVERIFY( t->isTristate() ); + QVERIFY( t->checkState() == Qt::CheckState::PartiallyChecked ); + QVERIFY( t1->isChecked() == true ); + QVERIFY( t2->isChecked() == false ); + + + t2->setChecked( true ); + QVERIFY( t->isChecked() ); + QVERIFY( t->isTristate() ); + QVERIFY( t->checkState() == Qt::CheckState::Checked ); + QVERIFY( t1->isChecked() == true ); + QVERIFY( t2->isChecked() == true ); + + t1->setChecked( false ); + QVERIFY( t->isChecked() ); + QVERIFY( t->isTristate() ); + QVERIFY( t->checkState() == Qt::CheckState::PartiallyChecked ); + QVERIFY( t1->isChecked() == false ); + QVERIFY( t2->isChecked() == true ); + + t2->setChecked( false ); + QVERIFY( t->isChecked() == false ); + QVERIFY( t->isTristate() ); + QVERIFY( t->checkState() == Qt::CheckState::Unchecked ); + QVERIFY( t1->isChecked() == false ); + QVERIFY( t2->isChecked() == false ); +} + +void CheckBoxTests::addToGroup() +{ + auto t = new QskCheckBox( root ); + + auto t1 = new QskCheckBox( root ); + auto t2 = new QskCheckBox( root ); + + t->addToGroup( t1 ); + t->addToGroup( t2 ); + + t->setChecked( true ); + QVERIFY( t->isChecked() ); + QVERIFY( t1->isChecked() ); + QVERIFY( t2->isChecked() ); + + auto t3 = new QskCheckBox( root ); + t->addToGroup( t3 ); + + QVERIFY( t->checkState() == Qt::CheckState::PartiallyChecked ); + + t3->setChecked( true ); + QVERIFY( t->checkState() == Qt::CheckState::Checked ); + auto t4 = new QskCheckBox( root ); + t4->setChecked( true ); + t->addToGroup( t4 ); + QVERIFY( t->checkState() == Qt::CheckState::Checked ); +} + +void CheckBoxTests::addPartlyToGroup() { + auto t = new QskCheckBox( root ); + + auto t1 = new QskCheckBox( root ); + auto t1a = new QskCheckBox( root ); + auto t1b = new QskCheckBox( root ); + + t1->addToGroup( t1a ); + t1->addToGroup( t1b ); + + t1a->setChecked( true ); + + QVERIFY( t1->checkState() == Qt::CheckState::PartiallyChecked ); + t->addToGroup( t1 ); + QVERIFY( t1->checkState() == Qt::CheckState::PartiallyChecked ); +} + +void CheckBoxTests::removeFromGroup() +{ + auto t = new QskCheckBox( root ); + + auto t1 = new QskCheckBox( root ); + auto t2 = new QskCheckBox( root ); + + t->addToGroup( t1 ); + t->addToGroup( t2 ); + + t2->setChecked( true ); + QVERIFY( t->checkState() == Qt::CheckState::PartiallyChecked ); + + t->removeFromGroup( t2 ); + QVERIFY( t->isChecked() == false ); +} + +void CheckBoxTests::groupMemberGetsDeleted() +{ + auto t = new QskCheckBox( root ); + auto t1 = new QskCheckBox( root ); + auto t2 = new QskCheckBox( root ); + + t->addToGroup( t1 ); + t->addToGroup( t2 ); + + t2->setChecked( true ); + QVERIFY( t->checkState() == Qt::CheckState::PartiallyChecked ); + + delete t2; + QVERIFY( t->isChecked() == false ); +} + +void CheckBoxTests::addTwiceToSameGroup() +{ + auto t = new QskCheckBox( root ); + + auto t1 = new QskCheckBox( root ); + auto t2 = new QskCheckBox( root ); + + t->addToGroup( t1 ); + t->addToGroup( t1 ); + t->removeFromGroup( t1 ); + + t->addToGroup( t2 ); + + t2->setChecked( true ); + + QVERIFY( t->checkState() == Qt::CheckState::Checked ); +} + +#include "moc_main.cpp" + diff --git a/tests/checkboxes/main.h b/tests/checkboxes/main.h new file mode 100644 index 00000000..f657b6c8 --- /dev/null +++ b/tests/checkboxes/main.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +class QskControl; +class CheckBoxTests : public QObject +{ + Q_OBJECT + + QskControl * root; + private Q_SLOTS: + void init(); + void cleanup(); + + void checkbox(); + void click(); + void toggle(); + void tristate(); + void higherGroupUpdatesLower(); + void lowerGroupUpdatesHigher(); + void addToGroup(); + void addPartlyToGroup(); + void removeFromGroup(); + void groupMemberGetsDeleted(); + void addTwiceToSameGroup(); +}; + +QTEST_MAIN(CheckBoxTests) + diff --git a/tests/tests.pro b/tests/tests.pro new file mode 100644 index 00000000..60e10ea4 --- /dev/null +++ b/tests/tests.pro @@ -0,0 +1,5 @@ +TEMPLATE = subdirs + +SUBDIRS += \ + checkboxes +