From 151a73cb0bbbcb385fa03234a4b3f4564038e41c Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Fri, 20 Apr 2018 08:52:26 +0200 Subject: [PATCH] QskInputContext improved --- inputcontext/QskHunspellCompositionModel.cpp | 80 ++--- inputcontext/QskHunspellCompositionModel.h | 8 +- inputcontext/QskInputCompositionModel.cpp | 265 +++------------ inputcontext/QskInputCompositionModel.h | 54 +-- inputcontext/QskInputContext.cpp | 335 ++++++++++++++----- inputcontext/QskInputContext.h | 13 +- inputcontext/QskPinyinCompositionModel.cpp | 96 +++--- inputcontext/QskPinyinCompositionModel.h | 8 +- 8 files changed, 430 insertions(+), 429 deletions(-) diff --git a/inputcontext/QskHunspellCompositionModel.cpp b/inputcontext/QskHunspellCompositionModel.cpp index dfba993a..acb2527d 100644 --- a/inputcontext/QskHunspellCompositionModel.cpp +++ b/inputcontext/QskHunspellCompositionModel.cpp @@ -11,7 +11,7 @@ public: }; QskHunspellCompositionModel::QskHunspellCompositionModel( QskInputContext* context ): - Inherited( context ), + Inherited( Words, context ), m_data( new PrivateData() ) { #if 1 @@ -28,21 +28,6 @@ QskHunspellCompositionModel::~QskHunspellCompositionModel() Hunspell_destroy( m_data->hunspellHandle ); } -bool QskHunspellCompositionModel::supportsSuggestions() const -{ - return true; -} - -void QskHunspellCompositionModel::commitCandidate( int index ) -{ - if( index < m_data->candidates.count() ) - { - // The user usually selects a full word, so we can add the space - QString commitString = candidate( index ) + " "; - commit( commitString ); - } -} - int QskHunspellCompositionModel::candidateCount() const { return m_data->candidates.count(); @@ -53,47 +38,38 @@ QString QskHunspellCompositionModel::candidate( int pos ) const return m_data->candidates[ pos ]; } -QString QskHunspellCompositionModel::polishPreedit( const QString& preedit ) +void QskHunspellCompositionModel::resetCandidates() { - if( preedit.isEmpty() ) + if ( !m_data->candidates.isEmpty() ) { - // new word: delete suggestions m_data->candidates.clear(); + Q_EMIT candidatesChanged(); } - else - { - char** suggestions; - QByteArray word = preedit.toUtf8(); // ### do we need to check the encoding (see qtvirtualkeyboard)? - int suggestionCount = Hunspell_suggest( - m_data->hunspellHandle, &suggestions, word.constData() ); - - QVector< QString > candidates; - candidates.reserve( suggestionCount ); - - for( int a = 0; a < suggestionCount; ++a ) - { - const QString suggestion = QString::fromUtf8( suggestions[a] ); // ### encoding? - - if( suggestion.startsWith( preedit ) ) - { - candidates.prepend( suggestion ); - } - else - { - candidates.append( suggestion ); - } - } - Hunspell_free_list( m_data->hunspellHandle, &suggestions, suggestionCount ); - - m_data->candidates = candidates; - } - - Q_EMIT candidatesChanged(); - - return preedit; } -bool QskHunspellCompositionModel::hasIntermediate() const +void QskHunspellCompositionModel::requestCandidates( const QString& text ) { - return true; + char** suggestions; + const QByteArray word = text.toUtf8(); // ### do we need to check the encoding + + const int count = Hunspell_suggest( + m_data->hunspellHandle, &suggestions, word.constData() ); + + QVector< QString > candidates; + candidates.reserve( count ); + + for( int i = 0; i < count; i++ ) + { + const QString suggestion = QString::fromUtf8( suggestions[i] ); // ### encoding? + + if( suggestion.startsWith( text ) ) + candidates.prepend( suggestion ); + else + candidates.append( suggestion ); + } + + Hunspell_free_list( m_data->hunspellHandle, &suggestions, count ); + + m_data->candidates = candidates; + Q_EMIT candidatesChanged(); } diff --git a/inputcontext/QskHunspellCompositionModel.h b/inputcontext/QskHunspellCompositionModel.h index 5904f85e..d7948c85 100644 --- a/inputcontext/QskHunspellCompositionModel.h +++ b/inputcontext/QskHunspellCompositionModel.h @@ -7,6 +7,7 @@ #define QSK_HUNSPELL_COMPOSITION_MODEL_H #include "QskInputCompositionModel.h" +#include class QskHunspellCompositionModel : public QskInputCompositionModel { @@ -16,15 +17,12 @@ public: QskHunspellCompositionModel( QskInputContext* context ); virtual ~QskHunspellCompositionModel() override; - virtual bool supportsSuggestions() const override final; - - virtual void commitCandidate( int index ) override; virtual int candidateCount() const override; virtual QString candidate( int pos ) const override; protected: - virtual bool hasIntermediate() const override; - virtual QString polishPreedit( const QString& ) override; + virtual void requestCandidates( const QString& ) override; + virtual void resetCandidates() override; private: class PrivateData; diff --git a/inputcontext/QskInputCompositionModel.cpp b/inputcontext/QskInputCompositionModel.cpp index 32a09b3a..7005e03a 100644 --- a/inputcontext/QskInputCompositionModel.cpp +++ b/inputcontext/QskInputCompositionModel.cpp @@ -6,45 +6,12 @@ #include "QskInputCompositionModel.h" #include "QskInputContext.h" -#include -#include -#include +#include -static inline QString qskKeyString( int code ) -{ - // Special case entry codes here, else default to the symbol - switch ( code ) - { - case Qt::Key_Shift: - case Qt::Key_CapsLock: - case Qt::Key_Mode_switch: - case Qt::Key_Backspace: - case Qt::Key_Muhenkan: - return QString(); - - case Qt::Key_Return: - case Qt::Key_Kanji: - return QChar( QChar::CarriageReturn ); - - case Qt::Key_Space: - return QChar( QChar::Space ); - - default: - break; - } - - return QChar( code ); -} - -class QskInputCompositionModel::PrivateData -{ -public: - QString preedit; -}; - -QskInputCompositionModel::QskInputCompositionModel( QskInputContext* context ): +QskInputCompositionModel::QskInputCompositionModel( + Attributes attributes, QskInputContext* context ): QObject( context ), - m_data( new PrivateData ) + m_attributes( attributes ) { } @@ -57,211 +24,67 @@ QskInputContext* QskInputCompositionModel::context() const return qobject_cast< QskInputContext* >( parent() ); } -bool QskInputCompositionModel::supportsSuggestions() const +void QskInputCompositionModel::composeKey( const QString& text, int spaceLeft ) { - return false; -} - -void QskInputCompositionModel::composeKey( int key ) -{ - /* - * This operation might be expensive (e.g. for Hunspell) and - * should be done asynchronously to be able to run e.g. suggestions - * in a separate thread to not block the UI. - * TODO - */ - - const auto queryEvent = context()->queryInputMethod( - Qt::ImSurroundingText | Qt::ImMaximumTextLength | Qt::ImHints ); - - const auto hints = static_cast< Qt::InputMethodHints >( - queryEvent.value( Qt::ImHints ).toInt() ); - const int maxLength = queryEvent.value( Qt::ImMaximumTextLength ).toInt(); - const int currentLength = queryEvent.value( Qt::ImSurroundingText ).toString().length(); - - int spaceLeft = -1; - if ( !( hints & Qt::ImhMultiLine ) && maxLength > 0 ) - spaceLeft = maxLength - currentLength; - - switch ( key ) + if ( candidateCount() > 0 ) { - case Qt::Key_Backspace: - case Qt::Key_Muhenkan: - { - if ( !m_data->preedit.isEmpty() ) - { - m_data->preedit.chop( 1 ); - sendPreeditTextEvent( polishPreedit( m_data->preedit ) ); - } - else - { - // Backspace one character only if preedit was inactive - sendKeyEvents( Qt::Key_Backspace ); - } - return; - } - case Qt::Key_Space: - { - if( !spaceLeft ) - { - return; - } + m_preedit += text; - if( !m_data->preedit.isEmpty() ) - { - commit( m_data->preedit.left( spaceLeft ) ); - } + requestCandidates( m_preedit ); + context()->sendText( m_preedit, false ); - commit( qskKeyString( key ) ); - return; - } - - case Qt::Key_Return: - { - if ( !spaceLeft ) - return; - - // Commit what is in the buffer - if( !m_data->preedit.isEmpty() ) - { - commit( m_data->preedit.left( spaceLeft ) ); - } - else if( hints & Qt::ImhMultiLine ) - { - commit( qskKeyString( key ) ); - } - else - { - sendKeyEvents( Qt::Key_Return ); - } - - return; - } - - case Qt::Key_Left: - case Qt::Key_Right: - { - if ( m_data->preedit.isEmpty() ) - sendKeyEvents( key ); - - return; - } - case Qt::Key_Escape: - { - sendKeyEvents( Qt::Key_Escape ); - return; - } - default: - { - if ( !spaceLeft ) - return; - } - } - - if ( hints & Qt::ImhHiddenText ) - { - commit( qskKeyString( key ) ); return; } - const auto firstCandidate = candidateCount() > 0 ? candidate( 0 ) : QString(); - const auto oldPreedit = m_data->preedit; + requestCandidates( m_preedit ); - m_data->preedit += qskKeyString( key ); - auto displayPreedit = polishPreedit( m_data->preedit ); - - // If there is no intermediate, decide between committing the first candidate and skipping - if ( !hasIntermediate() ) + QString txt; + if ( candidateCount() == 0 ) { - // Skip preedit phase if there are no candidates/intermediates - if ( firstCandidate.isEmpty() ) + txt = m_preedit.left( spaceLeft ); + spaceLeft -= txt.length(); + } + else + { + txt = candidate( 0 ); + --spaceLeft; + } + + context()->sendText( txt, true ); + m_preedit.clear(); + resetCandidates(); + + if ( spaceLeft ) + { + m_preedit = text; + requestCandidates( m_preedit ); + + if ( candidateCount() > 0 ) { - commit( oldPreedit.left( spaceLeft ) ); - spaceLeft -= oldPreedit.leftRef( spaceLeft ).length(); + context()->sendText( m_preedit, false ); } else { - commit( firstCandidate ); - --spaceLeft; - } - - if ( !spaceLeft ) - return; - - m_data->preedit = qskKeyString( key ); - displayPreedit = polishPreedit( m_data->preedit ); - - if ( !hasIntermediate() ) - { - commit( m_data->preedit ); - return; + context()->sendText( m_preedit, true ); + m_preedit.clear(); + resetCandidates(); } } - - sendPreeditTextEvent( displayPreedit ); } -void QskInputCompositionModel::clearPreedit() +void QskInputCompositionModel::setPreeditText( const QString& text ) { - m_data->preedit.clear(); - polishPreedit( m_data->preedit ); + if ( text != m_preedit ) + { + m_preedit = text; + requestCandidates( m_preedit ); + } } -int QskInputCompositionModel::candidateCount() const +void QskInputCompositionModel::reset() { - return 0; -} - -QString QskInputCompositionModel::candidate( int ) const -{ - return QString(); -} - -QString QskInputCompositionModel::polishPreedit( const QString& preedit ) -{ - return preedit; -} - -void QskInputCompositionModel::commit( const QString& text ) -{ - QInputMethodEvent event; - event.setCommitString( text ); - context()->sendEventToInputItem( &event ); - - clearPreedit(); -} - -void QskInputCompositionModel::commitCandidate( int index ) -{ - commit( candidate( index ) ); -} - -void QskInputCompositionModel::sendPreeditTextEvent( const QString& text ) -{ - QTextCharFormat format; - format.setFontUnderline( true ); - - const QInputMethodEvent::Attribute attribute( - QInputMethodEvent::TextFormat, 0, text.length(), format ); - - QInputMethodEvent event( text, { attribute } ); - context()->sendEventToInputItem( &event ); -} - -void QskInputCompositionModel::sendKeyEvents( int key ) -{ - auto context = this->context(); - - QKeyEvent keyPress( QEvent::KeyPress, key, Qt::NoModifier ); - context->sendEventToInputItem( &keyPress ); - - QKeyEvent keyRelease( QEvent::KeyRelease, key, Qt::NoModifier ); - context->sendEventToInputItem( &keyRelease ); -} - -bool QskInputCompositionModel::hasIntermediate() const -{ - return false; + m_preedit.clear(); + resetCandidates(); } #include "moc_QskInputCompositionModel.cpp" diff --git a/inputcontext/QskInputCompositionModel.h b/inputcontext/QskInputCompositionModel.h index 5d41a494..56f4aa46 100644 --- a/inputcontext/QskInputCompositionModel.h +++ b/inputcontext/QskInputCompositionModel.h @@ -7,7 +7,6 @@ #define QSK_INPUT_COMPOSITION_MODEL_H #include -#include class QskInputContext; @@ -16,36 +15,53 @@ class QskInputCompositionModel : public QObject Q_OBJECT public: - QskInputCompositionModel( QskInputContext* context ); + enum Attribute + { + Words = 1 << 0 + }; + + Q_ENUM( Attribute ) + Q_DECLARE_FLAGS( Attributes, Attribute ) + virtual ~QskInputCompositionModel(); - // to determine whether to show the suggestion bar: - virtual bool supportsSuggestions() const; + void composeKey( const QString& text, int spaceLeft ); - void commit( const QString& ); - virtual void commitCandidate( int ); + virtual int candidateCount() const = 0; + virtual QString candidate( int ) const = 0; - void composeKey( int key ); + void reset(); - virtual int candidateCount() const; - virtual QString candidate( int ) const; + QString preeditText() const; + void setPreeditText( const QString& ); + + Attributes attributes() const; protected: - virtual bool hasIntermediate() const; - virtual QString polishPreedit( const QString& preedit ); + QskInputCompositionModel( Attributes, QskInputContext* ); + + virtual void requestCandidates( const QString& preedit ) = 0; + virtual void resetCandidates() = 0; + + QskInputContext* context() const; Q_SIGNALS: void candidatesChanged(); private: - void clearPreedit(); - QskInputContext* context() const; - - void sendPreeditTextEvent( const QString& ); - void sendKeyEvents( int key ); - - class PrivateData; - std::unique_ptr< PrivateData > m_data; + QString m_preedit; + const Attributes m_attributes; }; +inline QString QskInputCompositionModel::preeditText() const +{ + return m_preedit; +} + +inline QskInputCompositionModel::Attributes +QskInputCompositionModel::attributes() const +{ + return m_attributes; +} + #endif diff --git a/inputcontext/QskInputContext.cpp b/inputcontext/QskInputContext.cpp index a82b6761..f3cbecbe 100644 --- a/inputcontext/QskInputContext.cpp +++ b/inputcontext/QskInputContext.cpp @@ -18,9 +18,52 @@ #include #include -#include +#include #include #include +#include + +static inline QString qskKeyString( int keyCode ) +{ + // Special case entry codes here, else default to the symbol + switch ( keyCode ) + { + case Qt::Key_Shift: + case Qt::Key_CapsLock: + case Qt::Key_Mode_switch: + case Qt::Key_Backspace: + case Qt::Key_Muhenkan: + return QString(); + + case Qt::Key_Return: + case Qt::Key_Kanji: + return QChar( QChar::CarriageReturn ); + + case Qt::Key_Space: + return QChar( QChar::Space ); + + default: + break; + } + + return QChar( keyCode ); +} + +static inline bool qskIsControlKey( int keyCode ) +{ + switch ( keyCode ) + { + case Qt::Key_Backspace: + case Qt::Key_Muhenkan: + case Qt::Key_Return: + case Qt::Key_Left: + case Qt::Key_Right: + case Qt::Key_Escape: + return true; + } + + return false; +} static void qskSetLocale( QQuickItem* inputPanel, const QLocale& locale ) { @@ -82,6 +125,11 @@ static void qskSetCandidates( QQuickItem* inputPanel, } } +static inline uint qskHashLocale( const QLocale& locale ) +{ + return uint( locale.language() + uint( locale.country() ) << 16 ); +} + class QskInputContext::PrivateData { public: @@ -95,8 +143,7 @@ public: QskPopup* inputPopup = nullptr; QskWindow* inputWindow = nullptr; - QskInputCompositionModel* compositionModel; - QHash< QLocale, QskInputCompositionModel* > compositionModels; + QHash< uint, QskInputCompositionModel* > compositionModels; // the input panel is embedded in a window bool ownsInputPanelWindow : 1; @@ -108,21 +155,15 @@ QskInputContext::QskInputContext(): setObjectName( "InputContext" ); #if 1 - m_data->compositionModel = new QskInputCompositionModel( this ); -#else - m_data->compositionModel = new QskHunspellCompositionModel( this ); + setCompositionModel( locale(), new QskHunspellCompositionModel( this ) ); #endif - - connect( m_data->compositionModel, &QskInputCompositionModel::candidatesChanged, - this, &QskInputContext::handleCandidatesChanged ); - - connect( qskSetup, &QskSetup::inputPanelChanged, - this, &QskInputContext::setInputPanel ); - #if 0 setCompositionModel( QLocale::Chinese, new QskPinyinCompositionModel( this ) ); #endif + connect( qskSetup, &QskSetup::inputPanelChanged, + this, &QskInputContext::setInputPanel ); + setInputPanel( qskSetup->inputPanel() ); } @@ -214,23 +255,33 @@ void QskInputContext::update( Qt::InputMethodQueries queries ) { const auto locale = queryEvent.value( Qt::ImPreferredLanguage ).toLocale(); - auto oldModel = compositionModel(); + const auto oldModel = compositionModel(); if( m_data->inputPanel ) qskSetLocale( m_data->inputPanel, locale ); auto newModel = compositionModel(); - if( oldModel != newModel ) + if( newModel && ( oldModel != newModel ) ) { connect( newModel, &QskInputCompositionModel::candidatesChanged, this, &QskInputContext::handleCandidatesChanged ); - - qskSetCandidatesEnabled( m_data->inputPanel, - newModel->supportsSuggestions() ); } + + qskSetCandidatesEnabled( m_data->inputPanel, newModel != nullptr ); } +#if 0 + if ( queryEvent.queries() & Qt::ImTextBeforeCursor + && queryEvent.queries() & Qt::ImTextAfterCursor ) + { + const auto text1 = queryEvent.value( Qt::ImTextBeforeCursor ).toString(); + const auto text2 = queryEvent.value( Qt::ImTextAfterCursor ).toString(); + + qDebug() << text1 << text2; + } +#endif + /* Qt::ImMicroFocus Qt::ImCursorRectangle @@ -496,14 +547,6 @@ void QskInputContext::setFocusObject( QObject* focusObject ) if ( isAccepted ) setInputItem( focusItem ); } - - if ( m_data->inputPanel && m_data->inputPanel->isVisible() ) - { - if ( m_data->inputItem && focusItem != m_data->inputItem ) - qGuiApp->installEventFilter( this ); - else - qGuiApp->removeEventFilter( this ); - } } void QskInputContext::setCompositionModel( @@ -511,9 +554,11 @@ void QskInputContext::setCompositionModel( { auto& models = m_data->compositionModels; + const auto key = qskHashLocale( locale ); + if ( model ) { - const auto it = models.find( locale ); + const auto it = models.find( key ); if ( it != models.end() ) { if ( it.value() == model ) @@ -524,7 +569,7 @@ void QskInputContext::setCompositionModel( } else { - models.insert( locale, model ); + models.insert( key, model ); } connect( model, &QskInputCompositionModel::candidatesChanged, @@ -532,7 +577,7 @@ void QskInputContext::setCompositionModel( } else { - const auto it = models.find( locale ); + const auto it = models.find( key ); if ( it != models.end() ) { delete it.value(); @@ -543,23 +588,33 @@ void QskInputContext::setCompositionModel( QskInputCompositionModel* QskInputContext::compositionModel() const { - return m_data->compositionModels.value( locale(), m_data->compositionModel ); + const auto key = qskHashLocale( locale() ); + return m_data->compositionModels.value( key, nullptr ); } void QskInputContext::invokeAction( QInputMethod::Action action, int value ) { - auto model = compositionModel(); - switch ( static_cast< int >( action ) ) { case QskInputPanel::Compose: { - model->composeKey( value ); + processKey( value ); break; } case QskInputPanel::SelectCandidate: { - model->commitCandidate( value ); + if ( auto model = compositionModel() ) + { + auto text = model->candidate( value ); + + if ( model->attributes() & QskInputCompositionModel::Words ) + text += " "; + + sendText( text, true ); + + model->reset(); + } + break; } case QInputMethod::Click: @@ -570,6 +625,130 @@ void QskInputContext::invokeAction( QInputMethod::Action action, int value ) } } +int QskInputContext::keysLeft() const +{ + const auto event = queryInputMethod( + Qt::ImSurroundingText | Qt::ImMaximumTextLength | Qt::ImHints ); + + const auto hints = static_cast< Qt::InputMethodHints >( + event.value( Qt::ImHints ).toInt() ); + + if ( !( hints & Qt::ImhMultiLine ) ) + { + const int max = event.value( Qt::ImMaximumTextLength ).toInt(); + + if ( max > 0 ) + { + const auto text = event.value( Qt::ImSurroundingText ).toString(); + return max - text.length(); + } + } + + return -1; // unlimited +} + +Qt::InputMethodHints QskInputContext::inputHints() const +{ + const auto e = queryInputMethod( Qt::ImHints ); + return static_cast< Qt::InputMethodHints >( e.value( Qt::ImHints ).toInt() ); +} + +void QskInputContext::processKey( int key ) +{ + const auto hints = inputHints(); + + auto spaceLeft = keysLeft(); + + QskInputCompositionModel* model = nullptr; + + if ( !( hints & Qt::ImhHiddenText ) ) + model = compositionModel(); + + /* + First we have to handle the control keys + */ + switch ( key ) + { + case Qt::Key_Backspace: + case Qt::Key_Muhenkan: + { + if ( model ) + { + auto preeditText = model->preeditText(); + if ( !preeditText.isEmpty() ) + { + preeditText.chop( 1 ); + sendText( preeditText, false ); + + model->setPreeditText( preeditText ); + return; + } + } + + sendKey( Qt::Key_Backspace ); + return; + } + case Qt::Key_Return: + { + if ( model ) + { + const auto preeditText = model->preeditText(); + if ( !preeditText.isEmpty() ) + { + if ( spaceLeft ) + sendText( preeditText.left( spaceLeft ), true ); + + model->reset(); + return; + } + } + + if( !( hints & Qt::ImhMultiLine ) ) + { + sendKey( Qt::Key_Return ); + return; + } + + break; + } + case Qt::Key_Space: + { + if ( model ) + { + auto preeditText = model->preeditText(); + if ( !preeditText.isEmpty() && spaceLeft) + { + preeditText = preeditText.left( spaceLeft ); + sendText( preeditText, true ); + spaceLeft -= preeditText.length(); + + model->reset(); + } + } + + break; + } + case Qt::Key_Left: + case Qt::Key_Right: + case Qt::Key_Escape: + { + sendKey( key ); + return; + } + } + + const QString text = qskKeyString( key ); + + if ( model ) + { + model->composeKey( text, spaceLeft ); + } + else + { + sendText( text, true ); + } +} + void QskInputContext::handleCandidatesChanged() { const auto model = compositionModel(); @@ -628,11 +807,7 @@ void QskInputContext::setInputPanel( QQuickItem* inputPanel ) this, &QPlatformInputContext::emitLocaleChanged ); } - if ( model ) - { - qskSetCandidatesEnabled( m_data->inputPanel, - model->supportsSuggestions() ); - } + qskSetCandidatesEnabled( inputPanel, model != nullptr ); } } @@ -668,8 +843,6 @@ bool QskInputContext::eventFilter( QObject* object, QEvent* event ) } case QEvent::DeferredDelete: { - object->removeEventFilter( this ); - qGuiApp->removeEventFilter( this ); m_data->inputWindow = nullptr; break; } @@ -677,45 +850,20 @@ bool QskInputContext::eventFilter( QObject* object, QEvent* event ) break; } } - else + else if ( object == m_data->inputPopup ) { switch( static_cast< int >( event->type() ) ) { case QskEvent::GeometryChange: { - if ( object == m_data->inputPanel ) - { - if ( event->type() == QskEvent::GeometryChange ) - emitKeyboardRectChanged(); - } + if ( event->type() == QskEvent::GeometryChange ) + emitKeyboardRectChanged(); break; } - case QEvent::InputMethodQuery: - { - /* - Qt/Quick expects that the item associated with the input context - holds 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. - */ - - if ( ( object != m_data->inputItem ) - && qskIsAncestorOf( m_data->inputPanel, m_data->inputItem ) ) - { - sendEventToInputItem( event ); - return true; - } - break; - } case QEvent::DeferredDelete: { - if ( object == m_data->inputPopup ) - { - object->removeEventFilter( this ); - qGuiApp->removeEventFilter( this ); - m_data->inputPopup = nullptr; - } + m_data->inputPopup = nullptr; break; } } @@ -724,10 +872,9 @@ bool QskInputContext::eventFilter( QObject* object, QEvent* event ) return Inherited::eventFilter( object, event ); } -bool QskInputContext::filterEvent( const QEvent* event ) +bool QskInputContext::filterEvent( const QEvent* ) { // called from QXcbKeyboard, but what about other platforms - Q_UNUSED( event ) return false; } @@ -735,15 +882,51 @@ QInputMethodQueryEvent QskInputContext::queryInputMethod( Qt::InputMethodQueries queries ) const { QInputMethodQueryEvent event( queries ); - sendEventToInputItem( &event ); + + if ( m_data->inputItem ) + QCoreApplication::sendEvent( m_data->inputItem, &event ); return event; } -void QskInputContext::sendEventToInputItem( QEvent* event ) const +void QskInputContext::sendText( + const QString& text, bool isFinal ) const { - if ( m_data->inputItem && event ) - QCoreApplication::sendEvent( m_data->inputItem, event ); + if ( m_data->inputItem == nullptr ) + return; + + if ( isFinal ) + { + QInputMethodEvent event; + event.setCommitString( text ); + + QCoreApplication::sendEvent( m_data->inputItem, &event ); + } + else + { + QTextCharFormat format; + format.setFontUnderline( true ); + + const QInputMethodEvent::Attribute attribute( + QInputMethodEvent::TextFormat, 0, text.length(), format ); + + QInputMethodEvent event( text, { attribute } ); + + QCoreApplication::sendEvent( m_data->inputItem, &event ); + } +} + + +void QskInputContext::sendKey( int key ) const +{ + if ( m_data->inputItem == nullptr ) + return; + + QKeyEvent keyPress( QEvent::KeyPress, key, Qt::NoModifier ); + QCoreApplication::sendEvent( m_data->inputItem, &keyPress ); + + QKeyEvent keyRelease( QEvent::KeyRelease, key, Qt::NoModifier ); + QCoreApplication::sendEvent( m_data->inputItem, &keyRelease ); } #include "moc_QskInputContext.cpp" diff --git a/inputcontext/QskInputContext.h b/inputcontext/QskInputContext.h index 887f3060..c00644f2 100644 --- a/inputcontext/QskInputContext.h +++ b/inputcontext/QskInputContext.h @@ -10,8 +10,12 @@ #include class QskInputCompositionModel; + class QQuickItem; + class QInputMethodQueryEvent; +class QInputMethodEvent; +class QKeyEvent; class QskInputContext : public QPlatformInputContext { @@ -51,7 +55,12 @@ public: virtual bool filterEvent( const QEvent* ) override; QInputMethodQueryEvent queryInputMethod( Qt::InputMethodQueries ) const; - void sendEventToInputItem( QEvent* ) const; + + void sendKey( int key ) const; + void sendText( const QString& text, bool isFinal ) const; + + Qt::InputMethodHints inputHints() const; + int keysLeft() const; private Q_SLOTS: void handleCandidatesChanged(); @@ -60,6 +69,8 @@ private Q_SLOTS: virtual bool eventFilter( QObject*, QEvent* ) override; private: + void processKey( int key ); + void setInputItem( QQuickItem* ); QskInputCompositionModel* compositionModel() const; diff --git a/inputcontext/QskPinyinCompositionModel.cpp b/inputcontext/QskPinyinCompositionModel.cpp index f0e87658..69850107 100644 --- a/inputcontext/QskPinyinCompositionModel.cpp +++ b/inputcontext/QskPinyinCompositionModel.cpp @@ -18,7 +18,7 @@ public: }; QskPinyinCompositionModel::QskPinyinCompositionModel( QskInputContext* context ): - Inherited( context ), + Inherited( Attributes(), context ), m_data( new PrivateData ) { #if 1 @@ -39,11 +39,6 @@ QskPinyinCompositionModel::~QskPinyinCompositionModel() ime_pinyin::im_close_decoder(); } -bool QskPinyinCompositionModel::supportsSuggestions() const -{ - return true; -} - int QskPinyinCompositionModel::candidateCount() const { return m_data->candidates.count(); @@ -57,53 +52,54 @@ QString QskPinyinCompositionModel::candidate( int index ) const return QString(); } -bool QskPinyinCompositionModel::hasIntermediate() const +void QskPinyinCompositionModel::resetCandidates() { - return m_data->candidates.count() > 0; -} + ime_pinyin::im_reset_search(); -QString QskPinyinCompositionModel::polishPreedit( const QString& preedit ) -{ - if( preedit.isEmpty() ) + if ( !m_data->candidates.isEmpty() ) { - ime_pinyin::im_reset_search(); - } - - QByteArray preeditBuffer = preedit.toLatin1(); - size_t numSearchResults = ime_pinyin::im_search( - preeditBuffer.constData(), size_t( preeditBuffer.length() ) ); - - if( numSearchResults > 0 ) - { - QStringList newCandidates; - newCandidates.reserve( 1 ); - - QVector< QChar > candidateBuffer; - candidateBuffer.resize( ime_pinyin::kMaxSearchSteps + 1 ); - - // ### numSearchResults is way too big, we should only do this for the first ten results or so - for( unsigned int a = 0; a < numSearchResults; a++ ) - { - size_t length = static_cast< size_t >( candidateBuffer.length() - 1 ); - bool getCandidate = ime_pinyin::im_get_candidate( a, reinterpret_cast< ime_pinyin::char16* >( candidateBuffer.data() ), length ); - - QString candidate; - - if( getCandidate ) - { - candidateBuffer.last() = 0; - candidate = QString( candidateBuffer.data() ); - } - - Qt::Key key = Qt::Key( candidate.at( 0 ).unicode() ); - QString string = QChar( key ); - - newCandidates.append( string ); - } - - m_data->candidates = newCandidates; + m_data->candidates.clear(); Q_EMIT candidatesChanged(); } - - return preedit; +} + +void QskPinyinCompositionModel::requestCandidates( const QString& text ) +{ + const QByteArray bytes = text.toLatin1(); + + size_t count = ime_pinyin::im_search( + bytes.constData(), size_t( bytes.length() ) ); + + if( count <= 0 ) + return; + + const size_t maxCount = 20; + if ( count > maxCount ) + count = maxCount; + + QVector< QChar > candidateBuffer; + candidateBuffer.resize( ime_pinyin::kMaxSearchSteps + 1 ); + + + QStringList candidates; + candidates.reserve( count ); + + for( unsigned int i = 0; i < count; i++ ) + { + size_t length = static_cast< size_t >( candidateBuffer.length() - 1 ); + const auto buf = reinterpret_cast< ime_pinyin::char16* >( candidateBuffer.data() ); + + const bool found = ime_pinyin::im_get_candidate( i, buf, length ); + Q_ASSERT( found ); + + candidateBuffer.last() = 0; + + auto candidate = QString( candidateBuffer.data() ); + candidate = QChar( Qt::Key( candidate[0].unicode() ) ); + + candidates += candidate; + } + + m_data->candidates = candidates; + Q_EMIT candidatesChanged(); } diff --git a/inputcontext/QskPinyinCompositionModel.h b/inputcontext/QskPinyinCompositionModel.h index 168f2059..5cad5b80 100644 --- a/inputcontext/QskPinyinCompositionModel.h +++ b/inputcontext/QskPinyinCompositionModel.h @@ -7,6 +7,7 @@ #define QSK_PINYIN_COMPOSITION_MODEL_H #include "QskInputCompositionModel.h" +#include class QskPinyinCompositionModel : public QskInputCompositionModel { @@ -16,15 +17,12 @@ public: QskPinyinCompositionModel( QskInputContext* ); virtual ~QskPinyinCompositionModel() override; - virtual bool supportsSuggestions() const override final; - virtual int candidateCount() const override; virtual QString candidate( int ) const override; protected: - // Used for text composition - virtual bool hasIntermediate() const override; - virtual QString polishPreedit( const QString& preedit ) override; + virtual void requestCandidates( const QString& ) override; + virtual void resetCandidates() override; private: class PrivateData;