more changes concering the input context

This commit is contained in:
Uwe Rathmann 2018-04-26 14:42:33 +02:00
parent 745b704ba8
commit 16efc695b9
18 changed files with 947 additions and 788 deletions

View File

@ -1,16 +1,16 @@
#include "QskHunspellCompositionModel.h" #include "QskHunspellTextPredictor.h"
#include <QVector> #include <QVector>
#include "hunspell.h" #include "hunspell.h"
class QskHunspellCompositionModel::PrivateData class QskHunspellTextPredictor::PrivateData
{ {
public: public:
Hunhandle* hunspellHandle; Hunhandle* hunspellHandle;
QVector< QString > candidates; QVector< QString > candidates;
}; };
QskHunspellCompositionModel::QskHunspellCompositionModel( QObject* object ): QskHunspellTextPredictor::QskHunspellTextPredictor( QObject* object ):
Inherited( Words, object ), Inherited( Words, object ),
m_data( new PrivateData() ) m_data( new PrivateData() )
{ {
@ -23,31 +23,31 @@ QskHunspellCompositionModel::QskHunspellCompositionModel( QObject* object ):
#endif #endif
} }
QskHunspellCompositionModel::~QskHunspellCompositionModel() QskHunspellTextPredictor::~QskHunspellTextPredictor()
{ {
Hunspell_destroy( m_data->hunspellHandle ); Hunspell_destroy( m_data->hunspellHandle );
} }
int QskHunspellCompositionModel::candidateCount() const int QskHunspellTextPredictor::candidateCount() const
{ {
return m_data->candidates.count(); return m_data->candidates.count();
} }
QString QskHunspellCompositionModel::candidate( int pos ) const QString QskHunspellTextPredictor::candidate( int pos ) const
{ {
return m_data->candidates[ pos ]; return m_data->candidates[ pos ];
} }
void QskHunspellCompositionModel::resetCandidates() void QskHunspellTextPredictor::reset()
{ {
if ( !m_data->candidates.isEmpty() ) if ( !m_data->candidates.isEmpty() )
{ {
m_data->candidates.clear(); m_data->candidates.clear();
Q_EMIT candidatesChanged(); Q_EMIT predictionChanged();
} }
} }
void QskHunspellCompositionModel::requestCandidates( const QString& text ) void QskHunspellTextPredictor::request( const QString& text )
{ {
char** suggestions; char** suggestions;
const QByteArray word = text.toUtf8(); // ### do we need to check the encoding const QByteArray word = text.toUtf8(); // ### do we need to check the encoding
@ -71,5 +71,5 @@ void QskHunspellCompositionModel::requestCandidates( const QString& text )
Hunspell_free_list( m_data->hunspellHandle, &suggestions, count ); Hunspell_free_list( m_data->hunspellHandle, &suggestions, count );
m_data->candidates = candidates; m_data->candidates = candidates;
Q_EMIT candidatesChanged(); Q_EMIT predictionChanged();
} }

View File

@ -3,26 +3,26 @@
* This file may be used under the terms of the QSkinny License, Version 1.0 * This file may be used under the terms of the QSkinny License, Version 1.0
*****************************************************************************/ *****************************************************************************/
#ifndef QSK_HUNSPELL_COMPOSITION_MODEL_H #ifndef QSK_HUNSPELL_TEXT_PREDICTOR_H
#define QSK_HUNSPELL_COMPOSITION_MODEL_H #define QSK_HUNSPELL_TEXT_PREDICTOR_H
#include "QskInputCompositionModel.h" #include "QskTextPredictor.h"
#include <memory> #include <memory>
class QskHunspellCompositionModel : public QskInputCompositionModel class QSK_EXPORT QskHunspellTextPredictor : public QskTextPredictor
{ {
using Inherited = QskInputCompositionModel; using Inherited = QskTextPredictor;
public: public:
QskHunspellCompositionModel( QObject* ); QskHunspellTextPredictor( QObject* );
virtual ~QskHunspellCompositionModel() override; virtual ~QskHunspellTextPredictor() override;
virtual int candidateCount() const override; virtual int candidateCount() const override;
virtual QString candidate( int pos ) const override; virtual QString candidate( int pos ) const override;
protected: protected:
virtual void requestCandidates( const QString& ) override; virtual void request( const QString& ) override;
virtual void resetCandidates() override; virtual void reset() override;
private: private:
class PrivateData; class PrivateData;

View File

@ -6,8 +6,8 @@
#include <qpa/qplatforminputcontextplugin_p.h> #include <qpa/qplatforminputcontextplugin_p.h>
#include "QskInputContext.h" #include "QskInputContext.h"
#include "QskPinyinCompositionModel.h" #include "QskPinyinTextPredictor.h"
#include "QskHunspellCompositionModel.h" #include "QskHunspellTextPredictor.h"
#include <QLocale> #include <QLocale>
@ -25,13 +25,13 @@ public:
auto context = new QskInputContext(); auto context = new QskInputContext();
#if 0 #if 0
context->setCompositionModel( QLocale(), context->registerPredictor( QLocale(),
new QskHunspellCompositionModel( this ) ); new QskHunspellTextPredictor( this ) );
#endif #endif
#if 0 #if 0
context->setCompositionModel( context->registerPredictor(
QLocale::Chinese, new QskPinyinCompositionModel( this ) ); QLocale::Chinese, new QskPinyinTextPredictor( this ) );
#endif #endif
return context; return context;

View File

@ -3,7 +3,7 @@
* This file may be used under the terms of the QSkinny License, Version 1.0 * This file may be used under the terms of the QSkinny License, Version 1.0
*****************************************************************************/ *****************************************************************************/
#include "QskPinyinCompositionModel.h" #include "QskPinyinTextPredictor.h"
#include "QskInputContext.h" #include "QskInputContext.h"
#include "pinyinime.h" #include "pinyinime.h"
@ -11,13 +11,13 @@
#include <QStringList> #include <QStringList>
#include <QDebug> #include <QDebug>
class QskPinyinCompositionModel::PrivateData class QskPinyinTextPredictor::PrivateData
{ {
public: public:
QStringList candidates; QStringList candidates;
}; };
QskPinyinCompositionModel::QskPinyinCompositionModel( QObject* parent ): QskPinyinTextPredictor::QskPinyinTextPredictor( QObject* parent ):
Inherited( Attributes(), parent ), Inherited( Attributes(), parent ),
m_data( new PrivateData ) m_data( new PrivateData )
{ {
@ -34,17 +34,17 @@ QskPinyinCompositionModel::QskPinyinCompositionModel( QObject* parent ):
} }
} }
QskPinyinCompositionModel::~QskPinyinCompositionModel() QskPinyinTextPredictor::~QskPinyinTextPredictor()
{ {
ime_pinyin::im_close_decoder(); ime_pinyin::im_close_decoder();
} }
int QskPinyinCompositionModel::candidateCount() const int QskPinyinTextPredictor::candidateCount() const
{ {
return m_data->candidates.count(); return m_data->candidates.count();
} }
QString QskPinyinCompositionModel::candidate( int index ) const QString QskPinyinTextPredictor::candidate( int index ) const
{ {
if ( ( index >= 0 ) && ( index < m_data->candidates.count() ) ) if ( ( index >= 0 ) && ( index < m_data->candidates.count() ) )
return m_data->candidates[ index ]; return m_data->candidates[ index ];
@ -52,18 +52,18 @@ QString QskPinyinCompositionModel::candidate( int index ) const
return QString(); return QString();
} }
void QskPinyinCompositionModel::resetCandidates() void QskPinyinTextPredictor::reset()
{ {
ime_pinyin::im_reset_search(); ime_pinyin::im_reset_search();
if ( !m_data->candidates.isEmpty() ) if ( !m_data->candidates.isEmpty() )
{ {
m_data->candidates.clear(); m_data->candidates.clear();
Q_EMIT candidatesChanged(); Q_EMIT predictionChanged();
} }
} }
void QskPinyinCompositionModel::requestCandidates( const QString& text ) void QskPinyinTextPredictor::request( const QString& text )
{ {
const QByteArray bytes = text.toLatin1(); const QByteArray bytes = text.toLatin1();
@ -101,5 +101,5 @@ void QskPinyinCompositionModel::requestCandidates( const QString& text )
} }
m_data->candidates = candidates; m_data->candidates = candidates;
Q_EMIT candidatesChanged(); Q_EMIT predictionChanged();
} }

View File

@ -3,26 +3,26 @@
* This file may be used under the terms of the QSkinny License, Version 1.0 * This file may be used under the terms of the QSkinny License, Version 1.0
*****************************************************************************/ *****************************************************************************/
#ifndef QSK_PINYIN_COMPOSITION_MODEL_H #ifndef QSK_PINYIN_TEXT_PREDICTOR_H
#define QSK_PINYIN_COMPOSITION_MODEL_H #define QSK_PINYIN_TEXT_PREDICTOR_H
#include "QskInputCompositionModel.h" #include "QskTextPredictor.h"
#include <memory> #include <memory>
class QskPinyinCompositionModel : public QskInputCompositionModel class QSK_EXPORT QskPinyinTextPredictor : public QskTextPredictor
{ {
using Inherited = QskInputCompositionModel; using Inherited = QskTextPredictor;
public: public:
QskPinyinCompositionModel( QObject* ); QskPinyinTextPredictor( QObject* );
virtual ~QskPinyinCompositionModel() override; virtual ~QskPinyinTextPredictor() override;
virtual int candidateCount() const override; virtual int candidateCount() const override;
virtual QString candidate( int ) const override; virtual QString candidate( int ) const override;
protected: protected:
virtual void requestCandidates( const QString& ) override; virtual void request( const QString& ) override;
virtual void resetCandidates() override; virtual void reset() override;
private: private:
class PrivateData; class PrivateData;

View File

@ -81,10 +81,10 @@ pinyin {
$${QSK_PINYIN_DIR}/include/utf16reader.h $${QSK_PINYIN_DIR}/include/utf16reader.h
HEADERS += \ HEADERS += \
QskPinyinCompositionModel.h QskPinyinTextPredictor.h
SOURCES += \ SOURCES += \
QskPinyinCompositionModel.cpp QskPinyinTextPredictor.cpp
} }
hunspell { hunspell {
@ -124,10 +124,10 @@ hunspell {
$${QSK_HUNSPELL_DIR}/suggestmgr.cxx $${QSK_HUNSPELL_DIR}/suggestmgr.cxx
HEADERS += \ HEADERS += \
QskHunspellCompositionModel.h QskHunspellTextPredictor.h
SOURCES += \ SOURCES += \
QskHunspellCompositionModel.cpp QskHunspellTextPredictor.cpp
OTHER_FILES +=\ OTHER_FILES +=\
$${QSK_HUNSPELL_DIR}/license.hunspell \ $${QSK_HUNSPELL_DIR}/license.hunspell \

View File

@ -26,6 +26,93 @@
#define STRINGIFY(x) #x #define STRINGIFY(x) #x
#define STRING(x) STRINGIFY(x) #define STRING(x) STRINGIFY(x)
static inline QString nativeLocaleString( const QLocale& locale )
{
switch( locale.language() )
{
case QLocale::Bulgarian:
return QStringLiteral( "български език" );
case QLocale::Czech:
return QStringLiteral( "Čeština" );
case QLocale::German:
return QStringLiteral( "Deutsch" );
case QLocale::Danish:
return QStringLiteral( "Dansk" );
case QLocale::Greek:
return QStringLiteral( "Eλληνικά" );
case QLocale::English:
{
switch( locale.country() )
{
case QLocale::Canada:
case QLocale::UnitedStates:
case QLocale::UnitedStatesMinorOutlyingIslands:
case QLocale::UnitedStatesVirginIslands:
return QStringLiteral( "English (US)" );
default:
return QStringLiteral( "English (UK)" );
}
}
case QLocale::Spanish:
return QStringLiteral( "Español" );
case QLocale::Finnish:
return QStringLiteral( "Suomi" );
case QLocale::French:
return QStringLiteral( "Français" );
case QLocale::Hungarian:
return QStringLiteral( "Magyar" );
case QLocale::Italian:
return QStringLiteral( "Italiano" );
case QLocale::Japanese:
return QStringLiteral( "日本語" );
case QLocale::Latvian:
return QStringLiteral( "Latviešu" );
case QLocale::Lithuanian:
return QStringLiteral( "Lietuvių" );
case QLocale::Dutch:
return QStringLiteral( "Nederlands" );
case QLocale::Portuguese:
return QStringLiteral( "Português" );
case QLocale::Romanian:
return QStringLiteral( "Română" );
case QLocale::Russia:
return QStringLiteral( "Русский" );
case QLocale::Slovenian:
return QStringLiteral( "Slovenščina" );
case QLocale::Slovak:
return QStringLiteral( "Slovenčina" );
case QLocale::Turkish:
return QStringLiteral( "Türkçe" );
case QLocale::Chinese:
return QStringLiteral( "中文" );
default:
return QLocale::languageToString( locale.language() );
}
}
class InputBox : public QskLinearBox class InputBox : public QskLinearBox
{ {
public: public:
@ -149,7 +236,7 @@ public:
private: private:
inline void append( const QLocale& locale ) inline void append( const QLocale& locale )
{ {
m_values += qMakePair( qskNativeLocaleString( locale ), locale ); m_values += qMakePair( nativeLocaleString( locale ), locale );
} }
QVector< QPair< QString, QLocale > > m_values; QVector< QPair< QString, QLocale > > m_values;

View File

@ -4,113 +4,73 @@
*****************************************************************************/ *****************************************************************************/
#include "QskInputContext.h" #include "QskInputContext.h"
#include "QskInputCompositionModel.h" #include "QskTextPredictor.h"
#include "QskInputPanel.h" #include "QskInputPanel.h"
#include "QskInputEngine.h"
#include "QskLinearBox.h" #include "QskLinearBox.h"
#include <QskDialog.h> #include <QskDialog.h>
#include <QskPopup.h> #include <QskPopup.h>
#include <QskWindow.h> #include <QskWindow.h>
#include <QskControl.h>
#include <QskSetup.h> #include <QskSetup.h>
#include <QskEvent.h> #include <QskEvent.h>
#include <QTextCharFormat>
#include <QHash> #include <QHash>
#include <QPointer> #include <QPointer>
#include <QGuiApplication> #include <QGuiApplication>
static inline QString qskKeyString( int keyCode )
{
// Special case entry codes here, else default to the symbol
switch ( keyCode )
{
case Qt::Key_Shift:
case Qt::Key_CapsLock:
case Qt::Key_Mode_switch:
case Qt::Key_Backspace:
case Qt::Key_Muhenkan:
return QString();
case Qt::Key_Return:
case Qt::Key_Kanji:
return QChar( QChar::CarriageReturn );
case Qt::Key_Space:
return QChar( QChar::Space );
default:
break;
}
return QChar( keyCode );
}
static void qskSetLocale( QQuickItem* inputPanel, const QLocale& locale )
{
if ( auto control = qobject_cast< QskControl* >( inputPanel ) )
{
control->setLocale( locale );
}
else
{
const auto mo = inputPanel->metaObject();
const auto property = mo->property( mo->indexOfProperty( "locale" ) );
if ( property.isWritable() )
property.write( inputPanel, locale );
}
}
static QLocale qskLocale( const QQuickItem* inputPanel )
{
if ( inputPanel == nullptr )
return QLocale();
if ( auto control = qobject_cast< const QskControl* >( inputPanel ) )
return control->locale();
return inputPanel->property( "locale" ).toLocale();
}
static void qskSetCandidatesEnabled( QQuickItem* inputPanel, bool on )
{
if ( inputPanel == nullptr )
return;
if ( auto panel = qobject_cast< QskInputPanel* >( inputPanel ) )
{
panel->setCandidatesEnabled( on );
}
else
{
QMetaObject::invokeMethod( inputPanel, "setCandidatesEnabled",
Qt::DirectConnection, Q_ARG( bool, on ) );
}
}
static void qskSetCandidates( QQuickItem* inputPanel,
const QVector< QString >& candidates )
{
if ( inputPanel == nullptr )
return;
if ( auto panel = qobject_cast< QskInputPanel* >( inputPanel ) )
{
panel->setCandidates( candidates );
}
else
{
QMetaObject::invokeMethod( inputPanel, "setCandidates",
Qt::DirectConnection, Q_ARG( QVector< QString >, candidates ) );
}
}
static inline uint qskHashLocale( const QLocale& locale ) static inline uint qskHashLocale( const QLocale& locale )
{ {
return uint( locale.language() + ( uint( locale.country() ) << 16 ) ); return uint( locale.language() + ( uint( locale.country() ) << 16 ) );
} }
namespace
{
class PredictorTable
{
public:
void replace( const QLocale& locale, QskTextPredictor* predictor )
{
const auto key = qskHashLocale( locale );
if ( predictor )
{
const auto it = hashTab.find( key );
if ( it != hashTab.end() )
{
if ( it.value() == predictor )
return;
delete it.value();
*it = predictor;
}
else
{
hashTab.insert( key, predictor );
}
}
else
{
const auto it = hashTab.find( key );
if ( it != hashTab.end() )
{
delete it.value();
hashTab.erase( it );
}
}
}
QskTextPredictor* find( const QLocale& locale )
{
const auto key = qskHashLocale( locale );
return hashTab.value( key, nullptr );
}
private:
QHash< uint, QskTextPredictor* > hashTab;
};
}
class QskInputContext::PrivateData class QskInputContext::PrivateData
{ {
public: public:
@ -124,9 +84,9 @@ public:
QskPopup* inputPopup = nullptr; QskPopup* inputPopup = nullptr;
QskWindow* inputWindow = nullptr; QskWindow* inputWindow = nullptr;
QHash< uint, QskInputCompositionModel* > compositionModels; PredictorTable predictorTable;
QString preedit; QskInputEngine* engine = nullptr;
// the input panel is embedded in a window // the input panel is embedded in a window
bool ownsInputPanelWindow : 1; bool ownsInputPanelWindow : 1;
@ -137,6 +97,8 @@ QskInputContext::QskInputContext():
{ {
setObjectName( "InputContext" ); setObjectName( "InputContext" );
m_data->engine = new QskInputEngine( this );
connect( qskSetup, &QskSetup::inputPanelChanged, connect( qskSetup, &QskSetup::inputPanelChanged,
this, &QskInputContext::setInputPanel ); this, &QskInputContext::setInputPanel );
@ -168,114 +130,48 @@ void QskInputContext::setInputItem( QQuickItem* item )
if ( m_data->inputItem == item ) if ( m_data->inputItem == item )
return; return;
m_data->inputItem = item; auto panel = qobject_cast< QskInputPanel* >( m_data->inputPanel );
if ( item ) if ( isInputPanelVisible() )
update( Qt::ImQueryAll ); {
if ( item == nullptr )
{
hideInputPanel();
}
else
{
if ( panel )
panel->attachInputItem( item );
update( Qt::ImQueryAll );
}
}
else else
hideInputPanel(); {
// no need for updates
if ( panel )
panel->attachInputItem( nullptr );
}
m_data->inputItem = item;
} }
void QskInputContext::update( Qt::InputMethodQueries queries ) void QskInputContext::update( Qt::InputMethodQueries queries )
{ {
if ( m_data->inputItem == nullptr || m_data->inputPanel == nullptr ) if ( queries & Qt::ImEnabled )
return;
const auto queryEvent = queryInputMethod( queries );
if ( queryEvent.queries() & Qt::ImEnabled )
{ {
if ( !queryEvent.value( Qt::ImEnabled ).toBool() ) QInputMethodQueryEvent event( Qt::ImEnabled );
QCoreApplication::sendEvent( m_data->inputItem, &event );
if ( !event.value( Qt::ImEnabled ).toBool() )
{ {
hideInputPanel(); hideInputPanel();
return; return;
} }
} }
if ( queryEvent.queries() & Qt::ImHints ) if ( auto panel = qobject_cast< QskInputPanel* >( m_data->inputPanel ) )
{ panel->processInputMethodQueries( queries );
/*
ImhHiddenText = 0x1, // might need to disable certain checks
ImhSensitiveData = 0x2, // shouldn't change anything
ImhNoAutoUppercase = 0x4, // if we support auto uppercase, disable it
ImhPreferNumbers = 0x8, // default to number keyboard
ImhPreferUppercase = 0x10, // start with shift on
ImhPreferLowercase = 0x20, // start with shift off
ImhNoPredictiveText = 0x40, // not use predictive text
ImhDate = 0x80, // ignored for now (no date keyboard)
ImhTime = 0x100, // ignored for know (no time keyboard)
ImhPreferLatin = 0x200, // can be used to launch chinese kb in english mode
ImhMultiLine = 0x400, // not useful?
ImhDigitsOnly // default to number keyboard, disable other keys
ImhFormattedNumbersOnly // hard to say
ImhUppercaseOnly // caps-lock, disable shift
ImhLowercaseOnly // disable shift
ImhDialableCharactersOnly // dial pad (calculator?)
ImhEmailCharactersOnly // disable certain symbols (email-only kb?)
ImhUrlCharactersOnly // disable certain symbols (url-only kb?)
ImhLatinOnly // disable chinese input
*/
#if 0
const auto hints = static_cast< Qt::InputMethodHints >(
queryEvent.value( Qt::ImHints ).toInt() );
#endif
}
if ( queryEvent.queries() & Qt::ImPreferredLanguage )
{
const auto locale = queryEvent.value( Qt::ImPreferredLanguage ).toLocale();
const auto oldModel = compositionModel();
if( m_data->inputPanel )
qskSetLocale( m_data->inputPanel, locale );
auto newModel = compositionModel();
if( newModel && ( oldModel != newModel ) )
{
connect( newModel, &QskInputCompositionModel::candidatesChanged,
this, &QskInputContext::handleCandidatesChanged );
}
qskSetCandidatesEnabled( m_data->inputPanel, newModel != nullptr );
}
#if 0
if ( queryEvent.queries() & Qt::ImTextBeforeCursor
&& queryEvent.queries() & Qt::ImTextAfterCursor )
{
const auto text1 = queryEvent.value( Qt::ImTextBeforeCursor ).toString();
const auto text2 = queryEvent.value( Qt::ImTextAfterCursor ).toString();
qDebug() << text1 << text2;
}
#endif
/*
Qt::ImMicroFocus
Qt::ImCursorRectangle
Qt::ImFont
Qt::ImCursorPosition
Qt::ImSurroundingText // important for chinese input
Qt::ImCurrentSelection // important for prediction
Qt::ImMaximumTextLength // should be monitored
Qt::ImAnchorPosition
Qt::ImAbsolutePosition
Qt::ImTextBeforeCursor // important for chinese
Qt::ImTextAfterCursor // important for chinese
Qt::ImPlatformData // hard to say...
Qt::ImEnterKeyType
Qt::ImAnchorRectangle
Qt::ImInputItemClipRectangle // could be used for the geometry of the panel
*/
} }
QRectF QskInputContext::keyboardRect() const QRectF QskInputContext::keyboardRect() const
@ -417,6 +313,11 @@ void QskInputContext::showInputPanel()
connect( inputPanel->window(), &QskWindow::visibleChanged, connect( inputPanel->window(), &QskWindow::visibleChanged,
this, &QskInputContext::emitInputPanelVisibleChanged ); this, &QskInputContext::emitInputPanelVisibleChanged );
updateInputPanel( m_data->inputItem );
m_data->engine->setPredictor(
m_data->predictorTable.find( locale() ) );
} }
void QskInputContext::hideInputPanel() void QskInputContext::hideInputPanel()
@ -425,6 +326,8 @@ void QskInputContext::hideInputPanel()
{ {
// to get rid of the scene graph nodes // to get rid of the scene graph nodes
m_data->inputPanel->setVisible( false ); m_data->inputPanel->setVisible( false );
if ( auto panel = qobject_cast< QskInputPanel* >( m_data->inputPanel ) )
panel->setEngine( nullptr );
} }
if ( m_data->inputPopup == m_data->inputPanel ) if ( m_data->inputPopup == m_data->inputPanel )
@ -465,9 +368,19 @@ void QskInputContext::hideInputPanel()
qGuiApp->removeEventFilter( this ); qGuiApp->removeEventFilter( this );
m_data->preedit.clear(); updateInputPanel( nullptr );
if ( auto model = compositionModel() ) }
model->resetCandidates();
void QskInputContext::updateInputPanel( QQuickItem* inputItem )
{
auto panel = qobject_cast< QskInputPanel* >( m_data->inputPanel );
if ( panel == nullptr )
return;
panel->setLocale( locale() );
panel->attachInputItem( inputItem );
panel->setEngine( inputItem ? m_data->engine : nullptr );
} }
bool QskInputContext::isInputPanelVisible() const bool QskInputContext::isInputPanelVisible() const
@ -480,7 +393,15 @@ bool QskInputContext::isInputPanelVisible() const
QLocale QskInputContext::locale() const QLocale QskInputContext::locale() const
{ {
return qskLocale( m_data->inputPanel ); if ( m_data->inputItem )
{
QInputMethodQueryEvent event( Qt::ImPreferredLanguage );
QCoreApplication::sendEvent( m_data->inputItem, &event );
return event.value( Qt::ImPreferredLanguage ).toLocale();
}
return QLocale();
} }
Qt::LayoutDirection QskInputContext::inputDirection() const Qt::LayoutDirection QskInputContext::inputDirection() const
@ -535,235 +456,32 @@ void QskInputContext::setFocusObject( QObject* focusObject )
} }
} }
void QskInputContext::setCompositionModel( void QskInputContext::registerPredictor(
const QLocale& locale, QskInputCompositionModel* model ) const QLocale& locale, QskTextPredictor* predictor )
{ {
auto& models = m_data->compositionModels; auto oldPredictor = m_data->predictorTable.find( locale );
if ( predictor == oldPredictor )
const auto key = qskHashLocale( locale );
if ( model )
{
const auto it = models.find( key );
if ( it != models.end() )
{
if ( it.value() == model )
return;
delete it.value();
*it = model;
}
else
{
models.insert( key, model );
}
connect( model, &QskInputCompositionModel::candidatesChanged,
this, &QskInputContext::handleCandidatesChanged );
}
else
{
const auto it = models.find( key );
if ( it != models.end() )
{
delete it.value();
models.erase( it );
}
}
}
QskInputCompositionModel* QskInputContext::compositionModel() const
{
const auto key = qskHashLocale( locale() );
return m_data->compositionModels.value( key, nullptr );
}
void QskInputContext::invokeAction( QInputMethod::Action action, int value )
{
switch ( static_cast< int >( action ) )
{
case QskInputPanel::Compose:
{
processKey( value );
break;
}
case QskInputPanel::SelectCandidate:
{
if ( auto model = compositionModel() )
{
auto text = model->candidate( value );
if ( model->attributes() & QskInputCompositionModel::Words )
text += " ";
sendText( text, true );
m_data->preedit.clear();
model->resetCandidates();
}
break;
}
case QInputMethod::Click:
case QInputMethod::ContextMenu:
{
break;
}
}
}
int QskInputContext::keysLeft() const
{
const auto event = queryInputMethod(
Qt::ImSurroundingText | Qt::ImMaximumTextLength | Qt::ImHints );
const auto hints = static_cast< Qt::InputMethodHints >(
event.value( Qt::ImHints ).toInt() );
if ( !( hints & Qt::ImhMultiLine ) )
{
const int max = event.value( Qt::ImMaximumTextLength ).toInt();
if ( max > 0 )
{
const auto text = event.value( Qt::ImSurroundingText ).toString();
return max - text.length();
}
}
return -1; // unlimited
}
Qt::InputMethodHints QskInputContext::inputHints() const
{
const auto e = queryInputMethod( Qt::ImHints );
return static_cast< Qt::InputMethodHints >( e.value( Qt::ImHints ).toInt() );
}
void QskInputContext::processKey( int key )
{
const auto hints = inputHints();
auto spaceLeft = keysLeft();
QskInputCompositionModel* model = nullptr;
if ( !( hints & Qt::ImhHiddenText ) )
model = compositionModel();
auto& preedit = m_data->preedit;
/*
First we have to handle the control keys
*/
switch ( key )
{
case Qt::Key_Backspace:
case Qt::Key_Muhenkan:
{
if ( model )
{
if ( !preedit.isEmpty() )
{
preedit.chop( 1 );
sendText( preedit, false );
model->requestCandidates( preedit );
return;
}
}
sendKey( Qt::Key_Backspace );
return;
}
case Qt::Key_Return:
{
if ( model )
{
if ( !preedit.isEmpty() )
{
if ( spaceLeft )
sendText( preedit.left( spaceLeft ), true );
preedit.clear();
model->resetCandidates();
return;
}
}
if( !( hints & Qt::ImhMultiLine ) )
{
sendKey( Qt::Key_Return );
return;
}
break;
}
case Qt::Key_Space:
{
if ( model )
{
if ( !preedit.isEmpty() && spaceLeft)
{
preedit = preedit.left( spaceLeft );
sendText( preedit, true );
spaceLeft -= preedit.length();
preedit.clear();
model->resetCandidates();
}
}
break;
}
case Qt::Key_Left:
case Qt::Key_Right:
case Qt::Key_Escape:
{
sendKey( key );
return;
}
}
const QString text = qskKeyString( key );
if ( model )
{
preedit += text;
model->requestCandidates( preedit );
if ( model->candidateCount() > 0 )
{
sendText( preedit, false );
}
else
{
sendText( preedit.left( spaceLeft ), true );
preedit.clear();
}
}
else
{
sendText( text, true );
}
}
void QskInputContext::handleCandidatesChanged()
{
const auto model = compositionModel();
if ( model == nullptr || m_data->inputPanel == nullptr )
return; return;
const auto count = model->candidateCount(); if ( predictor )
predictor->setParent( this );
QVector< QString > candidates; m_data->predictorTable.replace( locale, predictor );
candidates.reserve( count );
for( int i = 0; i < count; i++ ) if ( oldPredictor )
candidates += model->candidate( i ); delete oldPredictor;
qskSetCandidates( m_data->inputPanel, candidates ); if ( qskHashLocale( locale ) == qskHashLocale( this->locale() ) )
m_data->engine->setPredictor( predictor );
}
QskTextPredictor* QskInputContext::registeredPredictor( const QLocale& locale )
{
return m_data->predictorTable.find( locale );
}
void QskInputContext::invokeAction( QInputMethod::Action, int )
{
} }
void QskInputContext::setInputPanel( QQuickItem* inputPanel ) void QskInputContext::setInputPanel( QQuickItem* inputPanel )
@ -771,8 +489,6 @@ void QskInputContext::setInputPanel( QQuickItem* inputPanel )
if ( m_data->inputPanel == inputPanel ) if ( m_data->inputPanel == inputPanel )
return; return;
auto model = compositionModel();
if ( m_data->inputPanel ) if ( m_data->inputPanel )
{ {
m_data->inputPanel->disconnect( this ); m_data->inputPanel->disconnect( this );
@ -785,9 +501,6 @@ void QskInputContext::setInputPanel( QQuickItem* inputPanel )
{ {
m_data->inputPanel->setParentItem( nullptr ); m_data->inputPanel->setParentItem( nullptr );
} }
if ( model )
model->disconnect( m_data->inputPanel );
} }
m_data->inputPanel = inputPanel; m_data->inputPanel = inputPanel;
@ -806,8 +519,6 @@ void QskInputContext::setInputPanel( QQuickItem* inputPanel )
connect( control, &QskControl::localeChanged, connect( control, &QskControl::localeChanged,
this, &QPlatformInputContext::emitLocaleChanged ); this, &QPlatformInputContext::emitLocaleChanged );
} }
qskSetCandidatesEnabled( inputPanel, model != nullptr );
} }
} }
@ -817,6 +528,7 @@ void QskInputContext::reset()
void QskInputContext::commit() void QskInputContext::commit()
{ {
// called on focus changes
} }
bool QskInputContext::eventFilter( QObject* object, QEvent* event ) bool QskInputContext::eventFilter( QObject* object, QEvent* event )
@ -874,54 +586,4 @@ bool QskInputContext::filterEvent( const QEvent* )
return false; return false;
} }
QInputMethodQueryEvent QskInputContext::queryInputMethod(
Qt::InputMethodQueries queries ) const
{
QInputMethodQueryEvent event( queries );
if ( m_data->inputItem )
QCoreApplication::sendEvent( m_data->inputItem, &event );
return event;
}
void QskInputContext::sendText(
const QString& text, bool isFinal ) const
{
if ( m_data->inputItem == nullptr )
return;
if ( isFinal )
{
QInputMethodEvent event;
event.setCommitString( text );
QCoreApplication::sendEvent( m_data->inputItem, &event );
}
else
{
QTextCharFormat format;
format.setFontUnderline( true );
const QInputMethodEvent::Attribute attribute(
QInputMethodEvent::TextFormat, 0, text.length(), format );
QInputMethodEvent event( text, { attribute } );
QCoreApplication::sendEvent( m_data->inputItem, &event );
}
}
void QskInputContext::sendKey( int key ) const
{
if ( m_data->inputItem == nullptr )
return;
QKeyEvent keyPress( QEvent::KeyPress, key, Qt::NoModifier );
QCoreApplication::sendEvent( m_data->inputItem, &keyPress );
QKeyEvent keyRelease( QEvent::KeyRelease, key, Qt::NoModifier );
QCoreApplication::sendEvent( m_data->inputItem, &keyRelease );
}
#include "moc_QskInputContext.cpp" #include "moc_QskInputContext.cpp"

View File

@ -6,18 +6,14 @@
#ifndef QSK_INPUT_CONTEXT_H #ifndef QSK_INPUT_CONTEXT_H
#define QSK_INPUT_CONTEXT_H #define QSK_INPUT_CONTEXT_H
#include "QskGlobal.h"
#include <qpa/qplatforminputcontext.h> #include <qpa/qplatforminputcontext.h>
#include <memory> #include <memory>
class QskInputCompositionModel; class QskTextPredictor;
class QQuickItem; class QQuickItem;
class QInputMethodQueryEvent; class QSK_EXPORT QskInputContext : public QPlatformInputContext
class QInputMethodEvent;
class QKeyEvent;
class QskInputContext : public QPlatformInputContext
{ {
Q_OBJECT Q_OBJECT
@ -48,31 +44,23 @@ public:
virtual QLocale locale() const override; virtual QLocale locale() const override;
virtual Qt::LayoutDirection inputDirection() const override; virtual Qt::LayoutDirection inputDirection() const override;
void setCompositionModel( const QLocale&, QskInputCompositionModel* ); void registerPredictor( const QLocale&, QskTextPredictor* );
QskTextPredictor* registeredPredictor( const QLocale& );
Q_INVOKABLE QQuickItem* inputItem(); Q_INVOKABLE QQuickItem* inputItem();
virtual bool filterEvent( const QEvent* ) override; virtual bool filterEvent( const QEvent* ) override;
QInputMethodQueryEvent queryInputMethod( Qt::InputMethodQueries ) const; protected:
virtual void updateInputPanel( QQuickItem* inputItem );
void sendKey( int key ) const;
void sendText( const QString& text, bool isFinal ) const;
Qt::InputMethodHints inputHints() const;
int keysLeft() const;
private Q_SLOTS: private Q_SLOTS:
void handleCandidatesChanged();
void setInputPanel( QQuickItem* ); void setInputPanel( QQuickItem* );
virtual bool eventFilter( QObject*, QEvent* ) override; virtual bool eventFilter( QObject*, QEvent* ) override;
private: private:
void processKey( int key );
void setInputItem( QQuickItem* ); void setInputItem( QQuickItem* );
QskInputCompositionModel* compositionModel() const;
class PrivateData; class PrivateData;
std::unique_ptr< PrivateData > m_data; std::unique_ptr< PrivateData > m_data;

View File

@ -0,0 +1,246 @@
/******************************************************************************
* QSkinny - Copyright (C) 2016 Uwe Rathmann
* This file may be used under the terms of the QSkinny License, Version 1.0
*****************************************************************************/
#include "QskInputEngine.h"
#include "QskTextPredictor.h"
#include <QPointer>
#include <QVector>
#include <QString>
static inline QString qskKeyString( int keyCode )
{
// Special case entry codes here, else default to the symbol
switch ( keyCode )
{
case Qt::Key_Shift:
case Qt::Key_CapsLock:
case Qt::Key_Mode_switch:
case Qt::Key_Backspace:
case Qt::Key_Muhenkan:
return QString();
case Qt::Key_Return:
case Qt::Key_Kanji:
return QChar( QChar::CarriageReturn );
case Qt::Key_Space:
return QChar( QChar::Space );
default:
break;
}
return QChar( keyCode );
}
class QskInputEngine::PrivateData
{
public:
QPointer< QskTextPredictor > predictor;
QString preedit;
};
QskInputEngine::QskInputEngine( QObject* parent ):
QObject( parent ),
m_data( new PrivateData() )
{
}
QskInputEngine::~QskInputEngine()
{
}
void QskInputEngine::setPredictor( QskTextPredictor* predictor )
{
if ( predictor == m_data->predictor )
return;
reset();
if ( m_data->predictor )
{
if ( m_data->predictor->parent() == this )
{
delete m_data->predictor;
}
else
{
m_data->predictor->disconnect( this );
m_data->predictor = nullptr;
}
}
if ( predictor )
{
if ( predictor->parent() == nullptr )
predictor->setParent( this );
connect( predictor, &QskTextPredictor::predictionChanged,
this, &QskInputEngine::predictionChanged );
}
m_data->predictor = predictor;
}
QskTextPredictor* QskInputEngine::predictor() const
{
return m_data->predictor;
}
QVector< QString > QskInputEngine::prediction() const
{
QVector< QString > candidates;
if ( const auto predictor = m_data->predictor )
{
const auto count = predictor->candidateCount();
candidates.reserve( count );
for( int i = 0; i < count; i++ )
candidates += predictor->candidate( i );
}
return candidates;
}
QskInputEngine::Result QskInputEngine::processKey( int key,
Qt::InputMethodHints inputHints, int spaceLeft )
{
QskInputEngine::Result result;
auto& preedit = m_data->preedit;
QskTextPredictor* predictor = nullptr;
if ( !( inputHints & Qt::ImhHiddenText ) )
predictor = m_data->predictor;
/*
First we have to handle the control keys
*/
switch ( key )
{
case Qt::Key_Backspace:
case Qt::Key_Muhenkan:
{
if ( predictor )
{
if ( !preedit.isEmpty() )
{
preedit.chop( 1 );
result.text = preedit;
result.isFinal = false;
predictor->request( preedit );
return result;
}
}
result.key = Qt::Key_Backspace;
return result;
}
case Qt::Key_Return:
{
if ( predictor )
{
if ( !preedit.isEmpty() )
{
if ( spaceLeft )
{
result.text = 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 ( !preedit.isEmpty() && spaceLeft)
{
preedit = preedit.left( spaceLeft );
result.text = 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 = qskKeyString( key );
if ( predictor )
{
preedit += text;
predictor->request( preedit );
if ( predictor->candidateCount() > 0 )
{
result.text = preedit;
result.isFinal = false;
}
else
{
result.text = preedit.left( spaceLeft );
result.isFinal = true;
preedit.clear();
}
}
else
{
result.text = text;
result.isFinal = true;
}
return result;
}
QString QskInputEngine::predictiveText( int index ) const
{
if ( m_data->predictor )
return m_data->predictor->candidate( index );
return QString();
}
void QskInputEngine::reset()
{
if ( m_data->predictor )
m_data->predictor->reset();
m_data->preedit.clear();
}
#include "moc_QskInputEngine.cpp"

View File

@ -0,0 +1,54 @@
/******************************************************************************
* QSkinny - Copyright (C) 2016 Uwe Rathmann
* This file may be used under the terms of the QSkinny License, Version 1.0
*****************************************************************************/
#ifndef QSK_INPUT_ENGINE_H
#define QSK_INPUT_ENGINE_H
#include "QskGlobal.h"
#include <QObject>
#include <memory>
class QskTextPredictor;
class QQuickItem;
class QSK_EXPORT QskInputEngine : public QObject
{
Q_OBJECT
using Inherited = QObject;
public:
class Result
{
public:
int key = 0;
QString text;
bool isFinal = true;
};
QskInputEngine( QObject* parent = nullptr );
virtual ~QskInputEngine();
void setPredictor( QskTextPredictor* );
QskTextPredictor* predictor() const;
virtual Result processKey( int key,
Qt::InputMethodHints, int spaceLeft = -1 );
QString predictiveText( int ) const;
QVector< QString > prediction() const;
void reset();
Q_SIGNALS:
void predictionChanged();
private:
class PrivateData;
std::unique_ptr< PrivateData > m_data;
};
#endif

View File

@ -4,8 +4,9 @@
*****************************************************************************/ *****************************************************************************/
#include "QskInputPanel.h" #include "QskInputPanel.h"
#include "QskInputEngine.h"
#include "QskVirtualKeyboard.h" #include "QskVirtualKeyboard.h"
#include "QskInputSuggestionBar.h" #include "QskInputPredictionBar.h"
#include "QskTextInput.h" #include "QskTextInput.h"
#include "QskTextLabel.h" #include "QskTextLabel.h"
#include "QskLinearBox.h" #include "QskLinearBox.h"
@ -13,92 +14,47 @@
#include <QString> #include <QString>
#include <QLocale> #include <QLocale>
#include <QGuiApplication> #include <QGuiApplication>
#include <QPointer>
#include <QInputMethodQueryEvent>
#include <QTextCharFormat>
QString qskNativeLocaleString( const QLocale& locale ) static inline void qskSendText( QQuickItem* inputItem,
const QString& text, bool isFinal )
{ {
switch( locale.language() ) if ( inputItem == nullptr )
return;
if ( isFinal )
{ {
case QLocale::Bulgarian: QInputMethodEvent event;
return QStringLiteral( "български език" ); event.setCommitString( text );
case QLocale::Czech: QCoreApplication::sendEvent( inputItem, &event );
return QStringLiteral( "Čeština" );
case QLocale::German:
return QStringLiteral( "Deutsch" );
case QLocale::Danish:
return QStringLiteral( "Dansk" );
case QLocale::Greek:
return QStringLiteral( "Eλληνικά" );
case QLocale::English:
{
switch( locale.country() )
{
case QLocale::Canada:
case QLocale::UnitedStates:
case QLocale::UnitedStatesMinorOutlyingIslands:
case QLocale::UnitedStatesVirginIslands:
return QStringLiteral( "English (US)" );
default:
return QStringLiteral( "English (UK)" );
}
}
case QLocale::Spanish:
return QStringLiteral( "Español" );
case QLocale::Finnish:
return QStringLiteral( "Suomi" );
case QLocale::French:
return QStringLiteral( "Français" );
case QLocale::Hungarian:
return QStringLiteral( "Magyar" );
case QLocale::Italian:
return QStringLiteral( "Italiano" );
case QLocale::Japanese:
return QStringLiteral( "日本語" );
case QLocale::Latvian:
return QStringLiteral( "Latviešu" );
case QLocale::Lithuanian:
return QStringLiteral( "Lietuvių" );
case QLocale::Dutch:
return QStringLiteral( "Nederlands" );
case QLocale::Portuguese:
return QStringLiteral( "Português" );
case QLocale::Romanian:
return QStringLiteral( "Română" );
case QLocale::Russia:
return QStringLiteral( "Русский" );
case QLocale::Slovenian:
return QStringLiteral( "Slovenščina" );
case QLocale::Slovak:
return QStringLiteral( "Slovenčina" );
case QLocale::Turkish:
return QStringLiteral( "Türkçe" );
case QLocale::Chinese:
return QStringLiteral( "中文" );
default:
return QLocale::languageToString( locale.language() );
} }
else
{
QTextCharFormat format;
format.setFontUnderline( true );
const QInputMethodEvent::Attribute attribute(
QInputMethodEvent::TextFormat, 0, text.length(), format );
QInputMethodEvent event( text, { attribute } );
QCoreApplication::sendEvent( inputItem, &event );
}
}
static inline void qskSendKey( QQuickItem* inputItem, int key )
{
if ( inputItem == nullptr )
return;
QKeyEvent keyPress( QEvent::KeyPress, key, Qt::NoModifier );
QCoreApplication::sendEvent( inputItem, &keyPress );
QKeyEvent keyRelease( QEvent::KeyRelease, key, Qt::NoModifier );
QCoreApplication::sendEvent( inputItem, &keyRelease );
} }
namespace namespace
@ -123,10 +79,13 @@ public:
{ {
} }
QPointer< QskInputEngine > engine;
QPointer< QQuickItem > inputItem;
QskLinearBox* layout; QskLinearBox* layout;
QskTextLabel* prompt; QskTextLabel* prompt;
TextInput* inputProxy; TextInput* inputProxy;
QskInputSuggestionBar* suggestionBar; QskInputPredictionBar* predictionBar;
QskVirtualKeyboard* keyboard; QskVirtualKeyboard* keyboard;
bool hasInputProxy : 1; bool hasInputProxy : 1;
@ -145,8 +104,8 @@ QskInputPanel::QskInputPanel( QQuickItem* parent ):
m_data->inputProxy = new TextInput(); m_data->inputProxy = new TextInput();
m_data->inputProxy->setVisible( m_data->hasInputProxy ); m_data->inputProxy->setVisible( m_data->hasInputProxy );
m_data->suggestionBar = new QskInputSuggestionBar(); m_data->predictionBar = new QskInputPredictionBar();
m_data->suggestionBar->setVisible( false ); m_data->predictionBar->setVisible( false );
m_data->keyboard = new QskVirtualKeyboard(); m_data->keyboard = new QskVirtualKeyboard();
@ -155,13 +114,13 @@ QskInputPanel::QskInputPanel( QQuickItem* parent ):
layout->addItem( m_data->prompt, Qt::AlignLeft | Qt::AlignHCenter ); layout->addItem( m_data->prompt, Qt::AlignLeft | Qt::AlignHCenter );
layout->addItem( m_data->inputProxy, Qt::AlignLeft | Qt::AlignHCenter ); layout->addItem( m_data->inputProxy, Qt::AlignLeft | Qt::AlignHCenter );
layout->addStretch( 10 ); layout->addStretch( 10 );
layout->addItem( m_data->suggestionBar ); layout->addItem( m_data->predictionBar );
layout->addItem( m_data->keyboard ); layout->addItem( m_data->keyboard );
m_data->layout = layout; m_data->layout = layout;
connect( m_data->suggestionBar, &QskInputSuggestionBar::suggested, connect( m_data->predictionBar, &QskInputPredictionBar::predictiveTextSelected,
this, &QskInputPanel::commitCandidate ); this, &QskInputPanel::commitPredictiveText );
connect( m_data->keyboard, &QskVirtualKeyboard::keySelected, connect( m_data->keyboard, &QskVirtualKeyboard::keySelected,
this, &QskInputPanel::commitKey ); this, &QskInputPanel::commitKey );
@ -171,6 +130,60 @@ QskInputPanel::~QskInputPanel()
{ {
} }
void QskInputPanel::setEngine( QskInputEngine* engine )
{
if ( engine == m_data->engine )
return;
if ( m_data->engine )
m_data->engine->disconnect( this );
m_data->engine = engine;
if ( engine )
{
connect( engine, &QskInputEngine::predictionChanged,
this, &QskInputPanel::updatePredictionBar );
}
m_data->predictionBar->setVisible( engine && engine->predictor() );
}
void QskInputPanel::attachInputItem( QQuickItem* item )
{
if ( item == m_data->inputItem )
return;
m_data->inputItem = item;
if ( m_data->engine )
m_data->engine->reset();
if ( item )
{
Qt::InputMethodQueries queries = Qt::ImQueryAll;
queries &= ~Qt::ImEnabled;
processInputMethodQueries( queries );
}
}
QQuickItem* QskInputPanel::attachedInputItem() const
{
return m_data->inputItem;
}
void QskInputPanel::updatePredictionBar()
{
m_data->predictionBar->setPrediction(
m_data->engine->prediction() );
}
QskInputEngine* QskInputPanel::engine()
{
return m_data->engine;
}
QskAspect::Subcontrol QskInputPanel::effectiveSubcontrol( QskAspect::Subcontrol QskInputPanel::effectiveSubcontrol(
QskAspect::Subcontrol subControl ) const QskAspect::Subcontrol subControl ) const
{ {
@ -180,73 +193,6 @@ QskAspect::Subcontrol QskInputPanel::effectiveSubcontrol(
return subControl; return subControl;
} }
qreal QskInputPanel::heightForWidth( qreal width ) const
{
/*
This code looks like as it could be generalized
and moved to QskLinearBox. TODO ...
*/
const auto margins = this->margins();
width -= margins.left() + margins.right();
const auto padding = innerPadding(
Panel, QSizeF( width, width ) );
width -= padding.left() + padding.right();
qreal height = m_data->keyboard->heightForWidth( width );
const QskControl* controls[] =
{ m_data->prompt, m_data->inputProxy, m_data->suggestionBar };
for ( auto control : controls )
{
if ( control->isVisible() )
{
height += m_data->layout->spacing();
height += control->sizeHint().height();
}
}
height += padding.top() + padding.bottom();
height += margins.top() + margins.bottom();
return height;
}
qreal QskInputPanel::widthForHeight( qreal height ) const
{
const auto margins = this->margins();
height -= margins.top() + margins.bottom();
const auto padding = innerPadding(
Panel, QSizeF( height, height ) );
height -= padding.top() + padding.bottom();
const QskControl* controls[] =
{ m_data->prompt, m_data->inputProxy, m_data->suggestionBar };
for ( auto control : controls )
{
if ( control->isVisible() )
{
height -= m_data->layout->spacing();
height -= control->sizeHint().height();
}
}
qreal width = m_data->keyboard->widthForHeight( height );
width += padding.left() + padding.right();
width += margins.left() + margins.right();
return width;
}
QString QskInputPanel::inputPrompt() const QString QskInputPanel::inputPrompt() const
{ {
return m_data->prompt->text(); return m_data->prompt->text();
@ -338,38 +284,145 @@ void QskInputPanel::updateInputProxy( const QQuickItem* inputItem )
} }
} }
bool QskInputPanel::isCandidatesEnabled() const void QskInputPanel::commitPredictiveText( int index )
{ {
return m_data->suggestionBar->isVisible(); m_data->predictionBar->setPrediction( QVector< QString >() );
}
QVector< QString > QskInputPanel::candidates() const if ( m_data->engine )
{ {
return m_data->suggestionBar->candidates(); const QString text = m_data->engine->predictiveText( index );
} m_data->engine->reset();
void QskInputPanel::setCandidatesEnabled( bool on ) qskSendText( m_data->inputItem, text, true );
{ }
m_data->suggestionBar->setVisible( on );
}
void QskInputPanel::setCandidates( const QVector< QString >& candidates )
{
m_data->suggestionBar->setCandidates( candidates );
}
void QskInputPanel::commitCandidate( int index )
{
m_data->suggestionBar->setCandidates( QVector< QString >() );
QGuiApplication::inputMethod()->invokeAction(
static_cast< QInputMethod::Action >( SelectCandidate ), index );
} }
void QskInputPanel::commitKey( int key ) void QskInputPanel::commitKey( int key )
{ {
QGuiApplication::inputMethod()->invokeAction( if ( m_data->engine == nullptr || m_data->inputItem == nullptr )
static_cast< QInputMethod::Action >( Compose ), key ); return;
auto engine = m_data->engine;
auto inputItem = m_data->inputItem;
QInputMethodQueryEvent event( Qt::ImHints );
QCoreApplication::sendEvent( inputItem, &event );
const auto inputHints = static_cast< Qt::InputMethodHints >(
event.value( Qt::ImHints ).toInt() );
int spaceLeft = -1;
if ( !( inputHints & Qt::ImhMultiLine ) )
{
QInputMethodQueryEvent event(
Qt::ImSurroundingText | Qt::ImMaximumTextLength );
QCoreApplication::sendEvent( inputItem, &event );
const int max = event.value( Qt::ImMaximumTextLength ).toInt();
if ( max > 0 )
{
const auto text = event.value( Qt::ImSurroundingText ).toString();
spaceLeft = max - text.length();
}
}
processKey( key, inputHints, spaceLeft );
}
void QskInputPanel::processKey( int key,
Qt::InputMethodHints inputHints, int spaceLeft )
{
const auto result = m_data->engine->processKey( key, inputHints, spaceLeft );
auto inputItem = m_data->inputItem;
if ( result.key )
{
// sending a control key
qskSendKey( inputItem, result.key );
}
else if ( !result.text.isEmpty() )
{
// changing the current text
qskSendText( inputItem, result.text, result.isFinal );
}
}
void QskInputPanel::processInputMethodQueries( Qt::InputMethodQueries queries )
{
if ( m_data->inputItem == nullptr )
return;
/*
adjust the input panel to information provided from the input item
*/
QInputMethodQueryEvent queryEvent( queries );
QCoreApplication::sendEvent( m_data->inputItem, &queryEvent );
if ( queryEvent.queries() & Qt::ImHints )
{
/*
ImhHiddenText = 0x1, // might need to disable certain checks
ImhSensitiveData = 0x2, // shouldn't change anything
ImhNoAutoUppercase = 0x4, // if we support auto uppercase, disable it
ImhPreferNumbers = 0x8, // default to number keyboard
ImhPreferUppercase = 0x10, // start with shift on
ImhPreferLowercase = 0x20, // start with shift off
ImhNoPredictiveText = 0x40, // not use predictive text
ImhDate = 0x80, // ignored for now (no date keyboard)
ImhTime = 0x100, // ignored for know (no time keyboard)
ImhPreferLatin = 0x200, // can be used to launch chinese kb in english mode
ImhMultiLine = 0x400, // not useful?
ImhDigitsOnly // default to number keyboard, disable other keys
ImhFormattedNumbersOnly // hard to say
ImhUppercaseOnly // caps-lock, disable shift
ImhLowercaseOnly // disable shift
ImhDialableCharactersOnly // dial pad (calculator?)
ImhEmailCharactersOnly // disable certain symbols (email-only kb?)
ImhUrlCharactersOnly // disable certain symbols (url-only kb?)
ImhLatinOnly // disable chinese input
*/
#if 0
const auto hints = static_cast< Qt::InputMethodHints >(
queryEvent.value( Qt::ImHints ).toInt() );
#endif
}
#if 0
if ( queryEvent.queries() & Qt::ImPreferredLanguage )
{
}
#endif
/*
Qt::ImMicroFocus
Qt::ImCursorRectangle
Qt::ImFont
Qt::ImCursorPosition
Qt::ImSurroundingText // important for chinese input
Qt::ImCurrentSelection // important for prediction
Qt::ImMaximumTextLength // should be monitored
Qt::ImAnchorPosition
Qt::ImAbsolutePosition
Qt::ImTextBeforeCursor // important for chinese
Qt::ImTextAfterCursor // important for chinese
Qt::ImPlatformData // hard to say...
Qt::ImEnterKeyType
Qt::ImAnchorRectangle
Qt::ImInputItemClipRectangle // could be used for the geometry of the panel
*/
} }
void QskInputPanel::keyPressEvent( QKeyEvent* event ) void QskInputPanel::keyPressEvent( QKeyEvent* event )
@ -401,4 +454,71 @@ void QskInputPanel::keyReleaseEvent( QKeyEvent* event )
return Inherited::keyReleaseEvent( event ); return Inherited::keyReleaseEvent( event );
} }
qreal QskInputPanel::heightForWidth( qreal width ) const
{
/*
This code looks like as it could be generalized
and moved to QskLinearBox. TODO ...
*/
const auto margins = this->margins();
width -= margins.left() + margins.right();
const auto padding = innerPadding(
Panel, QSizeF( width, width ) );
width -= padding.left() + padding.right();
qreal height = m_data->keyboard->heightForWidth( width );
const QskControl* controls[] =
{ m_data->prompt, m_data->inputProxy, m_data->predictionBar };
for ( auto control : controls )
{
if ( control->isVisible() )
{
height += m_data->layout->spacing();
height += control->sizeHint().height();
}
}
height += padding.top() + padding.bottom();
height += margins.top() + margins.bottom();
return height;
}
qreal QskInputPanel::widthForHeight( qreal height ) const
{
const auto margins = this->margins();
height -= margins.top() + margins.bottom();
const auto padding = innerPadding(
Panel, QSizeF( height, height ) );
height -= padding.top() + padding.bottom();
const QskControl* controls[] =
{ m_data->prompt, m_data->inputProxy, m_data->predictionBar };
for ( auto control : controls )
{
if ( control->isVisible() )
{
height -= m_data->layout->spacing();
height -= control->sizeHint().height();
}
}
qreal width = m_data->keyboard->widthForHeight( height );
width += padding.left() + padding.right();
width += margins.left() + margins.right();
return width;
}
#include "moc_QskInputPanel.cpp" #include "moc_QskInputPanel.cpp"

View File

@ -9,6 +9,8 @@
#include "QskGlobal.h" #include "QskGlobal.h"
#include "QskBox.h" #include "QskBox.h"
class QskInputEngine;
class QString; class QString;
class QLocale; class QLocale;
@ -26,26 +28,21 @@ class QSK_EXPORT QskInputPanel: public QskBox
Q_PROPERTY( QString inputPrompt READ inputPrompt Q_PROPERTY( QString inputPrompt READ inputPrompt
WRITE setInputPrompt NOTIFY inputPromptChanged ) WRITE setInputPrompt NOTIFY inputPromptChanged )
public: public:
QSK_SUBCONTROLS( Panel ) QSK_SUBCONTROLS( Panel )
enum Action
{
Compose = 0x10,
SelectCandidate = 0x11
};
Q_ENUM( Action )
QskInputPanel( QQuickItem* parent = nullptr ); QskInputPanel( QQuickItem* parent = nullptr );
virtual ~QskInputPanel() override; virtual ~QskInputPanel() override;
void attachInputItem( QQuickItem* );
QQuickItem* attachedInputItem() const;
void setEngine( QskInputEngine* );
QskInputEngine* engine();
bool hasInputProxy() const; bool hasInputProxy() const;
QString inputPrompt() const; QString inputPrompt() const;
bool isCandidatesEnabled() const;
QVector< QString > candidates() const;
virtual qreal heightForWidth( qreal width ) const override; virtual qreal heightForWidth( qreal width ) const override;
virtual qreal widthForHeight( qreal height ) const override; virtual qreal widthForHeight( qreal height ) const override;
@ -53,6 +50,7 @@ public:
QskAspect::Subcontrol ) const override; QskAspect::Subcontrol ) const override;
void updateInputProxy( const QQuickItem* ); void updateInputProxy( const QQuickItem* );
virtual void processInputMethodQueries( Qt::InputMethodQueries );
Q_SIGNALS: Q_SIGNALS:
void inputProxyChanged( bool ); void inputProxyChanged( bool );
@ -61,21 +59,21 @@ Q_SIGNALS:
public Q_SLOTS: public Q_SLOTS:
void setInputPrompt( const QString& ); void setInputPrompt( const QString& );
void setInputProxy( bool ); void setInputProxy( bool );
void setCandidatesEnabled( bool );
void setCandidates( const QVector< QString >& );
protected: protected:
virtual void keyPressEvent( QKeyEvent* ) override; virtual void keyPressEvent( QKeyEvent* ) override;
virtual void keyReleaseEvent( QKeyEvent* ) override; virtual void keyReleaseEvent( QKeyEvent* ) override;
virtual void processKey( int key,
Qt::InputMethodHints, int spaceLeft );
private: private:
void updatePredictionBar();
void commitKey( int key ); void commitKey( int key );
void commitCandidate( int ); void commitPredictiveText( int );
class PrivateData; class PrivateData;
std::unique_ptr< PrivateData > m_data; std::unique_ptr< PrivateData > m_data;
}; };
QSK_EXPORT QString qskNativeLocaleString( const QLocale& );
#endif #endif

View File

@ -3,7 +3,7 @@
* This file may be used under the terms of the QSkinny License, Version 1.0 * This file may be used under the terms of the QSkinny License, Version 1.0
*****************************************************************************/ *****************************************************************************/
#include "QskInputSuggestionBar.h" #include "QskInputPredictionBar.h"
#include "QskPushButton.h" #include "QskPushButton.h"
#include "QskLinearBox.h" #include "QskLinearBox.h"
#include "QskTextOptions.h" #include "QskTextOptions.h"
@ -11,9 +11,9 @@
#include <QFontMetricsF> #include <QFontMetricsF>
#include <QVector> #include <QVector>
QSK_SUBCONTROL( QskInputSuggestionBar, Panel ) QSK_SUBCONTROL( QskInputPredictionBar, Panel )
QSK_SUBCONTROL( QskInputSuggestionBar, ButtonPanel ) QSK_SUBCONTROL( QskInputPredictionBar, ButtonPanel )
QSK_SUBCONTROL( QskInputSuggestionBar, ButtonText ) QSK_SUBCONTROL( QskInputPredictionBar, ButtonText )
namespace namespace
{ {
@ -46,27 +46,27 @@ namespace
QskAspect::Subcontrol subControl ) const override final QskAspect::Subcontrol subControl ) const override final
{ {
if( subControl == QskPushButton::Panel ) if( subControl == QskPushButton::Panel )
return QskInputSuggestionBar::ButtonPanel; return QskInputPredictionBar::ButtonPanel;
if( subControl == QskPushButton::Text ) if( subControl == QskPushButton::Text )
return QskInputSuggestionBar::ButtonText; return QskInputPredictionBar::ButtonText;
return subControl; return subControl;
} }
}; };
} }
class QskInputSuggestionBar::PrivateData class QskInputPredictionBar::PrivateData
{ {
public: public:
QskLinearBox* layoutBox; QskLinearBox* layoutBox;
QVector< QString > candidates; QVector< QString > candidates;
int candidateOffset = 0; int scrollOffset = 0;
const int buttonCount = 12; const int buttonCount = 12;
}; };
QskInputSuggestionBar::QskInputSuggestionBar( QQuickItem* parent ): QskInputPredictionBar::QskInputPredictionBar( QQuickItem* parent ):
Inherited( parent ), Inherited( parent ),
m_data( new PrivateData ) m_data( new PrivateData )
{ {
@ -82,7 +82,7 @@ QskInputSuggestionBar::QskInputSuggestionBar( QQuickItem* parent ):
button->setSizePolicy( Qt::Horizontal, QskSizePolicy::Maximum ); button->setSizePolicy( Qt::Horizontal, QskSizePolicy::Maximum );
connect( button, &QskPushButton::clicked, connect( button, &QskPushButton::clicked,
this, &QskInputSuggestionBar::candidateClicked ); this, &QskInputPredictionBar::buttonClicked );
if ( i == 0 ) if ( i == 0 )
{ {
@ -90,44 +90,43 @@ QskInputSuggestionBar::QskInputSuggestionBar( QQuickItem* parent ):
m_data->layoutBox->setRetainSizeWhenHidden( button, true ); m_data->layoutBox->setRetainSizeWhenHidden( button, true );
} }
} }
} }
QskInputSuggestionBar::~QskInputSuggestionBar() QskInputPredictionBar::~QskInputPredictionBar()
{ {
} }
QskAspect::Subcontrol QskInputSuggestionBar::effectiveSubcontrol( QskAspect::Subcontrol QskInputPredictionBar::effectiveSubcontrol(
QskAspect::Subcontrol subControl ) const QskAspect::Subcontrol subControl ) const
{ {
if( subControl == QskBox::Panel ) if( subControl == QskBox::Panel )
return QskInputSuggestionBar::Panel; return QskInputPredictionBar::Panel;
return subControl; return subControl;
} }
void QskInputSuggestionBar::setCandidates( const QVector< QString >& candidates ) void QskInputPredictionBar::setPrediction( const QVector< QString >& candidates )
{ {
if( m_data->candidates != candidates ) if( m_data->candidates != candidates )
{ {
m_data->candidates = candidates; m_data->candidates = candidates;
setCandidateOffset( 0 ); setScrollOffset( 0 );
} }
} }
QVector< QString > QskInputSuggestionBar::candidates() const QVector< QString > QskInputPredictionBar::candidates() const
{ {
return m_data->candidates; return m_data->candidates;
} }
void QskInputSuggestionBar::setCandidateOffset( int offset ) void QskInputPredictionBar::setScrollOffset( int offset )
{ {
m_data->candidateOffset = offset; m_data->scrollOffset = offset;
const auto candidateCount = m_data->candidates.length(); const auto candidateCount = m_data->candidates.length();
const auto count = std::min( candidateCount, m_data->buttonCount ); const auto count = std::min( candidateCount, m_data->buttonCount );
const bool continueLeft = m_data->candidateOffset > 0; const bool continueLeft = m_data->scrollOffset > 0;
const bool continueRight = ( candidateCount - m_data->candidateOffset ) > count; const bool continueRight = ( candidateCount - m_data->scrollOffset ) > count;
for( int i = 0; i < count; i++ ) for( int i = 0; i < count; i++ )
{ {
@ -144,7 +143,7 @@ void QskInputSuggestionBar::setCandidateOffset( int offset )
} }
else else
{ {
const int index = i + m_data->candidateOffset; const int index = i + m_data->scrollOffset;
button->setText( m_data->candidates[index] ); button->setText( m_data->candidates[index] );
} }
@ -155,18 +154,18 @@ void QskInputSuggestionBar::setCandidateOffset( int offset )
m_data->layoutBox->itemAtIndex( i )->setVisible( false ); m_data->layoutBox->itemAtIndex( i )->setVisible( false );
} }
void QskInputSuggestionBar::candidateClicked() void QskInputPredictionBar::buttonClicked()
{ {
const int index = m_data->layoutBox->indexOf( const int index = m_data->layoutBox->indexOf(
qobject_cast< QQuickItem* > ( sender() ) ); qobject_cast< QQuickItem* > ( sender() ) );
const int offset = m_data->candidateOffset; const int offset = m_data->scrollOffset;
if ( index == 0 ) if ( index == 0 )
{ {
if ( offset > 0 ) if ( offset > 0 )
{ {
setCandidateOffset( offset - 1 ); setScrollOffset( offset - 1 );
return; return;
} }
} }
@ -174,12 +173,12 @@ void QskInputSuggestionBar::candidateClicked()
{ {
if ( m_data->candidates.count() - offset > m_data->buttonCount ) if ( m_data->candidates.count() - offset > m_data->buttonCount )
{ {
setCandidateOffset( offset + 1 ); setScrollOffset( offset + 1 );
return; return;
} }
} }
Q_EMIT suggested( offset + index ); Q_EMIT predictiveTextSelected( offset + index );
} }
#include "moc_QskInputSuggestionBar.cpp" #include "moc_QskInputPredictionBar.cpp"

View File

@ -3,12 +3,12 @@
* This file may be used under the terms of the QSkinny License, Version 1.0 * This file may be used under the terms of the QSkinny License, Version 1.0
*****************************************************************************/ *****************************************************************************/
#ifndef QSK_INPUT_SUGGESTION_BAR_H #ifndef QSK_INPUT_PREDICTION_BAR_H
#define QSK_INPUT_SUGGESTION_BAR_H #define QSK_INPUT_PREDICTION_BAR_H
#include "QskBox.h" #include "QskBox.h"
class QSK_EXPORT QskInputSuggestionBar : public QskBox class QSK_EXPORT QskInputPredictionBar : public QskBox
{ {
Q_OBJECT Q_OBJECT
@ -17,8 +17,8 @@ class QSK_EXPORT QskInputSuggestionBar : public QskBox
public: public:
QSK_SUBCONTROLS( Panel, ButtonPanel, ButtonText ) QSK_SUBCONTROLS( Panel, ButtonPanel, ButtonText )
QskInputSuggestionBar( QQuickItem* parent = nullptr ); QskInputPredictionBar( QQuickItem* parent = nullptr );
virtual ~QskInputSuggestionBar(); virtual ~QskInputPredictionBar();
virtual QskAspect::Subcontrol effectiveSubcontrol( virtual QskAspect::Subcontrol effectiveSubcontrol(
QskAspect::Subcontrol subControl ) const override; QskAspect::Subcontrol subControl ) const override;
@ -26,14 +26,14 @@ public:
QVector< QString > candidates() const; QVector< QString > candidates() const;
Q_SIGNALS: Q_SIGNALS:
void suggested( int ); void predictiveTextSelected( int );
public Q_SLOTS: public Q_SLOTS:
void setCandidates( const QVector< QString >& ); void setPrediction( const QVector< QString >& );
private: private:
void candidateClicked(); void buttonClicked();
void setCandidateOffset( int ); void setScrollOffset( int );
class PrivateData; class PrivateData;
std::unique_ptr< PrivateData > m_data; std::unique_ptr< PrivateData > m_data;

View File

@ -3,22 +3,22 @@
* This file may be used under the terms of the QSkinny License, Version 1.0 * This file may be used under the terms of the QSkinny License, Version 1.0
*****************************************************************************/ *****************************************************************************/
#include "QskInputCompositionModel.h" #include "QskTextPredictor.h"
QskInputCompositionModel::QskInputCompositionModel( QskTextPredictor::QskTextPredictor(
Attributes attributes, QObject* parent ): Attributes attributes, QObject* parent ):
QObject( parent ), QObject( parent ),
m_attributes( attributes ) m_attributes( attributes )
{ {
} }
QskInputCompositionModel::~QskInputCompositionModel() QskTextPredictor::~QskTextPredictor()
{ {
} }
QskInputCompositionModel::Attributes QskInputCompositionModel::attributes() const QskTextPredictor::Attributes QskTextPredictor::attributes() const
{ {
return m_attributes; return m_attributes;
} }
#include "moc_QskInputCompositionModel.cpp" #include "moc_QskTextPredictor.cpp"

View File

@ -3,12 +3,15 @@
* This file may be used under the terms of the QSkinny License, Version 1.0 * This file may be used under the terms of the QSkinny License, Version 1.0
*****************************************************************************/ *****************************************************************************/
#ifndef QSK_INPUT_COMPOSITION_MODEL_H #ifndef QSK_TEXT_PREDICTOR_H
#define QSK_INPUT_COMPOSITION_MODEL_H #define QSK_TEXT_PREDICTOR_H
#include <QskGlobal.h>
#include <QObject> #include <QObject>
class QskInputCompositionModel : public QObject // abstract base class for input methods for retrieving predictive text
class QSK_EXPORT QskTextPredictor : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -21,10 +24,10 @@ public:
Q_ENUM( Attribute ) Q_ENUM( Attribute )
Q_DECLARE_FLAGS( Attributes, Attribute ) Q_DECLARE_FLAGS( Attributes, Attribute )
virtual ~QskInputCompositionModel(); virtual ~QskTextPredictor();
virtual void requestCandidates( const QString& preedit ) = 0; virtual void request( const QString& text ) = 0;
virtual void resetCandidates() = 0; virtual void reset() = 0;
virtual int candidateCount() const = 0; virtual int candidateCount() const = 0;
virtual QString candidate( int ) const = 0; virtual QString candidate( int ) const = 0;
@ -32,10 +35,10 @@ public:
Attributes attributes() const; Attributes attributes() const;
Q_SIGNALS: Q_SIGNALS:
void candidatesChanged(); void predictionChanged();
protected: protected:
QskInputCompositionModel( Attributes, QObject* ); QskTextPredictor( Attributes, QObject* );
private: private:
const Attributes m_attributes; const Attributes m_attributes;

View File

@ -298,15 +298,17 @@ SOURCES += \
dialogs/QskSelectionWindow.cpp dialogs/QskSelectionWindow.cpp
SOURCES += \ SOURCES += \
inputpanel/QskInputCompositionModel.cpp \ inputpanel/QskTextPredictor.cpp \
inputpanel/QskInputContext.cpp \ inputpanel/QskInputContext.cpp \
inputpanel/QskInputEngine.cpp \
inputpanel/QskInputPanel.cpp \ inputpanel/QskInputPanel.cpp \
inputpanel/QskInputSuggestionBar.cpp \ inputpanel/QskInputPredictionBar.cpp \
inputpanel/QskVirtualKeyboard.cpp inputpanel/QskVirtualKeyboard.cpp
HEADERS += \ HEADERS += \
inputpanel/QskInputCompositionModel.h \ inputpanel/QskTextPredictor.h \
inputpanel/QskInputContext.h \ inputpanel/QskInputContext.h \
inputpanel/QskInputEngine.h \
inputpanel/QskInputPanel.h \ inputpanel/QskInputPanel.h \
inputpanel/QskInputSuggestionBar.h \ inputpanel/QskInputPredictionBar.h \
inputpanel/QskVirtualKeyboard.h inputpanel/QskVirtualKeyboard.h