From a91dcd5a5622fec37badb4a30181ee88c97be5e2 Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Wed, 18 Dec 2024 12:37:18 +0100 Subject: [PATCH] QskTextField/QskTextInput seperated --- designsystems/fluent2/QskFluent2Skin.cpp | 14 +- designsystems/fusion/QskFusionSkin.cpp | 16 +- designsystems/material3/QskMaterial3Skin.cpp | 4 +- src/CMakeLists.txt | 4 + src/controls/QskTextField.cpp | 917 +------------------ src/controls/QskTextField.h | 192 +--- src/controls/QskTextFieldSkinlet.cpp | 55 +- src/controls/QskTextFieldSkinlet.h | 15 +- src/controls/QskTextInput.cpp | 917 +++++++++++++++++++ src/controls/QskTextInput.h | 207 +++++ src/controls/QskTextInputSkinlet.cpp | 74 ++ src/controls/QskTextInputSkinlet.h | 39 + src/inputpanel/QskInputPanelBox.cpp | 4 +- 13 files changed, 1294 insertions(+), 1164 deletions(-) create mode 100644 src/controls/QskTextInput.cpp create mode 100644 src/controls/QskTextInput.h create mode 100644 src/controls/QskTextInputSkinlet.cpp create mode 100644 src/controls/QskTextInputSkinlet.h diff --git a/designsystems/fluent2/QskFluent2Skin.cpp b/designsystems/fluent2/QskFluent2Skin.cpp index 093ab083e..dcffe1e75 100644 --- a/designsystems/fluent2/QskFluent2Skin.cpp +++ b/designsystems/fluent2/QskFluent2Skin.cpp @@ -1776,14 +1776,14 @@ void Editor::setupTextFieldMetrics() { using Q = QskTextField; - setStrutSize( Q::Panel, { -1, 30_px } ); - setPadding( Q::Panel, { 11_px, 0, 11_px, 0 } ); + setStrutSize( Q::TextPanel, { -1, 30_px } ); + setPadding( Q::TextPanel, { 11_px, 0, 11_px, 0 } ); - setBoxBorderMetrics( Q::Panel, 1_px ); + setBoxBorderMetrics( Q::TextPanel, 1_px ); for( const auto& state : { Q::Focused, Q::Editing } ) - setBoxBorderMetrics( Q::Panel | state, { 1_px, 1_px, 1_px, 2_px } ); + setBoxBorderMetrics( Q::TextPanel | state, { 1_px, 1_px, 1_px, 2_px } ); - setBoxShape( Q::Panel, 3_px ); + setBoxShape( Q::TextPanel, 3_px ); setAlignment( Q::Text, Qt::AlignLeft | Qt::AlignVCenter ); setFontRole( Q::Text, Fluent2::Body ); @@ -1801,7 +1801,7 @@ void Editor::setupTextFieldColors( const auto& pal = theme.palette; - setColor( Q::Panel | SK::Selected, pal.fillColor.accent.selectedTextBackground ); + setColor( Q::TextPanel | SK::Selected, pal.fillColor.accent.selectedTextBackground ); setColor( Q::Text | SK::Selected, pal.fillColor.textOnAccent.selectedText ); setColor( Q::PlaceholderText, pal.fillColor.text.secondary ); @@ -1837,7 +1837,7 @@ void Editor::setupTextFieldColors( textColor = pal.fillColor.text.disabled; } - const auto panel = Q::Panel | section | state; + const auto panel = Q::TextPanel | section | state; const auto text = Q::Text | section | state; panelColor = rgbSolid( panelColor, pal.background.solid.base ); diff --git a/designsystems/fusion/QskFusionSkin.cpp b/designsystems/fusion/QskFusionSkin.cpp index ff1a115a3..97cb96b02 100644 --- a/designsystems/fusion/QskFusionSkin.cpp +++ b/designsystems/fusion/QskFusionSkin.cpp @@ -397,25 +397,25 @@ void Editor::setupTextField() { const auto colorGroup = ( state == A::NoState ) ? P::Active : P::Disabled; - setColor( Q::Panel | state, m_pal.color( colorGroup, P::Base ) ); - setColor( Q::Panel | SK::Selected | state, m_pal.color( colorGroup, P::Highlight ) ); + setColor( Q::TextPanel | state, m_pal.color( colorGroup, P::Base ) ); + setColor( Q::TextPanel | SK::Selected | state, m_pal.color( colorGroup, P::Highlight ) ); setColor( Q::Text | state, m_pal.color( colorGroup, P::Text ) ); setColor( Q::Text | SK::Selected | state, m_pal.color( colorGroup, P::HighlightedText ) ); setColor( Q::PlaceholderText, m_pal.color( colorGroup, P::PlaceholderText ) ); } - setColor( Q::Panel | Q::ReadOnly, m_pal.disabled( P::Base ) ); + setColor( Q::TextPanel | Q::ReadOnly, m_pal.disabled( P::Base ) ); - setBoxBorderMetrics( Q::Panel, 1_px ); + setBoxBorderMetrics( Q::TextPanel, 1_px ); - setBoxBorderColors( Q::Panel, m_pal.outline ); + setBoxBorderColors( Q::TextPanel, m_pal.outline ); #ifdef SHOW_FOCUS - setBoxBorderColors( Q::Panel | Q::Focused, m_pal.highlightedOutline ); + setBoxBorderColors( Q::TextPanel | Q::Focused, m_pal.highlightedOutline ); #endif - setBoxShape( Q::Panel, 2_px ); - setPadding( Q::Panel, 4_px ); + setBoxShape( Q::TextPanel, 2_px ); + setPadding( Q::TextPanel, 4_px ); } void Editor::setupProgressBar() diff --git a/designsystems/material3/QskMaterial3Skin.cpp b/designsystems/material3/QskMaterial3Skin.cpp index 91ff1cb49..b087029fc 100644 --- a/designsystems/material3/QskMaterial3Skin.cpp +++ b/designsystems/material3/QskMaterial3Skin.cpp @@ -434,12 +434,12 @@ void Editor::setupTextLabel() void Editor::setupTextField() { using Q = QskTextField; - using SK = QskTextFieldSkinlet; + 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 ); - setColor( Q::Panel | SK::Selected, m_pal.primary12 ); + setColor( Q::TextPanel | SK::Selected, m_pal.primary12 ); setBoxShape( Q::Panel, m_pal.shapeExtraSmallTop ); setBoxBorderMetrics( Q::Panel, { 0, 0, 0, 1_px } ); setBoxBorderColors( Q::Panel, m_pal.onSurfaceVariant ); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1a71baf64..a8ed5d7f1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -281,6 +281,8 @@ list(APPEND HEADERS controls/QskTabViewSkinlet.h controls/QskTextField.h controls/QskTextFieldSkinlet.h + controls/QskTextInput.h + controls/QskTextInputSkinlet.h controls/QskTextLabel.h controls/QskTextLabelSkinlet.h controls/QskVariantAnimator.h @@ -387,6 +389,8 @@ list(APPEND SOURCES controls/QskTabViewSkinlet.cpp controls/QskTextField.cpp controls/QskTextFieldSkinlet.cpp + controls/QskTextInput.cpp + controls/QskTextInputSkinlet.cpp controls/QskTextLabel.cpp controls/QskTextLabelSkinlet.cpp controls/QskVariantAnimator.cpp diff --git a/src/controls/QskTextField.cpp b/src/controls/QskTextField.cpp index 031b5c729..0029bc6d0 100644 --- a/src/controls/QskTextField.cpp +++ b/src/controls/QskTextField.cpp @@ -4,514 +4,32 @@ *****************************************************************************/ #include "QskTextField.h" -#include "QskTextFieldSkinlet.h" -#include "QskFontRole.h" -#include "QskQuick.h" - -QSK_QT_PRIVATE_BEGIN -#include -#include -QSK_QT_PRIVATE_END QSK_SUBCONTROL( QskTextField, Panel ) -QSK_SUBCONTROL( QskTextField, Text ) QSK_SUBCONTROL( QskTextField, PlaceholderText ) -QSK_SYSTEM_STATE( QskTextField, ReadOnly, QskAspect::FirstSystemState << 1 ) -QSK_SYSTEM_STATE( QskTextField, Editing, QskAspect::FirstSystemState << 2 ) - -static inline void qskPropagateReadOnly( QskTextField* input ) -{ - Q_EMIT input->readOnlyChanged( input->isReadOnly() ); - - QEvent event( QEvent::ReadOnlyChange ); - QCoreApplication::sendEvent( input, &event ); -} - -static inline void qskBindSignals( - const QQuickTextInput* input, QskTextField* field ) -{ - QObject::connect( input, &QQuickTextInput::textChanged, - field, [ field ] { Q_EMIT field->textChanged( field->text() ); } ); - - QObject::connect( input, &QQuickTextInput::displayTextChanged, - field, [ field ] { Q_EMIT field->displayTextChanged( field->displayText() ); } ); - - QObject::connect( input, &QQuickTextInput::textEdited, - field, [ field ] { Q_EMIT field->textEdited( field->text() ); } ); - - QObject::connect( input, &QQuickTextInput::validatorChanged, - field, &QskTextField::validatorChanged ); - - QObject::connect( input, &QQuickTextInput::inputMaskChanged, - field, &QskTextField::inputMaskChanged ); - - QObject::connect( input, &QQuickTextInput::readOnlyChanged, - field, [ field ] { qskPropagateReadOnly( field ); } ); - - QObject::connect( input, &QQuickTextInput::overwriteModeChanged, - field, &QskTextField::overwriteModeChanged ); - - QObject::connect( input, &QQuickTextInput::maximumLengthChanged, - field, &QskTextField::maximumLengthChanged ); - - QObject::connect( input, &QQuickTextInput::wrapModeChanged, - field, [ field ] { Q_EMIT field->wrapModeChanged( field->wrapMode() ); } ); - - QObject::connect( input, &QQuickTextInput::echoModeChanged, - field, [ field ] { Q_EMIT field->echoModeChanged( field->echoMode() ); } ); - - QObject::connect( input, &QQuickTextInput::passwordCharacterChanged, - field, &QskTextField::passwordCharacterChanged ); - - QObject::connect( input, &QQuickTextInput::passwordMaskDelayChanged, - field, &QskTextField::passwordMaskDelayChanged ); - - QObject::connect( input, &QQuickItem::implicitWidthChanged, - field, &QskControl::resetImplicitSize ); - - QObject::connect( input, &QQuickItem::implicitHeightChanged, - field, &QskControl::resetImplicitSize ); -} - -namespace -{ - class TextInput final : public QQuickTextInput - { - using Inherited = QQuickTextInput; - - public: - TextInput( QskTextField* ); - - void setEditing( bool on ); - - inline void setAlignment( Qt::Alignment alignment ) - { - setHAlign( ( HAlignment ) ( int( alignment ) & 0x0f ) ); - setVAlign( ( VAlignment ) ( int( alignment ) & 0xf0 ) ); - } - - bool fixup() - { - return QQuickTextInputPrivate::get( this )->fixup(); - } - - bool hasAcceptableInput() const - { - /* - we would like to call QQuickTextInputPrivate::hasAcceptableInput - but unfortunately it is private, so we need to hack somthing - together - */ - - auto that = const_cast< TextInput* >( this ); - auto d = QQuickTextInputPrivate::get( that ); - - if ( d->m_validator ) - { - QString text = displayText(); - int pos = d->m_cursor; - - const auto state = d->m_validator->validate( text, pos ); - if ( state != QValidator::Acceptable ) - return false; - } - - if ( d->m_maskData ) - { - /* - We only want to do the check for the maskData here - and have to disable d->m_validator temporarily - */ - - class Validator final : public QValidator - { - public: - State validate( QString&, int& ) const override - { - return QValidator::Acceptable; - } - }; - - const auto validator = d->m_validator; - - const auto validInput = d->m_validInput; - const auto acceptableInput = d->m_acceptableInput; - - d->m_acceptableInput = true; - - static Validator noValidator; - that->setValidator( &noValidator ); // implicitly checking maskData - that->setValidator( d->m_validator ); - - const bool isAcceptable = d->m_acceptableInput; - - // restoring old values - d->m_validInput = validInput; - d->m_acceptableInput = acceptableInput; - - return isAcceptable; - } - - return true; - } - - void updateColors(); - void updateMetrics(); - - inline bool handleEvent( QEvent* event ) - { - return this->event( event ); - } - - 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 ); - } - }; - - TextInput::TextInput( QskTextField* textField ) - : QQuickTextInput( textField ) - { - classBegin(); - - setActiveFocusOnTab( false ); - setFlag( ItemAcceptsInputMethod, false ); - setFocusOnPress( false ); - - componentComplete(); - - connect( this, &TextInput::contentSizeChanged, - this, &TextInput::updateClip ); - } - - void TextInput::setEditing( bool on ) - { - auto d = QQuickTextInputPrivate::get( this ); - - if ( d->cursorVisible == on ) - return; - - setCursorVisible( on ); - d->setBlinkingCursorEnabled( on ); - - if ( !on ) - { - if ( d->m_passwordEchoEditing || d->m_passwordEchoTimer.isActive() ) - d->updatePasswordEchoEditing( false ); - } - - polish(); - update(); - } - - void TextInput::updateMetrics() - { - auto textField = static_cast< const QskTextField* >( parentItem() ); - - setAlignment( textField->alignment() ); - setFont( textField->font() ); - } - - void TextInput::updateColors() - { - auto textField = static_cast< const QskTextField* >( parentItem() ); - auto d = QQuickTextInputPrivate::get( this ); - - bool isDirty = false; - - QColor color; - - color = textField->color( QskTextField::Text ); - if ( d->color != color ) - { - d->color = color; - isDirty = true; - } - - if ( d->hasSelectedText() ) - { - color = textField->color( QskTextField::Panel | QskTextFieldSkinlet::Selected ); - if ( d->selectionColor != color ) - { - d->selectionColor = color; - isDirty = true; - } - - color = textField->color( QskTextField::Text | QskTextFieldSkinlet::Selected ); - if ( d->selectedTextColor != color ) - { - d->selectedTextColor = color; - isDirty = true; - } - } - - if ( isDirty ) - { - d->textLayoutDirty = true; - d->updateType = QQuickTextInputPrivate::UpdatePaintNode; - update(); - } - } -} - class QskTextField::PrivateData { public: - TextInput* textInput; QString placeholderText; - - unsigned int activationModes : 3; - bool hasPanel : 1; }; QskTextField::QskTextField( QQuickItem* parent ) : Inherited( parent ) , m_data( new PrivateData() ) { - m_data->activationModes = ActivationOnMouse | ActivationOnKey; - m_data->hasPanel = true; - - setPolishOnResize( true ); - - setAcceptHoverEvents( true ); - setFocusPolicy( Qt::StrongFocus ); - - setFlag( QQuickItem::ItemAcceptsInputMethod ); - - /* - QQuickTextInput is a beast of almost 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->textInput = new TextInput( this ); - qskBindSignals( m_data->textInput, this ); - - setAcceptedMouseButtons( m_data->textInput->acceptedMouseButtons() ); - m_data->textInput->setAcceptedMouseButtons( Qt::NoButton ); - - initSizePolicy( QskSizePolicy::Expanding, QskSizePolicy::Fixed ); } QskTextField::QskTextField( const QString& text, QQuickItem* parent ) : QskTextField( parent ) { - m_data->textInput->setText( text ); + setText( text ); } QskTextField::~QskTextField() { } -void QskTextField::setPanel( bool on ) -{ - if ( on != m_data->hasPanel ) - { - m_data->hasPanel = on; - - resetImplicitSize(); - polish(); - update(); - } -} - -bool QskTextField::hasPanel() const -{ - return m_data->hasPanel; -} - -bool QskTextField::event( QEvent* event ) -{ - if ( event->type() == QEvent::ShortcutOverride ) - { - return m_data->textInput->handleEvent( event ); - } - else if ( event->type() == QEvent::LocaleChange ) - { - qskUpdateInputMethod( this, Qt::ImPreferredLanguage ); - } - - return Inherited::event( event ); -} - -void QskTextField::keyPressEvent( QKeyEvent* event ) -{ - if ( isEditing() ) - { - switch ( event->key() ) - { - case Qt::Key_Enter: - case Qt::Key_Return: - { - if ( hasAcceptableInput() || fixup() ) - { - 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->textInput->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 QskTextField::keyReleaseEvent( QKeyEvent* event ) -{ - Inherited::keyReleaseEvent( event ); -} - -void QskTextField::mousePressEvent( QMouseEvent* event ) -{ - m_data->textInput->handleEvent( event ); - - if ( !isReadOnly() && !qGuiApp->styleHints()->setFocusOnTouchRelease() ) - setEditing( true ); -} - -void QskTextField::mouseMoveEvent( QMouseEvent* event ) -{ - m_data->textInput->handleEvent( event ); -} - -void QskTextField::mouseReleaseEvent( QMouseEvent* event ) -{ - m_data->textInput->handleEvent( event ); - - if ( !isReadOnly() && qGuiApp->styleHints()->setFocusOnTouchRelease() ) - setEditing( true ); -} - -void QskTextField::mouseDoubleClickEvent( QMouseEvent* event ) -{ - m_data->textInput->handleEvent( event ); -} - -void QskTextField::inputMethodEvent( QInputMethodEvent* event ) -{ - m_data->textInput->handleEvent( event ); -} - -void QskTextField::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 QskTextField::focusOutEvent( QFocusEvent* event ) -{ - switch ( event->reason() ) - { - case Qt::ActiveWindowFocusReason: - case Qt::PopupFocusReason: - { - break; - } - default: - { - m_data->textInput->deselect(); - setEditing( false ); - } - } - - Inherited::focusOutEvent( event ); -} - -void QskTextField::updateLayout() -{ - m_data->textInput->updateMetrics(); - qskSetItemGeometry( m_data->textInput, subControlRect( Text ) ); -} - -void QskTextField::updateNode( QSGNode* node ) -{ - m_data->textInput->updateColors(); - Inherited::updateNode( node ); -} - -QString QskTextField::text() const -{ - return m_data->textInput->text(); -} - -void QskTextField::setText( const QString& text ) -{ - m_data->textInput->setText( text ); -} - void QskTextField::setPlaceholderText( const QString& text ) { if ( m_data->placeholderText != text ) @@ -526,437 +44,4 @@ QString QskTextField::placeholderText() const return m_data->placeholderText; } -QskTextField::ActivationModes QskTextField::activationModes() const -{ - return static_cast< QskTextField::ActivationModes >( m_data->activationModes ); -} - -void QskTextField::setActivationModes( ActivationModes modes ) -{ - if ( static_cast< ActivationModes >( m_data->activationModes ) != modes ) - { - m_data->activationModes = modes; - Q_EMIT activationModesChanged(); - } -} - -static inline void qskUpdateInputMethodFont( const QskTextField* input ) -{ - const auto queries = Qt::ImCursorRectangle | Qt::ImFont | Qt::ImAnchorRectangle; - qskUpdateInputMethod( input, queries ); -} - -void QskTextField::setFontRole( const QskFontRole& role ) -{ - if ( setFontRoleHint( Text, role ) ) - { - qskUpdateInputMethodFont( this ); - Q_EMIT fontRoleChanged(); - } -} - -void QskTextField::resetFontRole() -{ - if ( resetFontRoleHint( Text ) ) - { - qskUpdateInputMethodFont( this ); - Q_EMIT fontRoleChanged(); - } -} - -QskFontRole QskTextField::fontRole() const -{ - return fontRoleHint( Text ); -} - -void QskTextField::setAlignment( Qt::Alignment alignment ) -{ - if ( setAlignmentHint( Text, alignment ) ) - { - m_data->textInput->setAlignment( alignment ); - Q_EMIT alignmentChanged(); - } -} - -void QskTextField::resetAlignment() -{ - if ( resetAlignmentHint( Text ) ) - { - m_data->textInput->setAlignment( alignment() ); - Q_EMIT alignmentChanged(); - } -} - -Qt::Alignment QskTextField::alignment() const -{ - return alignmentHint( Text, Qt::AlignLeft | Qt::AlignTop ); -} - -void QskTextField::setWrapMode( QskTextOptions::WrapMode wrapMode ) -{ - m_data->textInput->setWrapMode( - static_cast< QQuickTextInput::WrapMode >( wrapMode ) ); -} - -QskTextOptions::WrapMode QskTextField::wrapMode() const -{ - return static_cast< QskTextOptions::WrapMode >( - m_data->textInput->wrapMode() ); -} - -QFont QskTextField::font() const -{ - return effectiveFont( QskTextField::Text ); -} - -bool QskTextField::isReadOnly() const -{ - return m_data->textInput->isReadOnly(); -} - -void QskTextField::setReadOnly( bool on ) -{ - auto input = m_data->textInput; - - if ( input->isReadOnly() == on ) - return; - -#if 1 - // do we want to be able to restore the previous policy ? - setFocusPolicy( Qt::NoFocus ); -#endif - - input->setReadOnly( on ); - - // we are killing user settings here ? - input->setFlag( QQuickItem::ItemAcceptsInputMethod, !on ); - qskUpdateInputMethod( this, Qt::ImEnabled ); - - setSkinStateFlag( ReadOnly, on ); -} - -void QskTextField::setEditing( bool on ) -{ - if ( isReadOnly() || on == isEditing() ) - return; - - setSkinStateFlag( Editing, on ); - m_data->textInput->setEditing( on ); - - if ( on ) - { -#if 0 - updateInputMethod( Qt::ImCursorRectangle | Qt::ImAnchorRectangle ); - QGuiApplication::inputMethod()->inputDirection -#endif - qskInputMethodSetVisible( this, true ); - } - else - { - if ( hasAcceptableInput() || fixup() ) - Q_EMIT m_data->textInput->editingFinished(); - -#if 0 - inputMethod->reset(); -#endif - qskInputMethodSetVisible( this, false ); - } - - Q_EMIT editingChanged( on ); -} - -bool QskTextField::isEditing() const -{ - return hasSkinState( Editing ); -} - -void QskTextField::ensureVisible( int position ) -{ - m_data->textInput->ensureVisible( position ); -} - -int QskTextField::cursorPosition() const -{ - return m_data->textInput->cursorPosition(); -} - -void QskTextField::setCursorPosition( int pos ) -{ - m_data->textInput->setCursorPosition( pos ); -} - -int QskTextField::maxLength() const -{ - return m_data->textInput->maxLength(); -} - -void QskTextField::setMaxLength( int length ) -{ - m_data->textInput->setMaxLength( length ); -} - -QValidator* QskTextField::validator() const -{ - return m_data->textInput->validator(); -} - -void QskTextField::setValidator( QValidator* validator ) -{ - m_data->textInput->setValidator( validator ); -} - -QString QskTextField::inputMask() const -{ - return m_data->textInput->inputMask(); -} - -void QskTextField::setInputMask( const QString& mask ) -{ - m_data->textInput->setInputMask( mask ); -} - -QskTextField::EchoMode QskTextField::echoMode() const -{ - const auto mode = m_data->textInput->echoMode(); - return static_cast< QskTextField::EchoMode >( mode ); -} - -void QskTextField::setEchoMode( EchoMode mode ) -{ - if ( mode != echoMode() ) - { - m_data->textInput->setEchoMode( - static_cast< QQuickTextInput::EchoMode >( mode ) ); - - qskUpdateInputMethod( this, Qt::ImHints ); - } -} - -QString QskTextField::passwordCharacter() const -{ - return m_data->textInput->passwordCharacter(); -} - -void QskTextField::setPasswordCharacter( const QString& text ) -{ - m_data->textInput->setPasswordCharacter( text ); -} - -void QskTextField::resetPasswordCharacter() -{ - m_data->textInput->setPasswordCharacter( - QGuiApplication::styleHints()->passwordMaskCharacter() ); -} - -int QskTextField::passwordMaskDelay() const -{ - return m_data->textInput->passwordMaskDelay(); -} - -void QskTextField::setPasswordMaskDelay( int ms ) -{ - m_data->textInput->setPasswordMaskDelay( ms ); -} - -void QskTextField::resetPasswordMaskDelay() -{ - m_data->textInput->resetPasswordMaskDelay(); -} - -QString QskTextField::displayText() const -{ - return m_data->textInput->displayText(); -} - -QString QskTextField::preeditText() const -{ - const auto d = QQuickTextInputPrivate::get( m_data->textInput ); - return d->m_textLayout.preeditAreaText(); -} - -bool QskTextField::overwriteMode() const -{ - return m_data->textInput->overwriteMode(); -} - -void QskTextField::setOverwriteMode( bool overwrite ) -{ - m_data->textInput->setOverwriteMode( overwrite ); -} - -bool QskTextField::hasAcceptableInput() const -{ - return m_data->textInput->hasAcceptableInput(); -} - -bool QskTextField::fixup() -{ - return m_data->textInput->fixup(); -} - -QVariant QskTextField::inputMethodQuery( - Qt::InputMethodQuery property ) const -{ - return inputMethodQuery( property, QVariant() ); -} - -QVariant QskTextField::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->textInput->inputMethodQuery( query, argument ); -#if 1 - if ( v.canConvert< QRectF >() ) - v.setValue( v.toRectF().translated( m_data->textInput->position() ) ); -#endif - return v; - } - default: - { - return m_data->textInput->inputMethodQuery( query, argument ); - } - } -} - -bool QskTextField::canUndo() const -{ - return m_data->textInput->canUndo(); -} - -bool QskTextField::canRedo() const -{ - return m_data->textInput->canRedo(); -} - -Qt::InputMethodHints QskTextField::inputMethodHints() const -{ - return m_data->textInput->inputMethodHints(); -} - -void QskTextField::setInputMethodHints( Qt::InputMethodHints hints ) -{ - if ( m_data->textInput->inputMethodHints() != hints ) - { - m_data->textInput->setInputMethodHints( hints ); - qskUpdateInputMethod( this, Qt::ImHints ); - } -} - -void QskTextField::setupFrom( const QQuickItem* item ) -{ - if ( item == nullptr ) - return; - - // finding attributes from the input hints of item - - int maxCharacters = 32767; - QskTextField::EchoMode echoMode = QskTextField::Normal; - - Qt::InputMethodQueries queries = Qt::ImQueryAll; - queries &= ~Qt::ImEnabled; - - QInputMethodQueryEvent event( queries ); - QCoreApplication::sendEvent( const_cast< QQuickItem* >( item ), &event ); - - if ( event.queries() & Qt::ImHints ) - { - const auto hints = static_cast< Qt::InputMethodHints >( - event.value( Qt::ImHints ).toInt() ); - - if ( hints & Qt::ImhHiddenText ) - echoMode = QskTextField::NoEcho; - } - - 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 ); - } - - setMaxLength( 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 - } - - int passwordMaskDelay = -1; - QString passwordCharacter; - - if ( echoMode == QskTextField::NoEcho ) - { - /* - Qt::ImhHiddenText does not provide information - to decide between NoEcho/Password, or provides - more details about how to deal with hidden inputs. - So we try to find out more from trying some properties. - */ - - QVariant value; - - value = item->property( "passwordMaskDelay" ); - if ( value.canConvert< int >() ) - passwordMaskDelay = value.toInt(); - - value = item->property( "passwordCharacter" ); - if ( value.canConvert< QString >() ) - passwordCharacter = value.toString(); - - value = item->property( "echoMode" ); - if ( value.canConvert< int >() ) - { - const auto mode = value.toInt(); - if ( mode == QskTextField::Password ) - echoMode = QskTextField::Password; - } - } - - if ( passwordMaskDelay >= 0 ) - setPasswordMaskDelay( passwordMaskDelay ); - else - resetPasswordMaskDelay(); - - if ( !passwordCharacter.isEmpty() ) - setPasswordCharacter( passwordCharacter ); - else - resetPasswordCharacter(); - - setEchoMode( echoMode ); -} - #include "moc_QskTextField.cpp" diff --git a/src/controls/QskTextField.h b/src/controls/QskTextField.h index a171d29f5..96d9f5af3 100644 --- a/src/controls/QskTextField.h +++ b/src/controls/QskTextField.h @@ -6,218 +6,34 @@ #ifndef QSK_TEXT_FIELD_H #define QSK_TEXT_FIELD_H -#include "QskControl.h" -#include "QskTextOptions.h" +#include "QskTextInput.h" -class QValidator; -class QskFontRole; - -class QSK_EXPORT QskTextField : public QskControl +class QSK_EXPORT QskTextField : public QskTextInput { Q_OBJECT - Q_PROPERTY( QString text READ text WRITE setText NOTIFY textChanged USER true) - Q_PROPERTY( QString placeholderText READ placeholderText WRITE setPlaceholderText NOTIFY placeholderTextChanged ) - 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( ActivationModes activationModes READ activationModes - WRITE setActivationModes NOTIFY activationModesChanged ) - - Q_PROPERTY( bool editing READ isEditing - WRITE setEditing NOTIFY editingChanged ) - - Q_PROPERTY( EchoMode echoMode READ echoMode - WRITE setEchoMode NOTIFY echoModeChanged ) - - Q_PROPERTY( QString passwordCharacter READ passwordCharacter - WRITE setPasswordCharacter RESET resetPasswordCharacter - NOTIFY passwordCharacterChanged ) - - Q_PROPERTY( int passwordMaskDelay READ passwordMaskDelay - WRITE setPasswordMaskDelay RESET resetPasswordMaskDelay - NOTIFY passwordMaskDelayChanged ) - - Q_PROPERTY( bool panel READ hasPanel - WRITE setPanel NOTIFY panelChanged ) - - using Inherited = QskControl; + using Inherited = QskTextInput; public: - QSK_SUBCONTROLS( Panel, Text, PlaceholderText ) - QSK_STATES( ReadOnly, Editing ) - - enum ActivationMode : quint8 - { - 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 ) - - enum EchoMode : quint8 - { - Normal, - NoEcho, - Password, - PasswordEchoOnEdit - }; - - Q_ENUM( EchoMode ) + QSK_SUBCONTROLS( Panel, PlaceholderText ) QskTextField( QQuickItem* parent = nullptr ); QskTextField( const QString& text, QQuickItem* parent = nullptr ); ~QskTextField() override; - void setupFrom( const QQuickItem* ); - - QString text() const; - void setPlaceholderText( const QString& ); QString placeholderText() const; - void setPanel( bool ); - bool hasPanel() 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 setActivationModes( ActivationModes ); - ActivationModes activationModes() const; - - bool isEditing() const; - - QFont font() const; - - bool isReadOnly() const; - void setReadOnly( bool ); - - int cursorPosition() const; - void setCursorPosition( int ); - - int maxLength() const; - void setMaxLength( int ); - - QValidator* validator() const; - void setValidator( QValidator* ); - - QString inputMask() const; - void setInputMask( const QString& ); - - EchoMode echoMode() const; - void setEchoMode( EchoMode ); - - QString passwordCharacter() const; - void setPasswordCharacter( const QString& ); - void resetPasswordCharacter(); - - int passwordMaskDelay() const; - void setPasswordMaskDelay( int ); - void resetPasswordMaskDelay(); - - QString displayText() const; - QString preeditText() const; - - bool overwriteMode() const; - void setOverwriteMode( bool ); - - bool hasAcceptableInput() const; - bool fixup(); - - 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 ); - - void ensureVisible( int position ); - - public Q_SLOTS: - void setText( const QString& ); - void setEditing( bool ); - Q_SIGNALS: - void editingChanged( bool ); - - void activationModesChanged(); - void readOnlyChanged( bool ); - void panelChanged( bool ); - - void textChanged( const QString& ); - void displayTextChanged( const QString& ); - - void textEdited( const QString& ); void placeholderTextChanged( const QString& ); - void fontRoleChanged(); - void alignmentChanged(); - void wrapModeChanged( QskTextOptions::WrapMode ); - - void overwriteModeChanged( bool ); - void maximumLengthChanged( int ); - - void echoModeChanged( EchoMode ); - void passwordMaskDelayChanged(); - void passwordCharacterChanged(); - - void validatorChanged(); - void inputMaskChanged( const QString& ); - - 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( QskTextField::ActivationModes ) -Q_DECLARE_METATYPE( QskTextField::ActivationModes ) - #endif diff --git a/src/controls/QskTextFieldSkinlet.cpp b/src/controls/QskTextFieldSkinlet.cpp index 4e1a09461..919e22268 100644 --- a/src/controls/QskTextFieldSkinlet.cpp +++ b/src/controls/QskTextFieldSkinlet.cpp @@ -6,27 +6,12 @@ #include "QskTextFieldSkinlet.h" #include "QskTextField.h" -#include - using Q = QskTextField; -QSK_SYSTEM_STATE( QskTextFieldSkinlet, Selected, QskAspect::FirstSystemState << 3 ) - -static QString qskEffectivePlaceholderText( const QskTextField* textField ) -{ - if ( textField->text().isEmpty() && - !( textField->isReadOnly() || textField->isEditing() ) ) - { - return textField->placeholderText(); - } - - return QString(); -} - QskTextFieldSkinlet::QskTextFieldSkinlet( QskSkin* skin ) : Inherited( skin ) { - setNodeRoles( { PanelRole, PlaceholderTextRole, } ); + setNodeRoles( { PanelRole, TextPanelRole, PlaceholderTextRole } ); } QskTextFieldSkinlet::~QskTextFieldSkinlet() @@ -37,14 +22,12 @@ QRectF QskTextFieldSkinlet::subControlRect( const QskSkinnable* skinnable, const QRectF& contentsRect, QskAspect::Subcontrol subControl ) const { if ( subControl == Q::Panel ) - { return contentsRect; - } - else if ( subControl == Q::Text ) - { + + if ( subControl == Q::TextPanel ) return skinnable->subControlContentsRect( contentsRect, Q::Panel ); - } - else if ( subControl == Q::PlaceholderText ) + + if ( subControl == Q::PlaceholderText ) { const auto textField = static_cast< const QskTextField* >( skinnable ); if( textField->text().isEmpty() ) @@ -65,14 +48,12 @@ QSGNode* QskTextFieldSkinlet::updateSubNode( { case PanelRole: { - if ( !textField->hasPanel() ) - return nullptr; - return updateBoxNode( skinnable, node, Q::Panel ); } + case PlaceholderTextRole: { - const auto text = qskEffectivePlaceholderText( textField ); + const auto text = effectivePlaceholderText( textField ); if ( text.isEmpty() ) return nullptr; @@ -95,24 +76,28 @@ QSGNode* QskTextFieldSkinlet::updateSubNode( } QSizeF QskTextFieldSkinlet::sizeHint( const QskSkinnable* skinnable, - Qt::SizeHint which, const QSizeF& ) const + Qt::SizeHint which, const QSizeF& constraint ) const { if ( which != Qt::PreferredSize ) return QSizeF(); - const auto textField = static_cast< const QskTextField* >( skinnable ); - - const QFontMetricsF fm( textField->effectiveFont( Q::Text ) ); + auto hint = Inherited::sizeHint( skinnable, which, constraint ); + hint = skinnable->outerBoxSize( Q::Panel, hint ); + hint = hint.expandedTo( skinnable->strutSizeHint( Q::Panel ) ); - auto hint = fm.size( Qt::TextSingleLine | Qt::TextExpandTabs, textField->text() ); + return hint; +} - if ( textField->hasPanel() ) +QString QskTextFieldSkinlet::effectivePlaceholderText( + const QskTextField* textField ) const +{ + if ( textField->text().isEmpty() && + !( textField->isReadOnly() || textField->isEditing() ) ) { - hint = textField->outerBoxSize( Q::Panel, hint ); - hint = hint.expandedTo( textField->strutSizeHint( Q::Panel ) ); + return textField->placeholderText(); } - return hint; + return QString(); } #include "moc_QskTextFieldSkinlet.cpp" diff --git a/src/controls/QskTextFieldSkinlet.h b/src/controls/QskTextFieldSkinlet.h index 83cfe6400..29225a95c 100644 --- a/src/controls/QskTextFieldSkinlet.h +++ b/src/controls/QskTextFieldSkinlet.h @@ -6,20 +6,21 @@ #ifndef QSK_TEXT_FIELD_SKINLET_H #define QSK_TEXT_FIELD_SKINLET_H -#include "QskSkinlet.h" +#include "QskTextInputSkinlet.h" -class QSK_EXPORT QskTextFieldSkinlet : public QskSkinlet +class QskTextField; + +class QSK_EXPORT QskTextFieldSkinlet : public QskTextInputSkinlet { Q_GADGET - using Inherited = QskSkinlet; + using Inherited = QskTextInputSkinlet; public: - QSK_STATES( Selected ) - enum NodeRole : quint8 { - PanelRole, + PanelRole = QskTextInputSkinlet::RoleCount, + PlaceholderTextRole, RoleCount }; @@ -36,6 +37,8 @@ class QSK_EXPORT QskTextFieldSkinlet : public QskSkinlet protected: QSGNode* updateSubNode( const QskSkinnable*, quint8 nodeRole, QSGNode* ) const override; + + virtual QString effectivePlaceholderText( const QskTextField* ) const; }; #endif diff --git a/src/controls/QskTextInput.cpp b/src/controls/QskTextInput.cpp new file mode 100644 index 000000000..1dc3897f0 --- /dev/null +++ b/src/controls/QskTextInput.cpp @@ -0,0 +1,917 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#include "QskTextInput.h" +#include "QskTextInputSkinlet.h" +#include "QskFontRole.h" +#include "QskQuick.h" + +QSK_QT_PRIVATE_BEGIN +#include +#include +QSK_QT_PRIVATE_END + +QSK_SUBCONTROL( QskTextInput, Text ) +QSK_SUBCONTROL( QskTextInput, TextPanel ) + +QSK_SYSTEM_STATE( QskTextInput, ReadOnly, QskAspect::FirstSystemState << 1 ) +QSK_SYSTEM_STATE( QskTextInput, Editing, QskAspect::FirstSystemState << 2 ) +QSK_SYSTEM_STATE( QskTextInput, Error, QskAspect::FirstSystemState << 4 ) + +static inline void qskPropagateReadOnly( QskTextInput* input ) +{ + Q_EMIT input->readOnlyChanged( input->isReadOnly() ); + + QEvent event( QEvent::ReadOnlyChange ); + QCoreApplication::sendEvent( input, &event ); +} + +static inline void qskBindSignals( + const QQuickTextInput* wrappedInput, QskTextInput* input ) +{ + QObject::connect( wrappedInput, &QQuickTextInput::textChanged, + input, [ input ] { Q_EMIT input->textChanged( input->text() ); } ); + + QObject::connect( wrappedInput, &QQuickTextInput::displayTextChanged, + input, [ input ] { Q_EMIT input->displayTextChanged( input->displayText() ); } ); + + QObject::connect( wrappedInput, &QQuickTextInput::textEdited, + input, [ input ] { Q_EMIT input->textEdited( input->text() ); } ); + + QObject::connect( wrappedInput, &QQuickTextInput::validatorChanged, + input, &QskTextInput::validatorChanged ); + + QObject::connect( wrappedInput, &QQuickTextInput::inputMaskChanged, + input, &QskTextInput::inputMaskChanged ); + + QObject::connect( wrappedInput, &QQuickTextInput::readOnlyChanged, + input, [ input ] { qskPropagateReadOnly( input ); } ); + + QObject::connect( wrappedInput, &QQuickTextInput::overwriteModeChanged, + input, &QskTextInput::overwriteModeChanged ); + + QObject::connect( wrappedInput, &QQuickTextInput::maximumLengthChanged, + input, &QskTextInput::maximumLengthChanged ); + + QObject::connect( wrappedInput, &QQuickTextInput::wrapModeChanged, + input, [ input ] { Q_EMIT input->wrapModeChanged( input->wrapMode() ); } ); + + QObject::connect( wrappedInput, &QQuickTextInput::echoModeChanged, + input, [ input ] { Q_EMIT input->echoModeChanged( input->echoMode() ); } ); + + QObject::connect( wrappedInput, &QQuickTextInput::passwordCharacterChanged, + input, &QskTextInput::passwordCharacterChanged ); + + QObject::connect( wrappedInput, &QQuickTextInput::passwordMaskDelayChanged, + input, &QskTextInput::passwordMaskDelayChanged ); + + QObject::connect( wrappedInput, &QQuickItem::implicitWidthChanged, + input, &QskControl::resetImplicitSize ); + + QObject::connect( wrappedInput, &QQuickItem::implicitHeightChanged, + input, &QskControl::resetImplicitSize ); +} + +namespace +{ + class QuickTextInput final : public QQuickTextInput + { + using Inherited = QQuickTextInput; + + public: + QuickTextInput( QskTextInput* ); + + void setEditing( bool on ); + + inline void setAlignment( Qt::Alignment alignment ) + { + setHAlign( ( HAlignment ) ( int( alignment ) & 0x0f ) ); + setVAlign( ( VAlignment ) ( int( alignment ) & 0xf0 ) ); + } + + bool fixup() + { + return QQuickTextInputPrivate::get( this )->fixup(); + } + + bool hasAcceptableInput() const + { + /* + we would like to call QQuickTextInputPrivate::hasAcceptableInput + but unfortunately it is private, so we need to hack somthing + together + */ + + auto that = const_cast< QuickTextInput* >( this ); + auto d = QQuickTextInputPrivate::get( that ); + + if ( d->m_validator ) + { + QString text = displayText(); + int pos = d->m_cursor; + + const auto state = d->m_validator->validate( text, pos ); + if ( state != QValidator::Acceptable ) + return false; + } + + if ( d->m_maskData ) + { + /* + We only want to do the check for the maskData here + and have to disable d->m_validator temporarily + */ + + class Validator final : public QValidator + { + public: + State validate( QString&, int& ) const override + { + return QValidator::Acceptable; + } + }; + + const auto validator = d->m_validator; + + const auto validInput = d->m_validInput; + const auto acceptableInput = d->m_acceptableInput; + + d->m_acceptableInput = true; + + static Validator noValidator; + that->setValidator( &noValidator ); // implicitly checking maskData + that->setValidator( d->m_validator ); + + const bool isAcceptable = d->m_acceptableInput; + + // restoring old values + d->m_validInput = validInput; + d->m_acceptableInput = acceptableInput; + + return isAcceptable; + } + + return true; + } + + void updateColors(); + void updateMetrics(); + + inline bool handleEvent( QEvent* event ) + { + return this->event( event ); + } + + 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 ); + } + }; + + QuickTextInput::QuickTextInput( QskTextInput* textInput ) + : QQuickTextInput( textInput ) + { + classBegin(); + + setActiveFocusOnTab( false ); + setFlag( ItemAcceptsInputMethod, false ); + setFocusOnPress( false ); + + componentComplete(); + + connect( this, &QQuickTextInput::contentSizeChanged, + this, &QuickTextInput::updateClip ); + } + + void QuickTextInput::setEditing( bool on ) + { + auto d = QQuickTextInputPrivate::get( this ); + + if ( d->cursorVisible == on ) + return; + + setCursorVisible( on ); + d->setBlinkingCursorEnabled( on ); + + if ( !on ) + { + if ( d->m_passwordEchoEditing || d->m_passwordEchoTimer.isActive() ) + d->updatePasswordEchoEditing( false ); + } + + polish(); + update(); + } + + void QuickTextInput::updateMetrics() + { + auto textInput = static_cast< const QskTextInput* >( parentItem() ); + + setAlignment( textInput->alignment() ); + setFont( textInput->font() ); + } + + void QuickTextInput::updateColors() + { + auto textInput = static_cast< const QskTextInput* >( parentItem() ); + auto d = QQuickTextInputPrivate::get( this ); + + bool isDirty = false; + + QColor color; + + color = textInput->color( QskTextInput::Text ); + if ( d->color != color ) + { + d->color = color; + isDirty = true; + } + + if ( d->hasSelectedText() ) + { + QskAspect::States states = QskTextInputSkinlet::Selected; +#if 0 + states |= textInput->skinStates(); +#endif + + color = textInput->color( QskTextInput::TextPanel | states ); + if ( d->selectionColor != color ) + { + d->selectionColor = color; + isDirty = true; + } + + color = textInput->color( QskTextInput::Text | states ); + if ( d->selectedTextColor != color ) + { + d->selectedTextColor = color; + isDirty = true; + } + } + + if ( isDirty ) + { + d->textLayoutDirty = true; + d->updateType = QQuickTextInputPrivate::UpdatePaintNode; + update(); + } + } +} + +class QskTextInput::PrivateData +{ + public: + QuickTextInput* wrappedInput; + + ActivationModes activationModes; +}; + +QskTextInput::QskTextInput( QQuickItem* parent ) + : Inherited( parent ) + , m_data( new PrivateData() ) +{ + m_data->activationModes = ActivationOnMouse | ActivationOnKey; + + setPolishOnResize( true ); + + setAcceptHoverEvents( true ); + setFocusPolicy( Qt::StrongFocus ); + + setFlag( QQuickItem::ItemAcceptsInputMethod ); + + /* + QQuickTextInput is a beast of almost 5k lines of code, we don't + want to reimplement that - at least not now. + So QskTextInput is more or less a simple wrapper making everything + conforming to qskinny. + */ + + m_data->wrappedInput = new QuickTextInput( this ); + qskBindSignals( m_data->wrappedInput, this ); + + setAcceptedMouseButtons( m_data->wrappedInput->acceptedMouseButtons() ); + m_data->wrappedInput->setAcceptedMouseButtons( Qt::NoButton ); + + initSizePolicy( QskSizePolicy::Expanding, QskSizePolicy::Fixed ); +} + +QskTextInput::~QskTextInput() +{ +} + +bool QskTextInput::event( QEvent* event ) +{ + if ( event->type() == QEvent::ShortcutOverride ) + { + return m_data->wrappedInput->handleEvent( event ); + } + else if ( event->type() == QEvent::LocaleChange ) + { + qskUpdateInputMethod( this, Qt::ImPreferredLanguage ); + } + + return Inherited::event( event ); +} + +void QskTextInput::keyPressEvent( QKeyEvent* event ) +{ + if ( isEditing() ) + { + switch ( event->key() ) + { + case Qt::Key_Enter: + case Qt::Key_Return: + { + if ( hasAcceptableInput() || fixup() ) + { + 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->wrappedInput->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 QskTextInput::keyReleaseEvent( QKeyEvent* event ) +{ + Inherited::keyReleaseEvent( event ); +} + +void QskTextInput::mousePressEvent( QMouseEvent* event ) +{ + m_data->wrappedInput->handleEvent( event ); + + if ( !isReadOnly() && !qGuiApp->styleHints()->setFocusOnTouchRelease() ) + setEditing( true ); +} + +void QskTextInput::mouseMoveEvent( QMouseEvent* event ) +{ + m_data->wrappedInput->handleEvent( event ); +} + +void QskTextInput::mouseReleaseEvent( QMouseEvent* event ) +{ + m_data->wrappedInput->handleEvent( event ); + + if ( !isReadOnly() && qGuiApp->styleHints()->setFocusOnTouchRelease() ) + setEditing( true ); +} + +void QskTextInput::mouseDoubleClickEvent( QMouseEvent* event ) +{ + m_data->wrappedInput->handleEvent( event ); +} + +void QskTextInput::inputMethodEvent( QInputMethodEvent* event ) +{ + m_data->wrappedInput->handleEvent( event ); +} + +void QskTextInput::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 QskTextInput::focusOutEvent( QFocusEvent* event ) +{ + switch ( event->reason() ) + { + case Qt::ActiveWindowFocusReason: + case Qt::PopupFocusReason: + { + break; + } + default: + { + m_data->wrappedInput->deselect(); + setEditing( false ); + } + } + + Inherited::focusOutEvent( event ); +} + +void QskTextInput::updateLayout() +{ + m_data->wrappedInput->updateMetrics(); + qskSetItemGeometry( m_data->wrappedInput, subControlRect( Text ) ); +} + +void QskTextInput::updateNode( QSGNode* node ) +{ + m_data->wrappedInput->updateColors(); + Inherited::updateNode( node ); +} + +QString QskTextInput::text() const +{ + return m_data->wrappedInput->text(); +} + +void QskTextInput::setText( const QString& text ) +{ + m_data->wrappedInput->setText( text ); +} + +QskTextInput::ActivationModes QskTextInput::activationModes() const +{ + return static_cast< QskTextInput::ActivationModes >( m_data->activationModes ); +} + +void QskTextInput::setActivationModes( ActivationModes modes ) +{ + if ( static_cast< ActivationModes >( m_data->activationModes ) != modes ) + { + m_data->activationModes = modes; + Q_EMIT activationModesChanged(); + } +} + +static inline void qskUpdateInputMethodFont( const QskTextInput* input ) +{ + const auto queries = Qt::ImCursorRectangle | Qt::ImFont | Qt::ImAnchorRectangle; + qskUpdateInputMethod( input, queries ); +} + +void QskTextInput::setFontRole( const QskFontRole& role ) +{ + if ( setFontRoleHint( Text, role ) ) + { + qskUpdateInputMethodFont( this ); + Q_EMIT fontRoleChanged(); + } +} + +void QskTextInput::resetFontRole() +{ + if ( resetFontRoleHint( Text ) ) + { + qskUpdateInputMethodFont( this ); + Q_EMIT fontRoleChanged(); + } +} + +QskFontRole QskTextInput::fontRole() const +{ + return fontRoleHint( Text ); +} + +void QskTextInput::setAlignment( Qt::Alignment alignment ) +{ + if ( setAlignmentHint( Text, alignment ) ) + { + m_data->wrappedInput->setAlignment( alignment ); + Q_EMIT alignmentChanged(); + } +} + +void QskTextInput::resetAlignment() +{ + if ( resetAlignmentHint( Text ) ) + { + m_data->wrappedInput->setAlignment( alignment() ); + Q_EMIT alignmentChanged(); + } +} + +Qt::Alignment QskTextInput::alignment() const +{ + return alignmentHint( Text, Qt::AlignLeft | Qt::AlignTop ); +} + +void QskTextInput::setWrapMode( QskTextOptions::WrapMode wrapMode ) +{ + m_data->wrappedInput->setWrapMode( + static_cast< QQuickTextInput::WrapMode >( wrapMode ) ); +} + +QskTextOptions::WrapMode QskTextInput::wrapMode() const +{ + return static_cast< QskTextOptions::WrapMode >( + m_data->wrappedInput->wrapMode() ); +} + +QFont QskTextInput::font() const +{ + return effectiveFont( QskTextInput::Text ); +} + +bool QskTextInput::isReadOnly() const +{ + return m_data->wrappedInput->isReadOnly(); +} + +void QskTextInput::setReadOnly( bool on ) +{ + auto input = m_data->wrappedInput; + + if ( input->isReadOnly() == on ) + return; + +#if 1 + // do we want to be able to restore the previous policy ? + setFocusPolicy( Qt::NoFocus ); +#endif + + input->setReadOnly( on ); + + // we are killing user settings here ? + input->setFlag( QQuickItem::ItemAcceptsInputMethod, !on ); + qskUpdateInputMethod( this, Qt::ImEnabled ); + + setSkinStateFlag( ReadOnly, on ); +} + +void QskTextInput::setEditing( bool on ) +{ + if ( isReadOnly() || on == isEditing() ) + return; + + setSkinStateFlag( Editing, on ); + m_data->wrappedInput->setEditing( on ); + + if ( on ) + { +#if 0 + updateInputMethod( Qt::ImCursorRectangle | Qt::ImAnchorRectangle ); + QGuiApplication::inputMethod()->inputDirection +#endif + qskInputMethodSetVisible( this, true ); + } + else + { + if ( hasAcceptableInput() || fixup() ) + Q_EMIT m_data->wrappedInput->editingFinished(); + +#if 0 + inputMethod->reset(); +#endif + qskInputMethodSetVisible( this, false ); + } + + Q_EMIT editingChanged( on ); +} + +bool QskTextInput::isEditing() const +{ + return hasSkinState( Editing ); +} + +void QskTextInput::ensureVisible( int position ) +{ + m_data->wrappedInput->ensureVisible( position ); +} + +int QskTextInput::cursorPosition() const +{ + return m_data->wrappedInput->cursorPosition(); +} + +void QskTextInput::setCursorPosition( int pos ) +{ + m_data->wrappedInput->setCursorPosition( pos ); +} + +int QskTextInput::maxLength() const +{ + return m_data->wrappedInput->maxLength(); +} + +void QskTextInput::setMaxLength( int length ) +{ + m_data->wrappedInput->setMaxLength( length ); +} + +QValidator* QskTextInput::validator() const +{ + return m_data->wrappedInput->validator(); +} + +void QskTextInput::setValidator( QValidator* validator ) +{ + m_data->wrappedInput->setValidator( validator ); +} + +QString QskTextInput::inputMask() const +{ + return m_data->wrappedInput->inputMask(); +} + +void QskTextInput::setInputMask( const QString& mask ) +{ + m_data->wrappedInput->setInputMask( mask ); +} + +QskTextInput::EchoMode QskTextInput::echoMode() const +{ + const auto mode = m_data->wrappedInput->echoMode(); + return static_cast< QskTextInput::EchoMode >( mode ); +} + +void QskTextInput::setEchoMode( EchoMode mode ) +{ + if ( mode != echoMode() ) + { + m_data->wrappedInput->setEchoMode( + static_cast< QQuickTextInput::EchoMode >( mode ) ); + + qskUpdateInputMethod( this, Qt::ImHints ); + } +} + +QString QskTextInput::passwordCharacter() const +{ + return m_data->wrappedInput->passwordCharacter(); +} + +void QskTextInput::setPasswordCharacter( const QString& text ) +{ + m_data->wrappedInput->setPasswordCharacter( text ); +} + +void QskTextInput::resetPasswordCharacter() +{ + m_data->wrappedInput->setPasswordCharacter( + QGuiApplication::styleHints()->passwordMaskCharacter() ); +} + +int QskTextInput::passwordMaskDelay() const +{ + return m_data->wrappedInput->passwordMaskDelay(); +} + +void QskTextInput::setPasswordMaskDelay( int ms ) +{ + m_data->wrappedInput->setPasswordMaskDelay( ms ); +} + +void QskTextInput::resetPasswordMaskDelay() +{ + m_data->wrappedInput->resetPasswordMaskDelay(); +} + +QString QskTextInput::displayText() const +{ + return m_data->wrappedInput->displayText(); +} + +QString QskTextInput::preeditText() const +{ + const auto d = QQuickTextInputPrivate::get( m_data->wrappedInput ); + return d->m_textLayout.preeditAreaText(); +} + +bool QskTextInput::overwriteMode() const +{ + return m_data->wrappedInput->overwriteMode(); +} + +void QskTextInput::setOverwriteMode( bool overwrite ) +{ + m_data->wrappedInput->setOverwriteMode( overwrite ); +} + +bool QskTextInput::hasAcceptableInput() const +{ + return m_data->wrappedInput->hasAcceptableInput(); +} + +bool QskTextInput::fixup() +{ + return m_data->wrappedInput->fixup(); +} + +QVariant QskTextInput::inputMethodQuery( + Qt::InputMethodQuery property ) const +{ + return inputMethodQuery( property, QVariant() ); +} + +QVariant QskTextInput::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->wrappedInput->inputMethodQuery( query, argument ); +#if 1 + if ( v.canConvert< QRectF >() ) + v.setValue( v.toRectF().translated( m_data->wrappedInput->position() ) ); +#endif + return v; + } + default: + { + return m_data->wrappedInput->inputMethodQuery( query, argument ); + } + } +} + +Qt::InputMethodHints QskTextInput::inputMethodHints() const +{ + return m_data->wrappedInput->inputMethodHints(); +} + +void QskTextInput::setInputMethodHints( Qt::InputMethodHints hints ) +{ + if ( m_data->wrappedInput->inputMethodHints() != hints ) + { + m_data->wrappedInput->setInputMethodHints( hints ); + qskUpdateInputMethod( this, Qt::ImHints ); + } +} + +void QskTextInput::setupFrom( const QQuickItem* item ) +{ + if ( item == nullptr ) + return; + + // finding attributes from the input hints of item + + int maxCharacters = 32767; + QskTextInput::EchoMode echoMode = QskTextInput::Normal; + + Qt::InputMethodQueries queries = Qt::ImQueryAll; + queries &= ~Qt::ImEnabled; + + QInputMethodQueryEvent event( queries ); + QCoreApplication::sendEvent( const_cast< QQuickItem* >( item ), &event ); + + if ( event.queries() & Qt::ImHints ) + { + const auto hints = static_cast< Qt::InputMethodHints >( + event.value( Qt::ImHints ).toInt() ); + + if ( hints & Qt::ImhHiddenText ) + echoMode = QskTextInput::NoEcho; + } + + 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 ); + } + + setMaxLength( 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 + } + + int passwordMaskDelay = -1; + QString passwordCharacter; + + if ( echoMode == QskTextInput::NoEcho ) + { + /* + Qt::ImhHiddenText does not provide information + to decide between NoEcho/Password, or provides + more details about how to deal with hidden inputs. + So we try to find out more from trying some properties. + */ + + QVariant value; + + value = item->property( "passwordMaskDelay" ); + if ( value.canConvert< int >() ) + passwordMaskDelay = value.toInt(); + + value = item->property( "passwordCharacter" ); + if ( value.canConvert< QString >() ) + passwordCharacter = value.toString(); + + value = item->property( "echoMode" ); + if ( value.canConvert< int >() ) + { + const auto mode = value.toInt(); + if ( mode == QskTextInput::Password ) + echoMode = QskTextInput::Password; + } + } + + if ( passwordMaskDelay >= 0 ) + setPasswordMaskDelay( passwordMaskDelay ); + else + resetPasswordMaskDelay(); + + if ( !passwordCharacter.isEmpty() ) + setPasswordCharacter( passwordCharacter ); + else + resetPasswordCharacter(); + + setEchoMode( echoMode ); +} + +#include "moc_QskTextInput.cpp" diff --git a/src/controls/QskTextInput.h b/src/controls/QskTextInput.h new file mode 100644 index 000000000..f13e50d3b --- /dev/null +++ b/src/controls/QskTextInput.h @@ -0,0 +1,207 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#ifndef QSK_TEXT_INPUT_H +#define QSK_TEXT_INPUT_H + +#include "QskControl.h" +#include "QskTextOptions.h" + +class QValidator; +class QskFontRole; + +class QSK_EXPORT QskTextInput : 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( ActivationModes activationModes READ activationModes + WRITE setActivationModes NOTIFY activationModesChanged ) + + Q_PROPERTY( bool editing READ isEditing + WRITE setEditing NOTIFY editingChanged ) + + Q_PROPERTY( EchoMode echoMode READ echoMode + WRITE setEchoMode NOTIFY echoModeChanged ) + + Q_PROPERTY( QString passwordCharacter READ passwordCharacter + WRITE setPasswordCharacter RESET resetPasswordCharacter + NOTIFY passwordCharacterChanged ) + + Q_PROPERTY( int passwordMaskDelay READ passwordMaskDelay + WRITE setPasswordMaskDelay RESET resetPasswordMaskDelay + NOTIFY passwordMaskDelayChanged ) + + using Inherited = QskControl; + + public: + QSK_SUBCONTROLS( TextPanel, Text ) + + QSK_STATES( ReadOnly, Editing, Error ) + + enum ActivationMode : quint8 + { + 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 ) + + enum EchoMode : quint8 + { + Normal, + NoEcho, + Password, + PasswordEchoOnEdit + }; + + Q_ENUM( EchoMode ) + + ~QskTextInput() 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 setActivationModes( ActivationModes ); + ActivationModes activationModes() const; + + bool isEditing() const; + + QFont font() const; + + bool isReadOnly() const; + void setReadOnly( bool ); + + int cursorPosition() const; + void setCursorPosition( int ); + + int maxLength() const; + void setMaxLength( int ); + + QValidator* validator() const; + void setValidator( QValidator* ); + + QString inputMask() const; + void setInputMask( const QString& ); + + EchoMode echoMode() const; + void setEchoMode( EchoMode ); + + QString passwordCharacter() const; + void setPasswordCharacter( const QString& ); + void resetPasswordCharacter(); + + int passwordMaskDelay() const; + void setPasswordMaskDelay( int ); + void resetPasswordMaskDelay(); + + QString displayText() const; + QString preeditText() const; + + bool overwriteMode() const; + void setOverwriteMode( bool ); + + bool hasAcceptableInput() const; + bool fixup(); + + QVariant inputMethodQuery( Qt::InputMethodQuery ) const override; + QVariant inputMethodQuery( Qt::InputMethodQuery, const QVariant& argument ) const; + + Qt::InputMethodHints inputMethodHints() const; + void setInputMethodHints( Qt::InputMethodHints ); + + void ensureVisible( int position ); + + public Q_SLOTS: + void setText( const QString& ); + void setEditing( bool ); + + Q_SIGNALS: + void editingChanged( bool ); + + void activationModesChanged(); + void readOnlyChanged( bool ); + + void textChanged( const QString& ); + void displayTextChanged( const QString& ); + + void textEdited( const QString& ); + + void fontRoleChanged(); + void alignmentChanged(); + void wrapModeChanged( QskTextOptions::WrapMode ); + + void overwriteModeChanged( bool ); + void maximumLengthChanged( int ); + + void echoModeChanged( EchoMode ); + void passwordMaskDelayChanged(); + void passwordCharacterChanged(); + + void validatorChanged(); + void inputMaskChanged( const QString& ); + + protected: + QskTextInput( QQuickItem* parent = nullptr ); + + 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( QskTextInput::ActivationModes ) +Q_DECLARE_METATYPE( QskTextInput::ActivationModes ) + +#endif diff --git a/src/controls/QskTextInputSkinlet.cpp b/src/controls/QskTextInputSkinlet.cpp new file mode 100644 index 000000000..fe52b92b5 --- /dev/null +++ b/src/controls/QskTextInputSkinlet.cpp @@ -0,0 +1,74 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#include "QskTextInputSkinlet.h" +#include "QskTextInput.h" + +#include + +using Q = QskTextInput; + +QSK_SYSTEM_STATE( QskTextInputSkinlet, Selected, QskAspect::FirstSystemState << 3 ) + +QskTextInputSkinlet::QskTextInputSkinlet( QskSkin* skin ) + : Inherited( skin ) +{ + setNodeRoles( { TextPanelRole } ); +} + +QskTextInputSkinlet::~QskTextInputSkinlet() +{ +} + +QRectF QskTextInputSkinlet::subControlRect( const QskSkinnable* skinnable, + const QRectF& contentsRect, QskAspect::Subcontrol subControl ) const +{ + if ( subControl == Q::TextPanel ) + return contentsRect; + + if ( subControl == Q::Text ) + { + auto rect = skinnable->subControlContentsRect( contentsRect, Q::TextPanel ); + + const auto h = skinnable->effectiveFontHeight( Q::Text ); + rect.setTop( rect.center().y() - 0.5 * h ); + rect.setHeight( h ); + rect = rect.marginsAdded( skinnable->marginHint( Q::Text ) ); + + return rect; + } + + return Inherited::subControlRect( skinnable, contentsRect, subControl ); +} + +QSGNode* QskTextInputSkinlet::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 QskTextInputSkinlet::sizeHint( const QskSkinnable* skinnable, + Qt::SizeHint which, const QSizeF& ) const +{ + if ( which != Qt::PreferredSize ) + return QSizeF(); + + const auto text = static_cast< const QskTextInput* >( skinnable )->text(); + + const QFontMetricsF fm( skinnable->effectiveFont( Q::Text ) ); + + auto hint = fm.size( Qt::TextSingleLine | Qt::TextExpandTabs, text ); + + hint = skinnable->outerBoxSize( Q::TextPanel, hint ); + hint = hint.expandedTo( skinnable->strutSizeHint( Q::TextPanel ) ); + + return hint; +} diff --git a/src/controls/QskTextInputSkinlet.h b/src/controls/QskTextInputSkinlet.h new file mode 100644 index 000000000..e36081c55 --- /dev/null +++ b/src/controls/QskTextInputSkinlet.h @@ -0,0 +1,39 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#ifndef QSK_TEXT_INPUT_SKINLET_H +#define QSK_TEXT_INPUT_SKINLET_H + +#include "QskSkinlet.h" + +class QSK_EXPORT QskTextInputSkinlet : public QskSkinlet +{ + using Inherited = QskSkinlet; + + public: + QSK_STATES( Selected ) + + enum NodeRole : quint8 + { + TextPanelRole, + RoleCount + }; + + ~QskTextInputSkinlet() override; + + QRectF subControlRect( const QskSkinnable*, + const QRectF& rect, QskAspect::Subcontrol ) const override; + + QSizeF sizeHint( const QskSkinnable*, + Qt::SizeHint, const QSizeF& ) const override; + + protected: + QskTextInputSkinlet( QskSkin* = nullptr ); + + QSGNode* updateSubNode( const QskSkinnable*, + quint8 nodeRole, QSGNode* ) const override; +}; + +#endif diff --git a/src/inputpanel/QskInputPanelBox.cpp b/src/inputpanel/QskInputPanelBox.cpp index 597855d19..50ac0314a 100644 --- a/src/inputpanel/QskInputPanelBox.cpp +++ b/src/inputpanel/QskInputPanelBox.cpp @@ -32,7 +32,7 @@ namespace QskAspect::Subcontrol substitutedSubcontrol( QskAspect::Subcontrol subControl ) const override { - if ( subControl == QskTextField::Panel ) + if ( subControl == QskTextField::TextPanel ) return m_panelBox->effectiveSubcontrol( QskInputPanelBox::ProxyPanel ); if ( subControl == QskTextField::Text ) @@ -184,7 +184,7 @@ QskAspect::Subcontrol QskInputPanelBox::substitutedSubcontrol( #if 1 // TODO ... if ( subControl == QskInputPanelBox::ProxyPanel ) - return QskTextField::Panel; + return QskTextField::TextPanel; if ( subControl == QskInputPanelBox::ProxyText ) return QskTextField::Text;