virtual keyboard related stuff improved

This commit is contained in:
Uwe Rathmann 2018-04-05 14:18:15 +02:00
parent 7b2e63c7e5
commit f4060f2e75
8 changed files with 238 additions and 126 deletions

View File

@ -58,11 +58,19 @@ QskVirtualKeyboard* qskVirtualKeyboard( QQuickItem* inputPanel )
class QskInputContext::PrivateData class QskInputContext::PrivateData
{ {
public: public:
PrivateData():
ownsInputPanelWindow( false )
{
}
QPointer< QQuickItem > inputItem; QPointer< QQuickItem > inputItem;
QPointer< QQuickItem > inputPanel; QPointer< QQuickItem > inputPanel;
QskInputCompositionModel* compositionModel; QskInputCompositionModel* compositionModel;
QHash< QLocale, QskInputCompositionModel* > compositionModels; QHash< QLocale, QskInputCompositionModel* > compositionModels;
// the input panel is embedded in a window
bool ownsInputPanelWindow : 1;
}; };
QskInputContext::QskInputContext(): QskInputContext::QskInputContext():
@ -104,54 +112,79 @@ bool QskInputContext::hasCapability( Capability ) const
return true; return true;
} }
QQuickItem* QskInputContext::inputItem()
{
return m_data->inputItem;
}
void QskInputContext::setInputItem( QQuickItem* item )
{
if ( m_data->inputItem == item )
return;
m_data->inputItem = item;
if ( m_data->inputItem == nullptr )
{
hideInputPanel();
return;
}
update( Qt::ImQueryAll );
}
void QskInputContext::update( Qt::InputMethodQueries queries ) void QskInputContext::update( Qt::InputMethodQueries queries )
{ {
if ( m_data->inputItem == nullptr ) if ( m_data->inputItem == nullptr || m_data->inputPanel == nullptr )
return; return;
const auto queryEvent = queryInputMethod( queries ); const auto queryEvent = queryInputMethod( queries );
// Qt::ImCursorRectangle if ( queryEvent.queries() & Qt::ImEnabled )
// Qt::ImFont
// Qt::ImCursorPosition
// Qt::ImSurroundingText // important for chinese input
// Qt::ImCurrentSelection // important for prediction
// Qt::ImMaximumTextLength // should be monitored
// Qt::ImAnchorPosition
if ( queries & Qt::ImHints )
{ {
if ( !queryEvent.value( Qt::ImEnabled ).toBool() )
{
hideInputPanel();
return;
}
}
if ( queryEvent.queries() & Qt::ImHints )
{
/*
ImhHiddenText = 0x1, // might need to disable certain checks
ImhSensitiveData = 0x2, // shouldn't change anything
ImhNoAutoUppercase = 0x4, // if we support auto uppercase, disable it
ImhPreferNumbers = 0x8, // default to number keyboard
ImhPreferUppercase = 0x10, // start with shift on
ImhPreferLowercase = 0x20, // start with shift off
ImhNoPredictiveText = 0x40, // ignored for now
ImhDate = 0x80, // ignored for now (no date keyboard)
ImhTime = 0x100, // ignored for know (no time keyboard)
ImhPreferLatin = 0x200, // can be used to launch chinese kb in english mode
ImhMultiLine = 0x400, // not useful?
ImhDigitsOnly // default to number keyboard, disable other keys
ImhFormattedNumbersOnly // hard to say
ImhUppercaseOnly // caps-lock, disable shift
ImhLowercaseOnly // disable shift
ImhDialableCharactersOnly // dial pad (calculator?)
ImhEmailCharactersOnly // disable certain symbols (email-only kb?)
ImhUrlCharactersOnly // disable certain symbols (url-only kb?)
ImhLatinOnly // disable chinese input
*/
#if 0 #if 0
const auto hints = static_cast< Qt::InputMethodHints >( const auto hints = static_cast< Qt::InputMethodHints >(
queryEvent.value( Qt::ImHints ).toInt() ); queryEvent.value( Qt::ImHints ).toInt() );
//ImhHiddenText = 0x1, // might need to disable certain checks
//ImhSensitiveData = 0x2, // shouldn't change anything
//ImhNoAutoUppercase = 0x4, // if we support auto uppercase, disable it
//ImhPreferNumbers = 0x8, // default to number keyboard
//ImhPreferUppercase = 0x10, // start with shift on
//ImhPreferLowercase = 0x20, // start with shift off
//ImhNoPredictiveText = 0x40, // ignored for now
//ImhDate = 0x80, // ignored for now (no date keyboard)
//ImhTime = 0x100, // ignored for know (no time keyboard)
//ImhPreferLatin = 0x200, // can be used to launch chinese kb in english mode
//ImhMultiLine = 0x400, // not useful?
//ImhDigitsOnly // default to number keyboard, disable other keys
//ImhFormattedNumbersOnly // hard to say
//ImhUppercaseOnly // caps-lock, disable shift
//ImhLowercaseOnly // disable shift
//ImhDialableCharactersOnly // dial pad (calculator?)
//ImhEmailCharactersOnly // disable certain symbols (email-only kb?)
//ImhUrlCharactersOnly // disable certain symbols (url-only kb?)
//ImhLatinOnly // disable chinese input
#endif #endif
} }
if ( queries & Qt::ImPreferredLanguage ) if ( queryEvent.queries() & Qt::ImPreferredLanguage )
{ {
const auto locale = queryEvent.value( Qt::ImPreferredLanguage ).toLocale(); const auto locale = queryEvent.value( Qt::ImPreferredLanguage ).toLocale();
@ -172,20 +205,24 @@ void QskInputContext::update( Qt::InputMethodQueries queries )
} }
} }
// Qt::ImAbsolutePosition /*
// Qt::ImTextBeforeCursor // important for chinese Qt::ImMicroFocus
// Qt::ImTextAfterCursor // important for chinese Qt::ImCursorRectangle
// Qt::ImPlatformData // hard to say... Qt::ImFont
} Qt::ImCursorPosition
Qt::ImSurroundingText // important for chinese input
Qt::ImCurrentSelection // important for prediction
Qt::ImMaximumTextLength // should be monitored
Qt::ImAnchorPosition
QQuickItem* QskInputContext::inputItem() Qt::ImAbsolutePosition
{ Qt::ImTextBeforeCursor // important for chinese
return m_data->inputItem; Qt::ImTextAfterCursor // important for chinese
} Qt::ImPlatformData // hard to say...
Qt::ImEnterKeyType
void QskInputContext::setInputItem( QQuickItem* item ) Qt::ImAnchorRectangle
{ Qt::ImInputItemClipRectangle // could be used for the geometry of the panel
m_data->inputItem = item; */
} }
QRectF QskInputContext::keyboardRect() const QRectF QskInputContext::keyboardRect() const
@ -212,9 +249,12 @@ void QskInputContext::showInputPanel()
if ( QskDialog::instance()->policy() == QskDialog::TopLevelWindow ) if ( QskDialog::instance()->policy() == QskDialog::TopLevelWindow )
{ {
m_data->ownsInputPanelWindow = true;
auto window = new QskWindow; auto window = new QskWindow;
window->setFlags( Qt::Tool | Qt::WindowDoesNotAcceptFocus ); window->setFlags( Qt::Tool | Qt::WindowDoesNotAcceptFocus );
window->resize( 800, 240 ); // ### what size? window->resize( 800, 240 ); // ### what size?
m_data->inputPanel->setParentItem( window->contentItem() ); m_data->inputPanel->setParentItem( window->contentItem() );
connect( window, &QskWindow::visibleChanged, connect( window, &QskWindow::visibleChanged,
this, &QskInputContext::emitInputPanelVisibleChanged ); this, &QskInputContext::emitInputPanelVisibleChanged );
@ -230,24 +270,32 @@ void QskInputContext::showInputPanel()
} }
} }
auto window = m_data->inputPanel->window(); if ( m_data->ownsInputPanelWindow )
if ( window && window != QGuiApplication::focusWindow() ) {
window->show(); if ( m_data->inputPanel->window() )
m_data->inputPanel->window()->show();
}
else else
{
m_data->inputPanel->setVisible( true ); m_data->inputPanel->setVisible( true );
} }
}
void QskInputContext::hideInputPanel() void QskInputContext::hideInputPanel()
{ {
if ( m_data->inputPanel == nullptr ) if ( m_data->inputPanel == nullptr )
return; return;
auto window = m_data->inputPanel->window(); if ( m_data->ownsInputPanelWindow )
if ( window && window != QGuiApplication::focusWindow() ) {
if ( auto window = m_data->inputPanel->window() )
window->hide(); window->hide();
}
else else
{
m_data->inputPanel->setVisible( false ); m_data->inputPanel->setVisible( false );
} }
}
bool QskInputContext::isInputPanelVisible() const bool QskInputContext::isInputPanelVisible() const
{ {
@ -269,38 +317,28 @@ Qt::LayoutDirection QskInputContext::inputDirection() const
void QskInputContext::setFocusObject( QObject* focusObject ) void QskInputContext::setFocusObject( QObject* focusObject )
{ {
if ( focusObject == nullptr )
{
setInputItem( nullptr );
return;
}
bool inputItemChanged = false;
auto focusItem = qobject_cast< QQuickItem* >( focusObject ); auto focusItem = qobject_cast< QQuickItem* >( focusObject );
if( focusItem )
if ( focusItem == nullptr )
{ {
// Do not change the input item when panel buttons get the focus: if ( m_data->inputItem )
{
if ( m_data->inputItem->window() == QGuiApplication::focusWindow() )
setInputItem( nullptr );
}
}
else
{
/*
Do not change the input item when
navigating on or into the panel
*/
if( qskNearestFocusScope( focusItem ) != m_data->inputPanel ) if( qskNearestFocusScope( focusItem ) != m_data->inputPanel )
{
setInputItem( focusItem ); setInputItem( focusItem );
inputItemChanged = true;
} }
} }
if( inputItemChanged )
{
const auto queryEvent = queryInputMethod( Qt::ImEnabled );
if ( !queryEvent.value( Qt::ImEnabled ).toBool() )
{
hideInputPanel();
return;
}
}
update( Qt::InputMethodQuery( Qt::ImQueryAll & ~Qt::ImEnabled ) );
}
void QskInputContext::setCompositionModel( void QskInputContext::setCompositionModel(
const QLocale& locale, QskInputCompositionModel* model ) const QLocale& locale, QskInputCompositionModel* model )
{ {
@ -355,10 +393,6 @@ void QskInputContext::invokeAction( QInputMethod::Action action, int value )
case QskVirtualKeyboard::SelectCandidate: case QskVirtualKeyboard::SelectCandidate:
{ {
model->commitCandidate( value ); model->commitCandidate( value );
if ( auto keyboard = qskVirtualKeyboard( m_data->inputPanel ) )
keyboard->setPreeditCandidates( QVector< QString >() );
break; break;
} }
case QInputMethod::Click: case QInputMethod::Click:
@ -403,6 +437,7 @@ void QskInputContext::setInputPanel( QQuickItem* inputPanel )
} }
m_data->inputPanel = inputPanel; m_data->inputPanel = inputPanel;
m_data->ownsInputPanelWindow = false;
if ( inputPanel ) if ( inputPanel )
{ {

View File

@ -40,6 +40,7 @@ public:
auto* textInput = new QskTextInput( this ); auto* textInput = new QskTextInput( this );
textInput->setText( "I am a line edit. Press and edit Me." ); textInput->setText( "I am a line edit. Press and edit Me." );
textInput->setSelectByMouse( true );
#if LOCAL_PANEL #if LOCAL_PANEL
auto* inputPanel = new QskVirtualKeyboard( this ); auto* inputPanel = new QskVirtualKeyboard( this );
@ -179,6 +180,12 @@ int main( int argc, char* argv[] )
auto listView = new LocaleListView( box ); auto listView = new LocaleListView( box );
auto inputBox = new InputBox( box ); auto inputBox = new InputBox( box );
/*
Disable Qt::ClickFocus, so that the input panel stays open
when selecting a different locale
*/
listView->setFocusPolicy( Qt::TabFocus );
QObject::connect( listView, &QskListView::selectedRowChanged, QObject::connect( listView, &QskListView::selectedRowChanged,
inputBox, [ = ]( int row ) { inputBox->setLocale( listView->localeAt( row ) ); } ); inputBox, [ = ]( int row ) { inputBox->setLocale( listView->localeAt( row ) ); } );

View File

@ -18,6 +18,7 @@
QSK_QT_PRIVATE_BEGIN QSK_QT_PRIVATE_BEGIN
#include <private/qquickitem_p.h> #include <private/qquickitem_p.h>
#include <private/qinputmethod_p.h>
#if defined( QT_DEBUG ) #if defined( QT_DEBUG )
#include <private/qquickpositioners_p.h> #include <private/qquickpositioners_p.h>
#endif #endif
@ -144,6 +145,38 @@ QQuickItem* qskNearestFocusScope( const QQuickItem* item )
return nullptr; return nullptr;
} }
void qskUpdateInputMethod( const QQuickItem* item, Qt::InputMethodQueries queries )
{
auto inputMethod = QGuiApplication::inputMethod();
bool doUpdate = item->hasActiveFocus();
if ( !doUpdate )
{
const auto inputContext =
QInputMethodPrivate::get( inputMethod )->platformInputContext();
if ( inputContext && inputContext->isInputPanelVisible() )
{
/*
QskInputContext allows to navigate inside the input panel
without losing the connected input item
*/
QQuickItem* inputItem = nullptr;
if ( QMetaObject::invokeMethod( inputContext, "inputItem",
Qt::DirectConnection, Q_RETURN_ARG( QQuickItem*, inputItem ) ) )
{
doUpdate = ( item == inputItem );
}
}
}
if ( doUpdate )
inputMethod->update( queries );
}
QList< QQuickItem* > qskPaintOrderChildItems( const QQuickItem* item ) QList< QQuickItem* > qskPaintOrderChildItems( const QQuickItem* item )
{ {
if ( item ) if ( item )

View File

@ -212,7 +212,8 @@ private:
// don't use boundingRect - it seems to be deprecated // don't use boundingRect - it seems to be deprecated
virtual QRectF boundingRect() const override final { return rect(); } virtual QRectF boundingRect() const override final { return rect(); }
void setActiveFocusOnTab( bool ) = delete; // use setFocusPolicy instead void setActiveFocusOnTab( bool ) = delete; // use setFocusPolicy
void updateInputMethod( Qt::InputMethodQueries ) = delete; // use qskUpdateInputMethod
virtual QSGNode* updatePaintNode( QSGNode*, UpdatePaintNodeData* ) override final; virtual QSGNode* updatePaintNode( QSGNode*, UpdatePaintNodeData* ) override final;
virtual void updatePolish() override final; virtual void updatePolish() override final;
@ -261,6 +262,8 @@ QSK_EXPORT void qskSetItemGeometry( QQuickItem*, const QRectF& );
QSK_EXPORT QQuickItem* qskNearestFocusScope( const QQuickItem* ); QSK_EXPORT QQuickItem* qskNearestFocusScope( const QQuickItem* );
QSK_EXPORT QList< QQuickItem* > qskPaintOrderChildItems( const QQuickItem* ); QSK_EXPORT QList< QQuickItem* > qskPaintOrderChildItems( const QQuickItem* );
QSK_EXPORT void qskUpdateInputMethod( const QQuickItem*, Qt::InputMethodQueries );
QSK_EXPORT const QSGNode* qskItemNode( const QQuickItem* ); QSK_EXPORT const QSGNode* qskItemNode( const QQuickItem* );
QSK_EXPORT const QSGNode* qskPaintNode( const QQuickItem* ); QSK_EXPORT const QSGNode* qskPaintNode( const QQuickItem* );

View File

@ -11,13 +11,6 @@ QSK_QT_PRIVATE_BEGIN
#include <private/qquicktextinput_p_p.h> #include <private/qquicktextinput_p_p.h>
QSK_QT_PRIVATE_END QSK_QT_PRIVATE_END
static inline void qskUpdateInputMethod(
const QskTextInput*, Qt::InputMethodQueries queries )
{
auto inputMethod = QGuiApplication::inputMethod();
inputMethod->update( queries );
}
static inline void qskBindSignals( const QQuickTextInput* wrappedInput, static inline void qskBindSignals( const QQuickTextInput* wrappedInput,
QskTextInput* input ) QskTextInput* input )
{ {
@ -81,6 +74,7 @@ namespace
{ {
setActiveFocusOnTab( false ); setActiveFocusOnTab( false );
setFlag( ItemAcceptsInputMethod, false ); setFlag( ItemAcceptsInputMethod, false );
setFocusOnPress( false );
} }
void setAlignment( Qt::Alignment alignment ) void setAlignment( Qt::Alignment alignment )
@ -96,9 +90,8 @@ namespace
case QEvent::FocusIn: case QEvent::FocusIn:
case QEvent::FocusOut: case QEvent::FocusOut:
{ {
auto d = QQuickTextInputPrivate::get( this ); auto d = QQuickTextInputPrivate::get( this );
d->focusOnPress = true; d->focusOnPress = true;
d->handleFocusEvent( static_cast< QFocusEvent* >( event ) ); d->handleFocusEvent( static_cast< QFocusEvent* >( event ) );
d->focusOnPress = false; d->focusOnPress = false;
@ -131,6 +124,13 @@ QskTextInput::QskTextInput( QQuickItem* parent ):
setFlag( QQuickItem::ItemAcceptsInputMethod ); setFlag( QQuickItem::ItemAcceptsInputMethod );
/*
QQuickTextInput is a beast of almost 5k lines of code, we don't
want to reimplement that - at least not now.
So this is more or less a simple wrapper making everything
conforming to qskinny.
*/
m_data->textInput = new TextInput( this ); m_data->textInput = new TextInput( this );
qskBindSignals( m_data->textInput, this ); qskBindSignals( m_data->textInput, this );
@ -156,6 +156,10 @@ bool QskTextInput::event( QEvent* event )
{ {
return m_data->textInput->handleEvent( event ); return m_data->textInput->handleEvent( event );
} }
else if ( event->type() == QEvent::LocaleChange )
{
qskUpdateInputMethod( this, Qt::ImPreferredLanguage );
}
return Inherited::event( event ); return Inherited::event( event );
} }
@ -170,6 +174,32 @@ void QskTextInput::keyReleaseEvent( QKeyEvent* event )
Inherited::keyReleaseEvent( event ); Inherited::keyReleaseEvent( event );
} }
void QskTextInput::mousePressEvent( QMouseEvent* event )
{
m_data->textInput->handleEvent( event );
if ( !isReadOnly() && !qGuiApp->styleHints()->setFocusOnTouchRelease() )
qGuiApp->inputMethod()->show();
}
void QskTextInput::mouseMoveEvent( QMouseEvent* event )
{
m_data->textInput->handleEvent( event );
}
void QskTextInput::mouseReleaseEvent( QMouseEvent* event )
{
m_data->textInput->handleEvent( event );
if ( !isReadOnly() && qGuiApp->styleHints()->setFocusOnTouchRelease() )
qGuiApp->inputMethod()->show();
}
void QskTextInput::mouseDoubleClickEvent( QMouseEvent* event )
{
m_data->textInput->handleEvent( event );
}
void QskTextInput::inputMethodEvent( QInputMethodEvent* event ) void QskTextInput::inputMethodEvent( QInputMethodEvent* event )
{ {
m_data->textInput->handleEvent( event ); m_data->textInput->handleEvent( event );
@ -472,12 +502,17 @@ QVariant QskTextInput::inputMethodQuery(
{ {
return font(); return font();
} }
case Qt::ImCursorPosition: case Qt::ImPreferredLanguage:
{
return locale();
}
case Qt::ImCursorRectangle:
case Qt::ImInputItemClipRectangle:
{ {
QVariant v = m_data->textInput->inputMethodQuery( query, argument ); QVariant v = m_data->textInput->inputMethodQuery( query, argument );
#if 1 #if 1
if ( v.canConvert< QPointF >() ) if ( v.canConvert< QRectF >() )
v.setValue( v.toPointF() + m_data->textInput->position() ); v.setValue( v.toRectF().translated( m_data->textInput->position() ) );
#endif #endif
return v; return v;
} }

View File

@ -154,12 +154,20 @@ Q_SIGNALS:
protected: protected:
virtual bool event( QEvent* ) override; virtual bool event( QEvent* ) override;
virtual void keyPressEvent( QKeyEvent* ) override;
virtual void keyReleaseEvent( QKeyEvent* ) override;
virtual void inputMethodEvent( QInputMethodEvent* ) override; virtual void inputMethodEvent( QInputMethodEvent* ) override;
virtual void focusInEvent( QFocusEvent* ) override; virtual void focusInEvent( QFocusEvent* ) override;
virtual void focusOutEvent( QFocusEvent* ) override; virtual void focusOutEvent( QFocusEvent* ) override;
virtual void mousePressEvent( QMouseEvent* ) override;
virtual void mouseMoveEvent( QMouseEvent* ) override;
virtual void mouseReleaseEvent( QMouseEvent* ) override;
virtual void mouseDoubleClickEvent( QMouseEvent* ) override;
virtual void keyPressEvent( QKeyEvent* ) override;
virtual void keyReleaseEvent( QKeyEvent* ) override;
virtual void updateLayout() override; virtual void updateLayout() override;
private: private:

View File

@ -11,7 +11,7 @@
#include <QStyleHints> #include <QStyleHints>
QSK_QT_PRIVATE_BEGIN QSK_QT_PRIVATE_BEGIN
#include <private/qguiapplication_p.h> #include <private/qinputmethod_p.h>
QSK_QT_PRIVATE_END QSK_QT_PRIVATE_END
#include <qpa/qplatformintegration.h> #include <qpa/qplatformintegration.h>
@ -134,7 +134,8 @@ static bool qskIsAutorepeat( int key )
static inline QPlatformInputContext* qskInputContext() static inline QPlatformInputContext* qskInputContext()
{ {
return QGuiApplicationPrivate::platformIntegration()->inputContext(); auto inputMethod = QGuiApplication::inputMethod();
return QInputMethodPrivate::get( inputMethod )->platformInputContext();
} }
QSK_SUBCONTROL( QskVirtualKeyboardCandidateButton, Panel ) QSK_SUBCONTROL( QskVirtualKeyboardCandidateButton, Panel )
@ -505,14 +506,12 @@ QString QskVirtualKeyboard::displayLanguageName() const
void QskVirtualKeyboard::setPreeditCandidates( const QVector< QString >& candidates ) void QskVirtualKeyboard::setPreeditCandidates( const QVector< QString >& candidates )
{ {
if( m_data->candidates == candidates ) if( m_data->candidates != candidates )
{ {
return;
}
m_data->candidates = candidates; m_data->candidates = candidates;
setCandidateOffset( 0 ); setCandidateOffset( 0 );
} }
}
void QskVirtualKeyboard::setCandidateOffset( int candidateOffset ) void QskVirtualKeyboard::setCandidateOffset( int candidateOffset )
{ {
@ -548,13 +547,6 @@ void QskVirtualKeyboard::setCandidateOffset( int candidateOffset )
} }
} }
void QskVirtualKeyboard::geometryChanged(
const QRectF& newGeometry, const QRectF& oldGeometry )
{
Inherited::geometryChanged( newGeometry, oldGeometry );
Q_EMIT keyboardRectChanged();
}
void QskVirtualKeyboard::updateLayout() void QskVirtualKeyboard::updateLayout()
{ {
if( geometry().isNull() ) if( geometry().isNull() )
@ -680,6 +672,7 @@ void QskVirtualKeyboard::handleCandidateKey( int index, const QString& text )
else else
{ {
selectCandidate( index ); selectCandidate( index );
setPreeditCandidates( QVector< QString >() );
} }
} }
@ -879,9 +872,9 @@ bool QskVirtualKeyboard::eventFilter( QObject* object, QEvent* event )
if ( event->type() == QEvent::InputMethodQuery ) if ( event->type() == QEvent::InputMethodQuery )
{ {
/* /*
Qt/Quick expects that the item associated with the virtual keyboard Qt/Quick expects that the item associated with the input context
always has the focus. But you also find systems, where you have to always has the focus. But this does not work, when a virtual
navigate and select inside the virtual keyboard. keyboard is used, where you can navigate and select inside.
So we have to fix the receiver. So we have to fix the receiver.
*/ */

View File

@ -136,7 +136,6 @@ public Q_SLOTS:
protected: protected:
virtual bool eventFilter( QObject*, QEvent* ) override; virtual bool eventFilter( QObject*, QEvent* ) override;
virtual void geometryChanged( const QRectF&, const QRectF& ) override;
virtual void updateLayout() override; virtual void updateLayout() override;
private: private:
@ -151,7 +150,6 @@ private:
void updateKeyData(); void updateKeyData();
Q_SIGNALS: Q_SIGNALS:
void keyboardRectChanged();
void displayLanguageNameChanged(); void displayLanguageNameChanged();
void modeChanged( QskVirtualKeyboard::Mode ); void modeChanged( QskVirtualKeyboard::Mode );