QskInputContext improved

This commit is contained in:
Uwe Rathmann 2018-04-20 08:52:26 +02:00
parent 48c897f825
commit 151a73cb0b
8 changed files with 430 additions and 429 deletions

View File

@ -11,7 +11,7 @@ public:
};
QskHunspellCompositionModel::QskHunspellCompositionModel( QskInputContext* context ):
Inherited( context ),
Inherited( Words, context ),
m_data( new PrivateData() )
{
#if 1
@ -28,21 +28,6 @@ QskHunspellCompositionModel::~QskHunspellCompositionModel()
Hunspell_destroy( m_data->hunspellHandle );
}
bool QskHunspellCompositionModel::supportsSuggestions() const
{
return true;
}
void QskHunspellCompositionModel::commitCandidate( int index )
{
if( index < m_data->candidates.count() )
{
// The user usually selects a full word, so we can add the space
QString commitString = candidate( index ) + " ";
commit( commitString );
}
}
int QskHunspellCompositionModel::candidateCount() const
{
return m_data->candidates.count();
@ -53,47 +38,38 @@ QString QskHunspellCompositionModel::candidate( int pos ) const
return m_data->candidates[ pos ];
}
QString QskHunspellCompositionModel::polishPreedit( const QString& preedit )
void QskHunspellCompositionModel::resetCandidates()
{
if( preedit.isEmpty() )
if ( !m_data->candidates.isEmpty() )
{
// new word: delete suggestions
m_data->candidates.clear();
Q_EMIT candidatesChanged();
}
else
{
char** suggestions;
QByteArray word = preedit.toUtf8(); // ### do we need to check the encoding (see qtvirtualkeyboard)?
int suggestionCount = Hunspell_suggest(
m_data->hunspellHandle, &suggestions, word.constData() );
QVector< QString > candidates;
candidates.reserve( suggestionCount );
for( int a = 0; a < suggestionCount; ++a )
{
const QString suggestion = QString::fromUtf8( suggestions[a] ); // ### encoding?
if( suggestion.startsWith( preedit ) )
{
candidates.prepend( suggestion );
}
else
{
candidates.append( suggestion );
}
}
Hunspell_free_list( m_data->hunspellHandle, &suggestions, suggestionCount );
m_data->candidates = candidates;
}
Q_EMIT candidatesChanged();
return preedit;
}
bool QskHunspellCompositionModel::hasIntermediate() const
void QskHunspellCompositionModel::requestCandidates( const QString& text )
{
return true;
char** suggestions;
const QByteArray word = text.toUtf8(); // ### do we need to check the encoding
const int count = Hunspell_suggest(
m_data->hunspellHandle, &suggestions, word.constData() );
QVector< QString > candidates;
candidates.reserve( count );
for( int i = 0; i < count; i++ )
{
const QString suggestion = QString::fromUtf8( suggestions[i] ); // ### encoding?
if( suggestion.startsWith( text ) )
candidates.prepend( suggestion );
else
candidates.append( suggestion );
}
Hunspell_free_list( m_data->hunspellHandle, &suggestions, count );
m_data->candidates = candidates;
Q_EMIT candidatesChanged();
}

View File

@ -7,6 +7,7 @@
#define QSK_HUNSPELL_COMPOSITION_MODEL_H
#include "QskInputCompositionModel.h"
#include <memory>
class QskHunspellCompositionModel : public QskInputCompositionModel
{
@ -16,15 +17,12 @@ public:
QskHunspellCompositionModel( QskInputContext* context );
virtual ~QskHunspellCompositionModel() override;
virtual bool supportsSuggestions() const override final;
virtual void commitCandidate( int index ) override;
virtual int candidateCount() const override;
virtual QString candidate( int pos ) const override;
protected:
virtual bool hasIntermediate() const override;
virtual QString polishPreedit( const QString& ) override;
virtual void requestCandidates( const QString& ) override;
virtual void resetCandidates() override;
private:
class PrivateData;

View File

@ -6,45 +6,12 @@
#include "QskInputCompositionModel.h"
#include "QskInputContext.h"
#include <QGuiApplication>
#include <QInputMethodEvent>
#include <QTextCharFormat>
#include <QInputMethodQueryEvent>
static inline QString qskKeyString( int code )
{
// Special case entry codes here, else default to the symbol
switch ( code )
{
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( code );
}
class QskInputCompositionModel::PrivateData
{
public:
QString preedit;
};
QskInputCompositionModel::QskInputCompositionModel( QskInputContext* context ):
QskInputCompositionModel::QskInputCompositionModel(
Attributes attributes, QskInputContext* context ):
QObject( context ),
m_data( new PrivateData )
m_attributes( attributes )
{
}
@ -57,211 +24,67 @@ QskInputContext* QskInputCompositionModel::context() const
return qobject_cast< QskInputContext* >( parent() );
}
bool QskInputCompositionModel::supportsSuggestions() const
void QskInputCompositionModel::composeKey( const QString& text, int spaceLeft )
{
return false;
}
void QskInputCompositionModel::composeKey( int key )
{
/*
* This operation might be expensive (e.g. for Hunspell) and
* should be done asynchronously to be able to run e.g. suggestions
* in a separate thread to not block the UI.
* TODO
*/
const auto queryEvent = context()->queryInputMethod(
Qt::ImSurroundingText | Qt::ImMaximumTextLength | Qt::ImHints );
const auto hints = static_cast< Qt::InputMethodHints >(
queryEvent.value( Qt::ImHints ).toInt() );
const int maxLength = queryEvent.value( Qt::ImMaximumTextLength ).toInt();
const int currentLength = queryEvent.value( Qt::ImSurroundingText ).toString().length();
int spaceLeft = -1;
if ( !( hints & Qt::ImhMultiLine ) && maxLength > 0 )
spaceLeft = maxLength - currentLength;
switch ( key )
if ( candidateCount() > 0 )
{
case Qt::Key_Backspace:
case Qt::Key_Muhenkan:
{
if ( !m_data->preedit.isEmpty() )
{
m_data->preedit.chop( 1 );
sendPreeditTextEvent( polishPreedit( m_data->preedit ) );
}
else
{
// Backspace one character only if preedit was inactive
sendKeyEvents( Qt::Key_Backspace );
}
return;
}
case Qt::Key_Space:
{
if( !spaceLeft )
{
return;
}
m_preedit += text;
if( !m_data->preedit.isEmpty() )
{
commit( m_data->preedit.left( spaceLeft ) );
}
requestCandidates( m_preedit );
context()->sendText( m_preedit, false );
commit( qskKeyString( key ) );
return;
}
case Qt::Key_Return:
{
if ( !spaceLeft )
return;
// Commit what is in the buffer
if( !m_data->preedit.isEmpty() )
{
commit( m_data->preedit.left( spaceLeft ) );
}
else if( hints & Qt::ImhMultiLine )
{
commit( qskKeyString( key ) );
}
else
{
sendKeyEvents( Qt::Key_Return );
}
return;
}
case Qt::Key_Left:
case Qt::Key_Right:
{
if ( m_data->preedit.isEmpty() )
sendKeyEvents( key );
return;
}
case Qt::Key_Escape:
{
sendKeyEvents( Qt::Key_Escape );
return;
}
default:
{
if ( !spaceLeft )
return;
}
}
if ( hints & Qt::ImhHiddenText )
{
commit( qskKeyString( key ) );
return;
}
const auto firstCandidate = candidateCount() > 0 ? candidate( 0 ) : QString();
const auto oldPreedit = m_data->preedit;
requestCandidates( m_preedit );
m_data->preedit += qskKeyString( key );
auto displayPreedit = polishPreedit( m_data->preedit );
// If there is no intermediate, decide between committing the first candidate and skipping
if ( !hasIntermediate() )
QString txt;
if ( candidateCount() == 0 )
{
// Skip preedit phase if there are no candidates/intermediates
if ( firstCandidate.isEmpty() )
txt = m_preedit.left( spaceLeft );
spaceLeft -= txt.length();
}
else
{
txt = candidate( 0 );
--spaceLeft;
}
context()->sendText( txt, true );
m_preedit.clear();
resetCandidates();
if ( spaceLeft )
{
m_preedit = text;
requestCandidates( m_preedit );
if ( candidateCount() > 0 )
{
commit( oldPreedit.left( spaceLeft ) );
spaceLeft -= oldPreedit.leftRef( spaceLeft ).length();
context()->sendText( m_preedit, false );
}
else
{
commit( firstCandidate );
--spaceLeft;
}
if ( !spaceLeft )
return;
m_data->preedit = qskKeyString( key );
displayPreedit = polishPreedit( m_data->preedit );
if ( !hasIntermediate() )
{
commit( m_data->preedit );
return;
context()->sendText( m_preedit, true );
m_preedit.clear();
resetCandidates();
}
}
sendPreeditTextEvent( displayPreedit );
}
void QskInputCompositionModel::clearPreedit()
void QskInputCompositionModel::setPreeditText( const QString& text )
{
m_data->preedit.clear();
polishPreedit( m_data->preedit );
if ( text != m_preedit )
{
m_preedit = text;
requestCandidates( m_preedit );
}
}
int QskInputCompositionModel::candidateCount() const
void QskInputCompositionModel::reset()
{
return 0;
}
QString QskInputCompositionModel::candidate( int ) const
{
return QString();
}
QString QskInputCompositionModel::polishPreedit( const QString& preedit )
{
return preedit;
}
void QskInputCompositionModel::commit( const QString& text )
{
QInputMethodEvent event;
event.setCommitString( text );
context()->sendEventToInputItem( &event );
clearPreedit();
}
void QskInputCompositionModel::commitCandidate( int index )
{
commit( candidate( index ) );
}
void QskInputCompositionModel::sendPreeditTextEvent( const QString& text )
{
QTextCharFormat format;
format.setFontUnderline( true );
const QInputMethodEvent::Attribute attribute(
QInputMethodEvent::TextFormat, 0, text.length(), format );
QInputMethodEvent event( text, { attribute } );
context()->sendEventToInputItem( &event );
}
void QskInputCompositionModel::sendKeyEvents( int key )
{
auto context = this->context();
QKeyEvent keyPress( QEvent::KeyPress, key, Qt::NoModifier );
context->sendEventToInputItem( &keyPress );
QKeyEvent keyRelease( QEvent::KeyRelease, key, Qt::NoModifier );
context->sendEventToInputItem( &keyRelease );
}
bool QskInputCompositionModel::hasIntermediate() const
{
return false;
m_preedit.clear();
resetCandidates();
}
#include "moc_QskInputCompositionModel.cpp"

View File

@ -7,7 +7,6 @@
#define QSK_INPUT_COMPOSITION_MODEL_H
#include <QObject>
#include <memory>
class QskInputContext;
@ -16,36 +15,53 @@ class QskInputCompositionModel : public QObject
Q_OBJECT
public:
QskInputCompositionModel( QskInputContext* context );
enum Attribute
{
Words = 1 << 0
};
Q_ENUM( Attribute )
Q_DECLARE_FLAGS( Attributes, Attribute )
virtual ~QskInputCompositionModel();
// to determine whether to show the suggestion bar:
virtual bool supportsSuggestions() const;
void composeKey( const QString& text, int spaceLeft );
void commit( const QString& );
virtual void commitCandidate( int );
virtual int candidateCount() const = 0;
virtual QString candidate( int ) const = 0;
void composeKey( int key );
void reset();
virtual int candidateCount() const;
virtual QString candidate( int ) const;
QString preeditText() const;
void setPreeditText( const QString& );
Attributes attributes() const;
protected:
virtual bool hasIntermediate() const;
virtual QString polishPreedit( const QString& preedit );
QskInputCompositionModel( Attributes, QskInputContext* );
virtual void requestCandidates( const QString& preedit ) = 0;
virtual void resetCandidates() = 0;
QskInputContext* context() const;
Q_SIGNALS:
void candidatesChanged();
private:
void clearPreedit();
QskInputContext* context() const;
void sendPreeditTextEvent( const QString& );
void sendKeyEvents( int key );
class PrivateData;
std::unique_ptr< PrivateData > m_data;
QString m_preedit;
const Attributes m_attributes;
};
inline QString QskInputCompositionModel::preeditText() const
{
return m_preedit;
}
inline QskInputCompositionModel::Attributes
QskInputCompositionModel::attributes() const
{
return m_attributes;
}
#endif

View File

@ -18,9 +18,52 @@
#include <QskSetup.h>
#include <QskEvent.h>
#include <QGuiApplication>
#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 inline bool qskIsControlKey( int keyCode )
{
switch ( keyCode )
{
case Qt::Key_Backspace:
case Qt::Key_Muhenkan:
case Qt::Key_Return:
case Qt::Key_Left:
case Qt::Key_Right:
case Qt::Key_Escape:
return true;
}
return false;
}
static void qskSetLocale( QQuickItem* inputPanel, const QLocale& locale )
{
@ -82,6 +125,11 @@ static void qskSetCandidates( QQuickItem* inputPanel,
}
}
static inline uint qskHashLocale( const QLocale& locale )
{
return uint( locale.language() + uint( locale.country() ) << 16 );
}
class QskInputContext::PrivateData
{
public:
@ -95,8 +143,7 @@ public:
QskPopup* inputPopup = nullptr;
QskWindow* inputWindow = nullptr;
QskInputCompositionModel* compositionModel;
QHash< QLocale, QskInputCompositionModel* > compositionModels;
QHash< uint, QskInputCompositionModel* > compositionModels;
// the input panel is embedded in a window
bool ownsInputPanelWindow : 1;
@ -108,21 +155,15 @@ QskInputContext::QskInputContext():
setObjectName( "InputContext" );
#if 1
m_data->compositionModel = new QskInputCompositionModel( this );
#else
m_data->compositionModel = new QskHunspellCompositionModel( this );
setCompositionModel( locale(), new QskHunspellCompositionModel( this ) );
#endif
connect( m_data->compositionModel, &QskInputCompositionModel::candidatesChanged,
this, &QskInputContext::handleCandidatesChanged );
connect( qskSetup, &QskSetup::inputPanelChanged,
this, &QskInputContext::setInputPanel );
#if 0
setCompositionModel( QLocale::Chinese, new QskPinyinCompositionModel( this ) );
#endif
connect( qskSetup, &QskSetup::inputPanelChanged,
this, &QskInputContext::setInputPanel );
setInputPanel( qskSetup->inputPanel() );
}
@ -214,23 +255,33 @@ void QskInputContext::update( Qt::InputMethodQueries queries )
{
const auto locale = queryEvent.value( Qt::ImPreferredLanguage ).toLocale();
auto oldModel = compositionModel();
const auto oldModel = compositionModel();
if( m_data->inputPanel )
qskSetLocale( m_data->inputPanel, locale );
auto newModel = compositionModel();
if( oldModel != newModel )
if( newModel && ( oldModel != newModel ) )
{
connect( newModel, &QskInputCompositionModel::candidatesChanged,
this, &QskInputContext::handleCandidatesChanged );
qskSetCandidatesEnabled( m_data->inputPanel,
newModel->supportsSuggestions() );
}
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
@ -496,14 +547,6 @@ void QskInputContext::setFocusObject( QObject* focusObject )
if ( isAccepted )
setInputItem( focusItem );
}
if ( m_data->inputPanel && m_data->inputPanel->isVisible() )
{
if ( m_data->inputItem && focusItem != m_data->inputItem )
qGuiApp->installEventFilter( this );
else
qGuiApp->removeEventFilter( this );
}
}
void QskInputContext::setCompositionModel(
@ -511,9 +554,11 @@ void QskInputContext::setCompositionModel(
{
auto& models = m_data->compositionModels;
const auto key = qskHashLocale( locale );
if ( model )
{
const auto it = models.find( locale );
const auto it = models.find( key );
if ( it != models.end() )
{
if ( it.value() == model )
@ -524,7 +569,7 @@ void QskInputContext::setCompositionModel(
}
else
{
models.insert( locale, model );
models.insert( key, model );
}
connect( model, &QskInputCompositionModel::candidatesChanged,
@ -532,7 +577,7 @@ void QskInputContext::setCompositionModel(
}
else
{
const auto it = models.find( locale );
const auto it = models.find( key );
if ( it != models.end() )
{
delete it.value();
@ -543,23 +588,33 @@ void QskInputContext::setCompositionModel(
QskInputCompositionModel* QskInputContext::compositionModel() const
{
return m_data->compositionModels.value( locale(), m_data->compositionModel );
const auto key = qskHashLocale( locale() );
return m_data->compositionModels.value( key, nullptr );
}
void QskInputContext::invokeAction( QInputMethod::Action action, int value )
{
auto model = compositionModel();
switch ( static_cast< int >( action ) )
{
case QskInputPanel::Compose:
{
model->composeKey( value );
processKey( value );
break;
}
case QskInputPanel::SelectCandidate:
{
model->commitCandidate( value );
if ( auto model = compositionModel() )
{
auto text = model->candidate( value );
if ( model->attributes() & QskInputCompositionModel::Words )
text += " ";
sendText( text, true );
model->reset();
}
break;
}
case QInputMethod::Click:
@ -570,6 +625,130 @@ void QskInputContext::invokeAction( QInputMethod::Action action, int value )
}
}
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();
/*
First we have to handle the control keys
*/
switch ( key )
{
case Qt::Key_Backspace:
case Qt::Key_Muhenkan:
{
if ( model )
{
auto preeditText = model->preeditText();
if ( !preeditText.isEmpty() )
{
preeditText.chop( 1 );
sendText( preeditText, false );
model->setPreeditText( preeditText );
return;
}
}
sendKey( Qt::Key_Backspace );
return;
}
case Qt::Key_Return:
{
if ( model )
{
const auto preeditText = model->preeditText();
if ( !preeditText.isEmpty() )
{
if ( spaceLeft )
sendText( preeditText.left( spaceLeft ), true );
model->reset();
return;
}
}
if( !( hints & Qt::ImhMultiLine ) )
{
sendKey( Qt::Key_Return );
return;
}
break;
}
case Qt::Key_Space:
{
if ( model )
{
auto preeditText = model->preeditText();
if ( !preeditText.isEmpty() && spaceLeft)
{
preeditText = preeditText.left( spaceLeft );
sendText( preeditText, true );
spaceLeft -= preeditText.length();
model->reset();
}
}
break;
}
case Qt::Key_Left:
case Qt::Key_Right:
case Qt::Key_Escape:
{
sendKey( key );
return;
}
}
const QString text = qskKeyString( key );
if ( model )
{
model->composeKey( text, spaceLeft );
}
else
{
sendText( text, true );
}
}
void QskInputContext::handleCandidatesChanged()
{
const auto model = compositionModel();
@ -628,11 +807,7 @@ void QskInputContext::setInputPanel( QQuickItem* inputPanel )
this, &QPlatformInputContext::emitLocaleChanged );
}
if ( model )
{
qskSetCandidatesEnabled( m_data->inputPanel,
model->supportsSuggestions() );
}
qskSetCandidatesEnabled( inputPanel, model != nullptr );
}
}
@ -668,8 +843,6 @@ bool QskInputContext::eventFilter( QObject* object, QEvent* event )
}
case QEvent::DeferredDelete:
{
object->removeEventFilter( this );
qGuiApp->removeEventFilter( this );
m_data->inputWindow = nullptr;
break;
}
@ -677,45 +850,20 @@ bool QskInputContext::eventFilter( QObject* object, QEvent* event )
break;
}
}
else
else if ( object == m_data->inputPopup )
{
switch( static_cast< int >( event->type() ) )
{
case QskEvent::GeometryChange:
{
if ( object == m_data->inputPanel )
{
if ( event->type() == QskEvent::GeometryChange )
emitKeyboardRectChanged();
}
if ( event->type() == QskEvent::GeometryChange )
emitKeyboardRectChanged();
break;
}
case QEvent::InputMethodQuery:
{
/*
Qt/Quick expects that the item associated with the input context
holds the focus. But this does not work, when a virtual
keyboard is used, where you can navigate and select inside.
So we have to fix the receiver.
*/
if ( ( object != m_data->inputItem )
&& qskIsAncestorOf( m_data->inputPanel, m_data->inputItem ) )
{
sendEventToInputItem( event );
return true;
}
break;
}
case QEvent::DeferredDelete:
{
if ( object == m_data->inputPopup )
{
object->removeEventFilter( this );
qGuiApp->removeEventFilter( this );
m_data->inputPopup = nullptr;
}
m_data->inputPopup = nullptr;
break;
}
}
@ -724,10 +872,9 @@ bool QskInputContext::eventFilter( QObject* object, QEvent* event )
return Inherited::eventFilter( object, event );
}
bool QskInputContext::filterEvent( const QEvent* event )
bool QskInputContext::filterEvent( const QEvent* )
{
// called from QXcbKeyboard, but what about other platforms
Q_UNUSED( event )
return false;
}
@ -735,15 +882,51 @@ QInputMethodQueryEvent QskInputContext::queryInputMethod(
Qt::InputMethodQueries queries ) const
{
QInputMethodQueryEvent event( queries );
sendEventToInputItem( &event );
if ( m_data->inputItem )
QCoreApplication::sendEvent( m_data->inputItem, &event );
return event;
}
void QskInputContext::sendEventToInputItem( QEvent* event ) const
void QskInputContext::sendText(
const QString& text, bool isFinal ) const
{
if ( m_data->inputItem && event )
QCoreApplication::sendEvent( m_data->inputItem, event );
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

@ -10,8 +10,12 @@
#include <memory>
class QskInputCompositionModel;
class QQuickItem;
class QInputMethodQueryEvent;
class QInputMethodEvent;
class QKeyEvent;
class QskInputContext : public QPlatformInputContext
{
@ -51,7 +55,12 @@ public:
virtual bool filterEvent( const QEvent* ) override;
QInputMethodQueryEvent queryInputMethod( Qt::InputMethodQueries ) const;
void sendEventToInputItem( QEvent* ) const;
void sendKey( int key ) const;
void sendText( const QString& text, bool isFinal ) const;
Qt::InputMethodHints inputHints() const;
int keysLeft() const;
private Q_SLOTS:
void handleCandidatesChanged();
@ -60,6 +69,8 @@ private Q_SLOTS:
virtual bool eventFilter( QObject*, QEvent* ) override;
private:
void processKey( int key );
void setInputItem( QQuickItem* );
QskInputCompositionModel* compositionModel() const;

View File

@ -18,7 +18,7 @@ public:
};
QskPinyinCompositionModel::QskPinyinCompositionModel( QskInputContext* context ):
Inherited( context ),
Inherited( Attributes(), context ),
m_data( new PrivateData )
{
#if 1
@ -39,11 +39,6 @@ QskPinyinCompositionModel::~QskPinyinCompositionModel()
ime_pinyin::im_close_decoder();
}
bool QskPinyinCompositionModel::supportsSuggestions() const
{
return true;
}
int QskPinyinCompositionModel::candidateCount() const
{
return m_data->candidates.count();
@ -57,53 +52,54 @@ QString QskPinyinCompositionModel::candidate( int index ) const
return QString();
}
bool QskPinyinCompositionModel::hasIntermediate() const
void QskPinyinCompositionModel::resetCandidates()
{
return m_data->candidates.count() > 0;
}
ime_pinyin::im_reset_search();
QString QskPinyinCompositionModel::polishPreedit( const QString& preedit )
{
if( preedit.isEmpty() )
if ( !m_data->candidates.isEmpty() )
{
ime_pinyin::im_reset_search();
}
QByteArray preeditBuffer = preedit.toLatin1();
size_t numSearchResults = ime_pinyin::im_search(
preeditBuffer.constData(), size_t( preeditBuffer.length() ) );
if( numSearchResults > 0 )
{
QStringList newCandidates;
newCandidates.reserve( 1 );
QVector< QChar > candidateBuffer;
candidateBuffer.resize( ime_pinyin::kMaxSearchSteps + 1 );
// ### numSearchResults is way too big, we should only do this for the first ten results or so
for( unsigned int a = 0; a < numSearchResults; a++ )
{
size_t length = static_cast< size_t >( candidateBuffer.length() - 1 );
bool getCandidate = ime_pinyin::im_get_candidate( a, reinterpret_cast< ime_pinyin::char16* >( candidateBuffer.data() ), length );
QString candidate;
if( getCandidate )
{
candidateBuffer.last() = 0;
candidate = QString( candidateBuffer.data() );
}
Qt::Key key = Qt::Key( candidate.at( 0 ).unicode() );
QString string = QChar( key );
newCandidates.append( string );
}
m_data->candidates = newCandidates;
m_data->candidates.clear();
Q_EMIT candidatesChanged();
}
return preedit;
}
void QskPinyinCompositionModel::requestCandidates( const QString& text )
{
const QByteArray bytes = text.toLatin1();
size_t count = ime_pinyin::im_search(
bytes.constData(), size_t( bytes.length() ) );
if( count <= 0 )
return;
const size_t maxCount = 20;
if ( count > maxCount )
count = maxCount;
QVector< QChar > candidateBuffer;
candidateBuffer.resize( ime_pinyin::kMaxSearchSteps + 1 );
QStringList candidates;
candidates.reserve( count );
for( unsigned int i = 0; i < count; i++ )
{
size_t length = static_cast< size_t >( candidateBuffer.length() - 1 );
const auto buf = reinterpret_cast< ime_pinyin::char16* >( candidateBuffer.data() );
const bool found = ime_pinyin::im_get_candidate( i, buf, length );
Q_ASSERT( found );
candidateBuffer.last() = 0;
auto candidate = QString( candidateBuffer.data() );
candidate = QChar( Qt::Key( candidate[0].unicode() ) );
candidates += candidate;
}
m_data->candidates = candidates;
Q_EMIT candidatesChanged();
}

View File

@ -7,6 +7,7 @@
#define QSK_PINYIN_COMPOSITION_MODEL_H
#include "QskInputCompositionModel.h"
#include <memory>
class QskPinyinCompositionModel : public QskInputCompositionModel
{
@ -16,15 +17,12 @@ public:
QskPinyinCompositionModel( QskInputContext* );
virtual ~QskPinyinCompositionModel() override;
virtual bool supportsSuggestions() const override final;
virtual int candidateCount() const override;
virtual QString candidate( int ) const override;
protected:
// Used for text composition
virtual bool hasIntermediate() const override;
virtual QString polishPreedit( const QString& preedit ) override;
virtual void requestCandidates( const QString& ) override;
virtual void resetCandidates() override;
private:
class PrivateData;