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 "hunspell.h"
class QskHunspellCompositionModel::PrivateData
class QskHunspellTextPredictor::PrivateData
{
public:
Hunhandle* hunspellHandle;
QVector< QString > candidates;
};
QskHunspellCompositionModel::QskHunspellCompositionModel( QObject* object ):
QskHunspellTextPredictor::QskHunspellTextPredictor( QObject* object ):
Inherited( Words, object ),
m_data( new PrivateData() )
{
@ -23,31 +23,31 @@ QskHunspellCompositionModel::QskHunspellCompositionModel( QObject* object ):
#endif
}
QskHunspellCompositionModel::~QskHunspellCompositionModel()
QskHunspellTextPredictor::~QskHunspellTextPredictor()
{
Hunspell_destroy( m_data->hunspellHandle );
}
int QskHunspellCompositionModel::candidateCount() const
int QskHunspellTextPredictor::candidateCount() const
{
return m_data->candidates.count();
}
QString QskHunspellCompositionModel::candidate( int pos ) const
QString QskHunspellTextPredictor::candidate( int pos ) const
{
return m_data->candidates[ pos ];
}
void QskHunspellCompositionModel::resetCandidates()
void QskHunspellTextPredictor::reset()
{
if ( !m_data->candidates.isEmpty() )
{
m_data->candidates.clear();
Q_EMIT candidatesChanged();
Q_EMIT predictionChanged();
}
}
void QskHunspellCompositionModel::requestCandidates( const QString& text )
void QskHunspellTextPredictor::request( const QString& text )
{
char** suggestions;
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 );
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
*****************************************************************************/
#ifndef QSK_HUNSPELL_COMPOSITION_MODEL_H
#define QSK_HUNSPELL_COMPOSITION_MODEL_H
#ifndef QSK_HUNSPELL_TEXT_PREDICTOR_H
#define QSK_HUNSPELL_TEXT_PREDICTOR_H
#include "QskInputCompositionModel.h"
#include "QskTextPredictor.h"
#include <memory>
class QskHunspellCompositionModel : public QskInputCompositionModel
class QSK_EXPORT QskHunspellTextPredictor : public QskTextPredictor
{
using Inherited = QskInputCompositionModel;
using Inherited = QskTextPredictor;
public:
QskHunspellCompositionModel( QObject* );
virtual ~QskHunspellCompositionModel() override;
QskHunspellTextPredictor( QObject* );
virtual ~QskHunspellTextPredictor() override;
virtual int candidateCount() const override;
virtual QString candidate( int pos ) const override;
protected:
virtual void requestCandidates( const QString& ) override;
virtual void resetCandidates() override;
virtual void request( const QString& ) override;
virtual void reset() override;
private:
class PrivateData;

View File

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

View File

@ -3,7 +3,7 @@
* 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 "pinyinime.h"
@ -11,13 +11,13 @@
#include <QStringList>
#include <QDebug>
class QskPinyinCompositionModel::PrivateData
class QskPinyinTextPredictor::PrivateData
{
public:
QStringList candidates;
};
QskPinyinCompositionModel::QskPinyinCompositionModel( QObject* parent ):
QskPinyinTextPredictor::QskPinyinTextPredictor( QObject* parent ):
Inherited( Attributes(), parent ),
m_data( new PrivateData )
{
@ -34,17 +34,17 @@ QskPinyinCompositionModel::QskPinyinCompositionModel( QObject* parent ):
}
}
QskPinyinCompositionModel::~QskPinyinCompositionModel()
QskPinyinTextPredictor::~QskPinyinTextPredictor()
{
ime_pinyin::im_close_decoder();
}
int QskPinyinCompositionModel::candidateCount() const
int QskPinyinTextPredictor::candidateCount() const
{
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() ) )
return m_data->candidates[ index ];
@ -52,18 +52,18 @@ QString QskPinyinCompositionModel::candidate( int index ) const
return QString();
}
void QskPinyinCompositionModel::resetCandidates()
void QskPinyinTextPredictor::reset()
{
ime_pinyin::im_reset_search();
if ( !m_data->candidates.isEmpty() )
{
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();
@ -101,5 +101,5 @@ void QskPinyinCompositionModel::requestCandidates( const QString& text )
}
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
*****************************************************************************/
#ifndef QSK_PINYIN_COMPOSITION_MODEL_H
#define QSK_PINYIN_COMPOSITION_MODEL_H
#ifndef QSK_PINYIN_TEXT_PREDICTOR_H
#define QSK_PINYIN_TEXT_PREDICTOR_H
#include "QskInputCompositionModel.h"
#include "QskTextPredictor.h"
#include <memory>
class QskPinyinCompositionModel : public QskInputCompositionModel
class QSK_EXPORT QskPinyinTextPredictor : public QskTextPredictor
{
using Inherited = QskInputCompositionModel;
using Inherited = QskTextPredictor;
public:
QskPinyinCompositionModel( QObject* );
virtual ~QskPinyinCompositionModel() override;
QskPinyinTextPredictor( QObject* );
virtual ~QskPinyinTextPredictor() override;
virtual int candidateCount() const override;
virtual QString candidate( int ) const override;
protected:
virtual void requestCandidates( const QString& ) override;
virtual void resetCandidates() override;
virtual void request( const QString& ) override;
virtual void reset() override;
private:
class PrivateData;

View File

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

View File

@ -26,6 +26,93 @@
#define STRINGIFY(x) #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
{
public:
@ -149,7 +236,7 @@ public:
private:
inline void append( const QLocale& locale )
{
m_values += qMakePair( qskNativeLocaleString( locale ), locale );
m_values += qMakePair( nativeLocaleString( locale ), locale );
}
QVector< QPair< QString, QLocale > > m_values;

View File

@ -4,113 +4,73 @@
*****************************************************************************/
#include "QskInputContext.h"
#include "QskInputCompositionModel.h"
#include "QskTextPredictor.h"
#include "QskInputPanel.h"
#include "QskInputEngine.h"
#include "QskLinearBox.h"
#include <QskDialog.h>
#include <QskPopup.h>
#include <QskWindow.h>
#include <QskControl.h>
#include <QskSetup.h>
#include <QskEvent.h>
#include <QTextCharFormat>
#include <QHash>
#include <QPointer>
#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 )
{
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
{
public:
@ -124,9 +84,9 @@ public:
QskPopup* inputPopup = nullptr;
QskWindow* inputWindow = nullptr;
QHash< uint, QskInputCompositionModel* > compositionModels;
PredictorTable predictorTable;
QString preedit;
QskInputEngine* engine = nullptr;
// the input panel is embedded in a window
bool ownsInputPanelWindow : 1;
@ -137,6 +97,8 @@ QskInputContext::QskInputContext():
{
setObjectName( "InputContext" );
m_data->engine = new QskInputEngine( this );
connect( qskSetup, &QskSetup::inputPanelChanged,
this, &QskInputContext::setInputPanel );
@ -168,114 +130,48 @@ void QskInputContext::setInputItem( QQuickItem* item )
if ( m_data->inputItem == item )
return;
m_data->inputItem = item;
auto panel = qobject_cast< QskInputPanel* >( m_data->inputPanel );
if ( item )
update( Qt::ImQueryAll );
if ( isInputPanelVisible() )
{
if ( item == nullptr )
{
hideInputPanel();
}
else
{
if ( panel )
panel->attachInputItem( item );
update( Qt::ImQueryAll );
}
}
else
hideInputPanel();
{
// no need for updates
if ( panel )
panel->attachInputItem( nullptr );
}
m_data->inputItem = item;
}
void QskInputContext::update( Qt::InputMethodQueries queries )
{
if ( m_data->inputItem == nullptr || m_data->inputPanel == nullptr )
return;
const auto queryEvent = queryInputMethod( queries );
if ( queryEvent.queries() & Qt::ImEnabled )
if ( 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();
return;
}
}
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 ( 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
*/
if ( auto panel = qobject_cast< QskInputPanel* >( m_data->inputPanel ) )
panel->processInputMethodQueries( queries );
}
QRectF QskInputContext::keyboardRect() const
@ -417,6 +313,11 @@ void QskInputContext::showInputPanel()
connect( inputPanel->window(), &QskWindow::visibleChanged,
this, &QskInputContext::emitInputPanelVisibleChanged );
updateInputPanel( m_data->inputItem );
m_data->engine->setPredictor(
m_data->predictorTable.find( locale() ) );
}
void QskInputContext::hideInputPanel()
@ -425,6 +326,8 @@ void QskInputContext::hideInputPanel()
{
// to get rid of the scene graph nodes
m_data->inputPanel->setVisible( false );
if ( auto panel = qobject_cast< QskInputPanel* >( m_data->inputPanel ) )
panel->setEngine( nullptr );
}
if ( m_data->inputPopup == m_data->inputPanel )
@ -465,9 +368,19 @@ void QskInputContext::hideInputPanel()
qGuiApp->removeEventFilter( this );
m_data->preedit.clear();
if ( auto model = compositionModel() )
model->resetCandidates();
updateInputPanel( nullptr );
}
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
@ -480,7 +393,15 @@ bool QskInputContext::isInputPanelVisible() 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
@ -535,235 +456,32 @@ void QskInputContext::setFocusObject( QObject* focusObject )
}
}
void QskInputContext::setCompositionModel(
const QLocale& locale, QskInputCompositionModel* model )
void QskInputContext::registerPredictor(
const QLocale& locale, QskTextPredictor* predictor )
{
auto& models = m_data->compositionModels;
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 )
auto oldPredictor = m_data->predictorTable.find( locale );
if ( predictor == oldPredictor )
return;
const auto count = model->candidateCount();
if ( predictor )
predictor->setParent( this );
QVector< QString > candidates;
candidates.reserve( count );
m_data->predictorTable.replace( locale, predictor );
for( int i = 0; i < count; i++ )
candidates += model->candidate( i );
if ( oldPredictor )
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 )
@ -771,8 +489,6 @@ void QskInputContext::setInputPanel( QQuickItem* inputPanel )
if ( m_data->inputPanel == inputPanel )
return;
auto model = compositionModel();
if ( m_data->inputPanel )
{
m_data->inputPanel->disconnect( this );
@ -785,9 +501,6 @@ void QskInputContext::setInputPanel( QQuickItem* inputPanel )
{
m_data->inputPanel->setParentItem( nullptr );
}
if ( model )
model->disconnect( m_data->inputPanel );
}
m_data->inputPanel = inputPanel;
@ -806,8 +519,6 @@ void QskInputContext::setInputPanel( QQuickItem* inputPanel )
connect( control, &QskControl::localeChanged,
this, &QPlatformInputContext::emitLocaleChanged );
}
qskSetCandidatesEnabled( inputPanel, model != nullptr );
}
}
@ -817,6 +528,7 @@ void QskInputContext::reset()
void QskInputContext::commit()
{
// called on focus changes
}
bool QskInputContext::eventFilter( QObject* object, QEvent* event )
@ -874,54 +586,4 @@ bool QskInputContext::filterEvent( const QEvent* )
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"

View File

@ -6,18 +6,14 @@
#ifndef QSK_INPUT_CONTEXT_H
#define QSK_INPUT_CONTEXT_H
#include "QskGlobal.h"
#include <qpa/qplatforminputcontext.h>
#include <memory>
class QskInputCompositionModel;
class QskTextPredictor;
class QQuickItem;
class QInputMethodQueryEvent;
class QInputMethodEvent;
class QKeyEvent;
class QskInputContext : public QPlatformInputContext
class QSK_EXPORT QskInputContext : public QPlatformInputContext
{
Q_OBJECT
@ -48,31 +44,23 @@ public:
virtual QLocale locale() 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();
virtual bool filterEvent( const QEvent* ) override;
QInputMethodQueryEvent queryInputMethod( Qt::InputMethodQueries ) const;
void sendKey( int key ) const;
void sendText( const QString& text, bool isFinal ) const;
Qt::InputMethodHints inputHints() const;
int keysLeft() const;
protected:
virtual void updateInputPanel( QQuickItem* inputItem );
private Q_SLOTS:
void handleCandidatesChanged();
void setInputPanel( QQuickItem* );
virtual bool eventFilter( QObject*, QEvent* ) override;
private:
void processKey( int key );
void setInputItem( QQuickItem* );
QskInputCompositionModel* compositionModel() const;
class PrivateData;
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 "QskInputEngine.h"
#include "QskVirtualKeyboard.h"
#include "QskInputSuggestionBar.h"
#include "QskInputPredictionBar.h"
#include "QskTextInput.h"
#include "QskTextLabel.h"
#include "QskLinearBox.h"
@ -13,92 +14,47 @@
#include <QString>
#include <QLocale>
#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:
return QStringLiteral( "български език" );
QInputMethodEvent event;
event.setCommitString( text );
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() );
QCoreApplication::sendEvent( inputItem, &event );
}
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
@ -123,10 +79,13 @@ public:
{
}
QPointer< QskInputEngine > engine;
QPointer< QQuickItem > inputItem;
QskLinearBox* layout;
QskTextLabel* prompt;
TextInput* inputProxy;
QskInputSuggestionBar* suggestionBar;
QskInputPredictionBar* predictionBar;
QskVirtualKeyboard* keyboard;
bool hasInputProxy : 1;
@ -145,8 +104,8 @@ QskInputPanel::QskInputPanel( QQuickItem* parent ):
m_data->inputProxy = new TextInput();
m_data->inputProxy->setVisible( m_data->hasInputProxy );
m_data->suggestionBar = new QskInputSuggestionBar();
m_data->suggestionBar->setVisible( false );
m_data->predictionBar = new QskInputPredictionBar();
m_data->predictionBar->setVisible( false );
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->inputProxy, Qt::AlignLeft | Qt::AlignHCenter );
layout->addStretch( 10 );
layout->addItem( m_data->suggestionBar );
layout->addItem( m_data->predictionBar );
layout->addItem( m_data->keyboard );
m_data->layout = layout;
connect( m_data->suggestionBar, &QskInputSuggestionBar::suggested,
this, &QskInputPanel::commitCandidate );
connect( m_data->predictionBar, &QskInputPredictionBar::predictiveTextSelected,
this, &QskInputPanel::commitPredictiveText );
connect( m_data->keyboard, &QskVirtualKeyboard::keySelected,
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 subControl ) const
{
@ -180,73 +193,6 @@ QskAspect::Subcontrol QskInputPanel::effectiveSubcontrol(
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
{
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
{
return m_data->suggestionBar->candidates();
}
if ( m_data->engine )
{
const QString text = m_data->engine->predictiveText( index );
m_data->engine->reset();
void QskInputPanel::setCandidatesEnabled( bool on )
{
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 );
qskSendText( m_data->inputItem, text, true );
}
}
void QskInputPanel::commitKey( int key )
{
QGuiApplication::inputMethod()->invokeAction(
static_cast< QInputMethod::Action >( Compose ), key );
if ( m_data->engine == nullptr || m_data->inputItem == nullptr )
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 )
@ -401,4 +454,71 @@ void QskInputPanel::keyReleaseEvent( QKeyEvent* 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"

View File

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

View File

@ -3,7 +3,7 @@
* 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 "QskLinearBox.h"
#include "QskTextOptions.h"
@ -11,9 +11,9 @@
#include <QFontMetricsF>
#include <QVector>
QSK_SUBCONTROL( QskInputSuggestionBar, Panel )
QSK_SUBCONTROL( QskInputSuggestionBar, ButtonPanel )
QSK_SUBCONTROL( QskInputSuggestionBar, ButtonText )
QSK_SUBCONTROL( QskInputPredictionBar, Panel )
QSK_SUBCONTROL( QskInputPredictionBar, ButtonPanel )
QSK_SUBCONTROL( QskInputPredictionBar, ButtonText )
namespace
{
@ -46,27 +46,27 @@ namespace
QskAspect::Subcontrol subControl ) const override final
{
if( subControl == QskPushButton::Panel )
return QskInputSuggestionBar::ButtonPanel;
return QskInputPredictionBar::ButtonPanel;
if( subControl == QskPushButton::Text )
return QskInputSuggestionBar::ButtonText;
return QskInputPredictionBar::ButtonText;
return subControl;
}
};
}
class QskInputSuggestionBar::PrivateData
class QskInputPredictionBar::PrivateData
{
public:
QskLinearBox* layoutBox;
QVector< QString > candidates;
int candidateOffset = 0;
int scrollOffset = 0;
const int buttonCount = 12;
};
QskInputSuggestionBar::QskInputSuggestionBar( QQuickItem* parent ):
QskInputPredictionBar::QskInputPredictionBar( QQuickItem* parent ):
Inherited( parent ),
m_data( new PrivateData )
{
@ -82,7 +82,7 @@ QskInputSuggestionBar::QskInputSuggestionBar( QQuickItem* parent ):
button->setSizePolicy( Qt::Horizontal, QskSizePolicy::Maximum );
connect( button, &QskPushButton::clicked,
this, &QskInputSuggestionBar::candidateClicked );
this, &QskInputPredictionBar::buttonClicked );
if ( i == 0 )
{
@ -90,44 +90,43 @@ QskInputSuggestionBar::QskInputSuggestionBar( QQuickItem* parent ):
m_data->layoutBox->setRetainSizeWhenHidden( button, true );
}
}
}
QskInputSuggestionBar::~QskInputSuggestionBar()
QskInputPredictionBar::~QskInputPredictionBar()
{
}
QskAspect::Subcontrol QskInputSuggestionBar::effectiveSubcontrol(
QskAspect::Subcontrol QskInputPredictionBar::effectiveSubcontrol(
QskAspect::Subcontrol subControl ) const
{
if( subControl == QskBox::Panel )
return QskInputSuggestionBar::Panel;
return QskInputPredictionBar::Panel;
return subControl;
}
void QskInputSuggestionBar::setCandidates( const QVector< QString >& candidates )
void QskInputPredictionBar::setPrediction( const QVector< QString >& candidates )
{
if( 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;
}
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 count = std::min( candidateCount, m_data->buttonCount );
const bool continueLeft = m_data->candidateOffset > 0;
const bool continueRight = ( candidateCount - m_data->candidateOffset ) > count;
const bool continueLeft = m_data->scrollOffset > 0;
const bool continueRight = ( candidateCount - m_data->scrollOffset ) > count;
for( int i = 0; i < count; i++ )
{
@ -144,7 +143,7 @@ void QskInputSuggestionBar::setCandidateOffset( int offset )
}
else
{
const int index = i + m_data->candidateOffset;
const int index = i + m_data->scrollOffset;
button->setText( m_data->candidates[index] );
}
@ -155,18 +154,18 @@ void QskInputSuggestionBar::setCandidateOffset( int offset )
m_data->layoutBox->itemAtIndex( i )->setVisible( false );
}
void QskInputSuggestionBar::candidateClicked()
void QskInputPredictionBar::buttonClicked()
{
const int index = m_data->layoutBox->indexOf(
qobject_cast< QQuickItem* > ( sender() ) );
const int offset = m_data->candidateOffset;
const int offset = m_data->scrollOffset;
if ( index == 0 )
{
if ( offset > 0 )
{
setCandidateOffset( offset - 1 );
setScrollOffset( offset - 1 );
return;
}
}
@ -174,12 +173,12 @@ void QskInputSuggestionBar::candidateClicked()
{
if ( m_data->candidates.count() - offset > m_data->buttonCount )
{
setCandidateOffset( offset + 1 );
setScrollOffset( offset + 1 );
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
*****************************************************************************/
#ifndef QSK_INPUT_SUGGESTION_BAR_H
#define QSK_INPUT_SUGGESTION_BAR_H
#ifndef QSK_INPUT_PREDICTION_BAR_H
#define QSK_INPUT_PREDICTION_BAR_H
#include "QskBox.h"
class QSK_EXPORT QskInputSuggestionBar : public QskBox
class QSK_EXPORT QskInputPredictionBar : public QskBox
{
Q_OBJECT
@ -17,8 +17,8 @@ class QSK_EXPORT QskInputSuggestionBar : public QskBox
public:
QSK_SUBCONTROLS( Panel, ButtonPanel, ButtonText )
QskInputSuggestionBar( QQuickItem* parent = nullptr );
virtual ~QskInputSuggestionBar();
QskInputPredictionBar( QQuickItem* parent = nullptr );
virtual ~QskInputPredictionBar();
virtual QskAspect::Subcontrol effectiveSubcontrol(
QskAspect::Subcontrol subControl ) const override;
@ -26,14 +26,14 @@ public:
QVector< QString > candidates() const;
Q_SIGNALS:
void suggested( int );
void predictiveTextSelected( int );
public Q_SLOTS:
void setCandidates( const QVector< QString >& );
void setPrediction( const QVector< QString >& );
private:
void candidateClicked();
void setCandidateOffset( int );
void buttonClicked();
void setScrollOffset( int );
class PrivateData;
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
*****************************************************************************/
#include "QskInputCompositionModel.h"
#include "QskTextPredictor.h"
QskInputCompositionModel::QskInputCompositionModel(
QskTextPredictor::QskTextPredictor(
Attributes attributes, QObject* parent ):
QObject( parent ),
m_attributes( attributes )
{
}
QskInputCompositionModel::~QskInputCompositionModel()
QskTextPredictor::~QskTextPredictor()
{
}
QskInputCompositionModel::Attributes QskInputCompositionModel::attributes() const
QskTextPredictor::Attributes QskTextPredictor::attributes() const
{
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
*****************************************************************************/
#ifndef QSK_INPUT_COMPOSITION_MODEL_H
#define QSK_INPUT_COMPOSITION_MODEL_H
#ifndef QSK_TEXT_PREDICTOR_H
#define QSK_TEXT_PREDICTOR_H
#include <QskGlobal.h>
#include <QObject>
class QskInputCompositionModel : public QObject
// abstract base class for input methods for retrieving predictive text
class QSK_EXPORT QskTextPredictor : public QObject
{
Q_OBJECT
@ -21,10 +24,10 @@ public:
Q_ENUM( Attribute )
Q_DECLARE_FLAGS( Attributes, Attribute )
virtual ~QskInputCompositionModel();
virtual ~QskTextPredictor();
virtual void requestCandidates( const QString& preedit ) = 0;
virtual void resetCandidates() = 0;
virtual void request( const QString& text ) = 0;
virtual void reset() = 0;
virtual int candidateCount() const = 0;
virtual QString candidate( int ) const = 0;
@ -32,10 +35,10 @@ public:
Attributes attributes() const;
Q_SIGNALS:
void candidatesChanged();
void predictionChanged();
protected:
QskInputCompositionModel( Attributes, QObject* );
QskTextPredictor( Attributes, QObject* );
private:
const Attributes m_attributes;

View File

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