From 10a3435e912ece8996b5b69a7dbbc7c5a03b38e6 Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Tue, 10 Apr 2018 16:51:35 +0200 Subject: [PATCH] QskInputPanel reintroduced being a composite of QskVirtualKeyboard + QskInputSuggestionBar --- inputcontext/QskInputContext.cpp | 56 +++++--- playground/inputpanel/main.cpp | 5 +- src/controls/QskTextInput.cpp | 17 ++- src/inputpanel/QskInputPanel.cpp | 158 +++++++++++++++++++++++ src/inputpanel/QskInputPanel.h | 42 +++++- src/inputpanel/QskInputSuggestionBar.cpp | 43 ++++-- src/inputpanel/QskInputSuggestionBar.h | 4 +- src/inputpanel/QskVirtualKeyboard.cpp | 70 +--------- src/inputpanel/QskVirtualKeyboard.h | 14 +- 9 files changed, 294 insertions(+), 115 deletions(-) diff --git a/inputcontext/QskInputContext.cpp b/inputcontext/QskInputContext.cpp index 6a944310..7a1c0c3c 100644 --- a/inputcontext/QskInputContext.cpp +++ b/inputcontext/QskInputContext.cpp @@ -4,12 +4,12 @@ *****************************************************************************/ #include "QskInputContext.h" -#include "QskVirtualKeyboard.h" #include "QskInputCompositionModel.h" #include "QskPinyinCompositionModel.h" #include "QskHunspellCompositionModel.h" +#include "QskInputPanel.h" #include #include #include @@ -19,7 +19,7 @@ #include #include -void qskSetLocale( QQuickItem* inputPanel, const QLocale& locale ) +static void qskSetLocale( QQuickItem* inputPanel, const QLocale& locale ) { if ( auto control = qobject_cast< QskControl* >( inputPanel ) ) { @@ -35,7 +35,7 @@ void qskSetLocale( QQuickItem* inputPanel, const QLocale& locale ) } } -QLocale qskLocale( const QQuickItem* inputPanel ) +static QLocale qskLocale( const QQuickItem* inputPanel ) { if ( inputPanel == nullptr ) return QLocale(); @@ -46,14 +46,37 @@ QLocale qskLocale( const QQuickItem* inputPanel ) return inputPanel->property( "locale" ).toLocale(); } -QskVirtualKeyboard* qskVirtualKeyboard( QQuickItem* inputPanel ) +static void qskSetCandidatesEnabled( QQuickItem* inputPanel, bool on ) { - // we should not depend on QskVirtualKeyboard TODO ... + if ( inputPanel == nullptr ) + return; - if ( inputPanel ) - return inputPanel->findChild< QskVirtualKeyboard* >(); + if ( auto panel = qobject_cast< QskInputPanel* >( inputPanel ) ) + { + panel->setCandidatesEnabled( on ); + } + else + { + QMetaObject::invokeMethod( inputPanel, "setCandidatesEnabled", + Qt::DirectConnection, Q_ARG( bool, on ) ); + } +} - return nullptr; +static void qskSetCandidates( QQuickItem* inputPanel, + const QVector< QString >& candidates ) +{ + if ( inputPanel == nullptr ) + return; + + if ( auto panel = qobject_cast< QskInputPanel* >( inputPanel ) ) + { + panel->setCandidates( candidates ); + } + else + { + QMetaObject::invokeMethod( inputPanel, "setCandidates", + Qt::DirectConnection, Q_ARG( QVector< QString >, candidates ) ); + } } class QskInputContext::PrivateData @@ -205,8 +228,8 @@ void QskInputContext::update( Qt::InputMethodQueries queries ) connect( newModel, &QskInputCompositionModel::candidatesChanged, this, &QskInputContext::handleCandidatesChanged ); - if ( auto keyboard = qskVirtualKeyboard( m_data->inputPanel ) ) - keyboard->setCandidateBarVisible( newModel->supportsSuggestions() ); + qskSetCandidatesEnabled( m_data->inputPanel, + newModel->supportsSuggestions() ); } } @@ -250,7 +273,7 @@ void QskInputContext::showInputPanel() { if ( !m_data->inputPanel ) { - setInputPanel( new QskVirtualKeyboard() ); + setInputPanel( new QskInputPanel() ); if ( QskDialog::instance()->policy() == QskDialog::TopLevelWindow ) { @@ -390,12 +413,12 @@ void QskInputContext::invokeAction( QInputMethod::Action action, int value ) switch ( static_cast< int >( action ) ) { - case QskVirtualKeyboard::Compose: + case QskInputPanel::Compose: { model->composeKey( static_cast< Qt::Key >( value ) ); break; } - case QskVirtualKeyboard::SelectCandidate: + case QskInputPanel::SelectCandidate: { model->commitCandidate( value ); break; @@ -422,8 +445,7 @@ void QskInputContext::handleCandidatesChanged() for( int i = 0; i < count; i++ ) candidates += model->candidate( i ); - if ( auto keyboard = qskVirtualKeyboard( m_data->inputPanel ) ) - keyboard->setPreeditCandidates( candidates ); + qskSetCandidates( m_data->inputPanel, candidates ); } void QskInputContext::setInputPanel( QQuickItem* inputPanel ) @@ -472,8 +494,8 @@ void QskInputContext::setInputPanel( QQuickItem* inputPanel ) if ( model ) { - if ( auto keyboard = qskVirtualKeyboard( inputPanel ) ) - keyboard->setCandidateBarVisible( model->supportsSuggestions() ); + qskSetCandidatesEnabled( m_data->inputPanel, + model->supportsSuggestions() ); } } } diff --git a/playground/inputpanel/main.cpp b/playground/inputpanel/main.cpp index 8379a3eb..c074285e 100644 --- a/playground/inputpanel/main.cpp +++ b/playground/inputpanel/main.cpp @@ -6,7 +6,7 @@ #include #include -#include +#include #include #include #include @@ -45,7 +45,8 @@ public: textInput->setSizePolicy( Qt::Horizontal, QskSizePolicy::Preferred ); #if LOCAL_PANEL - auto* inputPanel = new QskVirtualKeyboard( this ); + auto* inputPanel = new QskInputPanel( this ); + inputPanel->setVisible( false ); /* QskInputContext is connected to QskSetup::inputPanelChanged, diff --git a/src/controls/QskTextInput.cpp b/src/controls/QskTextInput.cpp index b276a127..dd971d31 100644 --- a/src/controls/QskTextInput.cpp +++ b/src/controls/QskTextInput.cpp @@ -86,10 +86,10 @@ namespace setFlag( ItemAcceptsInputMethod, false ); setFocusOnPress( false ); + componentComplete(); + connect( this, &TextInput::contentSizeChanged, this, &TextInput::updateClip ); - - componentComplete(); } void setAlignment( Qt::Alignment alignment ) @@ -100,7 +100,7 @@ namespace inline bool handleEvent( QEvent* event ) { - return QQuickTextInput::event( event ); + return this->event( event ); } virtual void focusInEvent( QFocusEvent* ) override @@ -115,6 +115,9 @@ namespace #if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0) d->updateCursorBlinking(); d->setBlinkingCursorEnabled( true ); +#else + d->setCursorBlinkPeriod( + QGuiApplication::styleHints()->cursorFlashTime() ); #endif if ( d->determineHorizontalAlignment() ) @@ -133,6 +136,9 @@ namespace this, SLOT(q_updateAlignment()) ); qGuiApp->inputMethod()->show(); + + polish(); + update(); } virtual void focusOutEvent( QFocusEvent* event ) override @@ -147,6 +153,8 @@ namespace #if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0) d->updateCursorBlinking(); d->setBlinkingCursorEnabled( false ); +#else + d->setCursorBlinkPeriod( 0 ); #endif if ( d->m_passwordEchoEditing || d->m_passwordEchoTimer.isActive() ) @@ -171,6 +179,9 @@ namespace disconnect( QGuiApplication::inputMethod(), SIGNAL(inputDirectionChanged(Qt::LayoutDirection)), this, SLOT(q_updateAlignment()) ); + + polish(); + update(); } virtual void geometryChanged( diff --git a/src/inputpanel/QskInputPanel.cpp b/src/inputpanel/QskInputPanel.cpp index 78e8d500..70878579 100644 --- a/src/inputpanel/QskInputPanel.cpp +++ b/src/inputpanel/QskInputPanel.cpp @@ -4,10 +4,20 @@ *****************************************************************************/ #include "QskInputPanel.h" +#include "QskVirtualKeyboard.h" +#include "QskInputSuggestionBar.h" +#include "QskLinearBox.h" #include #include +QSK_QT_PRIVATE_BEGIN +#include +QSK_QT_PRIVATE_END + +#include +#include + QString qskNativeLocaleString( const QLocale& locale ) { switch( locale.language() ) @@ -97,5 +107,153 @@ QString qskNativeLocaleString( const QLocale& locale ) } } +static inline QQuickItem* qskInputItem() +{ + QPlatformInputContext* inputContext; +#if 1 + inputContext = QGuiApplicationPrivate::platformIntegration()->inputContext(); +#else + // for some reason the gcc sanitizer does not like this one + inputContext = QInputMethodPrivate::get( inputMethod )->platformInputContext(); +#endif + QQuickItem* item = nullptr; + QMetaObject::invokeMethod( inputContext, "inputItem", + Qt::DirectConnection, Q_RETURN_ARG( QQuickItem*, item ) ); + + return item; +} + +static inline void qskInstallEventFilter( QskInputPanel* panel, bool on ) +{ + if ( on ) + qGuiApp->installEventFilter( panel ); + else + qGuiApp->removeEventFilter( panel ); +} + +class QskInputPanel::PrivateData +{ +public: + QskInputSuggestionBar* suggestionBar; + QskVirtualKeyboard* keyboard; +}; + +QskInputPanel::QskInputPanel( QQuickItem* parent ): + QskControl( parent ), + m_data( new PrivateData() ) +{ + setAutoLayoutChildren( true ); + setFlag( ItemIsFocusScope, true ); +#if 0 + // TODO ... + setTabFence( true ); +#endif + + auto layout = new QskLinearBox( Qt::Vertical, this ); + + m_data->suggestionBar = new QskInputSuggestionBar( layout ); + m_data->suggestionBar->setVisible( false ); + + connect( m_data->suggestionBar, &QskInputSuggestionBar::suggested, + this, &QskInputPanel::commitCandidate ); + + m_data->keyboard = new QskVirtualKeyboard( layout ); + + connect( m_data->keyboard, &QskVirtualKeyboard::keySelected, + this, &QskInputPanel::commitKey ); +} + +QskInputPanel::~QskInputPanel() +{ +} + +bool QskInputPanel::isCandidatesEnabled() const +{ + return m_data->suggestionBar->isVisible(); +} + +QVector< QString > QskInputPanel::candidates() const +{ + return m_data->suggestionBar->candidates(); +} + +void QskInputPanel::setCandidatesEnabled( bool on ) +{ + m_data->suggestionBar->setVisible( on ); +} + +void QskInputPanel::setCandidates( const QVector< QString >& candidates ) +{ + m_data->suggestionBar->setCandidates( candidates ); +} + +void QskInputPanel::commitCandidate( int index ) +{ + m_data->suggestionBar->setCandidates( QVector< QString >() ); + + QGuiApplication::inputMethod()->invokeAction( + static_cast< QInputMethod::Action >( SelectCandidate ), index ); +} + +void QskInputPanel::commitKey( Qt::Key key ) +{ + QGuiApplication::inputMethod()->invokeAction( + static_cast< QInputMethod::Action >( Compose ), key ); +} + +void QskInputPanel::updateLayout() +{ + if ( !isInitiallyPainted() ) + qskInstallEventFilter( this, isVisible() ); + + Inherited::updateLayout(); +} + +void QskInputPanel::itemChange( QQuickItem::ItemChange change, + const QQuickItem::ItemChangeData& value ) +{ + switch( change ) + { + case QQuickItem::ItemVisibleHasChanged: + case QQuickItem::ItemSceneChange: + { + if ( isInitiallyPainted() ) + qskInstallEventFilter( this, isVisible() ); + + break; + } + default: + break; + } + + Inherited::itemChange( change, value ); +} + +bool QskInputPanel::eventFilter( QObject* object, QEvent* event ) +{ + if ( event->type() == QEvent::InputMethodQuery ) + { + const auto item = qskInputItem(); + + /* + 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. + + Maybe QEvent::EnterEditFocus is good for something ?? + */ + + if ( item && ( object != item ) && qskIsAncestorOf( this, item ) ) + { + QGuiApplication::sendEvent( item, event ); + return true; + } + } + + return Inherited::eventFilter( object, event ); +} + +#include "moc_QskInputPanel.cpp" diff --git a/src/inputpanel/QskInputPanel.h b/src/inputpanel/QskInputPanel.h index ac787932..ccdb84d6 100644 --- a/src/inputpanel/QskInputPanel.h +++ b/src/inputpanel/QskInputPanel.h @@ -7,9 +7,49 @@ #define QSK_INPUT_PANEL_H #include "QskGlobal.h" +#include "QskControl.h" -class QLocale; class QString; +class QLocale; + +template class QVector< QString >; + +class QSK_EXPORT QskInputPanel: public QskControl +{ + Q_OBJECT + + using Inherited = QskControl; + +public: + enum Action + { + Compose = 0x10, + SelectCandidate = 0x11, + }; + Q_ENUM( Action ) + + QskInputPanel( QQuickItem* parent = nullptr ); + virtual ~QskInputPanel() override; + + bool isCandidatesEnabled() const; + QVector< QString > candidates() const; + +public Q_SLOTS: + void setCandidatesEnabled( bool ); + void setCandidates( const QVector< QString >& ); + +protected: + virtual void updateLayout() override; + virtual void itemChange( ItemChange, const ItemChangeData& ) override; + virtual bool eventFilter( QObject*, QEvent* ) override; + +private: + void commitKey( Qt::Key ); + void commitCandidate( int ); + + class PrivateData; + std::unique_ptr< PrivateData > m_data; +}; QSK_EXPORT QString qskNativeLocaleString( const QLocale& ); diff --git a/src/inputpanel/QskInputSuggestionBar.cpp b/src/inputpanel/QskInputSuggestionBar.cpp index 4bae663b..9bba6967 100644 --- a/src/inputpanel/QskInputSuggestionBar.cpp +++ b/src/inputpanel/QskInputSuggestionBar.cpp @@ -8,6 +8,7 @@ #include "QskLinearBox.h" #include "QskTextOptions.h" +#include #include QSK_SUBCONTROL( QskInputSuggestionBar, Panel ) @@ -28,6 +29,19 @@ namespace setTextOptions( options ); } + virtual QSizeF contentsSizeHint() const override + { + auto size = QFontMetricsF( font() ).size( Qt::TextSingleLine, text() ); + + const QSizeF minSize( metric( Panel | QskAspect::MinimumWidth ), + metric( Panel | QskAspect::MinimumHeight ) ); + + size = size.expandedTo( minSize ); + size = outerBoxSize( Panel, size ); + + return size; + } + virtual QskAspect::Subcontrol effectiveSubcontrol( QskAspect::Subcontrol subControl ) const override final { @@ -57,16 +71,26 @@ QskInputSuggestionBar::QskInputSuggestionBar( QQuickItem* parent ): m_data( new PrivateData ) { setAutoLayoutChildren( true ); - initSizePolicy( QskSizePolicy::Fixed, QskSizePolicy::Expanding ); + initSizePolicy( QskSizePolicy::Expanding, QskSizePolicy::Fixed ); m_data->layoutBox = new QskLinearBox( Qt::Horizontal, this ); for( int i = 0; i < m_data->buttonCount; i++ ) { auto button = new Button( m_data->layoutBox ); + button->setVisible( false ); + button->setSizePolicy( Qt::Horizontal, QskSizePolicy::Maximum ); + connect( button, &QskPushButton::clicked, this, &QskInputSuggestionBar::candidateClicked ); + + if ( i == 0 ) + { + // to keep the height + m_data->layoutBox->setRetainSizeWhenHidden( button, true ); + } } + } QskInputSuggestionBar::~QskInputSuggestionBar() @@ -91,6 +115,11 @@ void QskInputSuggestionBar::setCandidates( const QVector< QString >& candidates } } +QVector< QString > QskInputSuggestionBar::candidates() const +{ + return m_data->candidates; +} + void QskInputSuggestionBar::setCandidateOffset( int offset ) { m_data->candidateOffset = offset; @@ -129,7 +158,7 @@ void QskInputSuggestionBar::setCandidateOffset( int offset ) void QskInputSuggestionBar::candidateClicked() { const int index = m_data->layoutBox->indexOf( - qobject_cast< QQuickItem*> ( sender() ) ); + qobject_cast< QQuickItem* > ( sender() ) ); const int offset = m_data->candidateOffset; @@ -143,20 +172,14 @@ void QskInputSuggestionBar::candidateClicked() } else if ( index == m_data->buttonCount - 1 ) { - if ( m_data->candidates.count() - offset >= m_data->buttonCount ) + if ( m_data->candidates.count() - offset > m_data->buttonCount ) { setCandidateOffset( offset + 1 ); return; } } -#if 0 - QGuiApplication::inputMethod()->invokeAction( - static_cast< QInputMethod::Action >( SelectCandidate ), index ); - - setPreeditCandidates( QVector< QString >() ); -#endif - Q_EMIT suggested( m_data->candidates[ index - offset ] ); + Q_EMIT suggested( offset + index ); } #include "moc_QskInputSuggestionBar.cpp" diff --git a/src/inputpanel/QskInputSuggestionBar.h b/src/inputpanel/QskInputSuggestionBar.h index 8b232edc..ac678906 100644 --- a/src/inputpanel/QskInputSuggestionBar.h +++ b/src/inputpanel/QskInputSuggestionBar.h @@ -23,8 +23,10 @@ public: virtual QskAspect::Subcontrol effectiveSubcontrol( QskAspect::Subcontrol subControl ) const override; + QVector< QString > candidates() const; + Q_SIGNALS: - void suggested( const QString& ); + void suggested( int ); public Q_SLOTS: void setCandidates( const QVector< QString >& ); diff --git a/src/inputpanel/QskVirtualKeyboard.cpp b/src/inputpanel/QskVirtualKeyboard.cpp index 0ce7a66c..55998ec8 100644 --- a/src/inputpanel/QskVirtualKeyboard.cpp +++ b/src/inputpanel/QskVirtualKeyboard.cpp @@ -10,13 +10,6 @@ #include #include -QSK_QT_PRIVATE_BEGIN -#include -QSK_QT_PRIVATE_END - -#include -#include - namespace { enum @@ -198,24 +191,6 @@ static bool qskIsAutorepeat( int key ) && key != Qt::Key_Mode_switch ); } -static inline QQuickItem* qskInputItem() -{ - QPlatformInputContext* inputContext; -#if 1 - inputContext = QGuiApplicationPrivate::platformIntegration()->inputContext(); -#else - // for some reason the gcc sanitizer does not like this one - inputContext = QInputMethodPrivate::get( inputMethod )->platformInputContext(); -#endif - - QQuickItem* item = nullptr; - - QMetaObject::invokeMethod( inputContext, "inputItem", - Qt::DirectConnection, Q_RETURN_ARG( QQuickItem*, item ) ); - - return item; -} - QSK_SUBCONTROL( QskVirtualKeyboard, Panel ) QSK_SUBCONTROL( QskVirtualKeyboard, ButtonPanel ) QSK_SUBCONTROL( QskVirtualKeyboard, ButtonText ) @@ -240,13 +215,6 @@ QskVirtualKeyboard::QskVirtualKeyboard( QQuickItem* parent ): Inherited( parent ), m_data( new PrivateData ) { - setFlag( ItemHasContents ); - setFlag( ItemIsFocusScope, true ); -#if 0 - // TODO ... - setTabFence( true ); -#endif - setPolishOnResize( true ); initSizePolicy( QskSizePolicy::Expanding, QskSizePolicy::Expanding ); @@ -298,13 +266,6 @@ QskVirtualKeyboard::Mode QskVirtualKeyboard::mode() const return m_data->mode; } -void QskVirtualKeyboard::setPreeditCandidates( const QVector< QString >& ) -{ -#if 0 - m_suggestionBar->setCandidates( candidates ); -#endif -} - void QskVirtualKeyboard::updateLayout() { const auto r = layoutRect(); @@ -314,7 +275,7 @@ void QskVirtualKeyboard::updateLayout() const auto spacing = metric( Panel | QskAspect::Spacing ); const auto totalVSpacing = ( RowCount - 1 ) * spacing; - const auto keyHeight = ( r.height() - totalVSpacing ) / RowCount; + const auto keyHeight = ( r.height() - totalVSpacing ) / RowCount; const auto& keyCodes = ( *m_data->currentLayout )[ m_data->mode ]; @@ -400,16 +361,11 @@ void QskVirtualKeyboard::buttonPressed() } default: { - QGuiApplication::inputMethod()->invokeAction( - static_cast< QInputMethod::Action >( Compose ), key ); + Q_EMIT keySelected( key ); } } } -void QskVirtualKeyboard::setCandidateBarVisible( bool ) -{ -} - void QskVirtualKeyboard::updateLocale( const QLocale& locale ) { switch( locale.language() ) @@ -539,28 +495,6 @@ void QskVirtualKeyboard::setMode( QskVirtualKeyboard::Mode mode ) Q_EMIT modeChanged( m_data->mode ); } -bool QskVirtualKeyboard::eventFilter( QObject* object, QEvent* event ) -{ - if ( event->type() == QEvent::InputMethodQuery ) - { - /* - 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. - - Maybe QEvent::EnterEditFocus is good for something ?? - */ - - if ( auto item = qskInputItem() ) - QGuiApplication::sendEvent( item, event ); - - return true; - } - - return Inherited::eventFilter( object, event ); -} - bool QskVirtualKeyboard::event( QEvent* event ) { /* diff --git a/src/inputpanel/QskVirtualKeyboard.h b/src/inputpanel/QskVirtualKeyboard.h index dcc5392d..fa90e8e1 100644 --- a/src/inputpanel/QskVirtualKeyboard.h +++ b/src/inputpanel/QskVirtualKeyboard.h @@ -17,13 +17,6 @@ class QSK_EXPORT QskVirtualKeyboard : public QskBox public: QSK_SUBCONTROLS( Panel, ButtonPanel, ButtonText ) - enum Action - { - Compose = 0x10, - SelectCandidate = 0x11, - }; - Q_ENUM( Action ) - enum Mode { CurrentMode = -1, @@ -47,15 +40,10 @@ public: Q_SIGNALS: void modeChanged( Mode ); - -public Q_SLOTS: - void setPreeditCandidates( const QVector< QString >& ); - void setCandidateBarVisible( bool visible ); + void keySelected( Qt::Key ); protected: - virtual bool eventFilter( QObject*, QEvent* ) override; virtual bool event( QEvent* ) override; - virtual void updateLayout() override; private Q_SLOTS: