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
{
public:
PrivateData():
ownsInputPanelWindow( false )
{
}
QPointer< QQuickItem > inputItem;
QPointer< QQuickItem > inputPanel;
QskInputCompositionModel* compositionModel;
QHash< QLocale, QskInputCompositionModel* > compositionModels;
// the input panel is embedded in a window
bool ownsInputPanelWindow : 1;
};
QskInputContext::QskInputContext():
@ -104,54 +112,79 @@ bool QskInputContext::hasCapability( Capability ) const
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 )
{
if ( m_data->inputItem == nullptr )
if ( m_data->inputItem == nullptr || m_data->inputPanel == nullptr )
return;
const auto queryEvent = queryInputMethod( queries );
// Qt::ImCursorRectangle
// 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.queries() & Qt::ImEnabled )
{
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
const auto hints = static_cast< Qt::InputMethodHints >(
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
}
if ( queries & Qt::ImPreferredLanguage )
if ( queryEvent.queries() & Qt::ImPreferredLanguage )
{
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::ImTextAfterCursor // important for chinese
// Qt::ImPlatformData // hard to say...
}
/*
Qt::ImMicroFocus
Qt::ImCursorRectangle
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()
{
return m_data->inputItem;
}
void QskInputContext::setInputItem( QQuickItem* item )
{
m_data->inputItem = item;
Qt::ImAbsolutePosition
Qt::ImTextBeforeCursor // important for chinese
Qt::ImTextAfterCursor // important for chinese
Qt::ImPlatformData // hard to say...
Qt::ImEnterKeyType
Qt::ImAnchorRectangle
Qt::ImInputItemClipRectangle // could be used for the geometry of the panel
*/
}
QRectF QskInputContext::keyboardRect() const
@ -212,9 +249,12 @@ void QskInputContext::showInputPanel()
if ( QskDialog::instance()->policy() == QskDialog::TopLevelWindow )
{
m_data->ownsInputPanelWindow = true;
auto window = new QskWindow;
window->setFlags( Qt::Tool | Qt::WindowDoesNotAcceptFocus );
window->resize( 800, 240 ); // ### what size?
m_data->inputPanel->setParentItem( window->contentItem() );
connect( window, &QskWindow::visibleChanged,
this, &QskInputContext::emitInputPanelVisibleChanged );
@ -230,11 +270,15 @@ void QskInputContext::showInputPanel()
}
}
auto window = m_data->inputPanel->window();
if ( window && window != QGuiApplication::focusWindow() )
window->show();
if ( m_data->ownsInputPanelWindow )
{
if ( m_data->inputPanel->window() )
m_data->inputPanel->window()->show();
}
else
{
m_data->inputPanel->setVisible( true );
}
}
void QskInputContext::hideInputPanel()
@ -242,11 +286,15 @@ void QskInputContext::hideInputPanel()
if ( m_data->inputPanel == nullptr )
return;
auto window = m_data->inputPanel->window();
if ( window && window != QGuiApplication::focusWindow() )
window->hide();
if ( m_data->ownsInputPanelWindow )
{
if ( auto window = m_data->inputPanel->window() )
window->hide();
}
else
{
m_data->inputPanel->setVisible( false );
}
}
bool QskInputContext::isInputPanelVisible() const
@ -269,36 +317,26 @@ Qt::LayoutDirection QskInputContext::inputDirection() const
void QskInputContext::setFocusObject( QObject* focusObject )
{
if ( focusObject == nullptr )
{
setInputItem( nullptr );
return;
}
bool inputItemChanged = false;
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 )
{
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(
@ -355,10 +393,6 @@ void QskInputContext::invokeAction( QInputMethod::Action action, int value )
case QskVirtualKeyboard::SelectCandidate:
{
model->commitCandidate( value );
if ( auto keyboard = qskVirtualKeyboard( m_data->inputPanel ) )
keyboard->setPreeditCandidates( QVector< QString >() );
break;
}
case QInputMethod::Click:
@ -403,6 +437,7 @@ void QskInputContext::setInputPanel( QQuickItem* inputPanel )
}
m_data->inputPanel = inputPanel;
m_data->ownsInputPanelWindow = false;
if ( inputPanel )
{

View File

@ -40,6 +40,7 @@ public:
auto* textInput = new QskTextInput( this );
textInput->setText( "I am a line edit. Press and edit Me." );
textInput->setSelectByMouse( true );
#if LOCAL_PANEL
auto* inputPanel = new QskVirtualKeyboard( this );
@ -179,6 +180,12 @@ int main( int argc, char* argv[] )
auto listView = new LocaleListView( 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,
inputBox, [ = ]( int row ) { inputBox->setLocale( listView->localeAt( row ) ); } );

View File

@ -18,6 +18,7 @@
QSK_QT_PRIVATE_BEGIN
#include <private/qquickitem_p.h>
#include <private/qinputmethod_p.h>
#if defined( QT_DEBUG )
#include <private/qquickpositioners_p.h>
#endif
@ -144,12 +145,44 @@ QQuickItem* qskNearestFocusScope( const QQuickItem* item )
return nullptr;
}
QList<QQuickItem *> qskPaintOrderChildItems( const QQuickItem* item )
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 )
{
if ( item )
return QQuickItemPrivate::get( item )->paintOrderChildItems();
return QList<QQuickItem *>();
return QList< QQuickItem* >();
}
const QSGNode* qskItemNode( const QQuickItem* item )
@ -1151,7 +1184,7 @@ bool QskControl::event( QEvent* event )
/*
We might have a totally different skinlet,
that can't deal with nodes created from other skinlets
*/
*/
d_func()->clearPreviousNodes = true;
resetImplicitSize();

View File

@ -212,7 +212,8 @@ private:
// don't use boundingRect - it seems to be deprecated
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 void updatePolish() override final;
@ -249,7 +250,7 @@ inline QSizeF QskControl::sizeHint() const
}
QSK_EXPORT bool qskIsItemComplete( const QQuickItem* item );
QSK_EXPORT bool qskIsAncestorOf( const QQuickItem* item, const QQuickItem *child );
QSK_EXPORT bool qskIsAncestorOf( const QQuickItem* item, const QQuickItem* child );
QSK_EXPORT bool qskIsTransparentForPositioner( const QQuickItem* );
QSK_EXPORT bool qskIsTabFence( const QQuickItem* );
QSK_EXPORT bool qskIsShortcutScope( const QQuickItem* );
@ -259,7 +260,9 @@ QSK_EXPORT QRectF qskItemGeometry( const QQuickItem* );
QSK_EXPORT void qskSetItemGeometry( QQuickItem*, const QRectF& );
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* qskPaintNode( const QQuickItem* );

View File

@ -11,13 +11,6 @@ QSK_QT_PRIVATE_BEGIN
#include <private/qquicktextinput_p_p.h>
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,
QskTextInput* input )
{
@ -81,6 +74,7 @@ namespace
{
setActiveFocusOnTab( false );
setFlag( ItemAcceptsInputMethod, false );
setFocusOnPress( false );
}
void setAlignment( Qt::Alignment alignment )
@ -96,9 +90,8 @@ namespace
case QEvent::FocusIn:
case QEvent::FocusOut:
{
auto d = QQuickTextInputPrivate::get( this );
d->focusOnPress = true;
d->handleFocusEvent( static_cast< QFocusEvent* >( event ) );
d->focusOnPress = false;
@ -131,6 +124,13 @@ QskTextInput::QskTextInput( QQuickItem* parent ):
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 );
qskBindSignals( m_data->textInput, this );
@ -156,6 +156,10 @@ bool QskTextInput::event( QEvent* event )
{
return m_data->textInput->handleEvent( event );
}
else if ( event->type() == QEvent::LocaleChange )
{
qskUpdateInputMethod( this, Qt::ImPreferredLanguage );
}
return Inherited::event( event );
}
@ -170,6 +174,32 @@ void QskTextInput::keyReleaseEvent( QKeyEvent* 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 )
{
m_data->textInput->handleEvent( event );
@ -472,12 +502,17 @@ QVariant QskTextInput::inputMethodQuery(
{
return font();
}
case Qt::ImCursorPosition:
case Qt::ImPreferredLanguage:
{
return locale();
}
case Qt::ImCursorRectangle:
case Qt::ImInputItemClipRectangle:
{
QVariant v = m_data->textInput->inputMethodQuery( query, argument );
#if 1
if ( v.canConvert< QPointF >() )
v.setValue( v.toPointF() + m_data->textInput->position() );
if ( v.canConvert< QRectF >() )
v.setValue( v.toRectF().translated( m_data->textInput->position() ) );
#endif
return v;
}

View File

@ -154,12 +154,20 @@ Q_SIGNALS:
protected:
virtual bool event( QEvent* ) override;
virtual void keyPressEvent( QKeyEvent* ) override;
virtual void keyReleaseEvent( QKeyEvent* ) override;
virtual void inputMethodEvent( QInputMethodEvent* ) override;
virtual void focusInEvent( 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;
private:

View File

@ -11,7 +11,7 @@
#include <QStyleHints>
QSK_QT_PRIVATE_BEGIN
#include <private/qguiapplication_p.h>
#include <private/qinputmethod_p.h>
QSK_QT_PRIVATE_END
#include <qpa/qplatformintegration.h>
@ -134,7 +134,8 @@ static bool qskIsAutorepeat( int key )
static inline QPlatformInputContext* qskInputContext()
{
return QGuiApplicationPrivate::platformIntegration()->inputContext();
auto inputMethod = QGuiApplication::inputMethod();
return QInputMethodPrivate::get( inputMethod )->platformInputContext();
}
QSK_SUBCONTROL( QskVirtualKeyboardCandidateButton, Panel )
@ -505,13 +506,11 @@ QString QskVirtualKeyboard::displayLanguageName() const
void QskVirtualKeyboard::setPreeditCandidates( const QVector< QString >& candidates )
{
if( m_data->candidates == candidates )
if( m_data->candidates != candidates )
{
return;
m_data->candidates = candidates;
setCandidateOffset( 0 );
}
m_data->candidates = candidates;
setCandidateOffset( 0 );
}
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()
{
if( geometry().isNull() )
@ -680,6 +672,7 @@ void QskVirtualKeyboard::handleCandidateKey( int index, const QString& text )
else
{
selectCandidate( index );
setPreeditCandidates( QVector< QString >() );
}
}
@ -879,9 +872,9 @@ bool QskVirtualKeyboard::eventFilter( QObject* object, QEvent* event )
if ( event->type() == QEvent::InputMethodQuery )
{
/*
Qt/Quick expects that the item associated with the virtual keyboard
always has the focus. But you also find systems, where you have to
navigate and select inside the virtual keyboard.
Qt/Quick expects that the item associated with the input context
always has 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.
*/

View File

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