diff --git a/inputcontext/QskHunspellTextPredictor.cpp b/inputcontext/QskHunspellTextPredictor.cpp deleted file mode 100644 index 511bf3f4..00000000 --- a/inputcontext/QskHunspellTextPredictor.cpp +++ /dev/null @@ -1,75 +0,0 @@ -#include "QskHunspellTextPredictor.h" -#include - -#include "hunspell.h" - -class QskHunspellTextPredictor::PrivateData -{ - public: - Hunhandle* hunspellHandle; - QVector< QString > candidates; -}; - -QskHunspellTextPredictor::QskHunspellTextPredictor( QObject* object ) - : Inherited( Words, object ) - , m_data( new PrivateData() ) -{ -#if 1 - // TODO: loading the language specific one depending on the locale - - m_data->hunspellHandle = Hunspell_create( - "/usr/share/hunspell/en_US.aff", - "/usr/share/hunspell/en_US.dic" ); -#endif -} - -QskHunspellTextPredictor::~QskHunspellTextPredictor() -{ - Hunspell_destroy( m_data->hunspellHandle ); -} - -int QskHunspellTextPredictor::candidateCount() const -{ - return m_data->candidates.count(); -} - -QString QskHunspellTextPredictor::candidate( int pos ) const -{ - return m_data->candidates[ pos ]; -} - -void QskHunspellTextPredictor::reset() -{ - if ( !m_data->candidates.isEmpty() ) - { - m_data->candidates.clear(); - Q_EMIT predictionChanged(); - } -} - -void QskHunspellTextPredictor::request( const QString& text ) -{ - 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 predictionChanged(); -} diff --git a/inputcontext/QskInputContextPlugin.cpp b/inputcontext/QskInputContextPlugin.cpp index 7755a346..5243b79e 100644 --- a/inputcontext/QskInputContextPlugin.cpp +++ b/inputcontext/QskInputContextPlugin.cpp @@ -8,37 +8,10 @@ #include "QskInputContext.h" -#define HUNSPELL 0 - -#if HUNSPELL -#include "QskHunspellTextPredictor.h" -#endif - #include #include #include -namespace -{ - class InputContextFactory final : public QskInputContextFactory - { - public: - QskTextPredictor* createPredictor( const QLocale& locale ) const override - { -#if HUNSPELL - /* - For the moment we manage the text prediction in the context - plugin - but of course it has to be moved somewhere else - */ - if ( locale.language() == QLocale::English ) - return new QskHunspellTextPredictor(); -#endif - - return QskInputContextFactory::createPredictor( locale ); - } - }; -} - /* QPlatformInputContext is no stable public API. So we forward everything to QskInputContext @@ -97,8 +70,7 @@ QskPlatformInputContext::QskPlatformInputContext() if ( context == nullptr ) { context = new QskInputContext(); - context->setFactory( new InputContextFactory() ); - + context->setFactory( new QskInputContextFactory() ); QskInputContext::setInstance( context ); } diff --git a/inputcontext/inputcontext.pro b/inputcontext/inputcontext.pro index 29b210a8..9d4e7af0 100644 --- a/inputcontext/inputcontext.pro +++ b/inputcontext/inputcontext.pro @@ -3,9 +3,6 @@ TARGET = $$qskPluginTarget(qskinputcontext) QT += gui-private -# CONFIG += pinyin -# CONFIG += hunspell - CONFIG += plugin CONFIG += qskinny diff --git a/src/inputpanel/QskHunspellTextPredictor.cpp b/src/inputpanel/QskHunspellTextPredictor.cpp new file mode 100644 index 00000000..5e7e920b --- /dev/null +++ b/src/inputpanel/QskHunspellTextPredictor.cpp @@ -0,0 +1,133 @@ +#include "QskHunspellTextPredictor.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +class QskHunspellTextPredictor::PrivateData +{ + public: + Hunhandle* hunspellHandle = nullptr; + QByteArray hunspellEncoding; + QStringList candidates; + QLocale locale; +}; + +QskHunspellTextPredictor::QskHunspellTextPredictor( const QLocale &locale, QObject* object ) + : Inherited( object ) + , m_data( new PrivateData() ) +{ + m_data->locale = locale; + + // make sure we call virtual functions: + QTimer::singleShot( 0, this, &QskHunspellTextPredictor::loadDictionaries ); +} + +QskHunspellTextPredictor::~QskHunspellTextPredictor() +{ + Hunspell_destroy( m_data->hunspellHandle ); +} + +void QskHunspellTextPredictor::reset() +{ + if ( !m_data->candidates.isEmpty() ) + { + m_data->candidates.clear(); + Q_EMIT predictionChanged( QString(), {} ); + } +} + +QPair< QString, QString > QskHunspellTextPredictor::affAndDicFile( const QString& path, const QLocale& locale ) +{ + QString prefix = QStringLiteral( "%1/%2" ).arg( path, locale.name() ); + QString affFile = prefix + QStringLiteral( ".aff" ); + QString dicFile = prefix + QStringLiteral( ".dic" ); + + if( QFile::exists( affFile ) && QFile::exists( dicFile ) ) + { + return qMakePair( affFile, dicFile ); + } + else + { + return {}; + } +} + +void QskHunspellTextPredictor::loadDictionaries() +{ + QString userPaths = QString::fromUtf8( qgetenv( "QSK_HUNSPELL_PATH" ) ); + +#if defined(Q_OS_WIN32) + QChar separator( ';' ); + QStringList defaultPaths; +#else + QChar separator( ':' ); + QStringList defaultPaths = { QStringLiteral( "/usr/share/hunspell" ), + QStringLiteral( "/usr/share/myspell/dicts" ) }; +#endif + + QStringList paths = userPaths.split( separator, QString::SkipEmptyParts ); + paths.append( defaultPaths ); + + for( const auto& path : paths ) + { + auto files = affAndDicFile( path, m_data->locale ); + + if( !files.first.isEmpty() && !files.second.isEmpty() ) + { + m_data->hunspellHandle = Hunspell_create( files.first.toUtf8(), files.second.toUtf8() ); + m_data->hunspellEncoding = Hunspell_get_dic_encoding( m_data->hunspellHandle ); + break; + } + } + + if( !m_data->hunspellHandle ) + { + qWarning() << "could not find Hunspell files for locale" << m_data->locale + << "in the following directories:" << paths + << ". Consider setting QSK_HUNSPELL_PATH to the directory " + << "containing Hunspell .aff and .dic files."; + } +} + +void QskHunspellTextPredictor::request( const QString& text ) +{ + if( !m_data->hunspellHandle ) + { + Q_EMIT predictionChanged( text, {} ); + return; + } + + char** suggestions; + + QTextCodec *codec = QTextCodec::codecForName( m_data->hunspellEncoding ); + const QByteArray word = codec ? codec->fromUnicode( text ) : text.toUtf8(); + + const int count = Hunspell_suggest( + m_data->hunspellHandle, &suggestions, word.constData() ); + + QStringList candidates; + candidates.reserve( count ); + + for ( int i = 0; i < count; i++ ) + { + const QString suggestion = codec ? codec->toUnicode( suggestions[ i ] ) + : QString::fromUtf8( suggestions [ i ] ); + + 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 predictionChanged( text, m_data->candidates ); +} diff --git a/inputcontext/QskHunspellTextPredictor.h b/src/inputpanel/QskHunspellTextPredictor.h similarity index 66% rename from inputcontext/QskHunspellTextPredictor.h rename to src/inputpanel/QskHunspellTextPredictor.h index c56a9288..610f83e7 100644 --- a/inputcontext/QskHunspellTextPredictor.h +++ b/src/inputpanel/QskHunspellTextPredictor.h @@ -6,26 +6,30 @@ #ifndef QSK_HUNSPELL_TEXT_PREDICTOR_H #define QSK_HUNSPELL_TEXT_PREDICTOR_H -#include "QskInputContextGlobal.h" -#include +#include "QskTextPredictor.h" + +#include + #include -class QSK_INPUTCONTEXT_EXPORT QskHunspellTextPredictor : public QskTextPredictor +class QSK_EXPORT QskHunspellTextPredictor : public QskTextPredictor { + Q_OBJECT + using Inherited = QskTextPredictor; public: - QskHunspellTextPredictor( QObject* = nullptr ); + QskHunspellTextPredictor( const QLocale& locale, QObject* = nullptr ); ~QskHunspellTextPredictor() override; - int candidateCount() const override; - QString candidate( int pos ) const override; - protected: void request( const QString& ) override; void reset() override; + virtual QPair< QString, QString > affAndDicFile( const QString&, const QLocale& ); private: + Q_INVOKABLE void loadDictionaries(); + class PrivateData; std::unique_ptr< PrivateData > m_data; }; diff --git a/src/inputpanel/QskInputContext.cpp b/src/inputpanel/QskInputContext.cpp index 9bf81427..2087eb28 100644 --- a/src/inputpanel/QskInputContext.cpp +++ b/src/inputpanel/QskInputContext.cpp @@ -11,6 +11,7 @@ #include "QskLinearBox.h" #include "QskPopup.h" #include "QskQuick.h" +#include "QskTextPredictor.h" #include "QskWindow.h" #include @@ -24,6 +25,10 @@ QSK_QT_PRIVATE_END #include #include +#if HUNSPELL +#include "QskHunspellTextPredictor.h" +#endif + namespace { class Panel final : public QskInputPanel @@ -66,6 +71,7 @@ namespace void setPrediction( const QStringList& prediction ) override { + QskInputPanel::setPrediction( prediction ); m_box->setPrediction( prediction ); } @@ -312,10 +318,10 @@ QskInputContextFactory* QskInputContext::factory() const return m_data->factory; } -QskTextPredictor* QskInputContext::textPredictor( const QLocale& locale ) +std::shared_ptr QskInputContext::textPredictor( const QLocale& locale ) { if ( m_data->factory ) - return m_data->factory->createPredictor( locale ); + return m_data->factory->setupPredictor( locale ); return nullptr; } @@ -494,18 +500,62 @@ void QskInputContext::commitPrediction( bool ) */ } +class QskInputContextFactory::PrivateData +{ + public: + QThread* thread = nullptr; + std::shared_ptr< QskTextPredictor > predictor; + QLocale predictorLocale; +}; + QskInputContextFactory::QskInputContextFactory( QObject* parent ) : QObject( parent ) + , m_data( new PrivateData() ) { } QskInputContextFactory::~QskInputContextFactory() { + if( m_data->thread ) + { + m_data->thread->quit(); + connect( m_data->thread, &QThread::finished, m_data->thread, &QObject::deleteLater ); + } } -QskTextPredictor* QskInputContextFactory::createPredictor( const QLocale& ) const +std::shared_ptr< QskTextPredictor > QskInputContextFactory::setupPredictor( const QLocale& locale ) { - return nullptr; + if( !m_data->predictor + || m_data->predictorLocale.language() != locale.language() + || m_data->predictorLocale.country() != locale.country() ) + { + m_data->predictor = std::shared_ptr< QskTextPredictor >( createPredictor( locale ) ); + m_data->predictorLocale = QLocale( locale.language(), locale.country() ); + + if( m_data->predictor ) + { + if( !m_data->thread ) + { + m_data->thread = new QThread(); + m_data->thread->start(); + } + + m_data->predictor->moveToThread( m_data->thread ); + } + } + + return m_data->predictor; +} + +QskTextPredictor* QskInputContextFactory::createPredictor( const QLocale& locale ) +{ +#if HUNSPELL + return new QskHunspellTextPredictor( locale ); +#else + Q_UNUSED( locale ); +#endif + + return nullptr; } QskInputPanel* QskInputContextFactory::createPanel() const diff --git a/src/inputpanel/QskInputContext.h b/src/inputpanel/QskInputContext.h index 50e6d040..d11e671f 100644 --- a/src/inputpanel/QskInputContext.h +++ b/src/inputpanel/QskInputContext.h @@ -29,8 +29,15 @@ class QSK_EXPORT QskInputContextFactory : public QObject QskInputContextFactory( QObject* parent = nullptr ); ~QskInputContextFactory() override; - virtual QskTextPredictor* createPredictor( const QLocale& ) const; + std::shared_ptr< QskTextPredictor > setupPredictor( const QLocale& ); virtual QskInputPanel* createPanel() const; + + protected: + virtual QskTextPredictor* createPredictor( const QLocale& ); + + private: + class PrivateData; + std::unique_ptr< PrivateData > m_data; }; class QSK_EXPORT QskInputContext : public QObject @@ -56,7 +63,7 @@ class QSK_EXPORT QskInputContext : public QObject static QskInputContext* instance(); static void setInstance( QskInputContext* ); - QskTextPredictor* textPredictor( const QLocale& locale ); + std::shared_ptr< QskTextPredictor > textPredictor( const QLocale& locale ); Q_SIGNALS: void activeChanged(); diff --git a/src/inputpanel/QskInputPanel.cpp b/src/inputpanel/QskInputPanel.cpp index f39b201d..8024866b 100644 --- a/src/inputpanel/QskInputPanel.cpp +++ b/src/inputpanel/QskInputPanel.cpp @@ -10,6 +10,23 @@ #include #include +namespace { + struct Result + { + int key = 0; + + QString text; + bool isFinal = true; + }; +} + +static void qskRegisterInputPanel() +{ + qRegisterMetaType< Result >( "Result" ); +} + +Q_CONSTRUCTOR_FUNCTION( qskRegisterInputPanel ) + static inline QQuickItem* qskReceiverItem( const QskInputPanel* panel ) { if ( auto item = panel->inputProxy() ) @@ -88,187 +105,255 @@ static inline void qskSendKey( QQuickItem* receiver, int key ) QCoreApplication::sendEvent( receiver, &keyRelease ); } -namespace +class KeyProcessor : public QObject { - class KeyProcessor + Q_OBJECT + + public: + QString preedit() const { - public: - struct Result + return m_preedit; + } + + void processKey( + int key, Qt::InputMethodHints inputHints, QskInputPanel* panel, + QskTextPredictor* predictor, int spaceLeft ) + { + // reset: + m_currentResult.isFinal = true; + m_currentResult.text.clear(); + m_currentResult.key = 0; + + m_predictor = predictor; + m_spaceLeft = spaceLeft; + + // First we have to handle the control keys + + switch ( key ) { - int key = 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: { - case Qt::Key_Backspace: - case Qt::Key_Muhenkan: + if ( predictor && !m_preedit.isEmpty() ) { - if ( predictor ) - { - if ( !m_preedit.isEmpty() ) - { - m_preedit.chop( 1 ); + m_preedit.chop( 1 ); - result.text = m_preedit; - result.isFinal = false; + m_currentResult.text = m_preedit; + m_currentResult.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 += keyString( 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 = keyString( key ); - - if ( predictor ) - { - m_preedit += text; - - predictor->request( m_preedit ); - - if ( predictor->candidateCount() > 0 ) - { - result.text = m_preedit; - result.isFinal = false; + Q_EMIT panel->predictionRequested( m_preedit ); + // Let the input field update right away, otherwise + // we'll get weird effects with fast backspace presses: + Q_EMIT keyProcessingFinished( m_currentResult ); + return; } else { - result.text = m_preedit.left( spaceLeft ); - result.isFinal = true; - - m_preedit.clear(); + m_currentResult.key = Qt::Key_Backspace; + Q_EMIT keyProcessingFinished( m_currentResult ); + return; } + + break; + } + case Qt::Key_Return: + { + if ( predictor ) + { + if ( !m_preedit.isEmpty() ) + { + if ( spaceLeft ) + { + m_currentResult.text = m_preedit.left( spaceLeft ); + m_currentResult.isFinal = true; + } + + reset(); + Q_EMIT keyProcessingFinished( m_currentResult ); + return; + } + } + + if ( !( inputHints & Qt::ImhMultiLine ) ) + { + m_currentResult.key = Qt::Key_Return; + Q_EMIT keyProcessingFinished( m_currentResult ); + return; + } + + break; + } + case Qt::Key_Space: + { + if ( predictor ) + { + if ( !m_preedit.isEmpty() && spaceLeft ) + { + m_preedit += keyString( key ); + m_preedit = m_preedit.left( spaceLeft ); + + m_currentResult.text = m_preedit; + m_currentResult.isFinal = true; + + reset(); + + Q_EMIT keyProcessingFinished( m_currentResult ); + return; + } + } + + break; + } + case Qt::Key_Left: + case Qt::Key_Right: + case Qt::Key_Escape: + case Qt::Key_Cancel: + { + m_currentResult.key = key; + Q_EMIT keyProcessingFinished( m_currentResult ); + return; + } + } + + const QString text = keyString( key ); + + if ( predictor ) + { + m_preedit += text; + Q_EMIT panel->predictionRequested( m_preedit ); + } + else + { + m_currentResult.text = text; + m_currentResult.isFinal = true; + Q_EMIT keyProcessingFinished( m_currentResult ); + } + } + + void reset() + { + m_preedit.clear(); + } + + void continueProcessingKey( const QStringList& candidates ) + { + if ( m_predictor ) + { + if ( candidates.count() > 0 ) + { + m_currentResult.text = m_preedit; + m_currentResult.isFinal = false; } else { - result.text = text; - result.isFinal = true; + m_currentResult.text = m_preedit.left( m_spaceLeft ); + m_currentResult.isFinal = true; + + m_preedit.clear(); } - - return result; } - void reset() + Q_EMIT keyProcessingFinished( m_currentResult ); + } + + Q_SIGNALS: + void keyProcessingFinished( const Result& result ); + + private: + inline QString keyString( int keyCode ) const + { + // Special case entry codes here, else default to the symbol + switch ( keyCode ) { - m_preedit.clear(); + 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; } - private: - inline QString keyString( int keyCode ) const - { - // 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(); + return QChar( keyCode ); + } - case Qt::Key_Return: - case Qt::Key_Kanji: - return QChar( QChar::CarriageReturn ); + QString m_preedit; + int m_spaceLeft = -1; + QskTextPredictor* m_predictor = nullptr; + Result m_currentResult; +}; - case Qt::Key_Space: - return QChar( QChar::Space ); - - default: - break; - } - - return QChar( keyCode ); - } - - QString m_preedit; - }; -} class QskInputPanel::PrivateData { public: + PrivateData( QskInputPanel* panel ) + : panel( panel ) + { + } + KeyProcessor keyProcessor; QPointer< QQuickItem > inputItem; QLocale predictorLocale; - QPointer< QskTextPredictor > predictor; + std::shared_ptr< QskTextPredictor > predictor; + QStringList candidates; Qt::InputMethodHints inputHints; bool hasPredictorLocale = false; + const QskInputPanel* panel; + + void handleKeyProcessingFinished( const Result& result ) + { + switch ( result.key ) + { + case 0: + { + qskSendText( qskReceiverItem( panel ), + result.text, result.isFinal ); + break; + } + case Qt::Key_Return: + { + if ( auto proxy = panel->inputProxy() ) + { + // using input method query instead + const auto value = proxy->property( "text" ); + if ( value.canConvert< QString >() ) + { + qskSendReplaceText( inputItem, value.toString() ); + } + } + + qskSendKey( inputItem, result.key ); + break; + } + case Qt::Key_Escape: + case Qt::Key_Cancel: + { + qskSendKey( inputItem, result.key ); + break; + } + default: + { + qskSendKey( qskReceiverItem( panel ), result.key ); + } + } + } }; QskInputPanel::QskInputPanel( QQuickItem* parent ) : Inherited( parent ) - , m_data( new PrivateData() ) + , m_data( new PrivateData( this ) ) { setAutoLayoutChildren( true ); initSizePolicy( QskSizePolicy::Expanding, QskSizePolicy::Constrained ); @@ -282,6 +367,12 @@ QskInputPanel::QskInputPanel( QQuickItem* parent ) connect( this, &QskControl::localeChanged, this, &QskInputPanel::updateLocale ); + connect( &m_data->keyProcessor, &KeyProcessor::keyProcessingFinished, + this, [this]( const Result& result ) + { + m_data->handleKeyProcessingFinished( result ); + } ); + updateLocale( locale() ); } @@ -305,7 +396,7 @@ void QskInputPanel::attachInputItem( QQuickItem* item ) if ( item ) { if ( m_data->predictor ) - m_data->predictor->reset(); + Q_EMIT predictionReset(); m_data->keyProcessor.reset(); m_data->inputHints = Qt::InputMethodHints(); @@ -393,27 +484,17 @@ void QskInputPanel::resetPredictor( const QLocale& locale ) if ( predictor == m_data->predictor ) return; - if ( m_data->predictor ) - { - if ( m_data->predictor->parent() == this ) - { - delete m_data->predictor; - } - else - { - m_data->predictor->disconnect( this ); - m_data->predictor = nullptr; - } - } - m_data->predictor = predictor; if ( predictor ) { - if ( predictor->parent() == nullptr ) - predictor->setParent( this ); + // text predictor lives in another thread, so these will all be QueuedConnections: + connect( this, &QskInputPanel::predictionReset, + predictor.get(), &QskTextPredictor::reset ); + connect( this, &QskInputPanel::predictionRequested, + predictor.get(), &QskTextPredictor::request ); - connect( predictor, &QskTextPredictor::predictionChanged, + connect( predictor.get(), &QskTextPredictor::predictionChanged, this, &QskInputPanel::updatePrediction ); } @@ -427,21 +508,34 @@ void QskInputPanel::commitPredictiveText( int index ) if ( m_data->predictor ) { - text = m_data->predictor->candidate( index ); - m_data->predictor->reset(); + text = m_data->candidates.at( index ); + Q_EMIT predictionReset(); } m_data->keyProcessor.reset(); - setPrediction( QStringList() ); + setPrediction( {} ); qskSendText( qskReceiverItem( this ), text, true ); } -void QskInputPanel::updatePrediction() +void QskInputPanel::updatePrediction( const QString& text, const QStringList& candidates ) { if ( m_data->predictor ) - setPrediction( m_data->predictor->candidates() ); + { + if( m_data->keyProcessor.preedit() != text ) + { + // This must be for another input panel + return; + } + + setPrediction( candidates ); + m_data->keyProcessor.continueProcessingKey( candidates ); + } + else + { + qWarning() << "got prediction update, but no predictor. Something is wrong"; + } } QQuickItem* QskInputPanel::inputProxy() const @@ -462,8 +556,9 @@ void QskInputPanel::setPredictionEnabled( bool ) { } -void QskInputPanel::setPrediction( const QStringList& ) +void QskInputPanel::setPrediction(const QStringList& candidates ) { + m_data->candidates = candidates; } Qt::Alignment QskInputPanel::alignment() const @@ -476,6 +571,11 @@ Qt::Alignment QskInputPanel::alignment() const return inputProxy() ? Qt::AlignVCenter : Qt::AlignBottom; } +QStringList QskInputPanel::candidates() const +{ + return m_data->candidates; +} + void QskInputPanel::commitKey( int key ) { if ( m_data->inputItem == nullptr ) @@ -501,47 +601,11 @@ void QskInputPanel::commitKey( int key ) QskTextPredictor* predictor = nullptr; if ( qskUsePrediction( m_data->inputHints ) ) - predictor = m_data->predictor; + predictor = m_data->predictor.get(); // ### we could also make the predictor member of keyProcessor a shared ptr? - const auto result = m_data->keyProcessor.processKey( - key, m_data->inputHints, predictor, spaceLeft ); - - switch ( result.key ) - { - case 0: - { - if ( !result.text.isEmpty() ) - { - qskSendText( qskReceiverItem( this ), - result.text, result.isFinal ); - } - break; - } - case Qt::Key_Return: - { - if ( auto proxy = inputProxy() ) - { - // using input method query instead - const auto value = proxy->property( "text" ); - if ( value.canConvert< QString >() ) - { - qskSendReplaceText( m_data->inputItem, value.toString() ); - } - } - - qskSendKey( m_data->inputItem, result.key ); - break; - } - case Qt::Key_Escape: - { - qskSendKey( m_data->inputItem, result.key ); - break; - } - default: - { - qskSendKey( qskReceiverItem( this ), result.key ); - } - } + m_data->keyProcessor.processKey( + key, m_data->inputHints, this, predictor, spaceLeft ); } #include "moc_QskInputPanel.cpp" +#include "QskInputPanel.moc" diff --git a/src/inputpanel/QskInputPanel.h b/src/inputpanel/QskInputPanel.h index bdd26e92..66bd74c8 100644 --- a/src/inputpanel/QskInputPanel.h +++ b/src/inputpanel/QskInputPanel.h @@ -30,6 +30,8 @@ class QSK_EXPORT QskInputPanel : public QskControl virtual Qt::Alignment alignment() const; + QStringList candidates() const; + public Q_SLOTS: void commitKey( int keyCode ); void commitPredictiveText( int index ); @@ -39,6 +41,9 @@ class QSK_EXPORT QskInputPanel : public QskControl void predictiveTextSelected( int ); void inputItemDestroyed(); + void predictionReset(); + void predictionRequested( const QString& text ); + public Q_SLOTS: virtual void setPrompt( const QString& ); virtual void setPrediction( const QStringList& ); @@ -48,9 +53,8 @@ class QSK_EXPORT QskInputPanel : public QskControl virtual void attachItem( QQuickItem* ) = 0; private: + void updatePrediction( const QString &text, const QStringList &candidates ); void resetPredictor( const QLocale& ); - void updatePrediction(); - void updateLocale( const QLocale& ); class PrivateData; diff --git a/inputcontext/QskPinyinTextPredictor.cpp b/src/inputpanel/QskPinyinTextPredictor.cpp similarity index 100% rename from inputcontext/QskPinyinTextPredictor.cpp rename to src/inputpanel/QskPinyinTextPredictor.cpp diff --git a/inputcontext/QskPinyinTextPredictor.h b/src/inputpanel/QskPinyinTextPredictor.h similarity index 100% rename from inputcontext/QskPinyinTextPredictor.h rename to src/inputpanel/QskPinyinTextPredictor.h diff --git a/src/inputpanel/QskTextPredictor.cpp b/src/inputpanel/QskTextPredictor.cpp index d66fb005..457c76a2 100644 --- a/src/inputpanel/QskTextPredictor.cpp +++ b/src/inputpanel/QskTextPredictor.cpp @@ -5,9 +5,10 @@ #include "QskTextPredictor.h" -QskTextPredictor::QskTextPredictor( Attributes attributes, QObject* parent ) +#include + +QskTextPredictor::QskTextPredictor( QObject* parent ) : QObject( parent ) - , m_attributes( attributes ) { } @@ -15,22 +16,4 @@ QskTextPredictor::~QskTextPredictor() { } -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 edc72c11..c35536f2 100644 --- a/src/inputpanel/QskTextPredictor.h +++ b/src/inputpanel/QskTextPredictor.h @@ -16,34 +16,17 @@ class QSK_EXPORT QskTextPredictor : public QObject Q_OBJECT public: - enum Attribute - { - Words = 1 << 0 - }; - - Q_ENUM( Attribute ) - Q_DECLARE_FLAGS( Attributes, Attribute ) - ~QskTextPredictor() override; + public Q_SLOTS: virtual void request( const QString& text ) = 0; virtual void reset() = 0; - virtual int candidateCount() const = 0; - virtual QString candidate( int ) const = 0; - - virtual QStringList candidates() const; - - Attributes attributes() const; - Q_SIGNALS: - void predictionChanged(); + void predictionChanged( const QString& text, const QStringList& candidates ); protected: - QskTextPredictor( Attributes, QObject* ); - - private: - const Attributes m_attributes; + QskTextPredictor( QObject* ); }; #endif diff --git a/src/src.pro b/src/src.pro index 0cd2722b..9358bdf2 100644 --- a/src/src.pro +++ b/src/src.pro @@ -9,6 +9,9 @@ QSK_SUBDIRS = common graphic nodes controls layouts dialogs inputpanel INCLUDEPATH *= $${QSK_SUBDIRS} DEPENDPATH *= $${QSK_SUBDIRS} +# CONFIG += pinyin +# CONFIG += hunspell + # DEFINES += QSK_LAYOUT_COMPAT HEADERS += \ @@ -352,6 +355,40 @@ SOURCES += \ inputpanel/QskInputPredictionBar.cpp \ inputpanel/QskVirtualKeyboard.cpp +pinyin { + + unix { + + DEFINES += PINYIN + + CONFIG += link_pkgconfig + PKGCONFIG += pinyin + + HEADERS += \ + inputpanel/QskPinyinTextPredictor.h + + SOURCES += \ + inputpanel/QskPinyinTextPredictor.cpp + } +} + +hunspell { + + unix { + + DEFINES += HUNSPELL + + CONFIG += link_pkgconfig + PKGCONFIG += hunspell + + HEADERS += \ + inputpanel/QskHunspellTextPredictor.h + + SOURCES += \ + inputpanel/QskHunspellTextPredictor.cpp + } +} + target.path = $${QSK_INSTALL_LIBS} INSTALLS = target