Hunspell: Move prediction to an own thread (#159)

* text prediction: Move predictors to input panel

* hunspell: Move predictor to an own thread and update implementation
This commit is contained in:
Peter Hartmann 2022-02-04 16:10:44 +01:00 committed by GitHub
parent ab0fe2ac1c
commit 4c7c369477
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 528 additions and 369 deletions

View File

@ -1,75 +0,0 @@
#include "QskHunspellTextPredictor.h"
#include <QVector>
#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();
}

View File

@ -8,37 +8,10 @@
#include "QskInputContext.h"
#define HUNSPELL 0
#if HUNSPELL
#include "QskHunspellTextPredictor.h"
#endif
#include <QEvent>
#include <QLocale>
#include <QRectF>
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 );
}

View File

@ -3,9 +3,6 @@ TARGET = $$qskPluginTarget(qskinputcontext)
QT += gui-private
# CONFIG += pinyin
# CONFIG += hunspell
CONFIG += plugin
CONFIG += qskinny

View File

@ -0,0 +1,133 @@
#include "QskHunspellTextPredictor.h"
#include <QDebug>
#include <QDir>
#include <QFileInfo>
#include <QLocale>
#include <QTextCodec>
#include <QTimer>
#include <QVector>
#include <hunspell/hunspell.h>
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 );
}

View File

@ -6,26 +6,30 @@
#ifndef QSK_HUNSPELL_TEXT_PREDICTOR_H
#define QSK_HUNSPELL_TEXT_PREDICTOR_H
#include "QskInputContextGlobal.h"
#include <QskTextPredictor.h>
#include "QskTextPredictor.h"
#include <QPair>
#include <memory>
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;
};

View File

@ -11,6 +11,7 @@
#include "QskLinearBox.h"
#include "QskPopup.h"
#include "QskQuick.h"
#include "QskTextPredictor.h"
#include "QskWindow.h"
#include <qguiapplication.h>
@ -24,6 +25,10 @@ QSK_QT_PRIVATE_END
#include <qpa/qplatforminputcontext.h>
#include <qpa/qplatformintegration.h>
#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<QskTextPredictor> 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

View File

@ -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();

View File

@ -10,6 +10,23 @@
#include <qpointer.h>
#include <qtextformat.h>
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"

View File

@ -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;

View File

@ -5,9 +5,10 @@
#include "QskTextPredictor.h"
QskTextPredictor::QskTextPredictor( Attributes attributes, QObject* parent )
#include <QVector>
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"

View File

@ -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

View File

@ -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