From 9f78660f30f33c1b728460766f5ef146db0c3c8a Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Mon, 4 Jun 2018 10:00:52 +0200 Subject: [PATCH] inputpanel classes reorganized, coming closer to something useful --- src/inputpanel/QskInputContext.cpp | 138 +++++-- src/inputpanel/QskInputContext.h | 6 +- src/inputpanel/QskInputEngine.cpp | 596 +++++++++++++++++++++------- src/inputpanel/QskInputEngine.h | 53 ++- src/inputpanel/QskInputManager.cpp | 369 ----------------- src/inputpanel/QskInputManager.h | 59 --- src/inputpanel/QskTextPredictor.cpp | 13 + src/inputpanel/QskTextPredictor.h | 4 + src/src.pro | 2 - 9 files changed, 606 insertions(+), 634 deletions(-) delete mode 100644 src/inputpanel/QskInputManager.cpp delete mode 100644 src/inputpanel/QskInputManager.h diff --git a/src/inputpanel/QskInputContext.cpp b/src/inputpanel/QskInputContext.cpp index c841a398..f9edd3a2 100644 --- a/src/inputpanel/QskInputContext.cpp +++ b/src/inputpanel/QskInputContext.cpp @@ -4,7 +4,8 @@ *****************************************************************************/ #include "QskInputContext.h" -#include "QskInputManager.h" +#include "QskInputEngine.h" +#include "QskInputPanel.h" #include "QskLinearBox.h" #include "QskDialog.h" @@ -23,6 +24,58 @@ QSK_QT_PRIVATE_END #include #include +namespace +{ + class InputEngine final : public QskInputEngine + { + public: + virtual void attachToPanel( QQuickItem* item ) override + { + if ( m_panel ) + m_panel->attachInputItem( item ); + } + + virtual QskControl* createPanel() override + { + m_panel = new QskInputPanel(); + + connect( m_panel, &QskInputPanel::keySelected, + this, &QskInputEngine::commitKey, Qt::UniqueConnection ); + + connect( m_panel, &QskInputPanel::predictiveTextSelected, + this, &QskInputEngine::commitPredictiveText, Qt::UniqueConnection ); + + return m_panel; + } + + virtual QQuickItem* inputProxy() const override + { + if ( m_panel ) + { + if ( m_panel->panelHints() & QskInputPanel::InputProxy ) + return m_panel->inputProxy(); + } + + return nullptr; + } + virtual void setPredictionEnabled( bool on ) override + { + if ( m_panel ) + m_panel->setPanelHint( QskInputPanel::Prediction, on ); + } + + virtual void showPrediction( const QStringList& prediction ) override + { + if ( m_panel ) + m_panel->setPrediction( prediction ); + } + + private: + QPointer< QskInputPanel > m_panel; + }; + +} + static QPointer< QskInputContext > qskInputContext = nullptr; static void qskSendToPlatformContext( QEvent::Type type ) @@ -39,7 +92,7 @@ static void qskSendToPlatformContext( QEvent::Type type ) static void qskInputContextHook() { - qAddPostRoutine( []{ delete qskInputContext; } ); + qAddPostRoutine( [] { delete qskInputContext; } ); } Q_COREAPP_STARTUP_FUNCTION( qskInputContextHook ) @@ -73,20 +126,53 @@ public: QskPopup* inputPopup = nullptr; QskWindow* inputWindow = nullptr; - QskInputManager* inputManager = nullptr; + QPointer< QskInputEngine > inputEngine; }; QskInputContext::QskInputContext(): m_data( new PrivateData() ) { setObjectName( "InputContext" ); - m_data->inputManager = new QskInputManager( this ); + setEngine( new InputEngine() ); } QskInputContext::~QskInputContext() { } +void QskInputContext::setEngine( QskInputEngine* engine ) +{ + if ( m_data->inputEngine == engine ) + return; + + if ( m_data->inputEngine && m_data->inputEngine->parent() == this ) + { + m_data->inputEngine->disconnect( this ); + + if ( m_data->inputEngine->parent() == this ) + delete m_data->inputEngine; + } + + m_data->inputEngine = engine; + + if ( engine ) + { + if ( engine->parent() == nullptr ) + engine->setParent( this ); + + connect( engine, &QskInputEngine::activeChanged, + this, &QskInputContext::activeChanged ); + + connect( engine, &QskInputEngine::localeChanged, + this, [] { qskSendToPlatformContext( QEvent::LocaleChange ); } ); + } +} + +QskInputEngine* QskInputContext::engine() const +{ + return m_data->inputEngine; +} + QQuickItem* QskInputContext::inputItem() const { return m_data->inputItem; @@ -94,19 +180,14 @@ QQuickItem* QskInputContext::inputItem() const QskControl* QskInputContext::inputPanel() const { - auto panel = m_data->inputManager->panel( true ); + if ( m_data->inputEngine == nullptr ) + return nullptr; - if ( panel->parent() != this ) - { + auto panel = m_data->inputEngine->panel( true ); + + if ( panel && panel->parent() != this ) panel->setParent( const_cast< QskInputContext* >( this ) ); - connect( panel, &QQuickItem::visibleChanged, - this, &QskInputContext::activeChanged, Qt::UniqueConnection ); - - connect( panel, &QskControl::localeChanged, - this, [] { qskSendToPlatformContext( QEvent::LocaleChange ); } ); - } - return panel; } @@ -124,7 +205,8 @@ void QskInputContext::update( Qt::InputMethodQueries queries ) } } - m_data->inputManager->processInputMethodQueries( queries ); + if ( m_data->inputEngine ) + m_data->inputEngine->updateInputPanel( queries ); } QRectF QskInputContext::panelRect() const @@ -146,8 +228,9 @@ QskPopup* QskInputContext::createEmbeddingPopup( QskControl* panel ) auto box = new QskLinearBox( popup ); box->addItem( panel ); - const auto alignment = - m_data->inputManager->panelAlignment() & Qt::AlignVertical_Mask; + Qt::Alignment alignment = Qt::AlignVCenter; + if ( m_data->inputEngine ) + alignment = m_data->inputEngine->panelAlignment() & Qt::AlignVertical_Mask; popup->setOverlay( alignment == Qt::AlignVCenter ); @@ -262,7 +345,8 @@ void QskInputContext::showPanel() } } - m_data->inputManager->attachInputItem( m_data->inputItem ); + if ( m_data->inputEngine ) + m_data->inputEngine->attachInputItem( m_data->inputItem ); } void QskInputContext::hidePanel() @@ -284,10 +368,13 @@ void QskInputContext::hidePanel() #endif } - if ( auto panel = m_data->inputManager->panel( false ) ) - panel->setParentItem( nullptr ); + if ( m_data->inputEngine ) + { + if ( auto panel = m_data->inputEngine->panel( false ) ) + panel->setParentItem( nullptr ); - m_data->inputManager->attachInputItem( nullptr ); + m_data->inputEngine->attachInputItem( nullptr ); + } if ( m_data->inputPopup ) m_data->inputPopup->deleteLater(); @@ -325,13 +412,8 @@ bool QskInputContext::isActive() const QLocale QskInputContext::locale() const { - if ( m_data->inputItem ) - { - QInputMethodQueryEvent event( Qt::ImPreferredLanguage ); - QCoreApplication::sendEvent( m_data->inputItem, &event ); - - return event.value( Qt::ImPreferredLanguage ).toLocale(); - } + if ( auto panel = inputPanel() ) + return panel->locale(); return QLocale(); } diff --git a/src/inputpanel/QskInputContext.h b/src/inputpanel/QskInputContext.h index 724ea0a3..35b02e1b 100644 --- a/src/inputpanel/QskInputContext.h +++ b/src/inputpanel/QskInputContext.h @@ -13,7 +13,7 @@ class QskTextPredictor; class QskControl; -class QskInputManager; +class QskInputEngine; class QskPopup; class QskWindow; class QQuickItem; @@ -28,8 +28,8 @@ public: QskInputContext(); virtual ~QskInputContext(); - void setManager( QskInputManager* ); - QskInputManager manager(); + void setEngine( QskInputEngine* ); + QskInputEngine* engine() const; QRectF panelRect() const; diff --git a/src/inputpanel/QskInputEngine.cpp b/src/inputpanel/QskInputEngine.cpp index 29ee2ffa..0f03eca4 100644 --- a/src/inputpanel/QskInputEngine.cpp +++ b/src/inputpanel/QskInputEngine.cpp @@ -4,11 +4,82 @@ *****************************************************************************/ #include "QskInputEngine.h" +#include "QskInputContext.h" #include "QskTextPredictor.h" +#include "QskControl.h" #include -#include -#include +#include + +static inline QQuickItem* qskReceiverItem( const QskInputEngine* engine ) +{ + auto item = engine->inputProxy(); + return item ? item : engine->inputItem(); +} + +static inline void qskSendReplaceText( QQuickItem* receiver, const QString& text ) +{ + if ( receiver == nullptr ) + return; + + QInputMethodEvent::Attribute attribute( + QInputMethodEvent::Selection, 0, 32767, QVariant() ); + + QInputMethodEvent event1( QString(), { attribute } ); + QCoreApplication::sendEvent( receiver, &event1 ); + + QInputMethodEvent event2; + event2.setCommitString( text ); + + QCoreApplication::sendEvent( receiver, &event2 ); +} + +static inline void qskSendText( QQuickItem* receiver, + const QString& text, bool isFinal ) +{ + if ( receiver == nullptr ) + return; + + if ( isFinal ) + { + QInputMethodEvent event; + event.setCommitString( text ); + + QCoreApplication::sendEvent( receiver, &event ); + } + else + { + QTextCharFormat format; + format.setFontUnderline( true ); + + const QInputMethodEvent::Attribute attribute( + QInputMethodEvent::TextFormat, 0, text.length(), format ); + + QInputMethodEvent event( text, { attribute } ); + + QCoreApplication::sendEvent( receiver, &event ); + } +} + +static inline void qskSendKey( QQuickItem* receiver, int key ) +{ + if ( receiver == nullptr ) + return; + + QKeyEvent keyPress( QEvent::KeyPress, key, Qt::NoModifier ); + QCoreApplication::sendEvent( receiver, &keyPress ); + + QKeyEvent keyRelease( QEvent::KeyRelease, key, Qt::NoModifier ); + QCoreApplication::sendEvent( receiver, &keyRelease ); +} + +static inline bool qskUsePrediction( Qt::InputMethodHints hints ) +{ + constexpr Qt::InputMethodHints mask = + Qt::ImhNoPredictiveText | Qt::ImhExclusiveInputMask | Qt::ImhHiddenText; + + return ( hints & mask ) == 0; +} static inline QString qskKeyString( int keyCode ) { @@ -36,23 +107,161 @@ static inline QString qskKeyString( int keyCode ) return QChar( keyCode ); } -static inline bool qskUsePrediction( Qt::InputMethodHints hints ) +namespace { - constexpr Qt::InputMethodHints mask = - Qt::ImhNoPredictiveText | Qt::ImhExclusiveInputMask; + class KeyProcessor + { + public: + class Result + { + public: + int key = 0; - return ( hints & mask ) == 0; + QString text; + bool isFinal = true; + }; + + Result processKey( int key, Qt::InputMethodHints inputHints, + QskTextPredictor* predictor, int spaceLeft ) + { + Result result; + + // First we have to handle the control keys + + switch ( key ) + { + case Qt::Key_Backspace: + case Qt::Key_Muhenkan: + { + if ( predictor ) + { + if ( !m_preedit.isEmpty() ) + { + m_preedit.chop( 1 ); + + result.text = m_preedit; + result.isFinal = false; + + predictor->request( m_preedit ); + + return result; + } + } + + result.key = Qt::Key_Backspace; + return result; + } + case Qt::Key_Return: + { + if ( predictor ) + { + if ( !m_preedit.isEmpty() ) + { + if ( spaceLeft ) + { + result.text = m_preedit.left( spaceLeft ); + result.isFinal = true; + } + + reset(); + + return result; + } + } + + if( !( inputHints & Qt::ImhMultiLine ) ) + { + result.key = Qt::Key_Return; + return result; + } + + break; + } + case Qt::Key_Space: + { + if ( predictor ) + { + if ( !m_preedit.isEmpty() && spaceLeft) + { + m_preedit += qskKeyString( key ); + m_preedit = m_preedit.left( spaceLeft ); + + result.text = m_preedit; + result.isFinal = true; + + reset(); + + return result; + } + } + + break; + } + case Qt::Key_Left: + case Qt::Key_Right: + case Qt::Key_Escape: + { + result.key = key; + return result; + } + } + + const QString text = qskKeyString( key ); + + if ( predictor ) + { + m_preedit += text; + + predictor->request( m_preedit ); + + if ( predictor->candidateCount() > 0 ) + { + result.text = m_preedit; + result.isFinal = false; + } + else + { + result.text = m_preedit.left( spaceLeft ); + result.isFinal = true; + + m_preedit.clear(); + } + } + else + { + result.text = text; + result.isFinal = true; + } + + return result; + } + + void reset() + { + m_preedit.clear(); + } + + private: + QString m_preedit; + }; } class QskInputEngine::PrivateData { public: + KeyProcessor keyProcessor; + QPointer< QskControl > panel; + QPointer< QQuickItem > inputItem; + + QLocale predictorLocale; QPointer< QskTextPredictor > predictor; - QString preedit; + + Qt::InputMethodHints inputHints = 0; + bool hasPredictorLocale = false; }; QskInputEngine::QskInputEngine( QObject* parent ): - QObject( parent ), + Inherited( parent ), m_data( new PrivateData() ) { } @@ -61,12 +270,139 @@ QskInputEngine::~QskInputEngine() { } -void QskInputEngine::setPredictor( QskTextPredictor* predictor ) +void QskInputEngine::attachInputItem( QQuickItem* item ) { - if ( predictor == m_data->predictor ) + if ( item == m_data->inputItem ) return; - reset(); + m_data->inputItem = item; + + if ( item ) + { + auto context = QskInputContext::instance(); + const auto locale = context->locale(); + + if ( m_data->predictor ) + m_data->predictor->reset(); + + m_data->keyProcessor.reset(); + m_data->inputHints = 0; + + attachToPanel( item ); + + Qt::InputMethodQueries queries = Qt::ImQueryAll; + queries &= ~Qt::ImEnabled; + + updateInputPanel( queries ); + } + else + { + attachToPanel( nullptr ); + } +} + +void QskInputEngine::updateInputPanel( + Qt::InputMethodQueries queries ) +{ + auto item = inputItem(); + if ( item == nullptr ) + return; + + QInputMethodQueryEvent event( queries ); + QCoreApplication::sendEvent( item, &event ); + + if ( queries & Qt::ImHints ) + { + m_data->inputHints = static_cast< Qt::InputMethodHints >( + event.value( Qt::ImHints ).toInt() ); + + updatePanel(); + } + + if ( queries & Qt::ImPreferredLanguage ) + { + if ( m_data->panel ) + { + m_data->panel->setLocale( + event.value( Qt::ImPreferredLanguage ).toLocale() ); + } + } +} + +QskControl* QskInputEngine::panel( bool doCreate ) +{ + if ( m_data->panel == nullptr && doCreate ) + { + auto panel = createPanel(); + + connect( panel, &QQuickItem::visibleChanged, + this, &QskInputEngine::activeChanged ); + + connect( panel, &QskControl::localeChanged, + this, &QskInputEngine::updateLocale ); + + m_data->panel = panel; + updateLocale( m_data->panel->locale() ); + } + + return m_data->panel; +} + +Qt::Alignment QskInputEngine::panelAlignment() const +{ + /* + When we have an input proxy, we don't care if + the input item becomes hidden + */ + + return inputProxy() ? Qt::AlignVCenter : Qt::AlignBottom; +} + +QLocale QskInputEngine::locale() const +{ + if ( m_data->panel ) + return m_data->panel->locale(); + + return QLocale(); +} + +void QskInputEngine::updateLocale( const QLocale& locale ) +{ + if ( !m_data->hasPredictorLocale || locale != m_data->predictorLocale ) + { + m_data->hasPredictorLocale = true; + m_data->predictorLocale = locale; + resetPredictor( locale ); + + m_data->keyProcessor.reset(); + updatePanel(); + } + + Q_EMIT localeChanged(); +} + +void QskInputEngine::updatePanel() +{ + setPredictionEnabled( + m_data->predictor && qskUsePrediction( m_data->inputHints ) ); +} + +QQuickItem* QskInputEngine::inputItem() const +{ + return m_data->inputItem; +} + +QQuickItem* QskInputEngine::inputProxy() const +{ + return nullptr; +} + +void QskInputEngine::resetPredictor( const QLocale& locale ) +{ + auto predictor = QskInputContext::instance()->textPredictor( locale ); + + if ( predictor == m_data->predictor ) + return; if ( m_data->predictor ) { @@ -87,172 +423,124 @@ void QskInputEngine::setPredictor( QskTextPredictor* predictor ) predictor->setParent( this ); connect( predictor, &QskTextPredictor::predictionChanged, - this, &QskInputEngine::predictionChanged ); + this, &QskInputEngine::updatePrediction ); } m_data->predictor = predictor; } -QskTextPredictor* QskInputEngine::predictor() const +void QskInputEngine::applyInput( bool success ) { - return m_data->predictor; + auto item = inputItem(); + if ( item == nullptr ) + return; + + if ( success ) + { + if ( auto proxy = inputProxy() ) + { + const auto value = proxy->property( "text" ); + if ( value.canConvert< QString >() ) + qskSendReplaceText( item, value.toString() ); + } + } + + qskSendKey( item, success ? Qt::Key_Return : Qt::Key_Escape ); } -QStringList QskInputEngine::prediction() const +void QskInputEngine::applyText( const QString& text, bool isFinal ) { - QStringList candidates; - - if ( const auto predictor = m_data->predictor ) - { - const auto count = predictor->candidateCount(); - candidates.reserve( count ); - - for( int i = 0; i < count; i++ ) - candidates += predictor->candidate( i ); - } - - return candidates; + qskSendText( qskReceiverItem( this ), text, isFinal ); } -QskInputEngine::Result QskInputEngine::processKey( int key, - Qt::InputMethodHints inputHints, int spaceLeft ) +void QskInputEngine::applyKey( int key ) { - QskInputEngine::Result result; - - auto& preedit = m_data->preedit; - - QskTextPredictor* predictor = nullptr; - if ( qskUsePrediction( inputHints ) ) - predictor = m_data->predictor; - - /* - First we have to handle the control keys - */ - switch ( key ) - { - case Qt::Key_Backspace: - case Qt::Key_Muhenkan: - { - if ( predictor ) - { - if ( !preedit.isEmpty() ) - { - preedit.chop( 1 ); - - result.text = preedit; - result.isFinal = false; - - predictor->request( preedit ); - - return result; - } - } - - result.key = Qt::Key_Backspace; - return result; - } - case Qt::Key_Return: - { - if ( predictor ) - { - if ( !preedit.isEmpty() ) - { - if ( spaceLeft ) - { - result.text = preedit.left( spaceLeft ); - result.isFinal = true; - } - - reset(); - - return result; - } - } - - if( !( inputHints & Qt::ImhMultiLine ) ) - { - result.key = Qt::Key_Return; - return result; - } - - break; - } - case Qt::Key_Space: - { - if ( predictor ) - { - if ( !preedit.isEmpty() && spaceLeft) - { - preedit += qskKeyString( key ); - preedit = preedit.left( spaceLeft ); - - result.text = preedit; - result.isFinal = true; - - reset(); - - return result; - } - } - - break; - } - case Qt::Key_Left: - case Qt::Key_Right: - case Qt::Key_Escape: - { - result.key = key; - return result; - } - } - - const QString text = qskKeyString( key ); - - if ( predictor ) - { - preedit += text; - - predictor->request( preedit ); - - if ( predictor->candidateCount() > 0 ) - { - result.text = preedit; - result.isFinal = false; - } - else - { - result.text = preedit.left( spaceLeft ); - result.isFinal = true; - - preedit.clear(); - } - } - else - { - result.text = text; - result.isFinal = true; - } - - return result; + // control keys like left/right + qskSendKey( qskReceiverItem( this ), key ); } -QString QskInputEngine::predictiveText( int index ) const +void QskInputEngine::commitPredictiveText( int index ) { - if ( const QskTextPredictor* predictor = m_data->predictor ) + QString text; + + if ( m_data->predictor ) { - if ( index >= 0 && index < predictor->candidateCount() ) - return predictor->candidate( index ); + text = m_data->predictor->candidate( index ); + m_data->predictor->reset(); } - return QString(); + m_data->keyProcessor.reset(); + + showPrediction( QStringList() ); + applyText( text, true ); } -void QskInputEngine::reset() +void QskInputEngine::updatePrediction() { if ( m_data->predictor ) - m_data->predictor->reset(); + showPrediction( m_data->predictor->candidates() ); +} - m_data->preedit.clear(); +void QskInputEngine::setPredictionEnabled( bool on ) +{ + Q_UNUSED( on ) +} + +void QskInputEngine::showPrediction( const QStringList& ) +{ +} + +void QskInputEngine::commitKey( int key ) +{ + int spaceLeft = -1; + + if ( !( m_data->inputHints & Qt::ImhMultiLine ) ) + { + QInputMethodQueryEvent event1( Qt::ImMaximumTextLength ); + QCoreApplication::sendEvent( inputItem(), &event1 ); + + const int maxChars = event1.value( Qt::ImMaximumTextLength ).toInt(); + if ( maxChars >= 0 ) + { + QInputMethodQueryEvent event2( Qt::ImSurroundingText ); + QCoreApplication::sendEvent( qskReceiverItem( this ), &event2 ); + + const auto text = event2.value( Qt::ImSurroundingText ).toString(); + spaceLeft = maxChars - text.length(); + } + } + + QskTextPredictor* predictor = nullptr; + if ( qskUsePrediction( m_data->inputHints ) ) + predictor = m_data->predictor; + + const auto result = m_data->keyProcessor.processKey( + key, m_data->inputHints, predictor, spaceLeft ); + + if ( result.key ) + { + switch( result.key ) + { + case Qt::Key_Return: + { + applyInput( true ); + break; + } + case Qt::Key_Escape: + { + applyInput( false ); + break; + } + default: + { + applyKey( result.key ); + } + } + } + else if ( !result.text.isEmpty() ) + { + applyText( result.text, result.isFinal ); + } } #include "moc_QskInputEngine.cpp" diff --git a/src/inputpanel/QskInputEngine.h b/src/inputpanel/QskInputEngine.h index aca0ba3a..1882eafc 100644 --- a/src/inputpanel/QskInputEngine.h +++ b/src/inputpanel/QskInputEngine.h @@ -11,8 +11,9 @@ #include class QskTextPredictor; +class QskControl; class QQuickItem; -class QStringList; +class QLocale; class QSK_EXPORT QskInputEngine : public QObject { @@ -21,33 +22,47 @@ class QSK_EXPORT QskInputEngine : public QObject using Inherited = QObject; public: - class Result - { - public: - int key = 0; - - QString text; - bool isFinal = true; - }; - QskInputEngine( QObject* parent = nullptr ); - virtual ~QskInputEngine(); + virtual ~QskInputEngine() override; - void setPredictor( QskTextPredictor* ); - QskTextPredictor* predictor() const; + virtual void attachInputItem( QQuickItem* ); + virtual void updateInputPanel( Qt::InputMethodQueries ); - virtual Result processKey( int key, - Qt::InputMethodHints, int spaceLeft = -1 ); + QskControl* panel( bool doCreate ); + virtual Qt::Alignment panelAlignment() const; - QString predictiveText( int ) const; - QStringList prediction() const; + virtual QQuickItem* inputProxy() const; + virtual QQuickItem* inputItem() const; - void reset(); + QLocale locale() const; + +public Q_SLOTS: + void commitKey( int keyCode ); + void commitPredictiveText( int index ); Q_SIGNALS: - void predictionChanged(); + void activeChanged(); + void localeChanged(); + +protected: + virtual QskControl* createPanel() = 0; + virtual void attachToPanel( QQuickItem* ) = 0; + + virtual void setPredictionEnabled( bool on ); + virtual void showPrediction( const QStringList& ); private: + void applyInput( bool success ); + void applyText( const QString&, bool isFinal ); + void applyKey( int keyCode ); + + void resetPredictor( const QLocale& ); + void updatePrediction(); + + void updatePanel(); + + void updateLocale( const QLocale& ); + class PrivateData; std::unique_ptr< PrivateData > m_data; }; diff --git a/src/inputpanel/QskInputManager.cpp b/src/inputpanel/QskInputManager.cpp deleted file mode 100644 index 96665e9c..00000000 --- a/src/inputpanel/QskInputManager.cpp +++ /dev/null @@ -1,369 +0,0 @@ -/****************************************************************************** - * QSkinny - Copyright (C) 2016 Uwe Rathmann - * This file may be used under the terms of the QSkinny License, Version 1.0 - *****************************************************************************/ - -#include "QskInputManager.h" -#include "QskInputPanel.h" -#include "QskInputEngine.h" -#include "QskInputContext.h" - -#include -#include - -static inline QQuickItem* qskReceiverItem( const QskInputManager* manager ) -{ - auto item = manager->inputProxy(); - return item ? item : manager->inputItem(); -} - -static inline void qskSendReplaceText( QQuickItem* receiver, const QString& text ) -{ - if ( receiver == nullptr ) - return; - - QInputMethodEvent::Attribute attribute( - QInputMethodEvent::Selection, 0, 32767, QVariant() ); - - QInputMethodEvent event1( QString(), { attribute } ); - QCoreApplication::sendEvent( receiver, &event1 ); - - QInputMethodEvent event2; - event2.setCommitString( text ); - - QCoreApplication::sendEvent( receiver, &event2 ); -} - -static inline void qskSendText( QQuickItem* receiver, - const QString& text, bool isFinal ) -{ - if ( receiver == nullptr ) - return; - - if ( isFinal ) - { - QInputMethodEvent event; - event.setCommitString( text ); - - QCoreApplication::sendEvent( receiver, &event ); - } - else - { - QTextCharFormat format; - format.setFontUnderline( true ); - - const QInputMethodEvent::Attribute attribute( - QInputMethodEvent::TextFormat, 0, text.length(), format ); - - QInputMethodEvent event( text, { attribute } ); - - QCoreApplication::sendEvent( receiver, &event ); - } -} - -static inline void qskSendKey( QQuickItem* receiver, int key ) -{ - if ( receiver == nullptr ) - return; - - QKeyEvent keyPress( QEvent::KeyPress, key, Qt::NoModifier ); - QCoreApplication::sendEvent( receiver, &keyPress ); - - QKeyEvent keyRelease( QEvent::KeyRelease, key, Qt::NoModifier ); - QCoreApplication::sendEvent( receiver, &keyRelease ); -} - -class QskInputManager::PrivateData -{ -public: - QPointer< QskInputEngine > engine; - QPointer< QskControl > panel; - - QLocale predictorLocale; - Qt::InputMethodHints inputHints = 0; -}; - -QskInputManager::QskInputManager( QObject* parent ): - Inherited( parent ), - m_data( new PrivateData() ) -{ -} - -QskInputManager::~QskInputManager() -{ -} - -void QskInputManager::attachInputItem( QQuickItem* item ) -{ - auto panel = qobject_cast< QskInputPanel* >( m_data->panel ); - if ( panel == nullptr ) - return; - - if ( item ) - { - auto context = QskInputContext::instance(); - const auto locale = context->locale(); - - updateEngine( locale ); - m_data->engine->reset(); - - panel->setLocale( locale ); - panel->attachInputItem( item ); - - Qt::InputMethodQueries queries = Qt::ImQueryAll; - queries &= ~Qt::ImEnabled; - - processInputMethodQueries( queries ); - } - else - { - panel->attachInputItem( nullptr ); - } -} - -void QskInputManager::processInputMethodQueries( - Qt::InputMethodQueries queries ) -{ - if ( queries & Qt::ImHints ) - { - m_data->inputHints = 0; - - if ( auto item = inputItem() ) - { - QInputMethodQueryEvent event( Qt::ImHints ); - QCoreApplication::sendEvent( item, &event ); - - m_data->inputHints = static_cast< Qt::InputMethodHints >( - event.value( Qt::ImHints ).toInt() ); - } - - updatePanel(); - } -} - -QskControl* QskInputManager::panel( bool doCreate ) -{ - if ( m_data->panel == nullptr && doCreate ) - { - m_data->panel = createPanel(); - - connect( m_data->panel, &QskControl::localeChanged, - this, &QskInputManager::updatePredictor, Qt::UniqueConnection ); - } - - return m_data->panel; -} - -Qt::Alignment QskInputManager::panelAlignment() const -{ - if ( auto panel = qobject_cast< QskInputPanel* >( m_data->panel ) ) - { - if ( panel->panelHints() & QskInputPanel::InputProxy ) - { - /* - When the panel has an input proxy we don't need to see - the input item and can put the panel in the center - */ - - return Qt::AlignVCenter; - } - } - - return Qt::AlignBottom; -} - -QskControl* QskInputManager::createPanel() -{ - auto panel = new QskInputPanel(); - - connect( panel, &QskInputPanel::keySelected, - this, &QskInputManager::commitKey, Qt::UniqueConnection ); - - connect( panel, &QskInputPanel::predictiveTextSelected, - this, &QskInputManager::commitPredictiveText, Qt::UniqueConnection ); - - return panel; -} - -QskInputEngine* QskInputManager::createEngine() -{ - return new QskInputEngine(); -} - -void QskInputManager::updateEngine( const QLocale& locale ) -{ - bool updatePredictor; - - if ( m_data->engine == nullptr) - { - auto engine = createEngine(); - if ( engine->parent() == nullptr ) - engine->setParent( this ); - - connect( engine, &QskInputEngine::predictionChanged, - this, &QskInputManager::updatePrediction ); - - m_data->engine = engine; - updatePredictor = true; - } - else - { - updatePredictor = ( locale != m_data->predictorLocale ); - } - - if ( updatePredictor ) - { - auto context = QskInputContext::instance(); - - m_data->predictorLocale = locale; - m_data->engine->setPredictor( context->textPredictor( locale ) ); - updatePanel(); - } -} - -void QskInputManager::updatePredictor() -{ - if ( m_data->panel && m_data->engine ) - { - auto context = QskInputContext::instance(); - const auto locale = context->locale(); - - if ( m_data->predictorLocale != locale ) - m_data->engine->setPredictor( context->textPredictor( locale ) ); - } -} - -void QskInputManager::updatePanel() -{ - if ( auto panel = qobject_cast< QskInputPanel* >( m_data->panel ) ) - { - const auto mask = Qt::ImhNoPredictiveText - | Qt::ImhExclusiveInputMask | Qt::ImhHiddenText; - - panel->setPanelHint( QskInputPanel::Prediction, - m_data->engine->predictor() && !( m_data->inputHints & mask ) ); - } -} - -QQuickItem* QskInputManager::inputItem() const -{ - if ( auto panel = qobject_cast< QskInputPanel* >( m_data->panel ) ) - return panel->attachedInputItem(); - - return nullptr; -} - -QQuickItem* QskInputManager::inputProxy() const -{ - if ( auto panel = qobject_cast< QskInputPanel* >( m_data->panel ) ) - { - if ( panel->panelHints() & QskInputPanel::InputProxy ) - return panel->inputProxy(); - } - - return nullptr; -} - -void QskInputManager::applyInput( bool success ) -{ - auto item = inputItem(); - if ( item == nullptr ) - return; - - if ( success ) - { - if ( auto proxy = inputProxy() ) - { - const auto value = proxy->property( "text" ); - if ( value.canConvert() ) - qskSendReplaceText( item, value.toString() ); - } - } - - qskSendKey( item, success ? Qt::Key_Return : Qt::Key_Escape ); -} - -void QskInputManager::applyText( const QString& text, bool isFinal ) -{ - qskSendText( qskReceiverItem( this ), text, isFinal ); -} - -void QskInputManager::applyKey( int key ) -{ - // control keys like left/right - qskSendKey( qskReceiverItem( this ), key ); -} - -void QskInputManager::commitPredictiveText( int index ) -{ - if ( auto panel = qobject_cast< QskInputPanel* >( m_data->panel ) ) - { - panel->setPrediction( QStringList() ); - - const QString text = m_data->engine->predictiveText( index ); - - m_data->engine->reset(); - applyText( text, true ); - } -} - -void QskInputManager::updatePrediction() -{ - if ( auto panel = qobject_cast< QskInputPanel* >( m_data->panel ) ) - panel->setPrediction( m_data->engine->prediction() ); -} - -void QskInputManager::commitKey( int key ) -{ - auto panel = qobject_cast< QskInputPanel* >( m_data->panel ); - if ( panel == nullptr ) - return; - - int spaceLeft = -1; - - if ( !( m_data->inputHints & Qt::ImhMultiLine ) ) - { - QInputMethodQueryEvent event1( Qt::ImMaximumTextLength ); - QCoreApplication::sendEvent( panel->attachedInputItem(), &event1 ); - - const int maxChars = event1.value( Qt::ImMaximumTextLength ).toInt(); - if ( maxChars >= 0 ) - { - QInputMethodQueryEvent event2( Qt::ImSurroundingText ); - QCoreApplication::sendEvent( qskReceiverItem( this ), &event2 ); - - const auto text = event2.value( Qt::ImSurroundingText ).toString(); - spaceLeft = maxChars - text.length(); - } - } - - const auto result = m_data->engine->processKey( - key, m_data->inputHints, spaceLeft ); - - if ( result.key ) - { - switch( result.key ) - { - case Qt::Key_Return: - { - applyInput( true ); - break; - } - case Qt::Key_Escape: - { - applyInput( false ); - break; - } - default: - { - applyKey( result.key ); - } - } - } - else if ( !result.text.isEmpty() ) - { - applyText( result.text, result.isFinal ); - } -} - -#include "moc_QskInputManager.cpp" diff --git a/src/inputpanel/QskInputManager.h b/src/inputpanel/QskInputManager.h deleted file mode 100644 index 4c0b33bd..00000000 --- a/src/inputpanel/QskInputManager.h +++ /dev/null @@ -1,59 +0,0 @@ -/****************************************************************************** - * QSkinny - Copyright (C) 2016 Uwe Rathmann - * This file may be used under the terms of the QSkinny License, Version 1.0 - *****************************************************************************/ - -#ifndef QSK_INPUT_MANAGER_H -#define QSK_INPUT_MANAGER_H - -#include "QskGlobal.h" -#include -#include - -class QskInputEngine; -class QskControl; -class QQuickItem; -class QLocale; - -class QSK_EXPORT QskInputManager : public QObject -{ - Q_OBJECT - - using Inherited = QObject; - -public: - QskInputManager( QObject* parent = nullptr ); - virtual ~QskInputManager() override; - - virtual void attachInputItem( QQuickItem* ); - virtual void processInputMethodQueries( Qt::InputMethodQueries ); - - QskControl* panel( bool doCreate ); - virtual Qt::Alignment panelAlignment() const; - - virtual QQuickItem* inputProxy() const; - virtual QQuickItem* inputItem() const; - -protected: - virtual QskControl* createPanel(); - virtual QskInputEngine* createEngine(); - - void updatePredictor(); - void updateEngine( const QLocale& ); - -private: - void applyInput( bool success ); - void applyText( const QString&, bool isFinal ); - void applyKey( int keyCode ); - - void commitKey( int keyCode ); - void commitPredictiveText( int index ); - - void updatePrediction(); - void updatePanel(); - - class PrivateData; - std::unique_ptr< PrivateData > m_data; -}; - -#endif diff --git a/src/inputpanel/QskTextPredictor.cpp b/src/inputpanel/QskTextPredictor.cpp index 6ff308c3..50f76f98 100644 --- a/src/inputpanel/QskTextPredictor.cpp +++ b/src/inputpanel/QskTextPredictor.cpp @@ -21,4 +21,17 @@ QskTextPredictor::Attributes QskTextPredictor::attributes() const return m_attributes; } +QStringList QskTextPredictor::candidates() const +{ + const auto count = candidateCount(); + + QStringList candidates; + candidates.reserve( count ); + + for( int i = 0; i < count; i++ ) + candidates += candidate( i ); + + return candidates; +} + #include "moc_QskTextPredictor.cpp" diff --git a/src/inputpanel/QskTextPredictor.h b/src/inputpanel/QskTextPredictor.h index 0b9552b6..292e0c3f 100644 --- a/src/inputpanel/QskTextPredictor.h +++ b/src/inputpanel/QskTextPredictor.h @@ -9,6 +9,8 @@ #include #include +class QStringList; + // abstract base class for input methods for retrieving predictive text class QSK_EXPORT QskTextPredictor : public QObject @@ -32,6 +34,8 @@ public: virtual int candidateCount() const = 0; virtual QString candidate( int ) const = 0; + virtual QStringList candidates() const; + Attributes attributes() const; Q_SIGNALS: diff --git a/src/src.pro b/src/src.pro index c1985fa4..4acd8e6d 100644 --- a/src/src.pro +++ b/src/src.pro @@ -300,7 +300,6 @@ SOURCES += \ SOURCES += \ inputpanel/QskTextPredictor.cpp \ inputpanel/QskInputContext.cpp \ - inputpanel/QskInputManager.cpp \ inputpanel/QskInputEngine.cpp \ inputpanel/QskInputPanel.cpp \ inputpanel/QskInputPredictionBar.cpp \ @@ -309,7 +308,6 @@ SOURCES += \ HEADERS += \ inputpanel/QskTextPredictor.h \ inputpanel/QskInputContext.h \ - inputpanel/QskInputManager.h \ inputpanel/QskInputEngine.h \ inputpanel/QskInputPanel.h \ inputpanel/QskInputPredictionBar.h \