diff --git a/inputcontext/QskInputContext.cpp b/inputcontext/QskInputContext.cpp index a911544c..52e80b6b 100644 --- a/inputcontext/QskInputContext.cpp +++ b/inputcontext/QskInputContext.cpp @@ -58,11 +58,19 @@ QskVirtualKeyboard* qskVirtualKeyboard( QQuickItem* inputPanel ) class QskInputContext::PrivateData { public: + PrivateData(): + ownsInputPanelWindow( false ) + { + } + QPointer< QQuickItem > inputItem; QPointer< QQuickItem > inputPanel; QskInputCompositionModel* compositionModel; QHash< QLocale, QskInputCompositionModel* > compositionModels; + + // the input panel is embedded in a window + bool ownsInputPanelWindow : 1; }; QskInputContext::QskInputContext(): @@ -104,54 +112,79 @@ bool QskInputContext::hasCapability( Capability ) const return true; } +QQuickItem* QskInputContext::inputItem() +{ + return m_data->inputItem; +} + +void QskInputContext::setInputItem( QQuickItem* item ) +{ + if ( m_data->inputItem == item ) + return; + + m_data->inputItem = item; + + if ( m_data->inputItem == nullptr ) + { + hideInputPanel(); + return; + } + + update( Qt::ImQueryAll ); +} + void QskInputContext::update( Qt::InputMethodQueries queries ) { - if ( m_data->inputItem == nullptr ) + if ( m_data->inputItem == nullptr || m_data->inputPanel == nullptr ) return; const auto queryEvent = queryInputMethod( queries ); - // Qt::ImCursorRectangle - // Qt::ImFont - // Qt::ImCursorPosition - // Qt::ImSurroundingText // important for chinese input - // Qt::ImCurrentSelection // important for prediction - // Qt::ImMaximumTextLength // should be monitored - // Qt::ImAnchorPosition - - if ( queries & Qt::ImHints ) + if ( queryEvent.queries() & Qt::ImEnabled ) { + if ( !queryEvent.value( Qt::ImEnabled ).toBool() ) + { + hideInputPanel(); + return; + } + } + + if ( queryEvent.queries() & Qt::ImHints ) + { + /* + ImhHiddenText = 0x1, // might need to disable certain checks + ImhSensitiveData = 0x2, // shouldn't change anything + ImhNoAutoUppercase = 0x4, // if we support auto uppercase, disable it + ImhPreferNumbers = 0x8, // default to number keyboard + ImhPreferUppercase = 0x10, // start with shift on + ImhPreferLowercase = 0x20, // start with shift off + ImhNoPredictiveText = 0x40, // ignored for now + + ImhDate = 0x80, // ignored for now (no date keyboard) + ImhTime = 0x100, // ignored for know (no time keyboard) + + ImhPreferLatin = 0x200, // can be used to launch chinese kb in english mode + + ImhMultiLine = 0x400, // not useful? + + ImhDigitsOnly // default to number keyboard, disable other keys + ImhFormattedNumbersOnly // hard to say + ImhUppercaseOnly // caps-lock, disable shift + ImhLowercaseOnly // disable shift + ImhDialableCharactersOnly // dial pad (calculator?) + ImhEmailCharactersOnly // disable certain symbols (email-only kb?) + ImhUrlCharactersOnly // disable certain symbols (url-only kb?) + ImhLatinOnly // disable chinese input + */ + #if 0 const auto hints = static_cast< Qt::InputMethodHints >( queryEvent.value( Qt::ImHints ).toInt() ); - //ImhHiddenText = 0x1, // might need to disable certain checks - //ImhSensitiveData = 0x2, // shouldn't change anything - //ImhNoAutoUppercase = 0x4, // if we support auto uppercase, disable it - //ImhPreferNumbers = 0x8, // default to number keyboard - //ImhPreferUppercase = 0x10, // start with shift on - //ImhPreferLowercase = 0x20, // start with shift off - //ImhNoPredictiveText = 0x40, // ignored for now - - //ImhDate = 0x80, // ignored for now (no date keyboard) - //ImhTime = 0x100, // ignored for know (no time keyboard) - - //ImhPreferLatin = 0x200, // can be used to launch chinese kb in english mode - - //ImhMultiLine = 0x400, // not useful? - - //ImhDigitsOnly // default to number keyboard, disable other keys - //ImhFormattedNumbersOnly // hard to say - //ImhUppercaseOnly // caps-lock, disable shift - //ImhLowercaseOnly // disable shift - //ImhDialableCharactersOnly // dial pad (calculator?) - //ImhEmailCharactersOnly // disable certain symbols (email-only kb?) - //ImhUrlCharactersOnly // disable certain symbols (url-only kb?) - //ImhLatinOnly // disable chinese input #endif } - if ( queries & Qt::ImPreferredLanguage ) + if ( queryEvent.queries() & Qt::ImPreferredLanguage ) { const auto locale = queryEvent.value( Qt::ImPreferredLanguage ).toLocale(); @@ -172,20 +205,24 @@ void QskInputContext::update( Qt::InputMethodQueries queries ) } } - // Qt::ImAbsolutePosition - // Qt::ImTextBeforeCursor // important for chinese - // Qt::ImTextAfterCursor // important for chinese - // Qt::ImPlatformData // hard to say... -} + /* + Qt::ImMicroFocus + Qt::ImCursorRectangle + Qt::ImFont + Qt::ImCursorPosition + Qt::ImSurroundingText // important for chinese input + Qt::ImCurrentSelection // important for prediction + Qt::ImMaximumTextLength // should be monitored + Qt::ImAnchorPosition -QQuickItem* QskInputContext::inputItem() -{ - return m_data->inputItem; -} - -void QskInputContext::setInputItem( QQuickItem* item ) -{ - m_data->inputItem = item; + Qt::ImAbsolutePosition + Qt::ImTextBeforeCursor // important for chinese + Qt::ImTextAfterCursor // important for chinese + Qt::ImPlatformData // hard to say... + Qt::ImEnterKeyType + Qt::ImAnchorRectangle + Qt::ImInputItemClipRectangle // could be used for the geometry of the panel + */ } QRectF QskInputContext::keyboardRect() const @@ -212,9 +249,12 @@ void QskInputContext::showInputPanel() if ( QskDialog::instance()->policy() == QskDialog::TopLevelWindow ) { + m_data->ownsInputPanelWindow = true; + auto window = new QskWindow; window->setFlags( Qt::Tool | Qt::WindowDoesNotAcceptFocus ); window->resize( 800, 240 ); // ### what size? + m_data->inputPanel->setParentItem( window->contentItem() ); connect( window, &QskWindow::visibleChanged, this, &QskInputContext::emitInputPanelVisibleChanged ); @@ -230,11 +270,15 @@ void QskInputContext::showInputPanel() } } - auto window = m_data->inputPanel->window(); - if ( window && window != QGuiApplication::focusWindow() ) - window->show(); + if ( m_data->ownsInputPanelWindow ) + { + if ( m_data->inputPanel->window() ) + m_data->inputPanel->window()->show(); + } else + { m_data->inputPanel->setVisible( true ); + } } void QskInputContext::hideInputPanel() @@ -242,11 +286,15 @@ void QskInputContext::hideInputPanel() if ( m_data->inputPanel == nullptr ) return; - auto window = m_data->inputPanel->window(); - if ( window && window != QGuiApplication::focusWindow() ) - window->hide(); + if ( m_data->ownsInputPanelWindow ) + { + if ( auto window = m_data->inputPanel->window() ) + window->hide(); + } else + { m_data->inputPanel->setVisible( false ); + } } bool QskInputContext::isInputPanelVisible() const @@ -269,36 +317,26 @@ Qt::LayoutDirection QskInputContext::inputDirection() const void QskInputContext::setFocusObject( QObject* focusObject ) { - if ( focusObject == nullptr ) - { - setInputItem( nullptr ); - return; - } - - bool inputItemChanged = false; - auto focusItem = qobject_cast< QQuickItem* >( focusObject ); - if( focusItem ) + + if ( focusItem == nullptr ) { - // Do not change the input item when panel buttons get the focus: + if ( m_data->inputItem ) + { + if ( m_data->inputItem->window() == QGuiApplication::focusWindow() ) + setInputItem( nullptr ); + } + } + else + { + /* + Do not change the input item when + navigating on or into the panel + */ + if( qskNearestFocusScope( focusItem ) != m_data->inputPanel ) - { setInputItem( focusItem ); - inputItemChanged = true; - } } - - if( inputItemChanged ) - { - const auto queryEvent = queryInputMethod( Qt::ImEnabled ); - if ( !queryEvent.value( Qt::ImEnabled ).toBool() ) - { - hideInputPanel(); - return; - } - } - - update( Qt::InputMethodQuery( Qt::ImQueryAll & ~Qt::ImEnabled ) ); } void QskInputContext::setCompositionModel( @@ -355,10 +393,6 @@ void QskInputContext::invokeAction( QInputMethod::Action action, int value ) case QskVirtualKeyboard::SelectCandidate: { model->commitCandidate( value ); - - if ( auto keyboard = qskVirtualKeyboard( m_data->inputPanel ) ) - keyboard->setPreeditCandidates( QVector< QString >() ); - break; } case QInputMethod::Click: @@ -403,6 +437,7 @@ void QskInputContext::setInputPanel( QQuickItem* inputPanel ) } m_data->inputPanel = inputPanel; + m_data->ownsInputPanelWindow = false; if ( inputPanel ) { diff --git a/playground/inputpanel/main.cpp b/playground/inputpanel/main.cpp index a681daff..026b4558 100644 --- a/playground/inputpanel/main.cpp +++ b/playground/inputpanel/main.cpp @@ -40,6 +40,7 @@ public: auto* textInput = new QskTextInput( this ); textInput->setText( "I am a line edit. Press and edit Me." ); + textInput->setSelectByMouse( true ); #if LOCAL_PANEL auto* inputPanel = new QskVirtualKeyboard( this ); @@ -179,6 +180,12 @@ int main( int argc, char* argv[] ) auto listView = new LocaleListView( box ); auto inputBox = new InputBox( box ); + /* + Disable Qt::ClickFocus, so that the input panel stays open + when selecting a different locale + */ + listView->setFocusPolicy( Qt::TabFocus ); + QObject::connect( listView, &QskListView::selectedRowChanged, inputBox, [ = ]( int row ) { inputBox->setLocale( listView->localeAt( row ) ); } ); diff --git a/src/controls/QskControl.cpp b/src/controls/QskControl.cpp index bb023530..b35f7aa9 100644 --- a/src/controls/QskControl.cpp +++ b/src/controls/QskControl.cpp @@ -18,6 +18,7 @@ QSK_QT_PRIVATE_BEGIN #include +#include #if defined( QT_DEBUG ) #include #endif @@ -144,12 +145,44 @@ QQuickItem* qskNearestFocusScope( const QQuickItem* item ) return nullptr; } -QList qskPaintOrderChildItems( const QQuickItem* item ) +void qskUpdateInputMethod( const QQuickItem* item, Qt::InputMethodQueries queries ) +{ + auto inputMethod = QGuiApplication::inputMethod(); + + bool doUpdate = item->hasActiveFocus(); + + if ( !doUpdate ) + { + const auto inputContext = + QInputMethodPrivate::get( inputMethod )->platformInputContext(); + + if ( inputContext && inputContext->isInputPanelVisible() ) + { + /* + QskInputContext allows to navigate inside the input panel + without losing the connected input item + */ + + QQuickItem* inputItem = nullptr; + + if ( QMetaObject::invokeMethod( inputContext, "inputItem", + Qt::DirectConnection, Q_RETURN_ARG( QQuickItem*, inputItem ) ) ) + { + doUpdate = ( item == inputItem ); + } + } + } + + if ( doUpdate ) + inputMethod->update( queries ); +} + +QList< QQuickItem* > qskPaintOrderChildItems( const QQuickItem* item ) { if ( item ) return QQuickItemPrivate::get( item )->paintOrderChildItems(); - return QList(); + return QList< QQuickItem* >(); } const QSGNode* qskItemNode( const QQuickItem* item ) @@ -1142,16 +1175,16 @@ bool QskControl::event( QEvent* event ) { /* When we don't have a local skinlet, the skinlet - from the previous skin might be cached. + from the previous skin might be cached. */ - + setSkinlet( nullptr ); } /* We might have a totally different skinlet, that can't deal with nodes created from other skinlets - */ + */ d_func()->clearPreviousNodes = true; resetImplicitSize(); @@ -1316,12 +1349,12 @@ void QskControl::itemChange( QQuickItem::ItemChange change, case QQuickItem::ItemVisibleHasChanged: { #if 1 - /* + /* ~QQuickItem sends QQuickItem::ItemVisibleHasChanged recursively to all childItems. When being a child ( not only a childItem() ) - we are short before being destructed too and any updates + we are short before being destructed too and any updates done here are totally pointless. TODO ... - */ + */ #endif if ( value.boolValue ) { diff --git a/src/controls/QskControl.h b/src/controls/QskControl.h index d7ccd23e..ad25ac8b 100644 --- a/src/controls/QskControl.h +++ b/src/controls/QskControl.h @@ -212,7 +212,8 @@ private: // don't use boundingRect - it seems to be deprecated virtual QRectF boundingRect() const override final { return rect(); } - void setActiveFocusOnTab( bool ) = delete; // use setFocusPolicy instead + void setActiveFocusOnTab( bool ) = delete; // use setFocusPolicy + void updateInputMethod( Qt::InputMethodQueries ) = delete; // use qskUpdateInputMethod virtual QSGNode* updatePaintNode( QSGNode*, UpdatePaintNodeData* ) override final; virtual void updatePolish() override final; @@ -249,7 +250,7 @@ inline QSizeF QskControl::sizeHint() const } QSK_EXPORT bool qskIsItemComplete( const QQuickItem* item ); -QSK_EXPORT bool qskIsAncestorOf( const QQuickItem* item, const QQuickItem *child ); +QSK_EXPORT bool qskIsAncestorOf( const QQuickItem* item, const QQuickItem* child ); QSK_EXPORT bool qskIsTransparentForPositioner( const QQuickItem* ); QSK_EXPORT bool qskIsTabFence( const QQuickItem* ); QSK_EXPORT bool qskIsShortcutScope( const QQuickItem* ); @@ -259,7 +260,9 @@ QSK_EXPORT QRectF qskItemGeometry( const QQuickItem* ); QSK_EXPORT void qskSetItemGeometry( QQuickItem*, const QRectF& ); QSK_EXPORT QQuickItem* qskNearestFocusScope( const QQuickItem* ); -QSK_EXPORT QList qskPaintOrderChildItems( const QQuickItem* ); +QSK_EXPORT QList< QQuickItem* > qskPaintOrderChildItems( const QQuickItem* ); + +QSK_EXPORT void qskUpdateInputMethod( const QQuickItem*, Qt::InputMethodQueries ); QSK_EXPORT const QSGNode* qskItemNode( const QQuickItem* ); QSK_EXPORT const QSGNode* qskPaintNode( const QQuickItem* ); diff --git a/src/controls/QskTextInput.cpp b/src/controls/QskTextInput.cpp index c0d402bb..f409c20c 100644 --- a/src/controls/QskTextInput.cpp +++ b/src/controls/QskTextInput.cpp @@ -11,13 +11,6 @@ QSK_QT_PRIVATE_BEGIN #include QSK_QT_PRIVATE_END -static inline void qskUpdateInputMethod( - const QskTextInput*, Qt::InputMethodQueries queries ) -{ - auto inputMethod = QGuiApplication::inputMethod(); - inputMethod->update( queries ); -} - static inline void qskBindSignals( const QQuickTextInput* wrappedInput, QskTextInput* input ) { @@ -81,6 +74,7 @@ namespace { setActiveFocusOnTab( false ); setFlag( ItemAcceptsInputMethod, false ); + setFocusOnPress( false ); } void setAlignment( Qt::Alignment alignment ) @@ -96,9 +90,8 @@ namespace case QEvent::FocusIn: case QEvent::FocusOut: { - - auto d = QQuickTextInputPrivate::get( this ); + d->focusOnPress = true; d->handleFocusEvent( static_cast< QFocusEvent* >( event ) ); d->focusOnPress = false; @@ -131,6 +124,13 @@ QskTextInput::QskTextInput( QQuickItem* parent ): 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 ); @@ -156,6 +156,10 @@ bool QskTextInput::event( QEvent* event ) { return m_data->textInput->handleEvent( event ); } + else if ( event->type() == QEvent::LocaleChange ) + { + qskUpdateInputMethod( this, Qt::ImPreferredLanguage ); + } return Inherited::event( event ); } @@ -170,6 +174,32 @@ void QskTextInput::keyReleaseEvent( QKeyEvent* event ) Inherited::keyReleaseEvent( event ); } +void QskTextInput::mousePressEvent( QMouseEvent* event ) +{ + m_data->textInput->handleEvent( event ); + + if ( !isReadOnly() && !qGuiApp->styleHints()->setFocusOnTouchRelease() ) + qGuiApp->inputMethod()->show(); +} + +void QskTextInput::mouseMoveEvent( QMouseEvent* event ) +{ + m_data->textInput->handleEvent( event ); +} + +void QskTextInput::mouseReleaseEvent( QMouseEvent* event ) +{ + m_data->textInput->handleEvent( event ); + + if ( !isReadOnly() && qGuiApp->styleHints()->setFocusOnTouchRelease() ) + qGuiApp->inputMethod()->show(); +} + +void QskTextInput::mouseDoubleClickEvent( QMouseEvent* event ) +{ + m_data->textInput->handleEvent( event ); +} + void QskTextInput::inputMethodEvent( QInputMethodEvent* event ) { m_data->textInput->handleEvent( event ); @@ -472,12 +502,17 @@ QVariant QskTextInput::inputMethodQuery( { return font(); } - case Qt::ImCursorPosition: + case Qt::ImPreferredLanguage: + { + return locale(); + } + case Qt::ImCursorRectangle: + case Qt::ImInputItemClipRectangle: { QVariant v = m_data->textInput->inputMethodQuery( query, argument ); #if 1 - if ( v.canConvert< QPointF >() ) - v.setValue( v.toPointF() + m_data->textInput->position() ); + if ( v.canConvert< QRectF >() ) + v.setValue( v.toRectF().translated( m_data->textInput->position() ) ); #endif return v; } diff --git a/src/controls/QskTextInput.h b/src/controls/QskTextInput.h index c3b09023..e77d7738 100644 --- a/src/controls/QskTextInput.h +++ b/src/controls/QskTextInput.h @@ -154,12 +154,20 @@ Q_SIGNALS: protected: virtual bool event( QEvent* ) override; - virtual void keyPressEvent( QKeyEvent* ) override; - virtual void keyReleaseEvent( QKeyEvent* ) override; + virtual void inputMethodEvent( QInputMethodEvent* ) override; + virtual void focusInEvent( QFocusEvent* ) override; virtual void focusOutEvent( QFocusEvent* ) override; + virtual void mousePressEvent( QMouseEvent* ) override; + virtual void mouseMoveEvent( QMouseEvent* ) override; + virtual void mouseReleaseEvent( QMouseEvent* ) override; + virtual void mouseDoubleClickEvent( QMouseEvent* ) override; + + virtual void keyPressEvent( QKeyEvent* ) override; + virtual void keyReleaseEvent( QKeyEvent* ) override; + virtual void updateLayout() override; private: diff --git a/src/controls/QskVirtualKeyboard.cpp b/src/controls/QskVirtualKeyboard.cpp index 6ab579e6..8947af2c 100644 --- a/src/controls/QskVirtualKeyboard.cpp +++ b/src/controls/QskVirtualKeyboard.cpp @@ -11,7 +11,7 @@ #include QSK_QT_PRIVATE_BEGIN -#include +#include QSK_QT_PRIVATE_END #include @@ -134,7 +134,8 @@ static bool qskIsAutorepeat( int key ) static inline QPlatformInputContext* qskInputContext() { - return QGuiApplicationPrivate::platformIntegration()->inputContext(); + auto inputMethod = QGuiApplication::inputMethod(); + return QInputMethodPrivate::get( inputMethod )->platformInputContext(); } QSK_SUBCONTROL( QskVirtualKeyboardCandidateButton, Panel ) @@ -505,13 +506,11 @@ QString QskVirtualKeyboard::displayLanguageName() const void QskVirtualKeyboard::setPreeditCandidates( const QVector< QString >& candidates ) { - if( m_data->candidates == candidates ) + if( m_data->candidates != candidates ) { - return; + m_data->candidates = candidates; + setCandidateOffset( 0 ); } - - m_data->candidates = candidates; - setCandidateOffset( 0 ); } void QskVirtualKeyboard::setCandidateOffset( int candidateOffset ) @@ -548,13 +547,6 @@ void QskVirtualKeyboard::setCandidateOffset( int candidateOffset ) } } -void QskVirtualKeyboard::geometryChanged( - const QRectF& newGeometry, const QRectF& oldGeometry ) -{ - Inherited::geometryChanged( newGeometry, oldGeometry ); - Q_EMIT keyboardRectChanged(); -} - void QskVirtualKeyboard::updateLayout() { if( geometry().isNull() ) @@ -680,6 +672,7 @@ void QskVirtualKeyboard::handleCandidateKey( int index, const QString& text ) else { selectCandidate( index ); + setPreeditCandidates( QVector< QString >() ); } } @@ -879,9 +872,9 @@ bool QskVirtualKeyboard::eventFilter( QObject* object, QEvent* event ) if ( event->type() == QEvent::InputMethodQuery ) { /* - Qt/Quick expects that the item associated with the virtual keyboard - always has the focus. But you also find systems, where you have to - navigate and select inside the virtual keyboard. + Qt/Quick expects that the item associated with the input context + always has the focus. But this does not work, when a virtual + keyboard is used, where you can navigate and select inside. So we have to fix the receiver. */ diff --git a/src/controls/QskVirtualKeyboard.h b/src/controls/QskVirtualKeyboard.h index a0a97ee4..824e9de6 100644 --- a/src/controls/QskVirtualKeyboard.h +++ b/src/controls/QskVirtualKeyboard.h @@ -136,7 +136,6 @@ public Q_SLOTS: protected: virtual bool eventFilter( QObject*, QEvent* ) override; - virtual void geometryChanged( const QRectF&, const QRectF& ) override; virtual void updateLayout() override; private: @@ -151,7 +150,6 @@ private: void updateKeyData(); Q_SIGNALS: - void keyboardRectChanged(); void displayLanguageNameChanged(); void modeChanged( QskVirtualKeyboard::Mode );