more keyboard refactoring

This commit is contained in:
Peter Hartmann 2018-03-29 14:48:38 +02:00
parent 66677067d5
commit bcd0bc94c0
6 changed files with 143 additions and 82 deletions

View File

@ -23,15 +23,19 @@ static QString qskKeyString( int code )
case Qt::Key_Backspace: case Qt::Key_Backspace:
case Qt::Key_Muhenkan: case Qt::Key_Muhenkan:
return QString(); return QString();
case Qt::Key_Return: case Qt::Key_Return:
case Qt::Key_Kanji: case Qt::Key_Kanji:
return QChar(QChar::CarriageReturn); return QChar( QChar::CarriageReturn );
case Qt::Key_Space: case Qt::Key_Space:
return QChar(QChar::Space); return QChar( QChar::Space );
default: default:
break; break;
} }
return QChar(code);
return QChar( code );
} }
class QskInputCompositionModel::PrivateData class QskInputCompositionModel::PrivateData
@ -51,6 +55,14 @@ public:
int groupIndex; int groupIndex;
}; };
static inline void sendCompositionEvent( QInputMethodEvent* e )
{
if( auto focusObject = QGuiApplication::focusObject() )
{
QCoreApplication::sendEvent( focusObject, e );
}
}
QskInputCompositionModel::QskInputCompositionModel(): QskInputCompositionModel::QskInputCompositionModel():
m_data( new PrivateData ) m_data( new PrivateData )
{ {
@ -67,6 +79,13 @@ QskInputCompositionModel::~QskInputCompositionModel()
void QskInputCompositionModel::composeKey( Qt::Key key ) void QskInputCompositionModel::composeKey( Qt::Key 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
*/
auto inputMethod = QGuiApplication::inputMethod(); auto inputMethod = QGuiApplication::inputMethod();
if ( !inputMethod ) if ( !inputMethod )
return; return;
@ -96,35 +115,51 @@ void QskInputCompositionModel::composeKey( Qt::Key key )
} }
case Qt::Key_Space: case Qt::Key_Space:
{ {
if ( !spaceLeft ) if( !spaceLeft )
{
return; return;
}
if ( !m_data->preedit.isEmpty() ) if( !m_data->preedit.isEmpty() )
{ {
if ( candidateCount() > 0 ) // Commit first candidate commit( m_data->preedit.left( spaceLeft ) );
commit( QChar( candidate( 0 ) ) );
else // Commit what is in the buffer
commit( m_data->preedit.left( spaceLeft ) );
}
else
{
commit( qskKeyString(key) );
} }
commit( qskKeyString( key ) );
return; return;
} }
case Qt::Key_Return: case Qt::Key_Return:
{ {
if ( !spaceLeft ) if ( !spaceLeft )
return; return;
// Commit what is in the buffer // Commit what is in the buffer
if ( !m_data->preedit.isEmpty() ) if( !m_data->preedit.isEmpty() )
{
commit( m_data->preedit.left( spaceLeft ) ); commit( m_data->preedit.left( spaceLeft ) );
else if ( hints & Qt::ImhMultiLine ) }
commit( qskKeyString( key ) ); else if( hints & Qt::ImhMultiLine )
{
commit( qskKeyString( key ) );
}
#if 0
else
{
auto focusWindow = QGuiApplication::focusWindow();
return; if( focusWindow )
{
QKeyEvent keyPress( QEvent::KeyPress, Qt::Key_Return, Qt::NoModifier );
QKeyEvent keyRelease( QEvent::KeyRelease, Qt::Key_Return, Qt::NoModifier ); QCoreApplication::sendEvent( focusWindow, &keyPress );
QCoreApplication::sendEvent( focusWindow, &keyRelease );
}
return;
}
#endif
} }
case Qt::Key_Left: case Qt::Key_Left:
case Qt::Key_Right: case Qt::Key_Right:
{ {
@ -169,16 +204,17 @@ void QskInputCompositionModel::composeKey( Qt::Key key )
m_data->preedit = qskKeyString( key ); m_data->preedit = qskKeyString( key );
displayPreedit = polishPreedit( m_data->preedit ); displayPreedit = polishPreedit( m_data->preedit );
if ( !hasIntermediate() ) if ( !hasIntermediate() )
{ {
commit(m_data->preedit); commit( m_data->preedit );
return; return;
} }
} }
m_data->preeditAttributes.first().length = displayPreedit.length(); m_data->preeditAttributes.first().length = displayPreedit.length();
QInputMethodEvent e(displayPreedit, m_data->preeditAttributes); QInputMethodEvent e( displayPreedit, m_data->preeditAttributes );
sendCompositionEvent(&e); sendCompositionEvent( &e );
} }
void QskInputCompositionModel::clearPreedit() void QskInputCompositionModel::clearPreedit()
@ -228,7 +264,7 @@ void QskInputCompositionModel::backspace()
if ( !m_data->preedit.isEmpty() ) if ( !m_data->preedit.isEmpty() )
{ {
m_data->preedit.chop(1); m_data->preedit.chop( 1 );
} }
else else
{ {
@ -258,7 +294,6 @@ void QskInputCompositionModel::moveCursor( Qt::Key key )
if ( !m_data->preedit.isEmpty() ) if ( !m_data->preedit.isEmpty() )
return; return;
// ### this should be in the panel:
QKeyEvent moveCursorPress( QEvent::KeyPress, key, Qt::NoModifier ); QKeyEvent moveCursorPress( QEvent::KeyPress, key, Qt::NoModifier );
QKeyEvent moveCursorRelease( QEvent::KeyRelease, key, Qt::NoModifier ); QKeyEvent moveCursorRelease( QEvent::KeyRelease, key, Qt::NoModifier );
#if 1 #if 1
@ -280,9 +315,9 @@ bool QskInputCompositionModel::hasIntermediate() const
return false; return false;
} }
bool QskInputCompositionModel::isComposable(const QStringRef& preedit) const bool QskInputCompositionModel::isComposable( const QStringRef& preedit ) const
{ {
Q_UNUSED(preedit); Q_UNUSED( preedit );
return false; return false;
} }
@ -313,10 +348,10 @@ void QskInputCompositionModel::setInputItem( QObject *inputItem )
m_data->inputItem = 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 );
Q_UNUSED(forward); Q_UNUSED( forward );
return false; return false;
} }

View File

@ -24,7 +24,7 @@ public:
virtual ~QskInputCompositionModel(); virtual ~QskInputCompositionModel();
void commit( const QString& ); void commit( const QString& );
void commitCandidate( int ); virtual void commitCandidate( int );
void composeKey( Qt::Key ); void composeKey( Qt::Key );
void clearPreedit(); void clearPreedit();
@ -33,7 +33,7 @@ public:
virtual Qt::Key candidate( int ) const; virtual Qt::Key candidate( int ) const;
int groupIndex() const; int groupIndex() const;
void setGroupIndex(int groupIndex); void setGroupIndex( int groupIndex );
virtual bool nextGroupIndex( int&, bool = true ) const; virtual bool nextGroupIndex( int&, bool = true ) const;
virtual QVector< Qt::Key > groups() const; virtual QVector< Qt::Key > groups() const;

View File

@ -14,20 +14,19 @@
#include <QskSetup.h> #include <QskSetup.h>
#include <QGuiApplication> #include <QGuiApplication>
#include <QQmlContext>
#include <QQmlEngine>
#include <QQuickItem>
#include <QQuickWindow>
#include <QRectF>
QskInputContext::QskInputContext(): QskInputContext::QskInputContext():
Inherited(), Inherited(),
m_inputCompositionModel( new QskInputCompositionModel ) m_defaultInputCompositionModel( new QskInputCompositionModel )
{ {
connect( qskSetup, &QskSetup::inputPanelChanged, connect( qskSetup, &QskSetup::inputPanelChanged,
this, &QskInputContext::setInputPanel ); this, &QskInputContext::setInputPanel );
setInputPanel( qskSetup->inputPanel() ); setInputPanel( qskSetup->inputPanel() );
QskPinyinCompositionModel* pinyinModel = new QskPinyinCompositionModel;
// For input methods outside skinny, call QskInputPanel::registerCompositionModelForLocale()
inputMethodRegistered( QLocale::Chinese, pinyinModel );
// We could connect candidatesChanged() here, but we don't emit // We could connect candidatesChanged() here, but we don't emit
// the signal in the normal composition model anyhow // the signal in the normal composition model anyhow
} }
@ -36,6 +35,11 @@ QskInputContext::~QskInputContext()
{ {
if ( m_inputPanel ) if ( m_inputPanel )
delete m_inputPanel; delete m_inputPanel;
for( int a = 0; a < m_inputModels.values().count(); a++ )
{
delete m_inputModels.values()[a];
}
} }
bool QskInputContext::isValid() const bool QskInputContext::isValid() const
@ -64,7 +68,7 @@ void QskInputContext::update( Qt::InputMethodQueries queries )
{ {
const Qt::InputMethodHints hints = const Qt::InputMethodHints hints =
static_cast< Qt::InputMethodHints >( queryEvent.value( Qt::ImHints ).toInt() ); static_cast< Qt::InputMethodHints >( queryEvent.value( Qt::ImHints ).toInt() );
Q_UNUSED(hints); Q_UNUSED( hints );
//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
@ -93,38 +97,28 @@ void QskInputContext::update( Qt::InputMethodQueries queries )
if ( queries & Qt::ImPreferredLanguage ) if ( queries & Qt::ImPreferredLanguage )
{ {
const auto locale = queryEvent.value( Qt::ImPreferredLanguage ).toLocale(); const auto locale = queryEvent.value( Qt::ImPreferredLanguage ).toLocale();
if ( m_inputPanel )
auto oldModel = currentInputCompositionModel();
if( m_inputPanel )
m_inputPanel->setLocale( locale ); m_inputPanel->setLocale( locale );
auto modelChanged = false; auto newModel = currentInputCompositionModel();
const bool currentModelIsPinyin =
dynamic_cast< QskPinyinCompositionModel* >( m_inputCompositionModel.get() );
const bool localeIsChinese = locale.language() == QLocale::Chinese;
if ( localeIsChinese && !currentModelIsPinyin )
{
if ( m_inputPanel )
m_inputPanel->disconnect( m_inputCompositionModel.get() );
m_inputCompositionModel.reset( new QskPinyinCompositionModel ); bool modelChanged = ( oldModel != newModel );
modelChanged = true;
}
else if ( !m_inputCompositionModel || ( !localeIsChinese && currentModelIsPinyin ) ) // Install default
{
if ( m_inputPanel )
m_inputPanel->disconnect( m_inputCompositionModel.get() );
m_inputCompositionModel.reset( new QskInputCompositionModel ); if( modelChanged )
modelChanged = true;
}
if ( modelChanged && m_inputPanel )
{ {
QObject::connect( if( m_inputPanel )
m_inputCompositionModel.get(), &QskInputCompositionModel::groupsChanged, {
m_inputPanel.data(), &QskVirtualKeyboard::setPreeditGroups ); m_inputPanel->disconnect( oldModel );
QObject::connect( QObject::connect(
m_inputCompositionModel.get(), &QskInputCompositionModel::candidatesChanged, newModel, &QskInputCompositionModel::groupsChanged,
this, &QskInputContext::handleCandidatesChanged ); m_inputPanel.data(), &QskVirtualKeyboard::setPreeditGroups );
QObject::connect(
newModel, &QskInputCompositionModel::candidatesChanged,
this, &QskInputContext::handleCandidatesChanged );
}
} }
} }
@ -212,7 +206,7 @@ void QskInputContext::setFocusObject( QObject* focusObject )
if ( !m_focusObject ) if ( !m_focusObject )
{ {
m_inputItem = nullptr; m_inputItem = nullptr;
m_inputCompositionModel->setInputItem( nullptr ); currentInputCompositionModel()->setInputItem( nullptr );
return; return;
} }
@ -225,7 +219,7 @@ void QskInputContext::setFocusObject( QObject* focusObject )
if( qskNearestFocusScope( focusQuickItem ) != m_inputPanel ) if( qskNearestFocusScope( focusQuickItem ) != m_inputPanel )
{ {
m_inputItem = focusQuickItem; m_inputItem = focusQuickItem;
m_inputCompositionModel->setInputItem( m_inputItem ); // ### use a signal/slot connection currentInputCompositionModel()->setInputItem( m_inputItem ); // ### use a signal/slot connection
inputItemChanged = true; inputItemChanged = true;
} }
} }
@ -246,9 +240,31 @@ void QskInputContext::setFocusObject( QObject* focusObject )
update( Qt::InputMethodQuery( Qt::ImQueryAll & ~Qt::ImEnabled ) ); update( Qt::InputMethodQuery( Qt::ImQueryAll & ~Qt::ImEnabled ) );
} }
void QskInputContext::inputMethodRegistered( const QLocale& locale, QskInputCompositionModel* model )
{
auto oldModel = m_inputModels.value( locale, nullptr );
if( oldModel != nullptr )
{
oldModel->deleteLater();
}
m_inputModels.insert( locale, model );
}
QskInputCompositionModel* QskInputContext::compositionModelForLocale( const QLocale& locale ) const
{
return m_inputModels.value( locale, m_defaultInputCompositionModel );
}
QskInputCompositionModel* QskInputContext::currentInputCompositionModel() const
{
return m_inputModels.value( locale(), m_defaultInputCompositionModel );
}
void QskInputContext::invokeAction( QInputMethod::Action action, int cursorPosition ) void QskInputContext::invokeAction( QInputMethod::Action action, int cursorPosition )
{ {
Q_UNUSED(cursorPosition); Q_UNUSED( cursorPosition );
if ( !m_inputPanel ) if ( !m_inputPanel )
return; return;
@ -256,13 +272,15 @@ void QskInputContext::invokeAction( QInputMethod::Action action, int cursorPosit
switch ( static_cast< QskVirtualKeyboard::Action >( action ) ) switch ( static_cast< QskVirtualKeyboard::Action >( action ) )
{ {
case QskVirtualKeyboard::Compose: case QskVirtualKeyboard::Compose:
m_inputCompositionModel->composeKey( static_cast< Qt::Key >( cursorPosition ) ); currentInputCompositionModel()->composeKey( static_cast< Qt::Key >( cursorPosition ) );
break; break;
case QskVirtualKeyboard::SelectGroup: case QskVirtualKeyboard::SelectGroup:
m_inputCompositionModel->setGroupIndex( cursorPosition ); currentInputCompositionModel()->setGroupIndex( cursorPosition );
break; break;
case QskVirtualKeyboard::SelectCandidate: case QskVirtualKeyboard::SelectCandidate:
m_inputCompositionModel->commitCandidate( cursorPosition ); currentInputCompositionModel()->commitCandidate( cursorPosition );
break; break;
} }
} }
@ -274,10 +292,11 @@ void QskInputContext::emitAnimatingChanged()
void QskInputContext::handleCandidatesChanged() void QskInputContext::handleCandidatesChanged()
{ {
QVector< Qt::Key > candidates( m_inputCompositionModel->candidateCount() ); QVector< Qt::Key > candidates( currentInputCompositionModel()->candidateCount() );
for ( int i = 0; i < candidates.length(); ++i )
for( int i = 0; i < candidates.length(); ++i )
{ {
candidates[i] = static_cast< Qt::Key >( m_inputCompositionModel->candidate( i ) ); candidates[i] = currentInputCompositionModel()->candidate( i );
} }
m_inputPanel->setPreeditCandidates( candidates ); m_inputPanel->setPreeditCandidates( candidates );
@ -296,8 +315,8 @@ void QskInputContext::setInputPanel( QskVirtualKeyboard* inputPanel )
this, &QPlatformInputContext::emitKeyboardRectChanged ); this, &QPlatformInputContext::emitKeyboardRectChanged );
QObject::disconnect( m_inputPanel, &QskVirtualKeyboard::localeChanged, QObject::disconnect( m_inputPanel, &QskVirtualKeyboard::localeChanged,
this, &QPlatformInputContext::emitLocaleChanged ); this, &QPlatformInputContext::emitLocaleChanged );
if ( m_inputCompositionModel ) if ( currentInputCompositionModel() )
m_inputPanel->disconnect( m_inputCompositionModel.get() ); m_inputPanel->disconnect( currentInputCompositionModel() );
} }
m_inputPanel = inputPanel; m_inputPanel = inputPanel;
@ -310,10 +329,10 @@ void QskInputContext::setInputPanel( QskVirtualKeyboard* inputPanel )
this, &QPlatformInputContext::emitKeyboardRectChanged ); this, &QPlatformInputContext::emitKeyboardRectChanged );
QObject::connect( m_inputPanel, &QskVirtualKeyboard::localeChanged, QObject::connect( m_inputPanel, &QskVirtualKeyboard::localeChanged,
this, &QPlatformInputContext::emitLocaleChanged ); this, &QPlatformInputContext::emitLocaleChanged );
if ( m_inputCompositionModel ) if ( currentInputCompositionModel() )
{ {
QObject::connect( QObject::connect(
m_inputCompositionModel.get(), &QskInputCompositionModel::groupsChanged, currentInputCompositionModel(), &QskInputCompositionModel::groupsChanged,
m_inputPanel.data(), &QskVirtualKeyboard::setPreeditGroups ); m_inputPanel.data(), &QskVirtualKeyboard::setPreeditGroups );
} }
} }

View File

@ -7,8 +7,9 @@
#define QSK_INPUT_CONTEXT_H #define QSK_INPUT_CONTEXT_H
#include <qpa/qplatforminputcontext.h> #include <qpa/qplatforminputcontext.h>
#include <QPointer> #include <QHash>
#include <QQuickItem> #include <QQuickItem>
#include <QPointer>
#include <memory> #include <memory>
@ -36,16 +37,22 @@ public:
QLocale locale() const override; QLocale locale() const override;
void setFocusObject( QObject* ) override; void setFocusObject( QObject* ) override;
QskInputCompositionModel* compositionModelForLocale( const QLocale& locale ) const;
private Q_SLOTS: private Q_SLOTS:
void emitAnimatingChanged(); void emitAnimatingChanged();
void handleCandidatesChanged(); void handleCandidatesChanged();
void setInputPanel( QskVirtualKeyboard* ); void setInputPanel( QskVirtualKeyboard* );
void inputMethodRegistered( const QLocale& locale, QskInputCompositionModel* model );
private: private:
QskInputCompositionModel* currentInputCompositionModel() const;
QPointer< QObject > m_focusObject; QPointer< QObject > m_focusObject;
QPointer< QQuickItem > m_inputItem; QPointer< QQuickItem > m_inputItem;
QPointer< QskVirtualKeyboard > m_inputPanel; QPointer< QskVirtualKeyboard > m_inputPanel;
std::unique_ptr< QskInputCompositionModel > m_inputCompositionModel; QskInputCompositionModel* m_defaultInputCompositionModel;
QHash< QLocale, QskInputCompositionModel* > m_inputModels;
}; };
#endif // QSK_INPUT_CONTEXT_H #endif // QSK_INPUT_CONTEXT_H

View File

@ -14,7 +14,7 @@ class QskPinyinCompositionModel : public QskInputCompositionModel
public: public:
QskPinyinCompositionModel(); QskPinyinCompositionModel();
~QskPinyinCompositionModel(); virtual ~QskPinyinCompositionModel() override;
int candidateCount() const override; int candidateCount() const override;
Qt::Key candidate( int ) const override; Qt::Key candidate( int ) const override;
@ -24,8 +24,8 @@ public:
protected: protected:
// Used for text composition // Used for text composition
bool hasIntermediate() const override; bool hasIntermediate() const override;
QString polishPreedit(const QString& preedit) override; QString polishPreedit( const QString& preedit ) override;
bool isComposable(const QStringRef& preedit) const override; bool isComposable( const QStringRef& preedit ) const override;
private: private:
void handleGroupIndexChanged(); void handleGroupIndexChanged();

View File

@ -22,6 +22,7 @@ class QSK_EXPORT QskVirtualKeyboardButton : public QskPushButton
public: public:
QSK_SUBCONTROLS( Panel, Text, TextCancelButton ) QSK_SUBCONTROLS( Panel, Text, TextCancelButton )
QskVirtualKeyboardButton( int keyIndex, QskVirtualKeyboard* inputPanel, QQuickItem* parent = nullptr ); QskVirtualKeyboardButton( int keyIndex, QskVirtualKeyboard* inputPanel, QQuickItem* parent = nullptr );
virtual QskAspect::Subcontrol effectiveSubcontrol( QskAspect::Subcontrol subControl ) const override; virtual QskAspect::Subcontrol effectiveSubcontrol( QskAspect::Subcontrol subControl ) const override;
@ -48,7 +49,6 @@ class QSK_EXPORT QskVirtualKeyboard : public QskBox
using Inherited = QskBox; using Inherited = QskBox;
public: public:
QSK_SUBCONTROLS( Panel ) QSK_SUBCONTROLS( Panel )
struct KeyData struct KeyData