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"
|
#include "QskInputContext.h"
|
||||||
|
|
||||||
#define HUNSPELL 0
|
|
||||||
|
|
||||||
#if HUNSPELL
|
|
||||||
#include "QskHunspellTextPredictor.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <QEvent>
|
#include <QEvent>
|
||||||
#include <QLocale>
|
#include <QLocale>
|
||||||
#include <QRectF>
|
#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.
|
QPlatformInputContext is no stable public API.
|
||||||
So we forward everything to QskInputContext
|
So we forward everything to QskInputContext
|
||||||
|
@ -97,8 +70,7 @@ QskPlatformInputContext::QskPlatformInputContext()
|
||||||
if ( context == nullptr )
|
if ( context == nullptr )
|
||||||
{
|
{
|
||||||
context = new QskInputContext();
|
context = new QskInputContext();
|
||||||
context->setFactory( new InputContextFactory() );
|
context->setFactory( new QskInputContextFactory() );
|
||||||
|
|
||||||
QskInputContext::setInstance( context );
|
QskInputContext::setInstance( context );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,6 @@ TARGET = $$qskPluginTarget(qskinputcontext)
|
||||||
|
|
||||||
QT += gui-private
|
QT += gui-private
|
||||||
|
|
||||||
# CONFIG += pinyin
|
|
||||||
# CONFIG += hunspell
|
|
||||||
|
|
||||||
CONFIG += plugin
|
CONFIG += plugin
|
||||||
CONFIG += qskinny
|
CONFIG += qskinny
|
||||||
|
|
||||||
|
|
|
@ -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
|
#ifndef QSK_HUNSPELL_TEXT_PREDICTOR_H
|
||||||
#define QSK_HUNSPELL_TEXT_PREDICTOR_H
|
#define QSK_HUNSPELL_TEXT_PREDICTOR_H
|
||||||
|
|
||||||
#include "QskInputContextGlobal.h"
|
#include "QskTextPredictor.h"
|
||||||
#include <QskTextPredictor.h>
|
|
||||||
|
#include <QPair>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
class QSK_INPUTCONTEXT_EXPORT QskHunspellTextPredictor : public QskTextPredictor
|
class QSK_EXPORT QskHunspellTextPredictor : public QskTextPredictor
|
||||||
{
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
using Inherited = QskTextPredictor;
|
using Inherited = QskTextPredictor;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
QskHunspellTextPredictor( QObject* = nullptr );
|
QskHunspellTextPredictor( const QLocale& locale, QObject* = nullptr );
|
||||||
~QskHunspellTextPredictor() override;
|
~QskHunspellTextPredictor() override;
|
||||||
|
|
||||||
int candidateCount() const override;
|
|
||||||
QString candidate( int pos ) const override;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void request( const QString& ) override;
|
void request( const QString& ) override;
|
||||||
void reset() override;
|
void reset() override;
|
||||||
|
virtual QPair< QString, QString > affAndDicFile( const QString&, const QLocale& );
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
Q_INVOKABLE void loadDictionaries();
|
||||||
|
|
||||||
class PrivateData;
|
class PrivateData;
|
||||||
std::unique_ptr< PrivateData > m_data;
|
std::unique_ptr< PrivateData > m_data;
|
||||||
};
|
};
|
|
@ -11,6 +11,7 @@
|
||||||
#include "QskLinearBox.h"
|
#include "QskLinearBox.h"
|
||||||
#include "QskPopup.h"
|
#include "QskPopup.h"
|
||||||
#include "QskQuick.h"
|
#include "QskQuick.h"
|
||||||
|
#include "QskTextPredictor.h"
|
||||||
#include "QskWindow.h"
|
#include "QskWindow.h"
|
||||||
|
|
||||||
#include <qguiapplication.h>
|
#include <qguiapplication.h>
|
||||||
|
@ -24,6 +25,10 @@ QSK_QT_PRIVATE_END
|
||||||
#include <qpa/qplatforminputcontext.h>
|
#include <qpa/qplatforminputcontext.h>
|
||||||
#include <qpa/qplatformintegration.h>
|
#include <qpa/qplatformintegration.h>
|
||||||
|
|
||||||
|
#if HUNSPELL
|
||||||
|
#include "QskHunspellTextPredictor.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
class Panel final : public QskInputPanel
|
class Panel final : public QskInputPanel
|
||||||
|
@ -66,6 +71,7 @@ namespace
|
||||||
|
|
||||||
void setPrediction( const QStringList& prediction ) override
|
void setPrediction( const QStringList& prediction ) override
|
||||||
{
|
{
|
||||||
|
QskInputPanel::setPrediction( prediction );
|
||||||
m_box->setPrediction( prediction );
|
m_box->setPrediction( prediction );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -312,10 +318,10 @@ QskInputContextFactory* QskInputContext::factory() const
|
||||||
return m_data->factory;
|
return m_data->factory;
|
||||||
}
|
}
|
||||||
|
|
||||||
QskTextPredictor* QskInputContext::textPredictor( const QLocale& locale )
|
std::shared_ptr<QskTextPredictor> QskInputContext::textPredictor( const QLocale& locale )
|
||||||
{
|
{
|
||||||
if ( m_data->factory )
|
if ( m_data->factory )
|
||||||
return m_data->factory->createPredictor( locale );
|
return m_data->factory->setupPredictor( locale );
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
@ -494,17 +500,61 @@ void QskInputContext::commitPrediction( bool )
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class QskInputContextFactory::PrivateData
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
QThread* thread = nullptr;
|
||||||
|
std::shared_ptr< QskTextPredictor > predictor;
|
||||||
|
QLocale predictorLocale;
|
||||||
|
};
|
||||||
|
|
||||||
QskInputContextFactory::QskInputContextFactory( QObject* parent )
|
QskInputContextFactory::QskInputContextFactory( QObject* parent )
|
||||||
: QObject( parent )
|
: QObject( parent )
|
||||||
|
, m_data( new PrivateData() )
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
QskInputContextFactory::~QskInputContextFactory()
|
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 )
|
||||||
{
|
{
|
||||||
|
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;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,8 +29,15 @@ class QSK_EXPORT QskInputContextFactory : public QObject
|
||||||
QskInputContextFactory( QObject* parent = nullptr );
|
QskInputContextFactory( QObject* parent = nullptr );
|
||||||
~QskInputContextFactory() override;
|
~QskInputContextFactory() override;
|
||||||
|
|
||||||
virtual QskTextPredictor* createPredictor( const QLocale& ) const;
|
std::shared_ptr< QskTextPredictor > setupPredictor( const QLocale& );
|
||||||
virtual QskInputPanel* createPanel() const;
|
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
|
class QSK_EXPORT QskInputContext : public QObject
|
||||||
|
@ -56,7 +63,7 @@ class QSK_EXPORT QskInputContext : public QObject
|
||||||
static QskInputContext* instance();
|
static QskInputContext* instance();
|
||||||
static void setInstance( QskInputContext* );
|
static void setInstance( QskInputContext* );
|
||||||
|
|
||||||
QskTextPredictor* textPredictor( const QLocale& locale );
|
std::shared_ptr< QskTextPredictor > textPredictor( const QLocale& locale );
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void activeChanged();
|
void activeChanged();
|
||||||
|
|
|
@ -10,6 +10,23 @@
|
||||||
#include <qpointer.h>
|
#include <qpointer.h>
|
||||||
#include <qtextformat.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 )
|
static inline QQuickItem* qskReceiverItem( const QskInputPanel* panel )
|
||||||
{
|
{
|
||||||
if ( auto item = panel->inputProxy() )
|
if ( auto item = panel->inputProxy() )
|
||||||
|
@ -88,24 +105,27 @@ static inline void qskSendKey( QQuickItem* receiver, int key )
|
||||||
QCoreApplication::sendEvent( receiver, &keyRelease );
|
QCoreApplication::sendEvent( receiver, &keyRelease );
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace
|
class KeyProcessor : public QObject
|
||||||
{
|
{
|
||||||
class KeyProcessor
|
Q_OBJECT
|
||||||
{
|
|
||||||
public:
|
public:
|
||||||
struct Result
|
QString preedit() const
|
||||||
{
|
{
|
||||||
int key = 0;
|
return m_preedit;
|
||||||
|
}
|
||||||
|
|
||||||
QString text;
|
void processKey(
|
||||||
bool isFinal = true;
|
int key, Qt::InputMethodHints inputHints, QskInputPanel* panel,
|
||||||
};
|
|
||||||
|
|
||||||
Result processKey(
|
|
||||||
int key, Qt::InputMethodHints inputHints,
|
|
||||||
QskTextPredictor* predictor, int spaceLeft )
|
QskTextPredictor* predictor, int spaceLeft )
|
||||||
{
|
{
|
||||||
Result result;
|
// 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
|
// First we have to handle the control keys
|
||||||
|
|
||||||
|
@ -114,23 +134,27 @@ namespace
|
||||||
case Qt::Key_Backspace:
|
case Qt::Key_Backspace:
|
||||||
case Qt::Key_Muhenkan:
|
case Qt::Key_Muhenkan:
|
||||||
{
|
{
|
||||||
if ( predictor )
|
if ( predictor && !m_preedit.isEmpty() )
|
||||||
{
|
|
||||||
if ( !m_preedit.isEmpty() )
|
|
||||||
{
|
{
|
||||||
m_preedit.chop( 1 );
|
m_preedit.chop( 1 );
|
||||||
|
|
||||||
result.text = m_preedit;
|
m_currentResult.text = m_preedit;
|
||||||
result.isFinal = false;
|
m_currentResult.isFinal = false;
|
||||||
|
|
||||||
predictor->request( m_preedit );
|
Q_EMIT panel->predictionRequested( m_preedit );
|
||||||
|
// Let the input field update right away, otherwise
|
||||||
return result;
|
// we'll get weird effects with fast backspace presses:
|
||||||
|
Q_EMIT keyProcessingFinished( m_currentResult );
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_currentResult.key = Qt::Key_Backspace;
|
||||||
|
Q_EMIT keyProcessingFinished( m_currentResult );
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
result.key = Qt::Key_Backspace;
|
break;
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
case Qt::Key_Return:
|
case Qt::Key_Return:
|
||||||
{
|
{
|
||||||
|
@ -140,20 +164,21 @@ namespace
|
||||||
{
|
{
|
||||||
if ( spaceLeft )
|
if ( spaceLeft )
|
||||||
{
|
{
|
||||||
result.text = m_preedit.left( spaceLeft );
|
m_currentResult.text = m_preedit.left( spaceLeft );
|
||||||
result.isFinal = true;
|
m_currentResult.isFinal = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
reset();
|
reset();
|
||||||
|
Q_EMIT keyProcessingFinished( m_currentResult );
|
||||||
return result;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !( inputHints & Qt::ImhMultiLine ) )
|
if ( !( inputHints & Qt::ImhMultiLine ) )
|
||||||
{
|
{
|
||||||
result.key = Qt::Key_Return;
|
m_currentResult.key = Qt::Key_Return;
|
||||||
return result;
|
Q_EMIT keyProcessingFinished( m_currentResult );
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -167,12 +192,13 @@ namespace
|
||||||
m_preedit += keyString( key );
|
m_preedit += keyString( key );
|
||||||
m_preedit = m_preedit.left( spaceLeft );
|
m_preedit = m_preedit.left( spaceLeft );
|
||||||
|
|
||||||
result.text = m_preedit;
|
m_currentResult.text = m_preedit;
|
||||||
result.isFinal = true;
|
m_currentResult.isFinal = true;
|
||||||
|
|
||||||
reset();
|
reset();
|
||||||
|
|
||||||
return result;
|
Q_EMIT keyProcessingFinished( m_currentResult );
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,9 +207,11 @@ namespace
|
||||||
case Qt::Key_Left:
|
case Qt::Key_Left:
|
||||||
case Qt::Key_Right:
|
case Qt::Key_Right:
|
||||||
case Qt::Key_Escape:
|
case Qt::Key_Escape:
|
||||||
|
case Qt::Key_Cancel:
|
||||||
{
|
{
|
||||||
result.key = key;
|
m_currentResult.key = key;
|
||||||
return result;
|
Q_EMIT keyProcessingFinished( m_currentResult );
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,36 +220,45 @@ namespace
|
||||||
if ( predictor )
|
if ( predictor )
|
||||||
{
|
{
|
||||||
m_preedit += text;
|
m_preedit += text;
|
||||||
|
Q_EMIT panel->predictionRequested( m_preedit );
|
||||||
predictor->request( m_preedit );
|
|
||||||
|
|
||||||
if ( predictor->candidateCount() > 0 )
|
|
||||||
{
|
|
||||||
result.text = m_preedit;
|
|
||||||
result.isFinal = false;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
result.text = m_preedit.left( spaceLeft );
|
m_currentResult.text = text;
|
||||||
result.isFinal = true;
|
m_currentResult.isFinal = true;
|
||||||
|
Q_EMIT keyProcessingFinished( m_currentResult );
|
||||||
m_preedit.clear();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
result.text = text;
|
|
||||||
result.isFinal = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void reset()
|
void reset()
|
||||||
{
|
{
|
||||||
m_preedit.clear();
|
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
|
||||||
|
{
|
||||||
|
m_currentResult.text = m_preedit.left( m_spaceLeft );
|
||||||
|
m_currentResult.isFinal = true;
|
||||||
|
|
||||||
|
m_preedit.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Q_EMIT keyProcessingFinished( m_currentResult );
|
||||||
|
}
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void keyProcessingFinished( const Result& result );
|
||||||
|
|
||||||
private:
|
private:
|
||||||
inline QString keyString( int keyCode ) const
|
inline QString keyString( int keyCode ) const
|
||||||
{
|
{
|
||||||
|
@ -250,25 +287,73 @@ namespace
|
||||||
}
|
}
|
||||||
|
|
||||||
QString m_preedit;
|
QString m_preedit;
|
||||||
};
|
int m_spaceLeft = -1;
|
||||||
}
|
QskTextPredictor* m_predictor = nullptr;
|
||||||
|
Result m_currentResult;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
class QskInputPanel::PrivateData
|
class QskInputPanel::PrivateData
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
PrivateData( QskInputPanel* panel )
|
||||||
|
: panel( panel )
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
KeyProcessor keyProcessor;
|
KeyProcessor keyProcessor;
|
||||||
QPointer< QQuickItem > inputItem;
|
QPointer< QQuickItem > inputItem;
|
||||||
|
|
||||||
QLocale predictorLocale;
|
QLocale predictorLocale;
|
||||||
QPointer< QskTextPredictor > predictor;
|
std::shared_ptr< QskTextPredictor > predictor;
|
||||||
|
QStringList candidates;
|
||||||
|
|
||||||
Qt::InputMethodHints inputHints;
|
Qt::InputMethodHints inputHints;
|
||||||
bool hasPredictorLocale = false;
|
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 )
|
QskInputPanel::QskInputPanel( QQuickItem* parent )
|
||||||
: Inherited( parent )
|
: Inherited( parent )
|
||||||
, m_data( new PrivateData() )
|
, m_data( new PrivateData( this ) )
|
||||||
{
|
{
|
||||||
setAutoLayoutChildren( true );
|
setAutoLayoutChildren( true );
|
||||||
initSizePolicy( QskSizePolicy::Expanding, QskSizePolicy::Constrained );
|
initSizePolicy( QskSizePolicy::Expanding, QskSizePolicy::Constrained );
|
||||||
|
@ -282,6 +367,12 @@ QskInputPanel::QskInputPanel( QQuickItem* parent )
|
||||||
connect( this, &QskControl::localeChanged,
|
connect( this, &QskControl::localeChanged,
|
||||||
this, &QskInputPanel::updateLocale );
|
this, &QskInputPanel::updateLocale );
|
||||||
|
|
||||||
|
connect( &m_data->keyProcessor, &KeyProcessor::keyProcessingFinished,
|
||||||
|
this, [this]( const Result& result )
|
||||||
|
{
|
||||||
|
m_data->handleKeyProcessingFinished( result );
|
||||||
|
} );
|
||||||
|
|
||||||
updateLocale( locale() );
|
updateLocale( locale() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -305,7 +396,7 @@ void QskInputPanel::attachInputItem( QQuickItem* item )
|
||||||
if ( item )
|
if ( item )
|
||||||
{
|
{
|
||||||
if ( m_data->predictor )
|
if ( m_data->predictor )
|
||||||
m_data->predictor->reset();
|
Q_EMIT predictionReset();
|
||||||
|
|
||||||
m_data->keyProcessor.reset();
|
m_data->keyProcessor.reset();
|
||||||
m_data->inputHints = Qt::InputMethodHints();
|
m_data->inputHints = Qt::InputMethodHints();
|
||||||
|
@ -393,27 +484,17 @@ void QskInputPanel::resetPredictor( const QLocale& locale )
|
||||||
if ( predictor == m_data->predictor )
|
if ( predictor == m_data->predictor )
|
||||||
return;
|
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;
|
m_data->predictor = predictor;
|
||||||
|
|
||||||
if ( predictor )
|
if ( predictor )
|
||||||
{
|
{
|
||||||
if ( predictor->parent() == nullptr )
|
// text predictor lives in another thread, so these will all be QueuedConnections:
|
||||||
predictor->setParent( this );
|
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 );
|
this, &QskInputPanel::updatePrediction );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -427,21 +508,34 @@ void QskInputPanel::commitPredictiveText( int index )
|
||||||
|
|
||||||
if ( m_data->predictor )
|
if ( m_data->predictor )
|
||||||
{
|
{
|
||||||
text = m_data->predictor->candidate( index );
|
text = m_data->candidates.at( index );
|
||||||
m_data->predictor->reset();
|
Q_EMIT predictionReset();
|
||||||
}
|
}
|
||||||
|
|
||||||
m_data->keyProcessor.reset();
|
m_data->keyProcessor.reset();
|
||||||
|
|
||||||
setPrediction( QStringList() );
|
setPrediction( {} );
|
||||||
|
|
||||||
qskSendText( qskReceiverItem( this ), text, true );
|
qskSendText( qskReceiverItem( this ), text, true );
|
||||||
}
|
}
|
||||||
|
|
||||||
void QskInputPanel::updatePrediction()
|
void QskInputPanel::updatePrediction( const QString& text, const QStringList& candidates )
|
||||||
{
|
{
|
||||||
if ( m_data->predictor )
|
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
|
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
|
Qt::Alignment QskInputPanel::alignment() const
|
||||||
|
@ -476,6 +571,11 @@ Qt::Alignment QskInputPanel::alignment() const
|
||||||
return inputProxy() ? Qt::AlignVCenter : Qt::AlignBottom;
|
return inputProxy() ? Qt::AlignVCenter : Qt::AlignBottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QStringList QskInputPanel::candidates() const
|
||||||
|
{
|
||||||
|
return m_data->candidates;
|
||||||
|
}
|
||||||
|
|
||||||
void QskInputPanel::commitKey( int key )
|
void QskInputPanel::commitKey( int key )
|
||||||
{
|
{
|
||||||
if ( m_data->inputItem == nullptr )
|
if ( m_data->inputItem == nullptr )
|
||||||
|
@ -501,47 +601,11 @@ void QskInputPanel::commitKey( int key )
|
||||||
|
|
||||||
QskTextPredictor* predictor = nullptr;
|
QskTextPredictor* predictor = nullptr;
|
||||||
if ( qskUsePrediction( m_data->inputHints ) )
|
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(
|
m_data->keyProcessor.processKey(
|
||||||
key, m_data->inputHints, predictor, spaceLeft );
|
key, m_data->inputHints, this, 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 );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#include "moc_QskInputPanel.cpp"
|
#include "moc_QskInputPanel.cpp"
|
||||||
|
#include "QskInputPanel.moc"
|
||||||
|
|
|
@ -30,6 +30,8 @@ class QSK_EXPORT QskInputPanel : public QskControl
|
||||||
|
|
||||||
virtual Qt::Alignment alignment() const;
|
virtual Qt::Alignment alignment() const;
|
||||||
|
|
||||||
|
QStringList candidates() const;
|
||||||
|
|
||||||
public Q_SLOTS:
|
public Q_SLOTS:
|
||||||
void commitKey( int keyCode );
|
void commitKey( int keyCode );
|
||||||
void commitPredictiveText( int index );
|
void commitPredictiveText( int index );
|
||||||
|
@ -39,6 +41,9 @@ class QSK_EXPORT QskInputPanel : public QskControl
|
||||||
void predictiveTextSelected( int );
|
void predictiveTextSelected( int );
|
||||||
void inputItemDestroyed();
|
void inputItemDestroyed();
|
||||||
|
|
||||||
|
void predictionReset();
|
||||||
|
void predictionRequested( const QString& text );
|
||||||
|
|
||||||
public Q_SLOTS:
|
public Q_SLOTS:
|
||||||
virtual void setPrompt( const QString& );
|
virtual void setPrompt( const QString& );
|
||||||
virtual void setPrediction( const QStringList& );
|
virtual void setPrediction( const QStringList& );
|
||||||
|
@ -48,9 +53,8 @@ class QSK_EXPORT QskInputPanel : public QskControl
|
||||||
virtual void attachItem( QQuickItem* ) = 0;
|
virtual void attachItem( QQuickItem* ) = 0;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void updatePrediction( const QString &text, const QStringList &candidates );
|
||||||
void resetPredictor( const QLocale& );
|
void resetPredictor( const QLocale& );
|
||||||
void updatePrediction();
|
|
||||||
|
|
||||||
void updateLocale( const QLocale& );
|
void updateLocale( const QLocale& );
|
||||||
|
|
||||||
class PrivateData;
|
class PrivateData;
|
||||||
|
|
|
@ -5,9 +5,10 @@
|
||||||
|
|
||||||
#include "QskTextPredictor.h"
|
#include "QskTextPredictor.h"
|
||||||
|
|
||||||
QskTextPredictor::QskTextPredictor( Attributes attributes, QObject* parent )
|
#include <QVector>
|
||||||
|
|
||||||
|
QskTextPredictor::QskTextPredictor( QObject* parent )
|
||||||
: 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"
|
#include "moc_QskTextPredictor.cpp"
|
||||||
|
|
|
@ -16,34 +16,17 @@ class QSK_EXPORT QskTextPredictor : public QObject
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum Attribute
|
|
||||||
{
|
|
||||||
Words = 1 << 0
|
|
||||||
};
|
|
||||||
|
|
||||||
Q_ENUM( Attribute )
|
|
||||||
Q_DECLARE_FLAGS( Attributes, Attribute )
|
|
||||||
|
|
||||||
~QskTextPredictor() override;
|
~QskTextPredictor() override;
|
||||||
|
|
||||||
|
public Q_SLOTS:
|
||||||
virtual void request( const QString& text ) = 0;
|
virtual void request( const QString& text ) = 0;
|
||||||
virtual void reset() = 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:
|
Q_SIGNALS:
|
||||||
void predictionChanged();
|
void predictionChanged( const QString& text, const QStringList& candidates );
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
QskTextPredictor( Attributes, QObject* );
|
QskTextPredictor( QObject* );
|
||||||
|
|
||||||
private:
|
|
||||||
const Attributes m_attributes;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#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}
|
INCLUDEPATH *= $${QSK_SUBDIRS}
|
||||||
DEPENDPATH *= $${QSK_SUBDIRS}
|
DEPENDPATH *= $${QSK_SUBDIRS}
|
||||||
|
|
||||||
|
# CONFIG += pinyin
|
||||||
|
# CONFIG += hunspell
|
||||||
|
|
||||||
# DEFINES += QSK_LAYOUT_COMPAT
|
# DEFINES += QSK_LAYOUT_COMPAT
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
|
@ -352,6 +355,40 @@ SOURCES += \
|
||||||
inputpanel/QskInputPredictionBar.cpp \
|
inputpanel/QskInputPredictionBar.cpp \
|
||||||
inputpanel/QskVirtualKeyboard.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}
|
target.path = $${QSK_INSTALL_LIBS}
|
||||||
INSTALLS = target
|
INSTALLS = target
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue