QskInputContext improved
This commit is contained in:
parent
48c897f825
commit
151a73cb0b
@ -11,7 +11,7 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
QskHunspellCompositionModel::QskHunspellCompositionModel( QskInputContext* context ):
|
QskHunspellCompositionModel::QskHunspellCompositionModel( QskInputContext* context ):
|
||||||
Inherited( context ),
|
Inherited( Words, context ),
|
||||||
m_data( new PrivateData() )
|
m_data( new PrivateData() )
|
||||||
{
|
{
|
||||||
#if 1
|
#if 1
|
||||||
@ -28,21 +28,6 @@ QskHunspellCompositionModel::~QskHunspellCompositionModel()
|
|||||||
Hunspell_destroy( m_data->hunspellHandle );
|
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
|
int QskHunspellCompositionModel::candidateCount() const
|
||||||
{
|
{
|
||||||
return m_data->candidates.count();
|
return m_data->candidates.count();
|
||||||
@ -53,47 +38,38 @@ QString QskHunspellCompositionModel::candidate( int pos ) const
|
|||||||
return m_data->candidates[ pos ];
|
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();
|
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();
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
#define QSK_HUNSPELL_COMPOSITION_MODEL_H
|
#define QSK_HUNSPELL_COMPOSITION_MODEL_H
|
||||||
|
|
||||||
#include "QskInputCompositionModel.h"
|
#include "QskInputCompositionModel.h"
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
class QskHunspellCompositionModel : public QskInputCompositionModel
|
class QskHunspellCompositionModel : public QskInputCompositionModel
|
||||||
{
|
{
|
||||||
@ -16,15 +17,12 @@ public:
|
|||||||
QskHunspellCompositionModel( QskInputContext* context );
|
QskHunspellCompositionModel( QskInputContext* context );
|
||||||
virtual ~QskHunspellCompositionModel() override;
|
virtual ~QskHunspellCompositionModel() override;
|
||||||
|
|
||||||
virtual bool supportsSuggestions() const override final;
|
|
||||||
|
|
||||||
virtual void commitCandidate( int index ) override;
|
|
||||||
virtual int candidateCount() const override;
|
virtual int candidateCount() const override;
|
||||||
virtual QString candidate( int pos ) const override;
|
virtual QString candidate( int pos ) const override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual bool hasIntermediate() const override;
|
virtual void requestCandidates( const QString& ) override;
|
||||||
virtual QString polishPreedit( const QString& ) override;
|
virtual void resetCandidates() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
class PrivateData;
|
class PrivateData;
|
||||||
|
@ -6,45 +6,12 @@
|
|||||||
#include "QskInputCompositionModel.h"
|
#include "QskInputCompositionModel.h"
|
||||||
#include "QskInputContext.h"
|
#include "QskInputContext.h"
|
||||||
|
|
||||||
#include <QGuiApplication>
|
#include <QInputMethodQueryEvent>
|
||||||
#include <QInputMethodEvent>
|
|
||||||
#include <QTextCharFormat>
|
|
||||||
|
|
||||||
static inline QString qskKeyString( int code )
|
QskInputCompositionModel::QskInputCompositionModel(
|
||||||
{
|
Attributes attributes, QskInputContext* context ):
|
||||||
// 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 ):
|
|
||||||
QObject( context ),
|
QObject( context ),
|
||||||
m_data( new PrivateData )
|
m_attributes( attributes )
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,211 +24,67 @@ QskInputContext* QskInputCompositionModel::context() const
|
|||||||
return qobject_cast< QskInputContext* >( parent() );
|
return qobject_cast< QskInputContext* >( parent() );
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QskInputCompositionModel::supportsSuggestions() const
|
void QskInputCompositionModel::composeKey( const QString& text, int spaceLeft )
|
||||||
{
|
{
|
||||||
return false;
|
if ( candidateCount() > 0 )
|
||||||
}
|
|
||||||
|
|
||||||
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 )
|
|
||||||
{
|
{
|
||||||
case Qt::Key_Backspace:
|
m_preedit += text;
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( !m_data->preedit.isEmpty() )
|
requestCandidates( m_preedit );
|
||||||
{
|
context()->sendText( m_preedit, false );
|
||||||
commit( m_data->preedit.left( spaceLeft ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto firstCandidate = candidateCount() > 0 ? candidate( 0 ) : QString();
|
requestCandidates( m_preedit );
|
||||||
const auto oldPreedit = m_data->preedit;
|
|
||||||
|
|
||||||
m_data->preedit += qskKeyString( key );
|
QString txt;
|
||||||
auto displayPreedit = polishPreedit( m_data->preedit );
|
if ( candidateCount() == 0 )
|
||||||
|
|
||||||
// If there is no intermediate, decide between committing the first candidate and skipping
|
|
||||||
if ( !hasIntermediate() )
|
|
||||||
{
|
{
|
||||||
// Skip preedit phase if there are no candidates/intermediates
|
txt = m_preedit.left( spaceLeft );
|
||||||
if ( firstCandidate.isEmpty() )
|
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 ) );
|
context()->sendText( m_preedit, false );
|
||||||
spaceLeft -= oldPreedit.leftRef( spaceLeft ).length();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
commit( firstCandidate );
|
context()->sendText( m_preedit, true );
|
||||||
--spaceLeft;
|
m_preedit.clear();
|
||||||
}
|
resetCandidates();
|
||||||
|
|
||||||
if ( !spaceLeft )
|
|
||||||
return;
|
|
||||||
|
|
||||||
m_data->preedit = qskKeyString( key );
|
|
||||||
displayPreedit = polishPreedit( m_data->preedit );
|
|
||||||
|
|
||||||
if ( !hasIntermediate() )
|
|
||||||
{
|
|
||||||
commit( m_data->preedit );
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sendPreeditTextEvent( displayPreedit );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void QskInputCompositionModel::clearPreedit()
|
void QskInputCompositionModel::setPreeditText( const QString& text )
|
||||||
{
|
{
|
||||||
m_data->preedit.clear();
|
if ( text != m_preedit )
|
||||||
polishPreedit( m_data->preedit );
|
{
|
||||||
|
m_preedit = text;
|
||||||
|
requestCandidates( m_preedit );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int QskInputCompositionModel::candidateCount() const
|
void QskInputCompositionModel::reset()
|
||||||
{
|
{
|
||||||
return 0;
|
m_preedit.clear();
|
||||||
}
|
resetCandidates();
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#include "moc_QskInputCompositionModel.cpp"
|
#include "moc_QskInputCompositionModel.cpp"
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
#define QSK_INPUT_COMPOSITION_MODEL_H
|
#define QSK_INPUT_COMPOSITION_MODEL_H
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
class QskInputContext;
|
class QskInputContext;
|
||||||
|
|
||||||
@ -16,36 +15,53 @@ class QskInputCompositionModel : public QObject
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
QskInputCompositionModel( QskInputContext* context );
|
enum Attribute
|
||||||
|
{
|
||||||
|
Words = 1 << 0
|
||||||
|
};
|
||||||
|
|
||||||
|
Q_ENUM( Attribute )
|
||||||
|
Q_DECLARE_FLAGS( Attributes, Attribute )
|
||||||
|
|
||||||
virtual ~QskInputCompositionModel();
|
virtual ~QskInputCompositionModel();
|
||||||
|
|
||||||
// to determine whether to show the suggestion bar:
|
void composeKey( const QString& text, int spaceLeft );
|
||||||
virtual bool supportsSuggestions() const;
|
|
||||||
|
|
||||||
void commit( const QString& );
|
virtual int candidateCount() const = 0;
|
||||||
virtual void commitCandidate( int );
|
virtual QString candidate( int ) const = 0;
|
||||||
|
|
||||||
void composeKey( int key );
|
void reset();
|
||||||
|
|
||||||
virtual int candidateCount() const;
|
QString preeditText() const;
|
||||||
virtual QString candidate( int ) const;
|
void setPreeditText( const QString& );
|
||||||
|
|
||||||
|
Attributes attributes() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual bool hasIntermediate() const;
|
QskInputCompositionModel( Attributes, QskInputContext* );
|
||||||
virtual QString polishPreedit( const QString& preedit );
|
|
||||||
|
virtual void requestCandidates( const QString& preedit ) = 0;
|
||||||
|
virtual void resetCandidates() = 0;
|
||||||
|
|
||||||
|
QskInputContext* context() const;
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void candidatesChanged();
|
void candidatesChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void clearPreedit();
|
QString m_preedit;
|
||||||
QskInputContext* context() const;
|
const Attributes m_attributes;
|
||||||
|
|
||||||
void sendPreeditTextEvent( const QString& );
|
|
||||||
void sendKeyEvents( int key );
|
|
||||||
|
|
||||||
class PrivateData;
|
|
||||||
std::unique_ptr< PrivateData > m_data;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
inline QString QskInputCompositionModel::preeditText() const
|
||||||
|
{
|
||||||
|
return m_preedit;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline QskInputCompositionModel::Attributes
|
||||||
|
QskInputCompositionModel::attributes() const
|
||||||
|
{
|
||||||
|
return m_attributes;
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -18,9 +18,52 @@
|
|||||||
#include <QskSetup.h>
|
#include <QskSetup.h>
|
||||||
#include <QskEvent.h>
|
#include <QskEvent.h>
|
||||||
|
|
||||||
#include <QGuiApplication>
|
#include <QTextCharFormat>
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
#include <QPointer>
|
#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 )
|
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
|
class QskInputContext::PrivateData
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -95,8 +143,7 @@ public:
|
|||||||
QskPopup* inputPopup = nullptr;
|
QskPopup* inputPopup = nullptr;
|
||||||
QskWindow* inputWindow = nullptr;
|
QskWindow* inputWindow = nullptr;
|
||||||
|
|
||||||
QskInputCompositionModel* compositionModel;
|
QHash< uint, QskInputCompositionModel* > compositionModels;
|
||||||
QHash< QLocale, QskInputCompositionModel* > compositionModels;
|
|
||||||
|
|
||||||
// the input panel is embedded in a window
|
// the input panel is embedded in a window
|
||||||
bool ownsInputPanelWindow : 1;
|
bool ownsInputPanelWindow : 1;
|
||||||
@ -108,21 +155,15 @@ QskInputContext::QskInputContext():
|
|||||||
setObjectName( "InputContext" );
|
setObjectName( "InputContext" );
|
||||||
|
|
||||||
#if 1
|
#if 1
|
||||||
m_data->compositionModel = new QskInputCompositionModel( this );
|
setCompositionModel( locale(), new QskHunspellCompositionModel( this ) );
|
||||||
#else
|
|
||||||
m_data->compositionModel = new QskHunspellCompositionModel( this );
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
connect( m_data->compositionModel, &QskInputCompositionModel::candidatesChanged,
|
|
||||||
this, &QskInputContext::handleCandidatesChanged );
|
|
||||||
|
|
||||||
connect( qskSetup, &QskSetup::inputPanelChanged,
|
|
||||||
this, &QskInputContext::setInputPanel );
|
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
setCompositionModel( QLocale::Chinese, new QskPinyinCompositionModel( this ) );
|
setCompositionModel( QLocale::Chinese, new QskPinyinCompositionModel( this ) );
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
connect( qskSetup, &QskSetup::inputPanelChanged,
|
||||||
|
this, &QskInputContext::setInputPanel );
|
||||||
|
|
||||||
setInputPanel( qskSetup->inputPanel() );
|
setInputPanel( qskSetup->inputPanel() );
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,23 +255,33 @@ void QskInputContext::update( Qt::InputMethodQueries queries )
|
|||||||
{
|
{
|
||||||
const auto locale = queryEvent.value( Qt::ImPreferredLanguage ).toLocale();
|
const auto locale = queryEvent.value( Qt::ImPreferredLanguage ).toLocale();
|
||||||
|
|
||||||
auto oldModel = compositionModel();
|
const auto oldModel = compositionModel();
|
||||||
|
|
||||||
if( m_data->inputPanel )
|
if( m_data->inputPanel )
|
||||||
qskSetLocale( m_data->inputPanel, locale );
|
qskSetLocale( m_data->inputPanel, locale );
|
||||||
|
|
||||||
auto newModel = compositionModel();
|
auto newModel = compositionModel();
|
||||||
|
|
||||||
if( oldModel != newModel )
|
if( newModel && ( oldModel != newModel ) )
|
||||||
{
|
{
|
||||||
connect( newModel, &QskInputCompositionModel::candidatesChanged,
|
connect( newModel, &QskInputCompositionModel::candidatesChanged,
|
||||||
this, &QskInputContext::handleCandidatesChanged );
|
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::ImMicroFocus
|
||||||
Qt::ImCursorRectangle
|
Qt::ImCursorRectangle
|
||||||
@ -496,14 +547,6 @@ void QskInputContext::setFocusObject( QObject* focusObject )
|
|||||||
if ( isAccepted )
|
if ( isAccepted )
|
||||||
setInputItem( focusItem );
|
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(
|
void QskInputContext::setCompositionModel(
|
||||||
@ -511,9 +554,11 @@ void QskInputContext::setCompositionModel(
|
|||||||
{
|
{
|
||||||
auto& models = m_data->compositionModels;
|
auto& models = m_data->compositionModels;
|
||||||
|
|
||||||
|
const auto key = qskHashLocale( locale );
|
||||||
|
|
||||||
if ( model )
|
if ( model )
|
||||||
{
|
{
|
||||||
const auto it = models.find( locale );
|
const auto it = models.find( key );
|
||||||
if ( it != models.end() )
|
if ( it != models.end() )
|
||||||
{
|
{
|
||||||
if ( it.value() == model )
|
if ( it.value() == model )
|
||||||
@ -524,7 +569,7 @@ void QskInputContext::setCompositionModel(
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
models.insert( locale, model );
|
models.insert( key, model );
|
||||||
}
|
}
|
||||||
|
|
||||||
connect( model, &QskInputCompositionModel::candidatesChanged,
|
connect( model, &QskInputCompositionModel::candidatesChanged,
|
||||||
@ -532,7 +577,7 @@ void QskInputContext::setCompositionModel(
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
const auto it = models.find( locale );
|
const auto it = models.find( key );
|
||||||
if ( it != models.end() )
|
if ( it != models.end() )
|
||||||
{
|
{
|
||||||
delete it.value();
|
delete it.value();
|
||||||
@ -543,23 +588,33 @@ void QskInputContext::setCompositionModel(
|
|||||||
|
|
||||||
QskInputCompositionModel* QskInputContext::compositionModel() const
|
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 )
|
void QskInputContext::invokeAction( QInputMethod::Action action, int value )
|
||||||
{
|
{
|
||||||
auto model = compositionModel();
|
|
||||||
|
|
||||||
switch ( static_cast< int >( action ) )
|
switch ( static_cast< int >( action ) )
|
||||||
{
|
{
|
||||||
case QskInputPanel::Compose:
|
case QskInputPanel::Compose:
|
||||||
{
|
{
|
||||||
model->composeKey( value );
|
processKey( value );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case QskInputPanel::SelectCandidate:
|
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;
|
break;
|
||||||
}
|
}
|
||||||
case QInputMethod::Click:
|
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()
|
void QskInputContext::handleCandidatesChanged()
|
||||||
{
|
{
|
||||||
const auto model = compositionModel();
|
const auto model = compositionModel();
|
||||||
@ -628,11 +807,7 @@ void QskInputContext::setInputPanel( QQuickItem* inputPanel )
|
|||||||
this, &QPlatformInputContext::emitLocaleChanged );
|
this, &QPlatformInputContext::emitLocaleChanged );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( model )
|
qskSetCandidatesEnabled( inputPanel, model != nullptr );
|
||||||
{
|
|
||||||
qskSetCandidatesEnabled( m_data->inputPanel,
|
|
||||||
model->supportsSuggestions() );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -668,8 +843,6 @@ bool QskInputContext::eventFilter( QObject* object, QEvent* event )
|
|||||||
}
|
}
|
||||||
case QEvent::DeferredDelete:
|
case QEvent::DeferredDelete:
|
||||||
{
|
{
|
||||||
object->removeEventFilter( this );
|
|
||||||
qGuiApp->removeEventFilter( this );
|
|
||||||
m_data->inputWindow = nullptr;
|
m_data->inputWindow = nullptr;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -677,45 +850,20 @@ bool QskInputContext::eventFilter( QObject* object, QEvent* event )
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else if ( object == m_data->inputPopup )
|
||||||
{
|
{
|
||||||
switch( static_cast< int >( event->type() ) )
|
switch( static_cast< int >( event->type() ) )
|
||||||
{
|
{
|
||||||
case QskEvent::GeometryChange:
|
case QskEvent::GeometryChange:
|
||||||
{
|
{
|
||||||
if ( object == m_data->inputPanel )
|
if ( event->type() == QskEvent::GeometryChange )
|
||||||
{
|
emitKeyboardRectChanged();
|
||||||
if ( event->type() == QskEvent::GeometryChange )
|
|
||||||
emitKeyboardRectChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
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:
|
case QEvent::DeferredDelete:
|
||||||
{
|
{
|
||||||
if ( object == m_data->inputPopup )
|
m_data->inputPopup = nullptr;
|
||||||
{
|
|
||||||
object->removeEventFilter( this );
|
|
||||||
qGuiApp->removeEventFilter( this );
|
|
||||||
m_data->inputPopup = nullptr;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -724,10 +872,9 @@ bool QskInputContext::eventFilter( QObject* object, QEvent* event )
|
|||||||
return Inherited::eventFilter( object, 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
|
// called from QXcbKeyboard, but what about other platforms
|
||||||
Q_UNUSED( event )
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -735,15 +882,51 @@ QInputMethodQueryEvent QskInputContext::queryInputMethod(
|
|||||||
Qt::InputMethodQueries queries ) const
|
Qt::InputMethodQueries queries ) const
|
||||||
{
|
{
|
||||||
QInputMethodQueryEvent event( queries );
|
QInputMethodQueryEvent event( queries );
|
||||||
sendEventToInputItem( &event );
|
|
||||||
|
if ( m_data->inputItem )
|
||||||
|
QCoreApplication::sendEvent( m_data->inputItem, &event );
|
||||||
|
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
|
|
||||||
void QskInputContext::sendEventToInputItem( QEvent* event ) const
|
void QskInputContext::sendText(
|
||||||
|
const QString& text, bool isFinal ) const
|
||||||
{
|
{
|
||||||
if ( m_data->inputItem && event )
|
if ( m_data->inputItem == nullptr )
|
||||||
QCoreApplication::sendEvent( m_data->inputItem, event );
|
return;
|
||||||
|
|
||||||
|
if ( isFinal )
|
||||||
|
{
|
||||||
|
QInputMethodEvent event;
|
||||||
|
event.setCommitString( text );
|
||||||
|
|
||||||
|
QCoreApplication::sendEvent( m_data->inputItem, &event );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QTextCharFormat format;
|
||||||
|
format.setFontUnderline( true );
|
||||||
|
|
||||||
|
const QInputMethodEvent::Attribute attribute(
|
||||||
|
QInputMethodEvent::TextFormat, 0, text.length(), format );
|
||||||
|
|
||||||
|
QInputMethodEvent event( text, { attribute } );
|
||||||
|
|
||||||
|
QCoreApplication::sendEvent( m_data->inputItem, &event );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void QskInputContext::sendKey( int key ) const
|
||||||
|
{
|
||||||
|
if ( m_data->inputItem == nullptr )
|
||||||
|
return;
|
||||||
|
|
||||||
|
QKeyEvent keyPress( QEvent::KeyPress, key, Qt::NoModifier );
|
||||||
|
QCoreApplication::sendEvent( m_data->inputItem, &keyPress );
|
||||||
|
|
||||||
|
QKeyEvent keyRelease( QEvent::KeyRelease, key, Qt::NoModifier );
|
||||||
|
QCoreApplication::sendEvent( m_data->inputItem, &keyRelease );
|
||||||
}
|
}
|
||||||
|
|
||||||
#include "moc_QskInputContext.cpp"
|
#include "moc_QskInputContext.cpp"
|
||||||
|
@ -10,8 +10,12 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
class QskInputCompositionModel;
|
class QskInputCompositionModel;
|
||||||
|
|
||||||
class QQuickItem;
|
class QQuickItem;
|
||||||
|
|
||||||
class QInputMethodQueryEvent;
|
class QInputMethodQueryEvent;
|
||||||
|
class QInputMethodEvent;
|
||||||
|
class QKeyEvent;
|
||||||
|
|
||||||
class QskInputContext : public QPlatformInputContext
|
class QskInputContext : public QPlatformInputContext
|
||||||
{
|
{
|
||||||
@ -51,7 +55,12 @@ public:
|
|||||||
virtual bool filterEvent( const QEvent* ) override;
|
virtual bool filterEvent( const QEvent* ) override;
|
||||||
|
|
||||||
QInputMethodQueryEvent queryInputMethod( Qt::InputMethodQueries ) const;
|
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:
|
private Q_SLOTS:
|
||||||
void handleCandidatesChanged();
|
void handleCandidatesChanged();
|
||||||
@ -60,6 +69,8 @@ private Q_SLOTS:
|
|||||||
virtual bool eventFilter( QObject*, QEvent* ) override;
|
virtual bool eventFilter( QObject*, QEvent* ) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void processKey( int key );
|
||||||
|
|
||||||
void setInputItem( QQuickItem* );
|
void setInputItem( QQuickItem* );
|
||||||
QskInputCompositionModel* compositionModel() const;
|
QskInputCompositionModel* compositionModel() const;
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
QskPinyinCompositionModel::QskPinyinCompositionModel( QskInputContext* context ):
|
QskPinyinCompositionModel::QskPinyinCompositionModel( QskInputContext* context ):
|
||||||
Inherited( context ),
|
Inherited( Attributes(), context ),
|
||||||
m_data( new PrivateData )
|
m_data( new PrivateData )
|
||||||
{
|
{
|
||||||
#if 1
|
#if 1
|
||||||
@ -39,11 +39,6 @@ QskPinyinCompositionModel::~QskPinyinCompositionModel()
|
|||||||
ime_pinyin::im_close_decoder();
|
ime_pinyin::im_close_decoder();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QskPinyinCompositionModel::supportsSuggestions() const
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int QskPinyinCompositionModel::candidateCount() const
|
int QskPinyinCompositionModel::candidateCount() const
|
||||||
{
|
{
|
||||||
return m_data->candidates.count();
|
return m_data->candidates.count();
|
||||||
@ -57,53 +52,54 @@ QString QskPinyinCompositionModel::candidate( int index ) const
|
|||||||
return QString();
|
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 ( !m_data->candidates.isEmpty() )
|
||||||
{
|
|
||||||
if( preedit.isEmpty() )
|
|
||||||
{
|
{
|
||||||
ime_pinyin::im_reset_search();
|
m_data->candidates.clear();
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
Q_EMIT candidatesChanged();
|
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();
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
#define QSK_PINYIN_COMPOSITION_MODEL_H
|
#define QSK_PINYIN_COMPOSITION_MODEL_H
|
||||||
|
|
||||||
#include "QskInputCompositionModel.h"
|
#include "QskInputCompositionModel.h"
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
class QskPinyinCompositionModel : public QskInputCompositionModel
|
class QskPinyinCompositionModel : public QskInputCompositionModel
|
||||||
{
|
{
|
||||||
@ -16,15 +17,12 @@ public:
|
|||||||
QskPinyinCompositionModel( QskInputContext* );
|
QskPinyinCompositionModel( QskInputContext* );
|
||||||
virtual ~QskPinyinCompositionModel() override;
|
virtual ~QskPinyinCompositionModel() override;
|
||||||
|
|
||||||
virtual bool supportsSuggestions() const override final;
|
|
||||||
|
|
||||||
virtual int candidateCount() const override;
|
virtual int candidateCount() const override;
|
||||||
virtual QString candidate( int ) const override;
|
virtual QString candidate( int ) const override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// Used for text composition
|
virtual void requestCandidates( const QString& ) override;
|
||||||
virtual bool hasIntermediate() const override;
|
virtual void resetCandidates() override;
|
||||||
virtual QString polishPreedit( const QString& preedit ) override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
class PrivateData;
|
class PrivateData;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user