From 1af5648965257ccafb51e905010dc256b4676271 Mon Sep 17 00:00:00 2001 From: Peter Hartmann Date: Fri, 6 Dec 2024 16:50:36 +0100 Subject: [PATCH] Say hello to QskTextEdit Resolves #109 --- designsystems/fluent2/CMakeLists.txt | 2 + designsystems/fluent2/QskFluent2Skin.cpp | 55 +- .../fluent2/QskFluent2TextAreaSkinlet.cpp | 32 + .../fluent2/QskFluent2TextAreaSkinlet.h | 29 + designsystems/fusion/QskFusionSkin.cpp | 36 +- designsystems/material3/CMakeLists.txt | 2 + designsystems/material3/QskMaterial3Skin.cpp | 34 +- .../material3/QskMaterial3TextAreaSkinlet.cpp | 36 + .../material3/QskMaterial3TextAreaSkinlet.h | 33 + examples/gallery/inputs/InputPage.cpp | 22 +- src/CMakeLists.txt | 8 + src/controls/QskSkin.cpp | 7 + src/controls/QskTextArea.cpp | 47 ++ src/controls/QskTextArea.h | 39 + src/controls/QskTextAreaSkinlet.cpp | 103 +++ src/controls/QskTextAreaSkinlet.h | 44 + src/controls/QskTextEdit.cpp | 758 ++++++++++++++++++ src/controls/QskTextEdit.h | 190 +++++ src/controls/QskTextEditSkinlet.cpp | 92 +++ src/controls/QskTextEditSkinlet.h | 39 + src/controls/QskTextInput.cpp | 2 +- 21 files changed, 1589 insertions(+), 21 deletions(-) create mode 100644 designsystems/fluent2/QskFluent2TextAreaSkinlet.cpp create mode 100644 designsystems/fluent2/QskFluent2TextAreaSkinlet.h create mode 100644 designsystems/material3/QskMaterial3TextAreaSkinlet.cpp create mode 100644 designsystems/material3/QskMaterial3TextAreaSkinlet.h create mode 100644 src/controls/QskTextArea.cpp create mode 100644 src/controls/QskTextArea.h create mode 100644 src/controls/QskTextAreaSkinlet.cpp create mode 100644 src/controls/QskTextAreaSkinlet.h create mode 100644 src/controls/QskTextEdit.cpp create mode 100644 src/controls/QskTextEdit.h create mode 100644 src/controls/QskTextEditSkinlet.cpp create mode 100644 src/controls/QskTextEditSkinlet.h diff --git a/designsystems/fluent2/CMakeLists.txt b/designsystems/fluent2/CMakeLists.txt index 1aaec81a..3b7ab78d 100644 --- a/designsystems/fluent2/CMakeLists.txt +++ b/designsystems/fluent2/CMakeLists.txt @@ -8,6 +8,7 @@ list(APPEND HEADERS ) list(APPEND PRIVATE_HEADERS + QskFluent2TextAreaSkinlet.h QskFluent2TextFieldSkinlet.h ) @@ -15,6 +16,7 @@ list(APPEND SOURCES QskFluent2Theme.cpp QskFluent2Skin.cpp QskFluent2SkinFactory.cpp + QskFluent2TextAreaSkinlet.cpp QskFluent2TextFieldSkinlet.cpp ) diff --git a/designsystems/fluent2/QskFluent2Skin.cpp b/designsystems/fluent2/QskFluent2Skin.cpp index 68cef4c9..0feaef8a 100644 --- a/designsystems/fluent2/QskFluent2Skin.cpp +++ b/designsystems/fluent2/QskFluent2Skin.cpp @@ -47,6 +47,7 @@ */ #include "QskFluent2Skin.h" #include "QskFluent2Theme.h" +#include "QskFluent2TextAreaSkinlet.h" #include "QskFluent2TextFieldSkinlet.h" #include @@ -80,6 +81,7 @@ #include #include #include +#include #include #include @@ -297,6 +299,15 @@ namespace void setupTabViewMetrics(); void setupTabViewColors( QskAspect::Section, const QskFluent2Theme& ); + template< typename Q > + void setupTextControlMetrics(); + + template< typename Q, typename SK > + void setupTextControlColors( QskAspect::Section section, const QskFluent2Theme& theme ); + + void setupTextAreaMetrics(); + void setupTextAreaColors( QskAspect::Section, const QskFluent2Theme& ); + void setupTextFieldMetrics(); void setupTextFieldColors( QskAspect::Section, const QskFluent2Theme& ); @@ -356,6 +367,7 @@ void Editor::setupMetrics() setupTabButtonMetrics(); setupTabBarMetrics(); setupTabViewMetrics(); + setupTextAreaMetrics(); setupTextFieldMetrics(); setupTextLabelMetrics(); setupVirtualKeyboardMetrics(); @@ -395,6 +407,7 @@ void Editor::setupColors( QskAspect::Section section, const QskFluent2Theme& the setupTabButtonColors( section, theme ); setupTabBarColors( section, theme ); setupTabViewColors( section, theme ); + setupTextAreaColors( section, theme ); setupTextFieldColors( section, theme ); setupTextLabelColors( section, theme ); setupVirtualKeyboardColors( section, theme ); @@ -1781,10 +1794,8 @@ void Editor::setupTextLabelColors( setColor( Q::Text | section, pal.fillColor.text.primary ); } -void Editor::setupTextFieldMetrics() +template< typename Q > void Editor::setupTextControlMetrics() { - using Q = QskTextField; - setStrutSize( Q::TextPanel, { -1, 30_px } ); setPadding( Q::TextPanel, { 11_px, 0, 11_px, 0 } ); @@ -1794,18 +1805,15 @@ void Editor::setupTextFieldMetrics() setBoxShape( Q::TextPanel, 3_px ); - setAlignment( Q::Text, Qt::AlignLeft | Qt::AlignVCenter ); setFontRole( Q::Text, Fluent2::Body ); setAlignment( Q::PlaceholderText, alignment( Q::Text ) ); setFontRole( Q::PlaceholderText, fontRole( Q::Text ) ); } -void Editor::setupTextFieldColors( - QskAspect::Section section, const QskFluent2Theme& theme ) +template< typename Q, typename SK > void Editor::setupTextControlColors( + QskAspect::Section section, const QskFluent2Theme& theme) { - using Q = QskTextField; - using SK = QskTextFieldSkinlet; using A = QskAspect; const auto& pal = theme.palette; @@ -1858,6 +1866,36 @@ void Editor::setupTextFieldColors( } } +void Editor::setupTextAreaMetrics() +{ + using Q = QskTextArea; + + setAlignment( Q::Text, Qt::AlignLeft | Qt::AlignTop ); + + setupTextControlMetrics< Q >(); +} + +void Editor::setupTextAreaColors( + QskAspect::Section section, const QskFluent2Theme& theme ) +{ + setupTextControlColors< QskTextArea, QskTextAreaSkinlet >( section, theme ); +} + +void Editor::setupTextFieldMetrics() +{ + using Q = QskTextField; + + setAlignment( Q::Text, Qt::AlignLeft | Qt::AlignVCenter ); + + setupTextControlMetrics< Q >(); +} + +void Editor::setupTextFieldColors( + QskAspect::Section section, const QskFluent2Theme& theme ) +{ + setupTextControlColors< QskTextField, QskTextFieldSkinlet >( section, theme ); +} + void Editor::setupSwitchButtonMetrics() { using Q = QskSwitchButton; @@ -2042,6 +2080,7 @@ void Editor::setupVirtualKeyboardColors( QskFluent2Skin::QskFluent2Skin( QObject* parent ) : Inherited( parent ) { + declareSkinlet< QskTextArea, QskFluent2TextAreaSkinlet >(); declareSkinlet< QskTextField, QskFluent2TextFieldSkinlet >(); setupFonts(); diff --git a/designsystems/fluent2/QskFluent2TextAreaSkinlet.cpp b/designsystems/fluent2/QskFluent2TextAreaSkinlet.cpp new file mode 100644 index 00000000..43633325 --- /dev/null +++ b/designsystems/fluent2/QskFluent2TextAreaSkinlet.cpp @@ -0,0 +1,32 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#include "QskFluent2TextAreaSkinlet.h" +#include "QskTextArea.h" + +using Q = QskTextArea; + +QskFluent2TextAreaSkinlet::QskFluent2TextAreaSkinlet( QskSkin* skin ) + : Inherited( skin ) +{ +} + +QskFluent2TextAreaSkinlet::~QskFluent2TextAreaSkinlet() +{ +} + +QRectF QskFluent2TextAreaSkinlet::subControlRect( const QskSkinnable* skinnable, + const QRectF& contentsRect, QskAspect::Subcontrol subControl ) const +{ + return Inherited::subControlRect( skinnable, contentsRect, subControl ); +} + +QSizeF QskFluent2TextAreaSkinlet::sizeHint( const QskSkinnable* skinnable, + Qt::SizeHint which, const QSizeF& constraint ) const +{ + return Inherited::sizeHint( skinnable, which, constraint ); +} + +#include "moc_QskFluent2TextAreaSkinlet.cpp" diff --git a/designsystems/fluent2/QskFluent2TextAreaSkinlet.h b/designsystems/fluent2/QskFluent2TextAreaSkinlet.h new file mode 100644 index 00000000..4b77e04c --- /dev/null +++ b/designsystems/fluent2/QskFluent2TextAreaSkinlet.h @@ -0,0 +1,29 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#ifndef QSK_FLUENT2_TEXTAREA_SKINLET_H +#define QSK_FLUENT2_TEXTAREA_SKINLET_H + +#include "QskFluent2Global.h" +#include "QskTextAreaSkinlet.h" + +class QSK_FLUENT2_EXPORT QskFluent2TextAreaSkinlet : public QskTextAreaSkinlet +{ + Q_GADGET + + using Inherited = QskTextAreaSkinlet; + + public: + Q_INVOKABLE QskFluent2TextAreaSkinlet( QskSkin* = nullptr ); + ~QskFluent2TextAreaSkinlet() override; + + QRectF subControlRect( const QskSkinnable*, + const QRectF& rect, QskAspect::Subcontrol ) const override; + + QSizeF sizeHint( const QskSkinnable*, + Qt::SizeHint, const QSizeF& ) const override; +}; + +#endif diff --git a/designsystems/fusion/QskFusionSkin.cpp b/designsystems/fusion/QskFusionSkin.cpp index d3d47038..cf173d4f 100644 --- a/designsystems/fusion/QskFusionSkin.cpp +++ b/designsystems/fusion/QskFusionSkin.cpp @@ -42,6 +42,8 @@ #include #include #include +#include +#include #include #include #include @@ -142,6 +144,10 @@ namespace Q_INVOKABLE void setupTabButton(); Q_INVOKABLE void setupTabBar(); Q_INVOKABLE void setupTabView(); + + template< typename Q, typename SK > + void setupTextControl(); + Q_INVOKABLE void setupTextArea(); Q_INVOKABLE void setupTextField(); Q_INVOKABLE void setupTextLabel(); @@ -383,16 +389,12 @@ void Editor::setupTextLabel() setBoxBorderColors( Q::Panel, QskRgb::lighter( m_pal.outline, 108 ) ); } -void Editor::setupTextField() +template< typename Q, typename SK > +void Editor::setupTextControl() { - using Q = QskTextField; - using SK = QskTextFieldSkinlet; using A = QskAspect; using P = QPalette; - setAlignment( Q::Text, Qt::AlignLeft | Qt::AlignVCenter ); - setAlignment( Q::PlaceholderText, Qt::AlignLeft | Qt::AlignVCenter ); - for ( auto state : { A::NoState, Q::Disabled } ) { const auto colorGroup = ( state == A::NoState ) ? P::Active : P::Disabled; @@ -418,6 +420,28 @@ void Editor::setupTextField() setPadding( Q::TextPanel, 4_px ); } +void Editor::setupTextArea() +{ + using Q = QskTextArea; + using SK = QskTextAreaSkinlet; + + setAlignment( Q::Text, Qt::AlignLeft | Qt::AlignTop ); + setAlignment( Q::PlaceholderText, Qt::AlignLeft | Qt::AlignTop ); + + setupTextControl< Q, SK >(); +} + +void Editor::setupTextField() +{ + using Q = QskTextField; + using SK = QskTextFieldSkinlet; + + setAlignment( Q::Text, Qt::AlignLeft | Qt::AlignVCenter ); + setAlignment( Q::PlaceholderText, Qt::AlignLeft | Qt::AlignVCenter ); + + setupTextControl< Q, SK >(); +} + void Editor::setupProgressBar() { // indeterminate style is different: TODO ... diff --git a/designsystems/material3/CMakeLists.txt b/designsystems/material3/CMakeLists.txt index 4017d9a8..d605ee55 100644 --- a/designsystems/material3/CMakeLists.txt +++ b/designsystems/material3/CMakeLists.txt @@ -10,6 +10,7 @@ list(APPEND HEADERS list(APPEND PRIVATE_HEADERS QskMaterial3ProgressBarSkinlet.h QskMaterial3SliderSkinlet.h + QskMaterial3TextAreaSkinlet.h QskMaterial3TextFieldSkinlet.h ) @@ -18,6 +19,7 @@ list(APPEND SOURCES QskMaterial3SkinFactory.cpp QskMaterial3ProgressBarSkinlet.cpp QskMaterial3SliderSkinlet.cpp + QskMaterial3TextAreaSkinlet.cpp QskMaterial3TextFieldSkinlet.cpp ) diff --git a/designsystems/material3/QskMaterial3Skin.cpp b/designsystems/material3/QskMaterial3Skin.cpp index 97f428b1..b790b259 100644 --- a/designsystems/material3/QskMaterial3Skin.cpp +++ b/designsystems/material3/QskMaterial3Skin.cpp @@ -11,6 +11,7 @@ #include "QskMaterial3Skin.h" #include "QskMaterial3ProgressBarSkinlet.h" #include "QskMaterial3SliderSkinlet.h" +#include "QskMaterial3TextAreaSkinlet.h" #include "QskMaterial3TextFieldSkinlet.h" #include @@ -46,6 +47,7 @@ #include #include #include +#include #include #include #include @@ -185,6 +187,10 @@ namespace Q_INVOKABLE void setupTabButton(); Q_INVOKABLE void setupTabBar(); Q_INVOKABLE void setupTabView(); + + template< typename Q, typename SK > + void setupTextControl(); + Q_INVOKABLE void setupTextArea(); Q_INVOKABLE void setupTextField(); Q_INVOKABLE void setupTextLabel(); @@ -431,11 +437,9 @@ void Editor::setupTextLabel() setPadding( Q::Panel, 5_px ); } -void Editor::setupTextField() +template< typename Q, typename SK > +void Editor::setupTextControl() { - using Q = QskTextField; - using SK = QskTextInputSkinlet; - setStrutSize( Q::Panel, -1.0, 56_px ); setPadding( Q::Panel, { 12_px, 8_px, 12_px, 8_px } ); setGradient( Q::Panel, m_pal.surfaceVariant ); @@ -457,7 +461,6 @@ void Editor::setupTextField() setColor( Q::Text, m_pal.onSurface ); setFontRole( Q::Text, BodyLarge ); - setAlignment( Q::Text, Qt::AlignLeft | Qt::AlignVCenter ); setAlignment( Q::PlaceholderText, Qt::AlignLeft | Qt::AlignVCenter ); @@ -474,6 +477,26 @@ void Editor::setupTextField() setAlignment( Q::PlaceholderText, alignment( Q::Text ) ); } +void Editor::setupTextArea() +{ + using Q = QskTextArea; + using SK = QskTextEditSkinlet; + + setAlignment( Q::Text, Qt::AlignLeft | Qt::AlignTop ); + + setupTextControl< Q, SK >(); +} + +void Editor::setupTextField() +{ + using Q = QskTextField; + using SK = QskTextInputSkinlet; + + setAlignment( Q::Text, Qt::AlignLeft | Qt::AlignVCenter ); + + setupTextControl< Q, SK >(); +} + void Editor::setupProgressBar() { using A = QskAspect; @@ -1618,6 +1641,7 @@ QskMaterial3Skin::QskMaterial3Skin( QObject* parent ) { declareSkinlet< QskProgressBar, QskMaterial3ProgressBarSkinlet >(); declareSkinlet< QskSlider, QskMaterial3SliderSkinlet >(); + declareSkinlet< QskTextArea, QskMaterial3TextAreaSkinlet >(); declareSkinlet< QskTextField, QskMaterial3TextFieldSkinlet >(); } diff --git a/designsystems/material3/QskMaterial3TextAreaSkinlet.cpp b/designsystems/material3/QskMaterial3TextAreaSkinlet.cpp new file mode 100644 index 00000000..81228477 --- /dev/null +++ b/designsystems/material3/QskMaterial3TextAreaSkinlet.cpp @@ -0,0 +1,36 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#include "QskMaterial3TextAreaSkinlet.h" +#include "QskTextArea.h" + +QskMaterial3TextAreaSkinlet::QskMaterial3TextAreaSkinlet( QskSkin* skin ) + : Inherited( skin ) +{ +} + +QskMaterial3TextAreaSkinlet::~QskMaterial3TextAreaSkinlet() +{ +} + +QRectF QskMaterial3TextAreaSkinlet::subControlRect( const QskSkinnable* skinnable, + const QRectF& contentsRect, QskAspect::Subcontrol subControl ) const +{ + return Inherited::subControlRect( skinnable, contentsRect, subControl ); +} + +QSGNode* QskMaterial3TextAreaSkinlet::updateSubNode( + const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const +{ + return Inherited::updateSubNode( skinnable, nodeRole, node ); +} + +QSizeF QskMaterial3TextAreaSkinlet::sizeHint( const QskSkinnable* skinnable, + Qt::SizeHint which, const QSizeF& constraint ) const +{ + return Inherited::sizeHint( skinnable, which, constraint ); +} + +#include "moc_QskMaterial3TextAreaSkinlet.cpp" diff --git a/designsystems/material3/QskMaterial3TextAreaSkinlet.h b/designsystems/material3/QskMaterial3TextAreaSkinlet.h new file mode 100644 index 00000000..dc667288 --- /dev/null +++ b/designsystems/material3/QskMaterial3TextAreaSkinlet.h @@ -0,0 +1,33 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#ifndef QSK_MATERIAL3_TEXTAREA_SKINLET_H +#define QSK_MATERIAL3_TEXTAREA_SKINLET_H + +#include "QskMaterial3Global.h" +#include "QskTextAreaSkinlet.h" + +class QSK_MATERIAL3_EXPORT QskMaterial3TextAreaSkinlet : public QskTextAreaSkinlet +{ + Q_GADGET + + using Inherited = QskTextAreaSkinlet; + + public: + Q_INVOKABLE QskMaterial3TextAreaSkinlet( QskSkin* = nullptr ); + ~QskMaterial3TextAreaSkinlet() override; + + QRectF subControlRect( const QskSkinnable*, + const QRectF& rect, QskAspect::Subcontrol ) const override; + + QSizeF sizeHint( const QskSkinnable*, + Qt::SizeHint, const QSizeF& ) const override; + + protected: + QSGNode* updateSubNode( const QskSkinnable*, + quint8 nodeRole, QSGNode* ) const override; +}; + +#endif diff --git a/examples/gallery/inputs/InputPage.cpp b/examples/gallery/inputs/InputPage.cpp index 93f20925..cdd9deaa 100644 --- a/examples/gallery/inputs/InputPage.cpp +++ b/examples/gallery/inputs/InputPage.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -92,6 +93,22 @@ namespace } } }; + + class TextAreaBox : public QskLinearBox + { + public: + TextAreaBox( QQuickItem* parent = nullptr ) + : QskLinearBox( Qt::Horizontal, parent ) + { + setSpacing( 20 ); + + { + auto textArea = new QskTextArea( "here enter longer text", this ); + textArea->setWrapMode( QskTextOptions::Wrap ); + textArea->setPlaceholderText( "placeholder text" ); + } + } + }; } InputPage::InputPage( QQuickItem* parent ) @@ -123,6 +140,8 @@ InputPage::InputPage( QQuickItem* parent ) auto textInputBox = new TextInputBox(); textInputBox->setSizePolicy( Qt::Vertical, QskSizePolicy::Fixed ); + auto textAreaBox = new TextAreaBox(); + auto vBox = new QskLinearBox( Qt::Vertical ); vBox->setSpacing( 30 ); vBox->setExtraSpacingAt( Qt::RightEdge | Qt::BottomEdge ); @@ -130,8 +149,9 @@ InputPage::InputPage( QQuickItem* parent ) vBox->addItem( sliders[0].continous ); vBox->addItem( sliders[0].discrete ); vBox->addItem( sliders[0].centered ); - vBox->addItem( textInputBox ); vBox->addItem( spinBox ); + vBox->addItem( textInputBox ); + vBox->addItem( textAreaBox ); auto mainBox = new QskLinearBox( Qt::Horizontal, this ); mainBox->setSpacing( 30 ); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9e0196a3..6b694ae5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -283,6 +283,10 @@ list(APPEND HEADERS controls/QskTabButtonSkinlet.h controls/QskTabView.h controls/QskTabViewSkinlet.h + controls/QskTextArea.h + controls/QskTextAreaSkinlet.h + controls/QskTextEdit.h + controls/QskTextEditSkinlet.h controls/QskTextField.h controls/QskTextFieldSkinlet.h controls/QskTextInput.h @@ -391,6 +395,10 @@ list(APPEND SOURCES controls/QskTabButtonSkinlet.cpp controls/QskTabView.cpp controls/QskTabViewSkinlet.cpp + controls/QskTextArea.cpp + controls/QskTextAreaSkinlet.cpp + controls/QskTextEdit.cpp + controls/QskTextEditSkinlet.cpp controls/QskTextField.cpp controls/QskTextFieldSkinlet.cpp controls/QskTextInput.cpp diff --git a/src/controls/QskSkin.cpp b/src/controls/QskSkin.cpp index 2019fa11..685ebeab 100644 --- a/src/controls/QskSkin.cpp +++ b/src/controls/QskSkin.cpp @@ -100,6 +100,12 @@ #include "QskTextLabel.h" #include "QskTextLabelSkinlet.h" +#include "QskTextArea.h" +#include "QskTextAreaSkinlet.h" + +#include "QskTextEdit.h" +#include "QskTextEditSkinlet.h" + #include "QskTextField.h" #include "QskTextFieldSkinlet.h" @@ -211,6 +217,7 @@ QskSkin::QskSkin( QObject* parent ) declareSkinlet< QskTabButton, QskTabButtonSkinlet >(); declareSkinlet< QskTabView, QskTabViewSkinlet >(); declareSkinlet< QskTextLabel, QskTextLabelSkinlet >(); + declareSkinlet< QskTextArea, QskTextAreaSkinlet >(); declareSkinlet< QskTextField, QskTextFieldSkinlet >(); declareSkinlet< QskProgressBar, QskProgressBarSkinlet >(); declareSkinlet< QskProgressRing, QskProgressRingSkinlet >(); diff --git a/src/controls/QskTextArea.cpp b/src/controls/QskTextArea.cpp new file mode 100644 index 00000000..679347e6 --- /dev/null +++ b/src/controls/QskTextArea.cpp @@ -0,0 +1,47 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#include "QskTextArea.h" + +QSK_SUBCONTROL( QskTextArea, Panel ) +QSK_SUBCONTROL( QskTextArea, PlaceholderText ) + +class QskTextArea::PrivateData +{ + public: + QString placeholderText; +}; + +QskTextArea::QskTextArea( QQuickItem* parent ) + : Inherited( parent ) + , m_data( new PrivateData() ) +{ +} + +QskTextArea::QskTextArea( const QString& text, QQuickItem* parent ) + : QskTextArea( parent ) +{ + setText( text ); +} + +QskTextArea::~QskTextArea() +{ +} + +void QskTextArea::setPlaceholderText( const QString& text ) +{ + if ( m_data->placeholderText != text ) + { + m_data->placeholderText = text; + Q_EMIT placeholderTextChanged( text ); + } +} + +QString QskTextArea::placeholderText() const +{ + return m_data->placeholderText; +} + +#include "moc_QskTextArea.cpp" diff --git a/src/controls/QskTextArea.h b/src/controls/QskTextArea.h new file mode 100644 index 00000000..28841b6f --- /dev/null +++ b/src/controls/QskTextArea.h @@ -0,0 +1,39 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#ifndef QSK_TEXT_AREA_H +#define QSK_TEXT_AREA_H + +#include "QskTextEdit.h" + +class QSK_EXPORT QskTextArea : public QskTextEdit +{ + Q_OBJECT + + Q_PROPERTY( QString placeholderText READ placeholderText + WRITE setPlaceholderText NOTIFY placeholderTextChanged ) + + using Inherited = QskTextEdit; + + public: + QSK_SUBCONTROLS( Panel, PlaceholderText ) + + QskTextArea( QQuickItem* parent = nullptr ); + QskTextArea( const QString& text, QQuickItem* parent = nullptr ); + + ~QskTextArea() override; + + void setPlaceholderText( const QString& ); + QString placeholderText() const; + + Q_SIGNALS: + void placeholderTextChanged( const QString& ); + + private: + class PrivateData; + std::unique_ptr< PrivateData > m_data; +}; + +#endif diff --git a/src/controls/QskTextAreaSkinlet.cpp b/src/controls/QskTextAreaSkinlet.cpp new file mode 100644 index 00000000..b57fc044 --- /dev/null +++ b/src/controls/QskTextAreaSkinlet.cpp @@ -0,0 +1,103 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#include "QskTextAreaSkinlet.h" +#include "QskTextArea.h" + +using Q = QskTextArea; + +QskTextAreaSkinlet::QskTextAreaSkinlet( QskSkin* skin ) + : Inherited( skin ) +{ + setNodeRoles( { PanelRole, TextPanelRole, PlaceholderTextRole } ); +} + +QskTextAreaSkinlet::~QskTextAreaSkinlet() +{ +} + +QRectF QskTextAreaSkinlet::subControlRect( const QskSkinnable* skinnable, + const QRectF& contentsRect, QskAspect::Subcontrol subControl ) const +{ + if ( subControl == Q::Panel ) + return contentsRect; + + if ( subControl == Q::TextPanel ) + return skinnable->subControlContentsRect( contentsRect, Q::Panel ); + + if ( subControl == Q::PlaceholderText ) + { + const auto textArea = static_cast< const QskTextArea* >( skinnable ); + if( textArea->text().isEmpty() ) + return subControlRect( skinnable, contentsRect, Q::Text ); + + return QRectF(); + } + + return Inherited::subControlRect( skinnable, contentsRect, subControl ); +} + +QSGNode* QskTextAreaSkinlet::updateSubNode( + const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const +{ + const auto textArea = static_cast< const QskTextArea* >( skinnable ); + + switch ( nodeRole ) + { + case PanelRole: + { + return updateBoxNode( skinnable, node, Q::Panel ); + } + + case PlaceholderTextRole: + { + const auto text = effectivePlaceholderText( textArea ); + if ( text.isEmpty() ) + return nullptr; + + const auto subControl = Q::PlaceholderText; + + QskSkinHintStatus status; + + auto options = skinnable->textOptionsHint( subControl, &status ); + if ( !status.isValid() ) + options.setElideMode( Qt::ElideRight ); + + return updateTextNode( skinnable, node, + textArea->subControlRect( subControl ), + textArea->alignmentHint( subControl, Qt::AlignLeft ), + options, text, subControl ); + } + } + + return Inherited::updateSubNode( skinnable, nodeRole, node ); +} + +QSizeF QskTextAreaSkinlet::sizeHint( const QskSkinnable* skinnable, + Qt::SizeHint which, const QSizeF& constraint ) const +{ + if ( which != Qt::PreferredSize ) + return QSizeF(); + + auto hint = Inherited::sizeHint( skinnable, which, constraint ); + hint = skinnable->outerBoxSize( Q::Panel, hint ); + hint = hint.expandedTo( skinnable->strutSizeHint( Q::Panel ) ); + + return hint; +} + +QString QskTextAreaSkinlet::effectivePlaceholderText( + const QskTextArea* textArea ) const +{ + if ( textArea->text().isEmpty() && + !( textArea->isReadOnly() || textArea->isEditing() ) ) + { + return textArea->placeholderText(); + } + + return QString(); +} + +#include "moc_QskTextAreaSkinlet.cpp" diff --git a/src/controls/QskTextAreaSkinlet.h b/src/controls/QskTextAreaSkinlet.h new file mode 100644 index 00000000..aeaa2a6d --- /dev/null +++ b/src/controls/QskTextAreaSkinlet.h @@ -0,0 +1,44 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#ifndef QSK_TEXT_AREA_SKINLET_H +#define QSK_TEXT_AREA_SKINLET_H + +#include "QskTextEditSkinlet.h" + +class QskTextArea; + +class QSK_EXPORT QskTextAreaSkinlet : public QskTextEditSkinlet +{ + Q_GADGET + + using Inherited = QskTextEditSkinlet; + + public: + enum NodeRole : quint8 + { + PanelRole = Inherited::RoleCount, + + PlaceholderTextRole, + RoleCount + }; + + Q_INVOKABLE QskTextAreaSkinlet( QskSkin* = nullptr ); + ~QskTextAreaSkinlet() override; + + QRectF subControlRect( const QskSkinnable*, + const QRectF& rect, QskAspect::Subcontrol ) const override; + + QSizeF sizeHint( const QskSkinnable*, + Qt::SizeHint, const QSizeF& ) const override; + + protected: + QSGNode* updateSubNode( const QskSkinnable*, + quint8 nodeRole, QSGNode* ) const override; + + virtual QString effectivePlaceholderText( const QskTextArea* ) const; +}; + +#endif diff --git a/src/controls/QskTextEdit.cpp b/src/controls/QskTextEdit.cpp new file mode 100644 index 00000000..87e3949d --- /dev/null +++ b/src/controls/QskTextEdit.cpp @@ -0,0 +1,758 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#include "QskTextEdit.h" +#include "QskTextEditSkinlet.h" +#include "QskFontRole.h" +#include "QskInternalMacros.h" +#include "QskQuick.h" + +QSK_QT_PRIVATE_BEGIN +#include +#include + +#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 ) +#include +#endif + +QSK_QT_PRIVATE_END + +QSK_SUBCONTROL( QskTextEdit, TextPanel ) +QSK_SUBCONTROL( QskTextEdit, Text ) + +QSK_SYSTEM_STATE( QskTextEdit, ReadOnly, QskAspect::FirstSystemState << 1 ) +QSK_SYSTEM_STATE( QskTextEdit, Editing, QskAspect::FirstSystemState << 2 ) +QSK_SYSTEM_STATE( QskTextEdit, Error, QskAspect::FirstSystemState << 4 ) + +static inline void qskPropagateReadOnly( QskTextEdit* edit ) +{ + Q_EMIT edit->readOnlyChanged( edit->isReadOnly() ); + + QEvent event( QEvent::ReadOnlyChange ); + QCoreApplication::sendEvent( edit, &event ); +} + +static inline void qskTranslateMouseEventPosition( + QMouseEvent* mouseEvent, const QPointF& offset ) +{ +#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 ) + auto& point = mouseEvent->point( 0 ); + + QMutableEventPoint::setPosition( + point, point.position() + offset ); +#else + mouseEvent->setLocalPos( mouseEvent->localPos() + offset ); +#endif +} + +static inline void qskBindSignals( + const QQuickTextEdit* wrappedEdit, QskTextEdit* edit ) +{ + QObject::connect( wrappedEdit, &QQuickTextEdit::textChanged, + edit, [ edit ] { Q_EMIT edit->textChanged( edit->text() ); } ); + + QObject::connect( wrappedEdit, &QQuickTextEdit::preeditTextChanged, + edit, [ edit ] { Q_EMIT edit->preeditTextChanged( edit->preeditText() ); } ); + + QObject::connect( wrappedEdit, &QQuickTextEdit::readOnlyChanged, + edit, [ edit ] { qskPropagateReadOnly( edit ); } ); + + QObject::connect( wrappedEdit, &QQuickTextEdit::overwriteModeChanged, + edit, &QskTextEdit::overwriteModeChanged ); + + QObject::connect( wrappedEdit, &QQuickTextEdit::wrapModeChanged, + edit, [ edit ] { Q_EMIT edit->wrapModeChanged( edit->wrapMode() ); } ); + + QObject::connect( wrappedEdit, &QQuickTextEdit::lineCountChanged, + [ edit ] { Q_EMIT edit->lineCountChanged( edit->lineCount() ); } ); + + QObject::connect( wrappedEdit, &QQuickTextEdit::textFormatChanged, + edit, [ edit ]( QQuickTextEdit::TextFormat format ) + { + Q_EMIT edit->textFormatChanged( static_cast< QskTextOptions::TextFormat >( format ) ); + } ); + + QObject::connect( wrappedEdit, &QQuickTextEdit::selectByMouseChanged, + edit, &QskTextEdit::selectByMouseChanged ); + + QObject::connect( wrappedEdit, &QQuickTextEdit::tabStopDistanceChanged, + edit, &QskTextEdit::tabStopDistanceChanged ); + + + QObject::connect( wrappedEdit, &QQuickItem::implicitWidthChanged, + edit, &QskControl::resetImplicitSize ); + + QObject::connect( wrappedEdit, &QQuickItem::implicitHeightChanged, + edit, &QskControl::resetImplicitSize ); +} + +namespace +{ + class QuickTextEdit final : public QQuickTextEdit + { + using Inherited = QQuickTextEdit; + + public: + QuickTextEdit( QskTextEdit* ); + + void setEditing( bool on ); + + inline void setAlignment( Qt::Alignment alignment ) + { + setHAlign( ( HAlignment ) ( int( alignment ) & 0x0f ) ); + setVAlign( ( VAlignment ) ( int( alignment ) & 0xf0 ) ); + } + + void updateColors(); + void updateMetrics(); + + inline bool handleEvent( QEvent* event ) + { + bool ok; + + switch( static_cast< int >( event->type() ) ) + { + case QEvent::MouseButtonDblClick: + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseMove: + { + auto mouseEvent = static_cast< QMouseEvent* >( event ); + + /* + As the event was sent for the parent item + we have to translate the position into + our coordinate system. + */ + qskTranslateMouseEventPosition( mouseEvent, -position() ); + ok = this->event( mouseEvent ); + qskTranslateMouseEventPosition( mouseEvent, position() ); + + break; + } + default: + ok = this->event( event ); + } + + return ok; + } + + inline bool hasSelectedText() const + { + return !selectedText().isEmpty(); + } + + protected: + +#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 ) + void geometryChange( + const QRectF& newGeometry, const QRectF& oldGeometry ) override + { + Inherited::geometryChange( newGeometry, oldGeometry ); + updateClip(); + } +#else + void geometryChanged( + const QRectF& newGeometry, const QRectF& oldGeometry ) override + { + Inherited::geometryChanged( newGeometry, oldGeometry ); + updateClip(); + } +#endif + + void updateClip() + { + setClip( ( contentWidth() > width() ) || + ( contentHeight() > height() ) ); + } + + QSGNode* updatePaintNode( + QSGNode* oldNode, UpdatePaintNodeData* data ) override + { + updateColors(); + return Inherited::updatePaintNode( oldNode, data ); + } + }; + + QuickTextEdit::QuickTextEdit( QskTextEdit* textField ) + : QQuickTextEdit( textField ) + { + classBegin(); + + setActiveFocusOnTab( false ); + setFlag( ItemAcceptsInputMethod, false ); + setFocusOnPress( false ); + setSelectByMouse( true ); + + componentComplete(); + + connect( this, &QuickTextEdit::contentSizeChanged, + this, &QuickTextEdit::updateClip ); + } + + void QuickTextEdit::setEditing( bool on ) + { + auto d = QQuickTextEditPrivate::get( this ); + + if ( d->cursorVisible == on ) + return; + + setCursorVisible( on ); + + polish(); + update(); + } + + void QuickTextEdit::updateMetrics() + { + auto textEdit = static_cast< const QskTextEdit* >( parentItem() ); + + setAlignment( textEdit->alignment() ); + setFont( textEdit->font() ); + } + + void QuickTextEdit::updateColors() + { + using Q = QskTextEdit; + + auto input = static_cast< const Q* >( parentItem() ); + + setColor( input->color( Q::Text ) ); + + const auto state = QskTextEditSkinlet::Selected; + + setSelectionColor( input->color( Q::TextPanel | state ) ); + setSelectedTextColor( input->color( Q::Text | state ) ); + } +} + +class QskTextEdit::PrivateData +{ + public: + QuickTextEdit* wrappedEdit; + + ActivationModes activationModes; +}; + +QskTextEdit::QskTextEdit( QQuickItem* parent ) + : Inherited( parent ) + , m_data( new PrivateData() ) +{ + m_data->activationModes = ActivationOnMouse | ActivationOnKey; + + setPolishOnResize( true ); + + setAcceptHoverEvents( true ); + setFocusPolicy( Qt::StrongFocus ); + + setFlag( QQuickItem::ItemAcceptsInputMethod ); + + /* + QQuickTextEdit is a beast of almost 3.5k lines of code, we don't + want to reimplement that - at least not now. + So this is more or less a simple wrapper making everything + conforming to qskinny. + */ + + m_data->wrappedEdit = new QuickTextEdit( this ); + qskBindSignals( m_data->wrappedEdit, this ); + + setAcceptedMouseButtons( m_data->wrappedEdit->acceptedMouseButtons() ); + m_data->wrappedEdit->setAcceptedMouseButtons( Qt::NoButton ); + + initSizePolicy( QskSizePolicy::Expanding, QskSizePolicy::Expanding ); +} + +QskTextEdit::~QskTextEdit() +{ +} + +bool QskTextEdit::event( QEvent* event ) +{ + if ( event->type() == QEvent::ShortcutOverride ) + { + return m_data->wrappedEdit->handleEvent( event ); + } + else if ( event->type() == QEvent::LocaleChange ) + { + qskUpdateInputMethod( this, Qt::ImPreferredLanguage ); + } + + return Inherited::event( event ); +} + +void QskTextEdit::keyPressEvent( QKeyEvent* event ) +{ + if ( isEditing() ) + { + switch ( event->key() ) + { + case Qt::Key_Enter: + case Qt::Key_Return: + { + QGuiApplication::inputMethod()->commit(); + + if ( !( inputMethodHints() & Qt::ImhMultiLine ) ) + { + setEditing( false ); + + // When returning from a virtual keyboard + qskForceActiveFocus( this, Qt::PopupFocusReason ); + } + break; + } +#if 1 + case Qt::Key_Escape: + { + setEditing( false ); + qskForceActiveFocus( this, Qt::PopupFocusReason ); + break; + } +#endif + default: + { + m_data->wrappedEdit->handleEvent( event ); + } + } + + return; + } + + if ( ( m_data->activationModes & ActivationOnKey ) && !event->isAutoRepeat() ) + { + if ( event->key() == Qt::Key_Select || event->key() == Qt::Key_Space ) + { + setEditing( true ); + return; + } + } + + Inherited::keyPressEvent( event ); +} + +void QskTextEdit::keyReleaseEvent( QKeyEvent* event ) +{ + Inherited::keyReleaseEvent( event ); +} + +void QskTextEdit::mousePressEvent( QMouseEvent* event ) +{ + m_data->wrappedEdit->handleEvent( event ); + + if ( !isReadOnly() && !qGuiApp->styleHints()->setFocusOnTouchRelease() ) + setEditing( true ); +} + +void QskTextEdit::mouseMoveEvent( QMouseEvent* event ) +{ + m_data->wrappedEdit->handleEvent( event ); +} + +void QskTextEdit::mouseReleaseEvent( QMouseEvent* event ) +{ + m_data->wrappedEdit->handleEvent( event ); + + if ( !isReadOnly() && qGuiApp->styleHints()->setFocusOnTouchRelease() ) + setEditing( true ); +} + +void QskTextEdit::mouseDoubleClickEvent( QMouseEvent* event ) +{ + m_data->wrappedEdit->handleEvent( event ); +} + +void QskTextEdit::inputMethodEvent( QInputMethodEvent* event ) +{ + m_data->wrappedEdit->handleEvent( event ); +} + +void QskTextEdit::focusInEvent( QFocusEvent* event ) +{ + if ( m_data->activationModes & ActivationOnFocus ) + { + switch ( event->reason() ) + { + case Qt::ActiveWindowFocusReason: + case Qt::PopupFocusReason: + break; + + default: +#if 1 + // auto selecting the complete text ??? +#endif + setEditing( true ); + } + } + + Inherited::focusInEvent( event ); +} + +void QskTextEdit::focusOutEvent( QFocusEvent* event ) +{ + switch ( event->reason() ) + { + case Qt::ActiveWindowFocusReason: + case Qt::PopupFocusReason: + { + break; + } + default: + { + m_data->wrappedEdit->deselect(); + setEditing( false ); + } + } + + Inherited::focusOutEvent( event ); +} + +void QskTextEdit::updateLayout() +{ + m_data->wrappedEdit->updateMetrics(); + qskSetItemGeometry( m_data->wrappedEdit, subControlRect( Text ) ); +} + +void QskTextEdit::updateNode( QSGNode* node ) +{ + m_data->wrappedEdit->updateColors(); + Inherited::updateNode( node ); +} + +QString QskTextEdit::text() const +{ + return m_data->wrappedEdit->text(); +} + +void QskTextEdit::setText( const QString& text ) +{ + m_data->wrappedEdit->setText( text ); +} + +void QskTextEdit::clear() +{ + m_data->wrappedEdit->clear(); +} + +void QskTextEdit::selectAll() +{ + m_data->wrappedEdit->selectAll(); +} + +QskTextEdit::ActivationModes QskTextEdit::activationModes() const +{ + return static_cast< QskTextEdit::ActivationModes >( m_data->activationModes ); +} + +void QskTextEdit::setActivationModes( ActivationModes modes ) +{ + if ( static_cast< ActivationModes >( m_data->activationModes ) != modes ) + { + m_data->activationModes = modes; + Q_EMIT activationModesChanged(); + } +} + +static inline void qskUpdateInputMethodFont( const QskTextEdit* input ) +{ + const auto queries = Qt::ImCursorRectangle | Qt::ImFont | Qt::ImAnchorRectangle; + qskUpdateInputMethod( input, queries ); +} + +void QskTextEdit::setFontRole( const QskFontRole& role ) +{ + if ( setFontRoleHint( Text, role ) ) + { + qskUpdateInputMethodFont( this ); + Q_EMIT fontRoleChanged(); + } +} + +void QskTextEdit::resetFontRole() +{ + if ( resetFontRoleHint( Text ) ) + { + qskUpdateInputMethodFont( this ); + Q_EMIT fontRoleChanged(); + } +} + +QskFontRole QskTextEdit::fontRole() const +{ + return fontRoleHint( Text ); +} + +void QskTextEdit::setAlignment( Qt::Alignment alignment ) +{ + if ( setAlignmentHint( Text, alignment ) ) + { + m_data->wrappedEdit->setAlignment( alignment ); + Q_EMIT alignmentChanged(); + } +} + +void QskTextEdit::resetAlignment() +{ + if ( resetAlignmentHint( Text ) ) + { + m_data->wrappedEdit->setAlignment( alignment() ); + Q_EMIT alignmentChanged(); + } +} + +Qt::Alignment QskTextEdit::alignment() const +{ + return alignmentHint( Text, Qt::AlignLeft | Qt::AlignTop ); +} + +void QskTextEdit::setWrapMode( QskTextOptions::WrapMode wrapMode ) +{ + m_data->wrappedEdit->setWrapMode( + static_cast< QQuickTextEdit::WrapMode >( wrapMode ) ); +} + +QskTextOptions::WrapMode QskTextEdit::wrapMode() const +{ + return static_cast< QskTextOptions::WrapMode >( + m_data->wrappedEdit->wrapMode() ); +} + +void QskTextEdit::setSelectByMouse( bool on ) +{ + m_data->wrappedEdit->setSelectByMouse( on ); +} + +bool QskTextEdit::selectByMouse() const +{ + return m_data->wrappedEdit->selectByMouse(); +} + +QFont QskTextEdit::font() const +{ + return effectiveFont( QskTextEdit::Text ); +} + +bool QskTextEdit::isReadOnly() const +{ + return m_data->wrappedEdit->isReadOnly(); +} + +void QskTextEdit::setReadOnly( bool on ) +{ + auto edit = m_data->wrappedEdit; + + if ( edit->isReadOnly() == on ) + return; + +#if 1 + // do we want to be able to restore the previous policy ? + setFocusPolicy( Qt::NoFocus ); +#endif + + edit->setReadOnly( on ); + + // we are killing user settings here ? + edit->setFlag( QQuickItem::ItemAcceptsInputMethod, !on ); + qskUpdateInputMethod( this, Qt::ImEnabled ); + + setSkinStateFlag( ReadOnly, on ); +} + +void QskTextEdit::setEditing( bool on ) +{ + if ( isReadOnly() || on == isEditing() ) + return; + + setSkinStateFlag( Editing, on ); + m_data->wrappedEdit->setEditing( on ); + + if ( on ) + { +#if 0 + updateInputMethod( Qt::ImCursorRectangle | Qt::ImAnchorRectangle ); + QGuiApplication::inputMethod()->inputDirection +#endif + qskInputMethodSetVisible( this, true ); + } + else + { + Q_EMIT m_data->wrappedEdit->editingFinished(); + +#if 0 + inputMethod->reset(); +#endif + qskInputMethodSetVisible( this, false ); + } + + Q_EMIT editingChanged( on ); +} + +bool QskTextEdit::isEditing() const +{ + return hasSkinState( Editing ); +} + +int QskTextEdit::cursorPosition() const +{ + return m_data->wrappedEdit->cursorPosition(); +} + +void QskTextEdit::setCursorPosition( int pos ) +{ + m_data->wrappedEdit->setCursorPosition( pos ); +} + +QString QskTextEdit::preeditText() const +{ + return m_data->wrappedEdit->preeditText(); +} + +bool QskTextEdit::overwriteMode() const +{ + return m_data->wrappedEdit->overwriteMode(); +} + +void QskTextEdit::setOverwriteMode( bool overwrite ) +{ + m_data->wrappedEdit->setOverwriteMode( overwrite ); +} + +void QskTextEdit::setTextFormat( QskTextOptions::TextFormat textFormat ) +{ + m_data->wrappedEdit->setTextFormat( + static_cast< QQuickTextEdit::TextFormat >( textFormat ) ); +} + +QskTextOptions::TextFormat QskTextEdit::textFormat() const +{ + return static_cast< QskTextOptions::TextFormat >( + m_data->wrappedEdit->textFormat() ); +} + +int QskTextEdit::lineCount() const +{ + return m_data->wrappedEdit->lineCount(); +} + +QVariant QskTextEdit::inputMethodQuery( + Qt::InputMethodQuery property ) const +{ + return inputMethodQuery( property, QVariant() ); +} + +QVariant QskTextEdit::inputMethodQuery( + Qt::InputMethodQuery query, const QVariant& argument ) const +{ + switch ( query ) + { + case Qt::ImEnabled: + { + return QVariant( ( bool ) ( flags() & ItemAcceptsInputMethod ) ); + } + case Qt::ImFont: + { + return font(); + } + case Qt::ImPreferredLanguage: + { + return locale(); + } + case Qt::ImInputItemClipRectangle: + case Qt::ImCursorRectangle: + { + QVariant v = m_data->wrappedEdit->inputMethodQuery( query, argument ); +#if 1 + if ( v.canConvert< QRectF >() ) + v.setValue( v.toRectF().translated( m_data->wrappedEdit->position() ) ); +#endif + return v; + } + default: + { + return m_data->wrappedEdit->inputMethodQuery( query, argument ); + } + } +} + +bool QskTextEdit::canUndo() const +{ + return m_data->wrappedEdit->canUndo(); +} + +bool QskTextEdit::canRedo() const +{ + return m_data->wrappedEdit->canRedo(); +} + +Qt::InputMethodHints QskTextEdit::inputMethodHints() const +{ + return m_data->wrappedEdit->inputMethodHints(); +} + +void QskTextEdit::setInputMethodHints( Qt::InputMethodHints hints ) +{ + if ( m_data->wrappedEdit->inputMethodHints() != hints ) + { + m_data->wrappedEdit->setInputMethodHints( hints ); + qskUpdateInputMethod( this, Qt::ImHints ); + } +} + +int QskTextEdit::tabStopDistance() const +{ + return m_data->wrappedEdit->tabStopDistance(); +} + +void QskTextEdit::setTabStopDistance( qreal distance ) +{ + m_data->wrappedEdit->setTabStopDistance( distance ); +} + +void QskTextEdit::setupFrom( const QQuickItem* item ) +{ + if ( item == nullptr ) + return; + + // finding attributes from the input hints of item + + int maxCharacters = 32767; + + Qt::InputMethodQueries queries = Qt::ImQueryAll; + queries &= ~Qt::ImEnabled; + + QInputMethodQueryEvent event( queries ); + QCoreApplication::sendEvent( const_cast< QQuickItem* >( item ), &event ); + + if ( event.queries() & Qt::ImMaximumTextLength ) + { + // needs to be handled before Qt::ImCursorPosition ! + + const auto max = event.value( Qt::ImMaximumTextLength ).toInt(); + maxCharacters = qBound( 0, max, maxCharacters ); + } + + if ( event.queries() & Qt::ImSurroundingText ) + { + const auto text = event.value( Qt::ImSurroundingText ).toString(); + setText( text ); + } + + if ( event.queries() & Qt::ImCursorPosition ) + { + const auto pos = event.value( Qt::ImCursorPosition ).toInt(); + setCursorPosition( pos ); + } + + if ( event.queries() & Qt::ImCurrentSelection ) + { +#if 0 + const auto text = event.value( Qt::ImCurrentSelection ).toString(); + if ( !text.isEmpty() ) + { + } +#endif + } +} + +#include "moc_QskTextEdit.cpp" diff --git a/src/controls/QskTextEdit.h b/src/controls/QskTextEdit.h new file mode 100644 index 00000000..7f4c518c --- /dev/null +++ b/src/controls/QskTextEdit.h @@ -0,0 +1,190 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#ifndef QSK_TEXT_EDIT_H +#define QSK_TEXT_EDIT_H + +#include "QskControl.h" +#include "QskTextOptions.h" + +class QValidator; +class QskFontRole; + +class QSK_EXPORT QskTextEdit : public QskControl +{ + Q_OBJECT + + Q_PROPERTY( QString text READ text + WRITE setText NOTIFY textChanged USER true ) + + Q_PROPERTY( QskFontRole fontRole READ fontRole + WRITE setFontRole RESET resetFontRole NOTIFY fontRoleChanged ) + + Q_PROPERTY( QFont font READ font ) + + Q_PROPERTY( Qt::Alignment alignment READ alignment + WRITE setAlignment RESET resetAlignment NOTIFY alignmentChanged ) + + Q_PROPERTY( QskTextOptions::WrapMode wrapMode READ wrapMode + WRITE setWrapMode NOTIFY wrapModeChanged ) + + Q_PROPERTY( int lineCount READ lineCount NOTIFY lineCountChanged ) + + Q_PROPERTY( QskTextOptions::TextFormat textFormat READ textFormat + WRITE setTextFormat NOTIFY textFormatChanged ) + + Q_PROPERTY( ActivationModes activationModes READ activationModes + WRITE setActivationModes NOTIFY activationModesChanged ) + + Q_PROPERTY( bool selectByMouse READ selectByMouse + WRITE setSelectByMouse NOTIFY selectByMouseChanged ) + + Q_PROPERTY( bool editing READ isEditing + WRITE setEditing NOTIFY editingChanged ) + + Q_PROPERTY( qreal tabStopDistance READ tabStopDistance + WRITE setTabStopDistance NOTIFY tabStopDistanceChanged ) + + using Inherited = QskControl; + + public: + QSK_SUBCONTROLS( TextPanel, Text ) + QSK_STATES( ReadOnly, Editing, Error ) + + enum ActivationMode + { + NoActivation, + + ActivationOnFocus = 1 << 0, + ActivationOnMouse = 1 << 1, + ActivationOnKey = 1 << 2, + + ActivationOnInput = ActivationOnMouse | ActivationOnKey, + ActivationOnAll = ActivationOnFocus | ActivationOnMouse | ActivationOnKey + }; + + Q_ENUM( ActivationMode ) + Q_DECLARE_FLAGS( ActivationModes, ActivationMode ) + + QskTextEdit( QQuickItem* parent = nullptr ); + + ~QskTextEdit() override; + + void setupFrom( const QQuickItem* ); + + QString text() const; + + void setFontRole( const QskFontRole& role ); + void resetFontRole(); + QskFontRole fontRole() const; + + void setAlignment( Qt::Alignment ); + void resetAlignment(); + Qt::Alignment alignment() const; + + void setWrapMode( QskTextOptions::WrapMode ); + QskTextOptions::WrapMode wrapMode() const; + + void setTextFormat( QskTextOptions::TextFormat ); + QskTextOptions::TextFormat textFormat() const; + + int lineCount() const; + + void setActivationModes( ActivationModes ); + ActivationModes activationModes() const; + + void setSelectByMouse( bool ); + bool selectByMouse() const; + + bool isEditing() const; + + QFont font() const; + + bool isReadOnly() const; + void setReadOnly( bool ); + + int cursorPosition() const; + void setCursorPosition( int ); + + QString preeditText() const; + + bool overwriteMode() const; + void setOverwriteMode( bool ); + + QVariant inputMethodQuery( Qt::InputMethodQuery ) const override; + QVariant inputMethodQuery( Qt::InputMethodQuery, const QVariant& argument ) const; + + bool canUndo() const; + bool canRedo() const; + + Qt::InputMethodHints inputMethodHints() const; + void setInputMethodHints( Qt::InputMethodHints ); + + int tabStopDistance() const; + void setTabStopDistance( qreal ); + + public Q_SLOTS: + void clear(); + void selectAll(); + void setText( const QString& ); + void setEditing( bool ); + + Q_SIGNALS: + void textChanged( const QString& ); + void preeditTextChanged( const QString& ); + + void editingChanged( bool ); + + void activationModesChanged(); + void readOnlyChanged( bool ); + void panelChanged( bool ); + + void displayTextChanged( const QString& ); + + void textEdited( const QString& ); + void placeholderTextChanged( const QString& ); + + void fontRoleChanged(); + void alignmentChanged(); + void wrapModeChanged( QskTextOptions::WrapMode ); + + void lineCountChanged( int ); + + void selectByMouseChanged( bool ); + + void textFormatChanged( QskTextOptions::TextFormat ); + + void overwriteModeChanged( bool ); + + void tabStopDistanceChanged( qreal ); + + protected: + bool event( QEvent* ) override; + + void inputMethodEvent( QInputMethodEvent* ) override; + + void focusInEvent( QFocusEvent* ) override; + void focusOutEvent( QFocusEvent* ) override; + + void mousePressEvent( QMouseEvent* ) override; + void mouseMoveEvent( QMouseEvent* ) override; + void mouseReleaseEvent( QMouseEvent* ) override; + void mouseDoubleClickEvent( QMouseEvent* ) override; + + void keyPressEvent( QKeyEvent* ) override; + void keyReleaseEvent( QKeyEvent* ) override; + + void updateLayout() override; + void updateNode( QSGNode* ) override; + + private: + class PrivateData; + std::unique_ptr< PrivateData > m_data; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS( QskTextEdit::ActivationModes ) +Q_DECLARE_METATYPE( QskTextEdit::ActivationModes ) + +#endif diff --git a/src/controls/QskTextEditSkinlet.cpp b/src/controls/QskTextEditSkinlet.cpp new file mode 100644 index 00000000..92695431 --- /dev/null +++ b/src/controls/QskTextEditSkinlet.cpp @@ -0,0 +1,92 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#include "QskTextEditSkinlet.h" +#include "QskTextEdit.h" + +#include + +using Q = QskTextEdit; + +QSK_SYSTEM_STATE( QskTextEditSkinlet, Selected, QskAspect::FirstSystemState << 3 ) + +QskTextEditSkinlet::QskTextEditSkinlet( QskSkin* skin ) + : Inherited( skin ) +{ + setNodeRoles( { TextPanelRole } ); +} + +QskTextEditSkinlet::~QskTextEditSkinlet() +{ +} + +QRectF QskTextEditSkinlet::subControlRect( const QskSkinnable* skinnable, + const QRectF& contentsRect, QskAspect::Subcontrol subControl ) const +{ + if ( subControl == Q::TextPanel ) + { + return contentsRect; + } + else if ( subControl == Q::Text ) + { + auto rect = skinnable->subControlContentsRect( contentsRect, Q::TextPanel ); + rect = rect.marginsAdded( skinnable->marginHint( Q::Text ) ); + + return rect; + } + + return Inherited::subControlRect( skinnable, contentsRect, subControl ); +} + +QSGNode* QskTextEditSkinlet::updateSubNode( + const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const +{ + switch ( nodeRole ) + { + case TextPanelRole: + { + return updateBoxNode( skinnable, node, Q::TextPanel ); + } + } + + return Inherited::updateSubNode( skinnable, nodeRole, node ); +} + +QSizeF QskTextEditSkinlet::sizeHint( const QskSkinnable* skinnable, + Qt::SizeHint which, const QSizeF& ) const +{ + if ( which != Qt::PreferredSize ) + return QSizeF(); + + const auto textEdit = static_cast< const QskTextEdit* >( skinnable ); + + const QFontMetricsF fm( textEdit->effectiveFont( Q::Text ) ); + + int flags = Qt::TextExpandTabs; + + const auto wm = textEdit->wrapMode(); + + if( wm & QskTextOptions::WordWrap ) + { + flags |= Qt::TextWordWrap; + } + else if( wm & QskTextOptions::WrapAnywhere ) + { + flags |= Qt::TextWrapAnywhere; + } + else if( wm & QskTextOptions::Wrap ) + { + flags |= Qt::TextWordWrap | Qt::TextWrapAnywhere; + } + + auto hint = fm.boundingRect( textEdit->subControlRect( Q::Text ), flags, textEdit->text() ).size(); + + hint = textEdit->outerBoxSize( Q::TextPanel, hint ); + hint = hint.expandedTo( textEdit->strutSizeHint( Q::TextPanel ) ); + + return hint; +} + +#include "moc_QskTextEditSkinlet.cpp" diff --git a/src/controls/QskTextEditSkinlet.h b/src/controls/QskTextEditSkinlet.h new file mode 100644 index 00000000..0d858239 --- /dev/null +++ b/src/controls/QskTextEditSkinlet.h @@ -0,0 +1,39 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#ifndef QSK_TEXT_EDIT_SKINLET_H +#define QSK_TEXT_EDIT_SKINLET_H + +#include "QskSkinlet.h" + +class QSK_EXPORT QskTextEditSkinlet : public QskSkinlet +{ + using Inherited = QskSkinlet; + + public: + QSK_STATES( Selected ) + + enum NodeRole : quint8 + { + TextPanelRole, + RoleCount + }; + + ~QskTextEditSkinlet() override; + + QRectF subControlRect( const QskSkinnable*, + const QRectF& rect, QskAspect::Subcontrol ) const override; + + QSizeF sizeHint( const QskSkinnable*, + Qt::SizeHint, const QSizeF& ) const override; + + protected: + QskTextEditSkinlet( QskSkin* = nullptr ); + + QSGNode* updateSubNode( const QskSkinnable*, + quint8 nodeRole, QSGNode* ) const override; +}; + +#endif diff --git a/src/controls/QskTextInput.cpp b/src/controls/QskTextInput.cpp index 7ad34b39..eff363ae 100644 --- a/src/controls/QskTextInput.cpp +++ b/src/controls/QskTextInput.cpp @@ -236,7 +236,7 @@ namespace const auto state = QskTextInputSkinlet::Selected; setSelectionColor( input->color( Q::TextPanel | state ) ); - setSelectedTextColor( input->color( QskTextInput::Text | state ) ); + setSelectedTextColor( input->color( Q::Text | state ) ); } }