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:
parent
ab0fe2ac1c
commit
4c7c369477
@ -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();
|
||||
}
|
@ -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 );
|
||||
}
|
||||
|
||||
|
@ -3,9 +3,6 @@ TARGET = $$qskPluginTarget(qskinputcontext)
|
||||
|
||||
QT += gui-private
|
||||
|
||||
# CONFIG += pinyin
|
||||
# CONFIG += hunspell
|
||||
|
||||
CONFIG += plugin
|
||||
CONFIG += qskinny
|
||||
|
||||
|
133
src/inputpanel/QskHunspellTextPredictor.cpp
Normal file
133
src/inputpanel/QskHunspellTextPredictor.cpp
Normal 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 );
|
||||
}
|
@ -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;
|
||||
};
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
37
src/src.pro
37
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
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user