From 09d4e367f528472ab97d17ddf3b31ab92720a045 Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Wed, 29 Jan 2025 11:45:31 +0100 Subject: [PATCH] ongoing work for QskTextEdit/QskTextInput --- examples/gallery/inputs/InputPage.cpp | 4 +- src/controls/QskAbstractTextInput.cpp | 471 ++++++++++---------------- src/controls/QskAbstractTextInput.h | 12 +- src/controls/QskTextEdit.cpp | 57 +++- src/controls/QskTextEdit.h | 5 +- src/controls/QskTextInput.cpp | 82 ++++- src/controls/QskTextInput.h | 3 +- 7 files changed, 303 insertions(+), 331 deletions(-) diff --git a/examples/gallery/inputs/InputPage.cpp b/examples/gallery/inputs/InputPage.cpp index 4487fb18..ed0c9367 100644 --- a/examples/gallery/inputs/InputPage.cpp +++ b/examples/gallery/inputs/InputPage.cpp @@ -77,7 +77,7 @@ namespace field->setPlaceholderText( "" ); connect( field, &QskTextField::textChanged, - []( const QString& text ) { qDebug() << "Text:" << text; } ); + [field]() { qDebug() << "Text:" << field->text(); } ); } @@ -113,7 +113,7 @@ connect( field, &QskTextField::textChanged, #if 1 connect( textArea, &QskTextArea::textChanged, - []( const QString& text ) { qDebug() << "Text:" << text; } ); + this, [textArea]() { qDebug() << "Text:" << textArea->text(); } ); #endif } } diff --git a/src/controls/QskAbstractTextInput.cpp b/src/controls/QskAbstractTextInput.cpp index 530d755a..9dfa1eb8 100644 --- a/src/controls/QskAbstractTextInput.cpp +++ b/src/controls/QskAbstractTextInput.cpp @@ -7,20 +7,20 @@ #include "QskFontRole.h" #include "QskQuick.h" #include "QskEvent.h" -#include "QskMetaInvokable.h" #include "QskInternalMacros.h" +#include +#include + QSK_QT_PRIVATE_BEGIN #include #include -#include #if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 ) +#include #include #endif -#include - QSK_QT_PRIVATE_END QSK_SUBCONTROL( QskAbstractTextInput, Text ) @@ -29,12 +29,6 @@ QSK_SYSTEM_STATE( QskAbstractTextInput, ReadOnly, QskAspect::FirstSystemState << QSK_SYSTEM_STATE( QskAbstractTextInput, Editing, QskAspect::FirstSystemState << 2 ) QSK_SYSTEM_STATE( QskAbstractTextInput, Selected, QskAspect::FirstSystemState << 3 ) -static inline void qskUpdateInputMethodFont( const QskAbstractTextInput* input ) -{ - const auto queries = Qt::ImCursorRectangle | Qt::ImFont | Qt::ImAnchorRectangle; - qskUpdateInputMethod( input, queries ); -} - static inline QVariant qskInputMethodQuery( const QQuickItem* item, Qt::InputMethodQuery query, QVariant argument ) { @@ -47,26 +41,6 @@ static inline QVariant qskInputMethodQuery( return QVariant(); } -static inline Qt::InputMethodHints qskInputMethodHints( const QQuickItem* item ) -{ - if ( auto input = qobject_cast< const QQuickTextInput* >( item ) ) - return input->inputMethodHints(); - - if ( auto edit = qobject_cast< const QQuickTextEdit* >( item ) ) - return edit->inputMethodHints(); - - return Qt::InputMethodHints(); -} - -static inline void qskSetInputMethodHints( - QQuickItem* item, Qt::InputMethodHints hints ) -{ - if ( auto input = qobject_cast< QQuickTextInput* >( item ) ) - input->setInputMethodHints( hints ); - else if ( auto edit = qobject_cast< QQuickTextEdit* >( item ) ) - edit->setInputMethodHints( hints ); -} - inline void qskSetAlignment( QQuickItem* item, Qt::Alignment alignment ) { item->setProperty( "horizontalAlignment", int( alignment ) & 0x0f ); @@ -119,200 +93,36 @@ static inline void qskForwardEvent( QQuickItem* item, QEvent* event ) } } -static inline bool qskIsIrrelevantProperty( const char* name ) -{ - static const char* properties[] = - { - /* - these are properties that are set from skin hints. - do we want to have convenience setters/getters for these hints ? - */ - "color", - "selectionColor", - "selectedTextColor", - - /* - covered by the alignment property - */ - "horizontalAlignment", - "effectiveHorizontalAlignment", - "verticalAlignment", - - /* - we don't want to offer cursorDelegates as this would be done - using subcontrols. - */ - "cursorRectangle", - "cursorDelegate", - - /* - hasSelectedText ? - */ - "selectionStart", - "selectionEnd", - "selectedText", - - /* - covered by QskAbstractTextInput::ActivationMode - */ - - "activeFocusOnPress", - - /* - These poperties correspond to Qt::TextInteractionFlags - we can't simply forward them as mouseSelectionMode returns - local enums of QQuickTextEdit and QQuickTextInput - */ - "selectByMouse", - "selectByKeyboard", - "mouseSelectionMode", - - /* - covered by unwrappedTextSize - */ - "contentWidth", - "contentHeight", - "paintedWidth", - "paintedHeight", - - /* - unused we have our own padding from the skin hints - */ - "padding", - "topPadding", - "leftPadding", - "rightPadding", - "bottomPadding", - - /* - ends up in QTextDocument::documentMargin. But how is its - effect different to what you get from the padding ? - */ - "textMargin", - - /* - not covered so far, but IMHO of little relevance - */ - "renderType" - }; - - for ( const auto n : properties ) - { - if ( strcmp( name, n ) == 0 ) - return true; - } - - return false; -} - -namespace -{ - class PropertyBinder : public QObject - { - Q_OBJECT - - public: - void setup( const QObject*, const QMetaObject*, QObject* ); - - private: - Q_INVOKABLE void forwardNotification() const; - - typedef QPair< int, QMetaMethod > Binding; - typedef QHash< int, Binding > BindingTable; - - Binding bindingOf( const QMetaObject*, int methodIndex ) const; - static QMap< const QMetaObject*, BindingTable > m_tables; - }; - - QMap< const QMetaObject*, PropertyBinder::BindingTable > PropertyBinder::m_tables; - - void PropertyBinder::setup( const QObject* input, - const QMetaObject* mo, QObject* proxy ) - { - setParent( proxy ); - - auto& hash = m_tables[ mo ]; - - if ( hash.isEmpty() ) - { - for ( int i = mo->propertyOffset(); i < mo->propertyCount(); i++ ) - { - const auto property = mo->property( i ); - - const auto signalIndex = property.notifySignalIndex(); - if ( signalIndex >= 0 ) - { - const auto moProxy = proxy->metaObject(); - - const int idx = moProxy->indexOfProperty( property.name() ); - if ( idx >= 0 ) - { - const auto method = moProxy->property( idx ).notifySignal(); - if ( method.isValid() ) - hash.insert( signalIndex, { i, method } ); - } - else - { - if ( !qskIsIrrelevantProperty( property.name() ) ) - qDebug() << "Missing:" << mo->className() << property.name(); - } - } - } - } - - const int idx = staticMetaObject.indexOfMethod( "forwardNotification()" ); - const auto forwardMethod = staticMetaObject.method( idx ); - - for ( auto it = hash.constBegin(); it != hash.constEnd(); ++it ) - { - connect( input, input->metaObject()->method( it.key() ), - this, forwardMethod, Qt::DirectConnection ); - } - } - - void PropertyBinder::forwardNotification() const - { - if ( thread() != QThread::currentThread() ) - return; - - const auto mo = sender()->metaObject(); - - const auto binding = bindingOf( mo, senderSignalIndex() ); - if ( !binding.second.isValid() ) - return; - - const auto property = mo->property( binding.first ); - - const auto value = property.read( sender() ); -#if 0 - qDebug() << property.name() << value - << binding.second.methodSignature(); -#endif - - void* args[3] = { nullptr, const_cast< void* >( value.data() ), nullptr }; - - qskInvokeMetaMethod( parent(), binding.second, args, Qt::DirectConnection ); - } - - PropertyBinder::Binding PropertyBinder::bindingOf( - const QMetaObject* metaObject, int methodIndex ) const - { - const auto method = metaObject->method( methodIndex ); - - const auto& hash = m_tables[ method.enclosingMetaObject() ]; - return hash.value( methodIndex ); - } -} - class QskAbstractTextInput::PrivateData { public: ActivationModes activationModes; - PropertyBinder binder; QQuickItem* input = nullptr; + QQuickTextInput* textInput = nullptr; + QQuickTextEdit* textEdit = nullptr; }; +#define INPUT_INVOKE(func) \ + ( m_data->textInput ? m_data->textInput->func() : m_data->textEdit->func() ) + +#define INPUT_INVOKE_ARG(func, arg) \ + ( m_data->textInput ? m_data->textInput->func( arg ) : m_data->textEdit->func( arg ) ) + +#define INPUT_CONNECT( func ) \ + m_data->textInput \ + ? connect( m_data->textInput, &QQuickTextInput::func, this, &QskAbstractTextInput::func ) \ + : connect( m_data->textEdit, &QQuickTextEdit::func, this, &QskAbstractTextInput::func ) + +#define INPUT_CONNECT1( func, get ) \ + do \ + { \ + auto f = [this]() { Q_EMIT func( get() ); }; \ + m_data->textInput \ + ? connect( m_data->textInput, &QQuickTextInput::func, this, f ) \ + : connect( m_data->textEdit, &QQuickTextEdit::func, this, f ); \ + } while ( false ) + QskAbstractTextInput::QskAbstractTextInput( QQuickItem* parent ) : Inherited( parent ) , m_data( new PrivateData() ) @@ -331,11 +141,94 @@ QskAbstractTextInput::~QskAbstractTextInput() { } -void QskAbstractTextInput::setup( QQuickItem* wrappedInput, - const QMetaObject* metaObject ) +void QskAbstractTextInput::setup( QQuickItem* wrappedInput ) { m_data->input = wrappedInput; - m_data->binder.setup( wrappedInput, metaObject, this ); + + m_data->textInput = qobject_cast< QQuickTextInput* >( wrappedInput ); + m_data->textEdit = qobject_cast< QQuickTextEdit* >( wrappedInput ); + + INPUT_CONNECT( textChanged ); + INPUT_CONNECT( preeditTextChanged ); + INPUT_CONNECT( readOnlyChanged ); + INPUT_CONNECT( overwriteModeChanged ); + INPUT_CONNECT( cursorVisibleChanged ); + INPUT_CONNECT1( cursorPositionChanged, cursorPosition ); + INPUT_CONNECT( selectByMouseChanged ); + INPUT_CONNECT1( persistentSelectionChanged, persistentSelection ); + INPUT_CONNECT1( wrapModeChanged, wrapMode ); + + INPUT_CONNECT1( canUndoChanged, canUndo ); + INPUT_CONNECT1( canRedoChanged, canRedo ); + INPUT_CONNECT1( canPasteChanged, canPaste ); + + INPUT_CONNECT1( inputMethodHintsChanged, inputMethodHints ); + INPUT_CONNECT1( inputMethodComposingChanged, isInputMethodComposing ); + + /* + Other properties offered from QQuickTextInput/QQuickTextEdit: + + - cursorRectangleChanged + - cursorDelegateChanged + + The default implementation creates a QQuickRectangle for the cursor + that can be replaced by a delegate. + + However the concept of QSkinny would be to customize the + the cursor using skin hints and/or creating the scene graph node + from the skinlet. TODO ... + + - colorChanged + - fontChanged + + Set from the skin hints - might be important enough to offer a + convenience API + + - selectionColorChanged + - selectedTextColorChanged + + This properties are set from the skin hints and are not worth to appear + at the public API + + - horizontalAlignmentChanged + - verticalAlignmentChanged + - effectiveHorizontalAlignmentChanged + + covered by QskAbstractTextInput::alignmentChanged + + - activeFocusOnPressChanged + + covered by QskAbstractTextInput::activationModesChanged + + - selectionStartChanged; + - selectionEndChanged; + - selectedTextChanged; + + Maybe there is a better API for the selection TODO ... + + - mouseSelectionModeChanged + + What to do with this mode. TODO ... + + - editingFinished + + This signal should never be emitted as it happens on + events ( focusOut, commit keys ) that are handled in + QskAbstractTextInput and indicated with editicgChanged( bool ); + ( Maybe having an assertion TODO ... ) + + - contentSizeChanged + + Need to understand what this information might be good for. + Maybe the sizeHints .... + + - renderTypeChanged + + This one is about the type of renderer to be used. This is similar + to QskItem::PreferRasterForTextures and instead of having a flag in + QskAbstractTextInput we might want to have a more general solution + in QskItem. TODO ... + */ } QskAbstractTextInput::ActivationModes QskAbstractTextInput::activationModes() const @@ -354,79 +247,81 @@ void QskAbstractTextInput::setActivationModes( ActivationModes modes ) bool QskAbstractTextInput::selectByMouse() const { - return m_data->input->property( "selectByMouse" ).value< bool >(); + return INPUT_INVOKE( selectByMouse ); } void QskAbstractTextInput::setSelectByMouse( bool on ) { - m_data->input->setProperty( "selectByMouse", on ); + INPUT_INVOKE_ARG( setSelectByMouse, on ); } bool QskAbstractTextInput::persistentSelection() const { - return m_data->input->property( "persistentSelection" ).value< bool >(); + return INPUT_INVOKE( persistentSelection ); } void QskAbstractTextInput::setPersistentSelection( bool on ) { - m_data->input->setProperty( "persistentSelection", on ); + INPUT_INVOKE_ARG( setPersistentSelection, on ); } int QskAbstractTextInput::length() const { - return m_data->input->property( "length" ).value< int >(); + return INPUT_INVOKE( length ); } QString QskAbstractTextInput::text() const { - return m_data->input->property( "text" ).value< QString >(); + return INPUT_INVOKE( text ); } void QskAbstractTextInput::setText( const QString& text ) { - m_data->input->setProperty( "text", text ); + INPUT_INVOKE_ARG( setText, text ); } QString QskAbstractTextInput::preeditText() const { - return m_data->input->property( "preeditText" ).value< QString >(); + return INPUT_INVOKE( preeditText ); } void QskAbstractTextInput::clear() { - QMetaObject::invokeMethod( m_data->input, "clear" ); + INPUT_INVOKE( clear ); } void QskAbstractTextInput::selectAll() { - QMetaObject::invokeMethod( m_data->input, "selectAll" ); + INPUT_INVOKE( selectAll ); } void QskAbstractTextInput::deselect() { - QMetaObject::invokeMethod( m_data->input, "deselect" ); + INPUT_INVOKE( deselect ); } bool QskAbstractTextInput::canUndo() const { - return m_data->input->property( "canUndo" ).value< bool >(); + return INPUT_INVOKE( canUndo ); } bool QskAbstractTextInput::canRedo() const { - return m_data->input->property( "canRedo" ).value< bool >(); + return INPUT_INVOKE( canRedo ); } bool QskAbstractTextInput::canPaste() const { - return m_data->input->property( "canPaste" ).value< bool >(); + return INPUT_INVOKE( canPaste ); } void QskAbstractTextInput::setFontRole( const QskFontRole& role ) { if ( setFontRoleHint( Text, role ) ) { - qskUpdateInputMethodFont( this ); + const auto queries = Qt::ImCursorRectangle | Qt::ImFont | Qt::ImAnchorRectangle; + qskUpdateInputMethod( this, queries ); + Q_EMIT fontRoleChanged(); } } @@ -435,7 +330,9 @@ void QskAbstractTextInput::resetFontRole() { if ( resetFontRoleHint( Text ) ) { - qskUpdateInputMethodFont( this ); + const auto queries = Qt::ImCursorRectangle | Qt::ImFont | Qt::ImAnchorRectangle; + qskUpdateInputMethod( this, queries ); + Q_EMIT fontRoleChanged(); } } @@ -451,9 +348,9 @@ QFont QskAbstractTextInput::font() const } QVariant QskAbstractTextInput::inputMethodQuery( - Qt::InputMethodQuery property ) const + Qt::InputMethodQuery query ) const { - return inputMethodQuery( property, QVariant() ); + return inputMethodQuery( query, QVariant() ); } QVariant QskAbstractTextInput::inputMethodQuery( @@ -492,14 +389,14 @@ QVariant QskAbstractTextInput::inputMethodQuery( Qt::InputMethodHints QskAbstractTextInput::inputMethodHints() const { - return qskInputMethodHints( m_data->input ); + return INPUT_INVOKE( inputMethodHints ); } void QskAbstractTextInput::setInputMethodHints( Qt::InputMethodHints hints ) { - if ( qskInputMethodHints( m_data->input ) != hints ) + if ( inputMethodHints() != hints ) { - qskSetInputMethodHints( m_data->input, hints ); + INPUT_INVOKE_ARG( setInputMethodHints, hints ); qskUpdateInputMethod( this, Qt::ImHints ); } } @@ -562,11 +459,21 @@ void QskAbstractTextInput::keyPressEvent( QKeyEvent* event ) const auto hints = inputMethodQuery( Qt::ImHints ).toInt(); if ( !( hints & Qt::ImhMultiLine ) ) { - if ( acceptInput() ) + if ( auto input = m_data->textInput ) { - QGuiApplication::inputMethod()->commit(); - setEditing( false ); + bool accept = input->hasAcceptableInput(); + if ( !accept ) + { + QMetaObject::invokeMethod( input, "fixup", + Qt::DirectConnection, Q_RETURN_ARG(bool, accept) ); + } + + if ( !accept ) + return; // ignore } + + QGuiApplication::inputMethod()->commit(); + setEditing( false ); } break; @@ -664,7 +571,7 @@ void QskAbstractTextInput::inputMethodEvent( QInputMethodEvent* event ) bool QskAbstractTextInput::isReadOnly() const { - return m_data->input->property( "readOnly" ).value< bool >(); + return INPUT_INVOKE( isReadOnly ); } void QskAbstractTextInput::setReadOnly( bool on ) @@ -677,11 +584,10 @@ void QskAbstractTextInput::setReadOnly( bool on ) setFocusPolicy( Qt::NoFocus ); #endif - auto input = m_data->input; - input->setProperty( "readOnly", on ); + INPUT_INVOKE_ARG( setReadOnly, on ); // we are killing user settings here ? - input->setFlag( QQuickItem::ItemAcceptsInputMethod, !on ); + m_data->input->setFlag( QQuickItem::ItemAcceptsInputMethod, !on ); #if QT_VERSION >= QT_VERSION_CHECK( 6, 2, 0 ) qskUpdateInputMethod( this, Qt::ImReadOnly ); @@ -694,7 +600,7 @@ void QskAbstractTextInput::setReadOnly( bool on ) bool QskAbstractTextInput::isInputMethodComposing() const { - return m_data->input->property( "inputMethodComposing" ).value< bool >(); + return INPUT_INVOKE( isInputMethodComposing ); } bool QskAbstractTextInput::isEditing() const @@ -704,90 +610,74 @@ bool QskAbstractTextInput::isEditing() const void QskAbstractTextInput::setEditing( bool on ) { - if ( isReadOnly() || on == isEditing() ) + if ( ( on == isEditing() ) || ( on && isReadOnly() ) ) return; setSkinStateFlag( Editing, on ); - auto input = m_data->input; - - if ( input->property( "cursorVisible" ).value< bool >() != on ) - { - input->setProperty( "cursorVisible", on ); - - if ( auto textInput = qobject_cast< QQuickTextInput* >( input ) ) - { - auto d = QQuickTextInputPrivate::get( textInput ); - d->setBlinkingCursorEnabled( on ); - - if ( !on ) - { - if ( d->m_passwordEchoEditing || d->m_passwordEchoTimer.isActive() ) - d->updatePasswordEchoEditing( false ); - } - } - - input->polish(); - input->update(); - } - - if ( !on && acceptInput() ) - QMetaObject::invokeMethod( input, "editingFinished" ); + QMetaObject::invokeMethod( m_data->input, "setEditing", + Qt::DirectConnection, Q_ARG( bool, on ) ); qskInputMethodSetVisible( this, on ); Q_EMIT editingChanged( on ); } -bool QskAbstractTextInput::acceptInput() -{ - return true; -} - bool QskAbstractTextInput::overwriteMode() const { - return m_data->input->property( "overwriteMode" ).value< bool >(); + return INPUT_INVOKE( overwriteMode ); } -void QskAbstractTextInput::setOverwriteMode( bool overwrite ) +void QskAbstractTextInput::setOverwriteMode( bool on ) { - m_data->input->setProperty( "overwriteMode", overwrite ); + INPUT_INVOKE_ARG( setOverwriteMode, on ); } int QskAbstractTextInput::cursorPosition() const { - return m_data->input->property( "cursorPosition" ).value< int >(); + return INPUT_INVOKE( cursorPosition ); } void QskAbstractTextInput::setCursorPosition( int pos ) { - m_data->input->setProperty( "cursorPosition", pos ); + INPUT_INVOKE_ARG( setCursorPosition, pos ); } bool QskAbstractTextInput::isCursorVisible() const { - return m_data->input->property( "cursorVisible" ).value< bool >(); + return INPUT_INVOKE( isCursorVisible ); } void QskAbstractTextInput::setCursorVisible( bool on ) { - m_data->input->setProperty( "cursorVisible", on ); + INPUT_INVOKE_ARG( setCursorVisible, on ); } void QskAbstractTextInput::setWrapMode( QskTextOptions::WrapMode wrapMode ) { - m_data->input->setProperty( "wrapMode", static_cast< int >( wrapMode ) ); + if ( m_data->textInput ) + { + auto mode = static_cast< QQuickTextInput::WrapMode >( wrapMode ); + m_data->textInput->setWrapMode( mode ); + } + else + { + auto mode = static_cast< QQuickTextEdit::WrapMode >( wrapMode ); + m_data->textEdit->setWrapMode( mode ); + } } QskTextOptions::WrapMode QskAbstractTextInput::wrapMode() const { - const auto mode = m_data->input->property( "wrapMode" ).value< int >(); - return static_cast< QskTextOptions::WrapMode >( mode ); + if ( m_data->textInput ) + return static_cast< QskTextOptions::WrapMode >( m_data->textInput->wrapMode() ); + else + return static_cast< QskTextOptions::WrapMode >( m_data->textEdit->wrapMode() ); } QSizeF QskAbstractTextInput::unwrappedTextSize() const { - const auto w = m_data->input->property( "contentWidth" ).value< qreal >(); - const auto h = m_data->input->property( "contentHeight" ).value< qreal >(); + const auto w = INPUT_INVOKE( contentWidth ); + const auto h = INPUT_INVOKE( contentHeight ); return QSizeF( w, h ); } @@ -838,5 +728,4 @@ void QskAbstractTextInput::forwardEvent( QEvent* event ) qskForwardEvent( m_data->input, event ); } -#include "QskAbstractTextInput.moc" #include "moc_QskAbstractTextInput.cpp" diff --git a/src/controls/QskAbstractTextInput.h b/src/controls/QskAbstractTextInput.h index 8417e84d..34077f7c 100644 --- a/src/controls/QskAbstractTextInput.h +++ b/src/controls/QskAbstractTextInput.h @@ -18,7 +18,7 @@ class QSK_EXPORT QskAbstractTextInput : public QskControl Q_PROPERTY( QString text READ text WRITE setText NOTIFY textChanged USER true ) - Q_PROPERTY( int length READ length NOTIFY lengthChanged ) + Q_PROPERTY( int length READ length NOTIFY textChanged ) Q_PROPERTY( QString preeditText READ preeditText NOTIFY preeditTextChanged ) @@ -167,11 +167,9 @@ class QSK_EXPORT QskAbstractTextInput : public QskControl void inputMethodComposingChanged( bool ); - void lengthChanged( int ); - void textChanged( const QString& ); + void textChanged(); void textEdited( const QString& ); - void displayTextChanged( const QString& ); - void preeditTextChanged( const QString& ); + void preeditTextChanged(); #if 1 void canUndoChanged( bool ); @@ -181,7 +179,7 @@ class QSK_EXPORT QskAbstractTextInput : public QskControl protected: QskAbstractTextInput( QQuickItem* parent = nullptr ); - void setup( QQuickItem*, const QMetaObject* ); + void setup( QQuickItem* ); void forwardEvent( QEvent* ); @@ -200,8 +198,6 @@ class QSK_EXPORT QskAbstractTextInput : public QskControl void inputMethodEvent( QInputMethodEvent* ) override; - virtual bool acceptInput(); - void updateLayout() override; void updateNode( QSGNode* ) override; diff --git a/src/controls/QskTextEdit.cpp b/src/controls/QskTextEdit.cpp index c4eb0643..6f1b9f59 100644 --- a/src/controls/QskTextEdit.cpp +++ b/src/controls/QskTextEdit.cpp @@ -14,6 +14,19 @@ QSK_QT_PRIVATE_END QSK_SUBCONTROL( QskTextEdit, TextPanel ) +/* + Other properties offered from QQuickTextEdit: + + - textMarginChanged + should be the TextPanel::Padding + + - selectByKeyboardChanged(bool selectByKeyboard); + Why is this one specific for QQuickTextEdit ) ? + + - tabStopDistanceChanged + Why is this one specific for QQuickTextEdit ? + */ + namespace { class QuickTextEdit final : public QQuickTextEdit @@ -33,6 +46,7 @@ namespace Q_INVOKABLE void updateColors(); Q_INVOKABLE void updateMetrics(); + Q_INVOKABLE void setEditing( bool ); Q_INVOKABLE void handleEvent( QEvent* ev ) { event( ev ); } protected: @@ -83,6 +97,14 @@ namespace this, &QuickTextEdit::updateClip ); } + void QuickTextEdit::setEditing( bool on ) + { + Q_ASSERT( focusOnPress() == false ); + + QFocusEvent event( on ? QEvent::FocusIn : QEvent::FocusOut ); + QQuickTextEditPrivate::get( this )->handleFocusEvent( &event ); + } + void QuickTextEdit::updateMetrics() { auto textEdit = static_cast< const QskTextEdit* >( parentItem() ); @@ -123,7 +145,19 @@ QskTextEdit::QskTextEdit( QQuickItem* parent ) initSizePolicy( QskSizePolicy::Expanding, QskSizePolicy::Expanding ); - setup( m_data->wrappedEdit, &QQuickTextEdit::staticMetaObject ); + setup( m_data->wrappedEdit ); + + connect( m_data->wrappedEdit, &QQuickTextEdit::lineCountChanged, + this, [this]() { Q_EMIT lineCountChanged( lineCount() ); } ); + + connect( m_data->wrappedEdit, &QQuickTextEdit::linkActivated, + this, &QskTextEdit::linkActivated ); + + connect( m_data->wrappedEdit, &QQuickTextEdit::linkHovered, + this, &QskTextEdit::linkHovered ); + + connect( m_data->wrappedEdit, &QQuickTextEdit::linkActivated, + this, &QskTextEdit::linkActivated ); } QskTextEdit::~QskTextEdit() @@ -137,7 +171,11 @@ QUrl QskTextEdit::baseUrl() const void QskTextEdit::setBaseUrl( const QUrl& url ) { - m_data->wrappedEdit->setBaseUrl( url ); + if ( url != m_data->wrappedEdit->baseUrl() ) + { + m_data->wrappedEdit->setBaseUrl( url ); + Q_EMIT baseUrlChanged( url ); + } } void QskTextEdit::resetBaseUrl() @@ -145,15 +183,15 @@ void QskTextEdit::resetBaseUrl() m_data->wrappedEdit->resetBaseUrl(); } -QString QskTextEdit::hoveredLink() const +void QskTextEdit::setTextFormat( QskTextOptions::TextFormat format ) { - return m_data->wrappedEdit->hoveredLink(); -} + if ( format != textFormat() ) + { + m_data->wrappedEdit->setTextFormat( + static_cast< QQuickTextEdit::TextFormat >( format ) ); -void QskTextEdit::setTextFormat( QskTextOptions::TextFormat textFormat ) -{ - m_data->wrappedEdit->setTextFormat( - static_cast< QQuickTextEdit::TextFormat >( textFormat ) ); + Q_EMIT textFormatChanged( format ); + } } QskTextOptions::TextFormat QskTextEdit::textFormat() const @@ -174,6 +212,7 @@ int QskTextEdit::tabStopDistance() const void QskTextEdit::setTabStopDistance( qreal distance ) { + // QTextOption ! m_data->wrappedEdit->setTabStopDistance( distance ); } diff --git a/src/controls/QskTextEdit.h b/src/controls/QskTextEdit.h index 6f0dfc22..b1d889f4 100644 --- a/src/controls/QskTextEdit.h +++ b/src/controls/QskTextEdit.h @@ -24,8 +24,6 @@ class QSK_EXPORT QskTextEdit : public QskAbstractTextInput Q_PROPERTY( QUrl baseUrl READ baseUrl WRITE setBaseUrl RESET resetBaseUrl NOTIFY baseUrlChanged ) - Q_PROPERTY( QString hoveredLink READ hoveredLink NOTIFY linkHovered ) - using Inherited = QskAbstractTextInput; public: @@ -46,8 +44,6 @@ class QSK_EXPORT QskTextEdit : public QskAbstractTextInput void setBaseUrl( const QUrl& ); void resetBaseUrl(); - QString hoveredLink() const; - Q_SIGNALS: void lineCountChanged( int ); void baseUrlChanged( QUrl ); @@ -56,6 +52,7 @@ class QSK_EXPORT QskTextEdit : public QskAbstractTextInput void tabStopDistanceChanged( qreal ); void linkHovered( const QString& ); + void linkActivated( const QString& ); private: class PrivateData; diff --git a/src/controls/QskTextInput.cpp b/src/controls/QskTextInput.cpp index aef2f21f..12a48502 100644 --- a/src/controls/QskTextInput.cpp +++ b/src/controls/QskTextInput.cpp @@ -15,6 +15,23 @@ QSK_QT_PRIVATE_END QSK_SUBCONTROL( QskTextInput, TextPanel ) QSK_SYSTEM_STATE( QskTextInput, Error, QskAspect::FirstSystemState << 4 ) +/* + Other properties offered from QQuickTextInput: + + - accepted(); + + What is this one good for ? + + - textEdited(); + + TODO ... + + - passwordCharacterChanged(); + - passwordMaskDelayChanged(int delay); + + Maybe we will have a QskPasswordField class, where we export + these properties. + */ namespace { class QuickTextInput final : public QQuickTextInput @@ -32,13 +49,10 @@ namespace setVAlign( ( VAlignment ) ( int( alignment ) & 0xf0 ) ); } - bool fixup() - { - return QQuickTextInputPrivate::get( this )->fixup(); - } - + Q_INVOKABLE bool fixup(); Q_INVOKABLE void updateColors(); Q_INVOKABLE void updateMetrics(); + Q_INVOKABLE void setEditing( bool ); Q_INVOKABLE void handleEvent( QEvent* ); protected: @@ -89,6 +103,18 @@ namespace this, &QuickTextInput::updateClip ); } + void QuickTextInput::setEditing( bool on ) + { + Q_ASSERT( focusOnPress() == false ); + + QFocusEvent event( on ? QEvent::FocusIn : QEvent::FocusOut ); + QQuickTextInputPrivate::get( this )->handleFocusEvent( &event ); + } + + bool QuickTextInput::fixup() + { + return QQuickTextInputPrivate::get( this )->fixup(); + } void QuickTextInput::updateMetrics() { @@ -136,13 +162,29 @@ QskTextInput::QskTextInput( QQuickItem* parent ) */ m_data->wrappedInput = new QuickTextInput( this ); + auto wrappedInput = m_data->wrappedInput; - setAcceptedMouseButtons( m_data->wrappedInput->acceptedMouseButtons() ); - m_data->wrappedInput->setAcceptedMouseButtons( Qt::NoButton ); + setAcceptedMouseButtons( wrappedInput->acceptedMouseButtons() ); + wrappedInput->setAcceptedMouseButtons( Qt::NoButton ); initSizePolicy( QskSizePolicy::Expanding, QskSizePolicy::Fixed ); - setup( m_data->wrappedInput, &QQuickTextInput::staticMetaObject ); + setup( wrappedInput ); + +#if 1 + connect( wrappedInput, &QQuickTextInput::maximumLengthChanged, + this, &QskTextInput::maximumLengthChanged ); + + connect( wrappedInput, &QQuickTextInput::displayTextChanged, + this, &QskTextInput::displayTextChanged ); + + connect( wrappedInput, &QQuickTextInput::inputMaskChanged, + this, &QskTextInput::inputMaskChanged ); + + connect( wrappedInput, &QQuickTextInput::acceptableInputChanged, + this, [this]() { Q_EMIT acceptableInputChanged( hasAcceptableInput() ); } ); + +#endif } QskTextInput::~QskTextInput() @@ -171,7 +213,11 @@ QValidator* QskTextInput::validator() const void QskTextInput::setValidator( QValidator* validator ) { - m_data->wrappedInput->setValidator( validator ); + if ( validator != m_data->wrappedInput->validator() ) + { + m_data->wrappedInput->setValidator( validator ); + Q_EMIT validatorChanged( validator ); + } } QString QskTextInput::inputMask() const @@ -191,7 +237,11 @@ bool QskTextInput::autoScroll() const void QskTextInput::setAutoScroll( bool on ) { - m_data->wrappedInput->setAutoScroll( on ); + if ( m_data->wrappedInput->autoScroll() != on ) + { + m_data->wrappedInput->setAutoScroll( on ); + Q_EMIT autoScrollChanged( on ); + } } QskTextInput::EchoMode QskTextInput::echoMode() const @@ -208,6 +258,8 @@ void QskTextInput::setEchoMode( EchoMode mode ) static_cast< QQuickTextInput::EchoMode >( mode ) ); qskUpdateInputMethod( this, Qt::ImHints ); + + Q_EMIT echoModeChanged( mode ); } } @@ -249,6 +301,11 @@ QString QskTextInput::displayText() const bool QskTextInput::hasAcceptableInput() const { + /* + We might want to make visual adjustments while having + an "invalid" text. Don't we need a QSkinny state + for this: TODO ... + */ return m_data->wrappedInput->hasAcceptableInput(); } @@ -257,10 +314,5 @@ bool QskTextInput::fixup() return m_data->wrappedInput->fixup(); } -bool QskTextInput::acceptInput() -{ - return hasAcceptableInput() || fixup(); -} - #include "QskTextInput.moc" #include "moc_QskTextInput.cpp" diff --git a/src/controls/QskTextInput.h b/src/controls/QskTextInput.h index 15706cf9..e0711632 100644 --- a/src/controls/QskTextInput.h +++ b/src/controls/QskTextInput.h @@ -90,7 +90,6 @@ class QSK_EXPORT QskTextInput : public QskAbstractTextInput bool hasAcceptableInput() const; bool fixup(); - bool acceptInput() override; void ensureVisible( int position ); @@ -107,7 +106,7 @@ class QSK_EXPORT QskTextInput : public QskAbstractTextInput void inputMaskChanged( const QString& ); void acceptableInputChanged( bool ); - void displayTextChanged( const QString& ); + void displayTextChanged(); private: class PrivateData;