From e5d6fe0dc303824c4e95c29b519a4e6d63f86f90 Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Wed, 9 May 2018 15:45:43 +0200 Subject: [PATCH] QskInputContext is no platform input context anymore to avoid forwarding private APIs to applicaton code --- inputcontext/QskInputContextPlugin.cpp | 245 +++++++++++++++++++++++-- src/inputpanel/QskInputContext.cpp | 227 +++++++++++------------ src/inputpanel/QskInputContext.h | 51 ++--- 3 files changed, 364 insertions(+), 159 deletions(-) diff --git a/inputcontext/QskInputContextPlugin.cpp b/inputcontext/QskInputContextPlugin.cpp index 8fdc8204..c7936327 100644 --- a/inputcontext/QskInputContextPlugin.cpp +++ b/inputcontext/QskInputContextPlugin.cpp @@ -4,12 +4,244 @@ *****************************************************************************/ #include +#include #include "QskInputContext.h" #include "QskPinyinTextPredictor.h" #include "QskHunspellTextPredictor.h" #include +#include +#include + +/* + QPlatformInputContext is no stable public API. + So we forward everything to QskInputContext + */ +class QskPlatformInputContext final : public QPlatformInputContext +{ + using Inherited = QPlatformInputContext; + +public: + QskPlatformInputContext(); + virtual ~QskPlatformInputContext() = default; + + virtual bool isValid() const override; + virtual bool hasCapability( Capability ) const override; + + virtual void update( Qt::InputMethodQueries ) override; + virtual void invokeAction( QInputMethod::Action, int ) override; + + virtual QRectF keyboardRect() const override; + virtual bool isAnimating() const override; + + virtual void showInputPanel() override; + virtual void hideInputPanel() override; + virtual bool isInputPanelVisible() const override; + + virtual void reset() override; + virtual void commit() override; + + virtual void setFocusObject( QObject* ) override; + + virtual QLocale locale() const override; + virtual Qt::LayoutDirection inputDirection() const override; + + virtual bool filterEvent( const QEvent* ) override; + + Q_INVOKABLE QQuickItem* inputItem(); + +protected: + virtual bool event( QEvent* ) override; + +private: + void updateContext(); + void updateLocale(); + + QLocale m_locale; + QPointer< QskInputContext > m_context; +}; + +QskPlatformInputContext::QskPlatformInputContext() +{ + auto context = QskInputContext::instance(); + if ( context == nullptr ) + { + context = new QskInputContext(); + QskInputContext::setInstance( context ); + } + + updateContext(); + updateLocale(); +} + +void QskPlatformInputContext::updateContext() +{ + if ( m_context ) + m_context->disconnect( this ); + + m_context = QskInputContext::instance(); + + if ( m_context ) + { + connect( m_context, &QskInputContext::activeChanged, + this, &QPlatformInputContext::emitInputPanelVisibleChanged ); + + connect( m_context, &QskInputContext::panelRectChanged, + this, &QPlatformInputContext::emitKeyboardRectChanged ); + +#if 1 + m_context->registerPredictor( QLocale(), + new QskHunspellTextPredictor() ); +#endif + +#if 0 + m_context->registerPredictor( + QLocale::Chinese, new QskPinyinTextPredictor() ); +#endif + } +} + +void QskPlatformInputContext::updateLocale() +{ + if ( m_context ) + { + const auto oldLocale = m_locale; + m_locale = m_context->locale(); + + if ( oldLocale != m_locale ) + emitLocaleChanged(); + + if ( m_locale.textDirection() != oldLocale.textDirection() ) + emitInputDirectionChanged( m_locale.textDirection() ); + } +} + +bool QskPlatformInputContext::isValid() const +{ + return true; +} + +bool QskPlatformInputContext::hasCapability( Capability ) const +{ + // what is QPlatformInputContext::HiddenTextCapability ??? + return true; +} + +void QskPlatformInputContext::update( Qt::InputMethodQueries queries ) +{ + if ( m_context ) + m_context->update( queries ); +} + +void QskPlatformInputContext::invokeAction( + QInputMethod::Action action, int cursorPosition ) +{ + if ( m_context ) + { + if ( action == QInputMethod::Click ) + m_context->processClickAt( cursorPosition ); + } +} + +QRectF QskPlatformInputContext::keyboardRect() const +{ + if ( m_context ) + return m_context->panelRect(); + + return QRectF(); +} + +bool QskPlatformInputContext::isAnimating() const +{ + // who is interested in this ? + // also: emitAnimatingChanged + + return false; +} + +void QskPlatformInputContext::showInputPanel() +{ + if ( m_context ) + m_context->setActive( true ); +} + +void QskPlatformInputContext::hideInputPanel() +{ + if ( m_context ) + m_context->setActive( false ); +} + +bool QskPlatformInputContext::isInputPanelVisible() const +{ + if ( m_context ) + return m_context->isActive(); + + return false; +} + +void QskPlatformInputContext::reset() +{ + if ( m_context ) + m_context->commitPrediction( false ); +} + +void QskPlatformInputContext::commit() +{ + if ( m_context ) + m_context->commitPrediction( true ); +} + +void QskPlatformInputContext::setFocusObject( QObject* object ) +{ + if ( m_context ) + m_context->setFocusObject( object ); +} + +QLocale QskPlatformInputContext::locale() const +{ + return m_locale; +} + +Qt::LayoutDirection QskPlatformInputContext::inputDirection() const +{ + return m_locale.textDirection(); +} + +bool QskPlatformInputContext::filterEvent( const QEvent* ) +{ + // called from QXcbKeyboard, but what about other platforms + return false; +} + +QQuickItem* QskPlatformInputContext::inputItem() +{ + if ( m_context ) + return m_context->inputItem(); + + return nullptr; +} + +bool QskPlatformInputContext::event( QEvent* event ) +{ + switch( event->type() ) + { + case QEvent::LocaleChange: + { + updateLocale(); + break; + } + case QEvent::PlatformPanel: + { + updateContext(); + break; + } + default: + break; + } + + return Inherited::event( event ); +} class QskInputContextPlugin final : public QPlatformInputContextPlugin { @@ -22,18 +254,7 @@ public: { if ( system.compare( QStringLiteral( "skinny" ), Qt::CaseInsensitive ) == 0 ) { - auto context = new QskInputContext(); - -#if 0 - context->registerPredictor( QLocale(), - new QskHunspellTextPredictor( this ) ); -#endif - -#if 0 - context->registerPredictor( - QLocale::Chinese, new QskPinyinTextPredictor( this ) ); -#endif - + auto context = new QskPlatformInputContext(); return context; } diff --git a/src/inputpanel/QskInputContext.cpp b/src/inputpanel/QskInputContext.cpp index f3d115aa..2ae8bd92 100644 --- a/src/inputpanel/QskInputContext.cpp +++ b/src/inputpanel/QskInputContext.cpp @@ -24,47 +24,46 @@ QSK_QT_PRIVATE_BEGIN QSK_QT_PRIVATE_END #include +#include -static QPointer< QskInputPanel > qskInputPanel = nullptr; +static QPointer< QskInputContext > qskInputContext = nullptr; -static void qskDeletePanel() +static void qskSendToPlatformContext( QEvent::Type type ) { - delete qskInputPanel; -} - -static void qskInputPanelHook() -{ - qAddPostRoutine( qskDeletePanel ); -} - -Q_COREAPP_STARTUP_FUNCTION( qskInputPanelHook ) - -static void qskSetInputPanel( QskInputPanel* inputPanel ) -{ - if ( inputPanel == qskInputPanel ) - return; - - delete qskInputPanel; - qskInputPanel = inputPanel; -} - -void QskInputContext::setInputPanel( QskInputPanel* inputPanel ) -{ - if ( inputPanel == qskInputPanel ) - return; - - qskSetInputPanel( inputPanel ); - - const auto inputContext = + const auto platformInputContext = QGuiApplicationPrivate::platformIntegration()->inputContext(); - if ( auto context = qobject_cast< QskInputContext* >( inputContext ) ) - context->hideInputPanel(); + if ( platformInputContext ) + { + QEvent event( type ); + QCoreApplication::sendEvent( platformInputContext, &event ); + } } -QskInputPanel* QskInputContext::inputPanel() +static void qskInputContextHook() { - return qskInputPanel; + qAddPostRoutine( []{ delete qskInputContext; } ); +} + +Q_COREAPP_STARTUP_FUNCTION( qskInputContextHook ) + +void QskInputContext::setInstance( QskInputContext* inputContext ) +{ + if ( inputContext != qskInputContext ) + { + const auto oldContext = qskInputContext; + qskInputContext = inputContext; + + if ( oldContext && oldContext->parent() == nullptr ) + delete oldContext; + + qskSendToPlatformContext( QEvent::PlatformPanel ); + } +} + +QskInputContext* QskInputContext::instance() +{ + return qskInputContext; } static inline uint qskHashLocale( const QLocale& locale ) @@ -124,8 +123,9 @@ class QskInputContext::PrivateData public: // item receiving the input QPointer< QQuickItem > inputItem; + QPointer< QskInputPanel > inputPanel; - // popup or window embedding qskInputPanel + // popup or window embedding the panel QskPopup* inputPopup = nullptr; QskWindow* inputWindow = nullptr; @@ -145,22 +145,32 @@ QskInputContext::~QskInputContext() { } -bool QskInputContext::isValid() const -{ - return true; -} - -bool QskInputContext::hasCapability( Capability ) const -{ - // what is QPlatformInputContext::HiddenTextCapability ??? - return true; -} - -QQuickItem* QskInputContext::inputItem() +QQuickItem* QskInputContext::inputItem() const { return m_data->inputItem; } +QskInputPanel* QskInputContext::inputPanel() const +{ + if ( m_data->inputPanel == nullptr ) + { + auto that = const_cast< QskInputContext* >( this ); + + auto panel = new QskInputPanel(); + panel->setParent( that ); + + connect( panel, &QQuickItem::visibleChanged, + this, &QskInputContext::activeChanged ); + + connect( panel, &QskControl::localeChanged, + this, []{ qskSendToPlatformContext( QEvent::LocaleChange ); } ); + + m_data->inputPanel = panel; + } + + return m_data->inputPanel; +} + void QskInputContext::update( Qt::InputMethodQueries queries ) { if ( queries & Qt::ImEnabled ) @@ -170,28 +180,21 @@ void QskInputContext::update( Qt::InputMethodQueries queries ) if ( !event.value( Qt::ImEnabled ).toBool() ) { - hideInputPanel(); + hidePanel(); return; } } - if ( qskInputPanel ) - qskInputPanel->processInputMethodQueries( queries ); + if ( auto panel = inputPanel() ) + panel->processInputMethodQueries( queries ); } -QRectF QskInputContext::keyboardRect() const +QRectF QskInputContext::panelRect() const { - // is this correct and what is this good for ? if ( m_data->inputPopup ) return m_data->inputPopup->geometry(); - return Inherited::keyboardRect(); -} - -bool QskInputContext::isAnimating() const -{ - // can be implemented once we have some sliding/fading effects - return false; + return QRectF(); } QskPopup* QskInputContext::createEmbeddingPopup( QskInputPanel* panel ) @@ -238,15 +241,18 @@ QskWindow* QskInputContext::createEmbeddingWindow( QskInputPanel* panel ) return window; } -void QskInputContext::showInputPanel() +void QskInputContext::showPanel() { auto focusItem = qobject_cast< QQuickItem* >( qGuiApp->focusObject() ); - if ( focusItem == nullptr ) return; - if ( ( focusItem == qskInputPanel ) - || qskIsAncestorOf( qskInputPanel, focusItem ) ) + auto panel = inputPanel(); + if ( panel == nullptr ) + return; + + if ( ( focusItem == panel ) + || qskIsAncestorOf( panel, focusItem ) ) { // ignore: usually the input proxy of the panel return; @@ -254,26 +260,6 @@ void QskInputContext::showInputPanel() m_data->inputItem = focusItem; - if ( qskInputPanel == nullptr ) - qskSetInputPanel( new QskInputPanel() ); - - connect( qskInputPanel, &QQuickItem::visibleChanged, - this, &QPlatformInputContext::emitInputPanelVisibleChanged, - Qt::UniqueConnection ); - - connect( qskInputPanel, &QskControl::localeChanged, - this, &QPlatformInputContext::emitLocaleChanged, - Qt::UniqueConnection ); - - if ( qskInputPanel->parent() == nullptr ) - { - /* - Take ownership to avoid, that the panel gets - destroyed together with the popup/window - */ - qskInputPanel->setParent( this ); - } - if ( QskDialog::instance()->policy() == QskDialog::TopLevelWindow ) { // The input panel is embedded in a top level window @@ -282,7 +268,7 @@ void QskInputContext::showInputPanel() if ( m_data->inputWindow == nullptr ) { - auto window = createEmbeddingWindow( qskInputPanel ); + auto window = createEmbeddingWindow( panel ); if ( window ) { @@ -311,7 +297,7 @@ void QskInputContext::showInputPanel() if ( m_data->inputPopup == nullptr ) { - auto popup = createEmbeddingPopup( qskInputPanel ); + auto popup = createEmbeddingPopup( panel ); if ( popup ) { @@ -330,12 +316,12 @@ void QskInputContext::showInputPanel() m_data->engine->setPredictor( m_data->predictorTable.find( locale() ) ); - qskInputPanel->setLocale( locale() ); - qskInputPanel->attachInputItem( m_data->inputItem ); - qskInputPanel->setEngine( m_data->engine ); + panel->setLocale( locale() ); + panel->attachInputItem( m_data->inputItem ); + panel->setEngine( m_data->engine ); } -void QskInputContext::hideInputPanel() +void QskInputContext::hidePanel() { if ( m_data->inputPopup ) { @@ -354,11 +340,11 @@ void QskInputContext::hideInputPanel() #endif } - if ( qskInputPanel ) + if ( auto panel = inputPanel() ) { - qskInputPanel->setParentItem( nullptr ); - qskInputPanel->attachInputItem( nullptr ); - qskInputPanel->setEngine( nullptr ); + panel->setParentItem( nullptr ); + panel->attachInputItem( nullptr ); + panel->setEngine( nullptr ); } if ( m_data->inputPopup ) @@ -378,10 +364,23 @@ void QskInputContext::hideInputPanel() m_data->inputItem = nullptr; } -bool QskInputContext::isInputPanelVisible() const +void QskInputContext::setActive( bool on ) { - return qskInputPanel && qskInputPanel->isVisible() - && qskInputPanel->window() && qskInputPanel->window()->isVisible(); + if ( on ) + showPanel(); + else + hidePanel(); +} + +bool QskInputContext::isActive() const +{ + if ( auto panel = inputPanel() ) + { + return panel && panel->isVisible() + && panel->window() && panel->window()->isVisible(); + } + + return false; } QLocale QskInputContext::locale() const @@ -397,11 +396,6 @@ QLocale QskInputContext::locale() const return QLocale(); } -Qt::LayoutDirection QskInputContext::inputDirection() const -{ - return Inherited::inputDirection(); -} - void QskInputContext::setFocusObject( QObject* focusObject ) { if ( m_data->inputItem == nullptr || m_data->inputItem == focusObject ) @@ -421,7 +415,7 @@ void QskInputContext::setFocusObject( QObject* focusObject ) if ( m_data->inputItem->hasFocus() ) { /* - As long as the focus is noewhere and + As long as the focus is nowhere and the local focus stay on the input item we don't care */ @@ -448,7 +442,7 @@ void QskInputContext::setFocusObject( QObject* focusObject ) } } - hideInputPanel(); + hidePanel(); m_data->inputItem = nullptr; } @@ -476,20 +470,17 @@ QskTextPredictor* QskInputContext::registeredPredictor( const QLocale& locale ) return m_data->predictorTable.find( locale ); } -void QskInputContext::invokeAction( QInputMethod::Action, int ) +void QskInputContext::processClickAt( int cursorPosition ) { + Q_UNUSED( cursorPosition ); } -void QskInputContext::reset() -{ -} - -void QskInputContext::commit() +void QskInputContext::commitPrediction( bool ) { /* - commit is called, when the input item loses the focus. + called, when the input item loses the focus. As it it should be possible to navigate inside of the - inputPanel this is no valid reason to hide the panel. + inputPanel what should we do here ? */ } @@ -501,15 +492,13 @@ bool QskInputContext::eventFilter( QObject* object, QEvent* event ) { case QEvent::Move: { - if ( qskInputPanel ) - emitKeyboardRectChanged(); - + Q_EMIT panelRectChanged(); break; } case QEvent::Resize: { - if ( qskInputPanel ) - qskInputPanel->setSize( m_data->inputWindow->size() ); + if ( auto panel = inputPanel() ) + panel->setSize( m_data->inputWindow->size() ); break; } @@ -528,7 +517,7 @@ bool QskInputContext::eventFilter( QObject* object, QEvent* event ) { case QskEvent::GeometryChange: { - emitKeyboardRectChanged(); + Q_EMIT panelRectChanged(); break; } case QEvent::DeferredDelete: @@ -542,10 +531,4 @@ bool QskInputContext::eventFilter( QObject* object, QEvent* event ) return Inherited::eventFilter( object, event ); } -bool QskInputContext::filterEvent( const QEvent* ) -{ - // called from QXcbKeyboard, but what about other platforms - return false; -} - #include "moc_QskInputContext.cpp" diff --git a/src/inputpanel/QskInputContext.h b/src/inputpanel/QskInputContext.h index 247b45ed..ce73c770 100644 --- a/src/inputpanel/QskInputContext.h +++ b/src/inputpanel/QskInputContext.h @@ -7,7 +7,8 @@ #define QSK_INPUT_CONTEXT_H #include "QskGlobal.h" -#include +#include +#include #include class QskTextPredictor; @@ -16,46 +17,35 @@ class QskPopup; class QskWindow; class QQuickItem; -class QSK_EXPORT QskInputContext : public QPlatformInputContext +class QSK_EXPORT QskInputContext : public QObject { Q_OBJECT - using Inherited = QPlatformInputContext; + using Inherited = QObject; public: QskInputContext(); virtual ~QskInputContext(); - virtual bool isValid() const override; - virtual bool hasCapability( Capability ) const override; + virtual QRectF panelRect() const; - virtual void update( Qt::InputMethodQueries ) override; - virtual void invokeAction( QInputMethod::Action, int ) override; + virtual void setActive( bool ); + virtual bool isActive() const; - virtual QRectF keyboardRect() const override; - virtual bool isAnimating() const override; - - virtual void showInputPanel() override; - virtual void hideInputPanel() override; - virtual bool isInputPanelVisible() const override; - - virtual void reset() override; - virtual void commit() override; - - virtual void setFocusObject( QObject* ) override; - - virtual QLocale locale() const override; - virtual Qt::LayoutDirection inputDirection() const override; + virtual QLocale locale() const; void registerPredictor( const QLocale&, QskTextPredictor* ); QskTextPredictor* registeredPredictor( const QLocale& ); - Q_INVOKABLE QQuickItem* inputItem(); + virtual QQuickItem* inputItem() const; + virtual QskInputPanel* inputPanel() const; - virtual bool filterEvent( const QEvent* ) override; + static QskInputContext* instance(); + static void setInstance( QskInputContext* ); - static void setInputPanel( QskInputPanel* ); - static QskInputPanel* inputPanel(); +Q_SIGNALS: + void activeChanged(); + void panelRectChanged(); protected: virtual bool eventFilter( QObject*, QEvent* ) override; @@ -63,7 +53,18 @@ protected: virtual QskPopup* createEmbeddingPopup( QskInputPanel* ); virtual QskWindow* createEmbeddingWindow( QskInputPanel* ); + virtual void showPanel(); + virtual void hidePanel(); + private: + friend class QskPlatformInputContext; + + // called from QskPlatformInputContext + virtual void setFocusObject( QObject* ); + virtual void update( Qt::InputMethodQueries ); + virtual void processClickAt( int cursorPosition ); + virtual void commitPrediction( bool ); + class PrivateData; std::unique_ptr< PrivateData > m_data; };