more changes concering the input context
This commit is contained in:
parent
745b704ba8
commit
16efc695b9
@ -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();
|
||||
}
|
@ -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;
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
@ -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;
|
@ -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 \
|
||||
|
@ -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;
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
|
246
src/inputpanel/QskInputEngine.cpp
Normal file
246
src/inputpanel/QskInputEngine.cpp
Normal 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"
|
54
src/inputpanel/QskInputEngine.h
Normal file
54
src/inputpanel/QskInputEngine.h
Normal 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
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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"
|
@ -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;
|
@ -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"
|
@ -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;
|
10
src/src.pro
10
src/src.pro
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user