improving inputcontext

This commit is contained in:
Uwe Rathmann 2018-04-04 15:19:51 +02:00
parent 26f8e1b936
commit 67052eb60a
8 changed files with 89 additions and 119 deletions

View File

@ -10,13 +10,12 @@ public:
QVector< QString > candidates; QVector< QString > candidates;
}; };
QskHunspellCompositionModel::QskHunspellCompositionModel( QObject* parent ): QskHunspellCompositionModel::QskHunspellCompositionModel( QskInputContext* context ):
Inherited( parent ), Inherited( context ),
m_data( new PrivateData() ) m_data( new PrivateData() )
{ {
#if 1 #if 1
// ship with code if license allows: // TODO: loading the language specific one depending on the locale
// loading the language specific one depending on the locale
m_data->hunspellHandle = Hunspell_create( m_data->hunspellHandle = Hunspell_create(
"/usr/share/hunspell/en_US.aff", "/usr/share/hunspell/en_US.aff",

View File

@ -13,7 +13,7 @@ class QskHunspellCompositionModel : public QskInputCompositionModel
using Inherited = QskInputCompositionModel; using Inherited = QskInputCompositionModel;
public: public:
QskHunspellCompositionModel( QObject* parent = nullptr ); QskHunspellCompositionModel( QskInputContext* context );
virtual ~QskHunspellCompositionModel() override; virtual ~QskHunspellCompositionModel() override;
virtual bool supportsSuggestions() const override final; virtual bool supportsSuggestions() const override final;
@ -24,8 +24,8 @@ public:
protected: protected:
virtual bool hasIntermediate() const override; virtual bool hasIntermediate() const override;
virtual QString polishPreedit( const QString& preedit ) override; virtual QString polishPreedit( const QString& ) override;
virtual bool isComposable( const QStringRef& preedit ) const override; virtual bool isComposable( const QStringRef& ) const override;
private: private:
class PrivateData; class PrivateData;

View File

@ -4,11 +4,11 @@
*****************************************************************************/ *****************************************************************************/
#include "QskInputCompositionModel.h" #include "QskInputCompositionModel.h"
#include "QskInputContext.h"
#include <QGuiApplication> #include <QGuiApplication>
#include <QInputMethodEvent> #include <QInputMethodEvent>
#include <QTextCharFormat> #include <QTextCharFormat>
#include <QWindow>
static inline QString qskKeyString( int code ) static inline QString qskKeyString( int code )
{ {
@ -36,38 +36,20 @@ static inline QString qskKeyString( int code )
return QChar( code ); return QChar( code );
} }
static inline void qskSendKeyEvents( QObject* receiver, int key )
{
QKeyEvent keyPress( QEvent::KeyPress, key, Qt::NoModifier );
QCoreApplication::sendEvent( receiver, &keyPress );
QKeyEvent keyRelease( QEvent::KeyRelease, key, Qt::NoModifier );
QCoreApplication::sendEvent( receiver, &keyRelease );
}
class QskInputCompositionModel::PrivateData class QskInputCompositionModel::PrivateData
{ {
public: public:
PrivateData() :
inputItem( nullptr ),
groupIndex( 0 )
{
}
// QInputMethod
QString preedit; QString preedit;
QTextCharFormat preeditFormat; QTextCharFormat preeditFormat;
QList< QInputMethodEvent::Attribute > preeditAttributes; QList< QInputMethodEvent::Attribute > preeditAttributes;
QObject* inputItem; int groupIndex = 0;
int groupIndex;
}; };
QskInputCompositionModel::QskInputCompositionModel( QObject* parent ): QskInputCompositionModel::QskInputCompositionModel( QskInputContext* context ):
QObject( parent ), QObject( context ),
m_data( new PrivateData ) m_data( new PrivateData )
{ {
m_data->groupIndex = 0;
m_data->preeditFormat.setFontUnderline( true ); m_data->preeditFormat.setFontUnderline( true );
m_data->preeditAttributes.append( QInputMethodEvent::Attribute( m_data->preeditAttributes.append( QInputMethodEvent::Attribute(
QInputMethodEvent::TextFormat, 0, 0, m_data->preeditFormat ) ); QInputMethodEvent::TextFormat, 0, 0, m_data->preeditFormat ) );
@ -77,6 +59,11 @@ QskInputCompositionModel::~QskInputCompositionModel()
{ {
} }
QskInputContext* QskInputCompositionModel::context() const
{
return qobject_cast< QskInputContext* >( parent() );
}
bool QskInputCompositionModel::supportsSuggestions() const bool QskInputCompositionModel::supportsSuggestions() const
{ {
return false; return false;
@ -91,23 +78,16 @@ void QskInputCompositionModel::composeKey( Qt::Key key )
* TODO * TODO
*/ */
auto inputMethod = QGuiApplication::inputMethod(); const auto queryEvent = context()->queryInputMethod(
if ( !inputMethod )
return;
if ( !m_data->inputItem )
return;
QInputMethodQueryEvent queryEvent(
Qt::ImSurroundingText | Qt::ImMaximumTextLength | Qt::ImHints ); Qt::ImSurroundingText | Qt::ImMaximumTextLength | Qt::ImHints );
QCoreApplication::sendEvent( m_data->inputItem, &queryEvent );
const auto hints = static_cast< Qt::InputMethodHints >( const auto hints = static_cast< Qt::InputMethodHints >(
queryEvent.value( Qt::ImHints ).toInt() ); queryEvent.value( Qt::ImHints ).toInt() );
const int maxLength = queryEvent.value( Qt::ImMaximumTextLength ).toInt(); const int maxLength = queryEvent.value( Qt::ImMaximumTextLength ).toInt();
const int currentLength = queryEvent.value( Qt::ImSurroundingText ).toString().length(); const int currentLength = queryEvent.value( Qt::ImSurroundingText ).toString().length();
int spaceLeft = -1; int spaceLeft = -1;
if ( !( hints& Qt::ImhMultiLine ) && maxLength > 0 ) if ( !( hints & Qt::ImhMultiLine ) && maxLength > 0 )
spaceLeft = maxLength - currentLength; spaceLeft = maxLength - currentLength;
switch ( key ) switch ( key )
@ -115,7 +95,21 @@ void QskInputCompositionModel::composeKey( Qt::Key key )
case Qt::Key_Backspace: case Qt::Key_Backspace:
case Qt::Key_Muhenkan: case Qt::Key_Muhenkan:
{ {
backspace(); if ( !m_data->preedit.isEmpty() )
{
m_data->preedit.chop( 1 );
const QString displayText = polishPreedit( m_data->preedit );
m_data->preeditAttributes.first().length = displayText.length();
QInputMethodEvent event( displayText, m_data->preeditAttributes );
sendCompositionEvent( &event );
}
else
{
// Backspace one character only if preedit was inactive
sendKeyEvents( Qt::Key_Backspace );
}
return; return;
} }
case Qt::Key_Space: case Qt::Key_Space:
@ -150,8 +144,7 @@ void QskInputCompositionModel::composeKey( Qt::Key key )
} }
else else
{ {
if( auto focusWindow = QGuiApplication::focusWindow() ) sendKeyEvents( Qt::Key_Return );
qskSendKeyEvents( focusWindow, Qt::Key_Return );
} }
return; return;
@ -160,7 +153,9 @@ void QskInputCompositionModel::composeKey( Qt::Key key )
case Qt::Key_Left: case Qt::Key_Left:
case Qt::Key_Right: case Qt::Key_Right:
{ {
moveCursor( key ); if ( m_data->preedit.isEmpty() )
sendKeyEvents( key );
return; return;
} }
default: default:
@ -256,54 +251,20 @@ void QskInputCompositionModel::commitCandidate( int index )
commit( candidate( index ) ); commit( candidate( index ) );
} }
void QskInputCompositionModel::backspace() void QskInputCompositionModel::sendCompositionEvent( QInputMethodEvent* event )
{ {
if ( m_data->inputItem == nullptr ) context()->sendEventToInputItem( event );
return;
if ( !m_data->preedit.isEmpty() )
{
m_data->preedit.chop( 1 );
const QString displayText = polishPreedit( m_data->preedit );
m_data->preeditAttributes.first().length = displayText.length();
QInputMethodEvent event( displayText, m_data->preeditAttributes );
sendCompositionEvent( &event );
}
else
{
// Backspace one character only if preedit was inactive
qskSendKeyEvents( m_data->inputItem, Qt::Key_Backspace );
}
} }
void QskInputCompositionModel::moveCursor( Qt::Key key ) void QskInputCompositionModel::sendKeyEvents( int key )
{ {
if ( key != Qt::Key_Left && key != Qt::Key_Right ) auto context = this->context();
return;
if ( !m_data->inputItem ) QKeyEvent keyPress( QEvent::KeyPress, key, Qt::NoModifier );
return; context->sendEventToInputItem( &keyPress );
// Moving cursor is disabled when preedit is active. QKeyEvent keyRelease( QEvent::KeyRelease, key, Qt::NoModifier );
if ( !m_data->preedit.isEmpty() ) context->sendEventToInputItem( &keyRelease );
return;
QKeyEvent moveCursorPress( QEvent::KeyPress, key, Qt::NoModifier );
QKeyEvent moveCursorRelease( QEvent::KeyRelease, key, Qt::NoModifier );
#if 1
QFocusEvent focusIn( QEvent::FocusIn ); // hack to display the cursor
#endif
QCoreApplication::sendEvent( m_data->inputItem, &focusIn );
QCoreApplication::sendEvent( m_data->inputItem, &moveCursorPress );
QCoreApplication::sendEvent( m_data->inputItem, &moveCursorRelease );
}
void QskInputCompositionModel::sendCompositionEvent( QInputMethodEvent* e )
{
if ( m_data->inputItem )
QCoreApplication::sendEvent( m_data->inputItem, e );
} }
bool QskInputCompositionModel::hasIntermediate() const bool QskInputCompositionModel::hasIntermediate() const
@ -340,11 +301,6 @@ QVector< Qt::Key > QskInputCompositionModel::groups() const
return QVector< Qt::Key >(); return QVector< Qt::Key >();
} }
void QskInputCompositionModel::setInputItem( QObject* inputItem )
{
m_data->inputItem = inputItem;
}
bool QskInputCompositionModel::nextGroupIndex( int& index, bool forward ) const bool QskInputCompositionModel::nextGroupIndex( int& index, bool forward ) const
{ {
Q_UNUSED( index ); Q_UNUSED( index );

View File

@ -11,7 +11,7 @@
#include <memory> #include <memory>
class QInputMethodEvent; class QInputMethodEvent;
class QStringList; class QskInputContext;
class QskInputCompositionModel : public QObject class QskInputCompositionModel : public QObject
{ {
@ -20,7 +20,7 @@ class QskInputCompositionModel : public QObject
Q_PROPERTY( QVector< Qt::Key > groups READ groups NOTIFY groupsChanged ) Q_PROPERTY( QVector< Qt::Key > groups READ groups NOTIFY groupsChanged )
public: public:
QskInputCompositionModel( QObject* parent = nullptr ); QskInputCompositionModel( QskInputContext* context );
virtual ~QskInputCompositionModel(); virtual ~QskInputCompositionModel();
// to determine whether to show the suggestion bar: // to determine whether to show the suggestion bar:
@ -41,22 +41,21 @@ public:
virtual QVector< Qt::Key > groups() const; virtual QVector< Qt::Key > groups() const;
void setInputItem( QObject* inputItem );
protected: protected:
// Used for text composition // Used for text composition
virtual bool hasIntermediate() const; virtual bool hasIntermediate() const;
virtual QString polishPreedit( const QString& preedit ); virtual QString polishPreedit( const QString& preedit );
virtual bool isComposable( const QStringRef& preedit ) const; virtual bool isComposable( const QStringRef& preedit ) const;
QskInputContext* context() const;
Q_SIGNALS: Q_SIGNALS:
void groupsChanged( const QVector< Qt::Key >& ); void groupsChanged( const QVector< Qt::Key >& );
void candidatesChanged(); void candidatesChanged();
private: private:
void backspace();
void moveCursor( Qt::Key key );
void sendCompositionEvent( QInputMethodEvent* e ); void sendCompositionEvent( QInputMethodEvent* e );
void sendKeyEvents( int key );
class PrivateData; class PrivateData;
std::unique_ptr< PrivateData > m_data; std::unique_ptr< PrivateData > m_data;

View File

@ -26,7 +26,7 @@ public:
QHash< QLocale, QskInputCompositionModel* > compositionModels; QHash< QLocale, QskInputCompositionModel* > compositionModels;
}; };
QskInputContext::QskInputContext() : QskInputContext::QskInputContext():
m_data( new PrivateData() ) m_data( new PrivateData() )
{ {
setObjectName( "InputContext" ); setObjectName( "InputContext" );
@ -70,8 +70,7 @@ void QskInputContext::update( Qt::InputMethodQueries queries )
if ( m_data->inputItem == nullptr ) if ( m_data->inputItem == nullptr )
return; return;
QInputMethodQueryEvent queryEvent( queries ); const auto queryEvent = queryInputMethod( queries );
QCoreApplication::sendEvent( m_data->inputItem, &queryEvent );
// Qt::ImCursorRectangle // Qt::ImCursorRectangle
// Qt::ImFont // Qt::ImFont
@ -83,9 +82,10 @@ void QskInputContext::update( Qt::InputMethodQueries queries )
if ( queries & Qt::ImHints ) if ( queries & Qt::ImHints )
{ {
const Qt::InputMethodHints hints = #if 0
static_cast< Qt::InputMethodHints >( queryEvent.value( Qt::ImHints ).toInt() ); const auto hints = static_cast< Qt::InputMethodHints >(
Q_UNUSED( hints ); queryEvent.value( Qt::ImHints ).toInt() );
//ImhHiddenText = 0x1, // might need to disable certain checks //ImhHiddenText = 0x1, // might need to disable certain checks
//ImhSensitiveData = 0x2, // shouldn't change anything //ImhSensitiveData = 0x2, // shouldn't change anything
//ImhNoAutoUppercase = 0x4, // if we support auto uppercase, disable it //ImhNoAutoUppercase = 0x4, // if we support auto uppercase, disable it
@ -109,6 +109,7 @@ void QskInputContext::update( Qt::InputMethodQueries queries )
//ImhEmailCharactersOnly // disable certain symbols (email-only kb?) //ImhEmailCharactersOnly // disable certain symbols (email-only kb?)
//ImhUrlCharactersOnly // disable certain symbols (url-only kb?) //ImhUrlCharactersOnly // disable certain symbols (url-only kb?)
//ImhLatinOnly // disable chinese input //ImhLatinOnly // disable chinese input
#endif
} }
if ( queries & Qt::ImPreferredLanguage ) if ( queries & Qt::ImPreferredLanguage )
@ -149,6 +150,11 @@ QQuickItem* QskInputContext::inputItem()
return m_data->inputItem; return m_data->inputItem;
} }
void QskInputContext::setInputItem( QQuickItem* item )
{
m_data->inputItem = item;
}
QRectF QskInputContext::keyboardRect() const QRectF QskInputContext::keyboardRect() const
{ {
if ( m_data->inputPanel if ( m_data->inputPanel
@ -232,8 +238,7 @@ void QskInputContext::setFocusObject( QObject* focusObject )
{ {
if ( focusObject == nullptr ) if ( focusObject == nullptr )
{ {
m_data->inputItem = nullptr; setInputItem( nullptr );
compositionModel()->setInputItem( nullptr );
return; return;
} }
@ -245,17 +250,14 @@ void QskInputContext::setFocusObject( QObject* focusObject )
// Do not change the input item when panel buttons get the focus: // Do not change the input item when panel buttons get the focus:
if( qskNearestFocusScope( focusItem ) != m_data->inputPanel ) if( qskNearestFocusScope( focusItem ) != m_data->inputPanel )
{ {
m_data->inputItem = focusItem; setInputItem( focusItem );
compositionModel()->setInputItem( focusItem );
inputItemChanged = true; inputItemChanged = true;
} }
} }
if( inputItemChanged ) if( inputItemChanged )
{ {
QInputMethodQueryEvent queryEvent( Qt::ImEnabled ); const auto queryEvent = queryInputMethod( Qt::ImEnabled );
QCoreApplication::sendEvent( m_data->inputItem, &queryEvent );
if ( !queryEvent.value( Qt::ImEnabled ).toBool() ) if ( !queryEvent.value( Qt::ImEnabled ).toBool() )
{ {
hideInputPanel(); hideInputPanel();
@ -403,17 +405,26 @@ void QskInputContext::commit()
{ {
} }
bool QskInputContext::eventFilter( QObject* object, QEvent* event ) bool QskInputContext::filterEvent( const QEvent* event )
{ {
if ( object == m_data->inputItem ) // called from QXcbKeyboard, but what about other platforms
return filterEvent( event ); Q_UNUSED( event )
return false; return false;
} }
bool QskInputContext::filterEvent( const QEvent* ) QInputMethodQueryEvent QskInputContext::queryInputMethod(
Qt::InputMethodQueries queries ) const
{ {
return false; QInputMethodQueryEvent event( queries );
sendEventToInputItem( &event );
return event;
}
void QskInputContext::sendEventToInputItem( QEvent* event ) const
{
if ( m_data->inputItem && event )
QCoreApplication::sendEvent( m_data->inputItem, event );
} }
#include "moc_QskInputContext.cpp" #include "moc_QskInputContext.cpp"

View File

@ -12,6 +12,7 @@
class QskVirtualKeyboard; class QskVirtualKeyboard;
class QskInputCompositionModel; class QskInputCompositionModel;
class QQuickItem; class QQuickItem;
class QInputMethodQueryEvent;
class QskInputContext : public QPlatformInputContext class QskInputContext : public QPlatformInputContext
{ {
@ -48,14 +49,17 @@ public:
Q_INVOKABLE QQuickItem* inputItem(); Q_INVOKABLE QQuickItem* inputItem();
virtual bool eventFilter( QObject*, QEvent * ) override;
virtual bool filterEvent( const QEvent* ) override; virtual bool filterEvent( const QEvent* ) override;
QInputMethodQueryEvent queryInputMethod( Qt::InputMethodQueries ) const;
void sendEventToInputItem( QEvent* ) const;
private Q_SLOTS: private Q_SLOTS:
void handleCandidatesChanged(); void handleCandidatesChanged();
void setInputPanel( QskVirtualKeyboard* ); void setInputPanel( QskVirtualKeyboard* );
private: private:
void setInputItem( QQuickItem* );
QskInputCompositionModel* compositionModel() const; QskInputCompositionModel* compositionModel() const;
class PrivateData; class PrivateData;

View File

@ -4,6 +4,7 @@
*****************************************************************************/ *****************************************************************************/
#include "QskPinyinCompositionModel.h" #include "QskPinyinCompositionModel.h"
#include "QskInputContext.h"
#include "pinyinime.h" #include "pinyinime.h"
@ -18,8 +19,8 @@ public:
QVector< Qt::Key > groups; QVector< Qt::Key > groups;
}; };
QskPinyinCompositionModel::QskPinyinCompositionModel( QObject* parent ): QskPinyinCompositionModel::QskPinyinCompositionModel( QskInputContext* context ):
Inherited( parent ), Inherited( context ),
m_data( new PrivateData ) m_data( new PrivateData )
{ {
#if 1 #if 1

View File

@ -13,7 +13,7 @@ class QskPinyinCompositionModel : public QskInputCompositionModel
using Inherited = QskInputCompositionModel; using Inherited = QskInputCompositionModel;
public: public:
QskPinyinCompositionModel( QObject* parent = nullptr ); QskPinyinCompositionModel( QskInputContext* );
virtual ~QskPinyinCompositionModel() override; virtual ~QskPinyinCompositionModel() override;
virtual bool supportsSuggestions() const override final; virtual bool supportsSuggestions() const override final;