From 0ee83c1e32c1b3afc174b335c2c916b09277fdd3 Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Wed, 11 Apr 2018 17:33:43 +0200 Subject: [PATCH] QskInputContext improvements --- inputcontext/QskInputContext.cpp | 260 +++++++++++++++++++++---------- playground/inputpanel/main.cpp | 38 ++--- src/controls/QskControl.cpp | 9 +- src/inputpanel/QskInputPanel.cpp | 5 - 4 files changed, 204 insertions(+), 108 deletions(-) diff --git a/inputcontext/QskInputContext.cpp b/inputcontext/QskInputContext.cpp index 45ae6705..2f37f1a6 100644 --- a/inputcontext/QskInputContext.cpp +++ b/inputcontext/QskInputContext.cpp @@ -11,9 +11,11 @@ #include "QskInputPanel.h" #include +#include #include #include #include +#include #include #include @@ -82,13 +84,15 @@ static void qskSetCandidates( QQuickItem* inputPanel, class QskInputContext::PrivateData { public: - PrivateData(): - ownsInputPanelWindow( false ) - { - } - + // item receiving the input QPointer< QQuickItem > inputItem; - QPointer< QQuickItem > inputPanel; + + // item, wher the user enters texts/keys + QPointer< QQuickItem > inputPanel; + + // popup or window embedding the inputPanel + QPointer< QskPopup > inputPopup; + QPointer< QskWindow > inputWindow; QskInputCompositionModel* compositionModel; QHash< QLocale, QskInputCompositionModel* > compositionModels; @@ -123,10 +127,6 @@ QskInputContext::QskInputContext(): QskInputContext::~QskInputContext() { -#if 1 - if ( m_data->inputPanel ) - delete m_data->inputPanel; -#endif } bool QskInputContext::isValid() const @@ -152,13 +152,10 @@ void QskInputContext::setInputItem( QQuickItem* item ) m_data->inputItem = item; - if ( m_data->inputItem == nullptr ) - { + if ( item ) + update( Qt::ImQueryAll ); + else hideInputPanel(); - return; - } - - update( Qt::ImQueryAll ); } void QskInputContext::update( Qt::InputMethodQueries queries ) @@ -271,57 +268,123 @@ bool QskInputContext::isAnimating() const void QskInputContext::showInputPanel() { - if ( !m_data->inputPanel ) + auto& inputPanel = m_data->inputPanel; + auto& inputPopup = m_data->inputPopup; + auto& inputWindow = m_data->inputWindow; + + if ( inputPanel == nullptr ) { - setInputPanel( new QskInputPanel() ); + auto panel = new QskInputPanel(); + panel->setParent( this ); - 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 ); - } - else - { - auto window = qobject_cast< QQuickWindow* >( QGuiApplication::focusWindow() ); - if ( window ) - { - m_data->inputPanel->setParentItem( window->contentItem() ); - m_data->inputPanel->setSize( window->size() ); - } - } + setInputPanel( panel ); } - if ( m_data->ownsInputPanelWindow ) + const bool isPopupPanel = qobject_cast< QskPopup* >( inputPanel ); + + bool useWindow = false; + if ( !isPopupPanel ) { - if ( m_data->inputPanel->window() ) - m_data->inputPanel->window()->show(); + useWindow = ( QskDialog::instance()->policy() == QskDialog::TopLevelWindow ); + } + + if ( useWindow ) + { + delete inputPopup; + + if ( inputWindow == nullptr ) + { + inputWindow = new QskWindow(); + inputWindow->setDeleteOnClose( true ); + inputWindow->setFlags( Qt::Tool | Qt::WindowDoesNotAcceptFocus ); + + inputPanel->setParentItem( inputWindow->contentItem() ); + + inputWindow->resize( 800, 240 ); // ### what size? + inputWindow->show(); + + inputWindow->installEventFilter( this ); + } } else { - m_data->inputPanel->setVisible( true ); + delete inputWindow; + + if ( inputPopup == nullptr ) + { + if ( isPopupPanel ) + { + inputPopup = qobject_cast< QskPopup* >( inputPanel ); + } + else + { + auto popup = new QskPopup( m_data->inputItem->window()->contentItem() ); + popup->setAutoLayoutChildren( true ); + popup->setModal( true ); + + inputPanel->setParentItem( popup ); + inputPopup = popup; + } + + inputPopup->installEventFilter( this ); + } + + if ( inputPopup->window() == nullptr ) + { + QQuickWindow* window = nullptr; + if ( m_data->inputItem ) + window = m_data->inputItem->window(); + else + window = qobject_cast< QQuickWindow* >( QGuiApplication::focusWindow() ); + + if ( window ) + { + inputPopup->setParentItem( window->contentItem() ); + } + } + + inputPopup->setGeometry( qskItemGeometry( inputPopup->parentItem() ) ); + inputPopup->setVisible( true ); } + + inputPanel->setVisible( true ); + + connect( inputPanel->window(), &QskWindow::visibleChanged, + this, &QskInputContext::emitInputPanelVisibleChanged ); } void QskInputContext::hideInputPanel() { - if ( m_data->inputPanel == nullptr ) - return; - - if ( m_data->ownsInputPanelWindow ) + if ( m_data->inputPanel ) { - if ( auto window = m_data->inputPanel->window() ) - window->hide(); + // to get rid of the scene graph nodes + m_data->inputPanel->setVisible( false ); + } + + if ( m_data->inputPopup == m_data->inputPanel ) + { + + m_data->inputPopup->removeEventFilter( this ); + m_data->inputPopup = nullptr; } else { - m_data->inputPanel->setVisible( false ); + if ( m_data->inputPopup ) + { + auto popup = m_data->inputPopup.data(); + m_data->inputPopup = nullptr; + + popup->deleteLater(); + } + } + + QskWindow* window = m_data->inputWindow; + m_data->inputWindow = nullptr; + + if ( window ) + { + window->removeEventFilter( this ); + window->close(); // deleteOnClose is set } qGuiApp->removeEventFilter( this ); @@ -361,10 +424,29 @@ void QskInputContext::setFocusObject( QObject* focusObject ) { /* Do not change the input item when - navigating on or into the panel + navigating to or inside the input popup/window */ - if( qskNearestFocusScope( focusItem ) != m_data->inputPanel ) + bool isAccepted = ( m_data->inputItem == nullptr ); + + if ( !isAccepted ) + { + if ( m_data->inputWindow ) + { + if ( focusItem->window() != m_data->inputWindow ) + isAccepted = true; + } + else if ( m_data->inputPopup ) + { + if ( ( focusItem != m_data->inputPopup ) + && !qskIsAncestorOf( m_data->inputPopup, focusItem ) ) + { + isAccepted = true; + } + } + } + + if ( isAccepted ) setInputItem( focusItem ); } @@ -469,6 +551,15 @@ void QskInputContext::setInputPanel( QQuickItem* inputPanel ) { m_data->inputPanel->disconnect( this ); + if ( m_data->inputPanel->parent() == this ) + { + delete m_data->inputPanel; + } + else + { + m_data->inputPanel->setParentItem( nullptr ); + } + if ( model ) model->disconnect( m_data->inputPanel ); } @@ -478,25 +569,12 @@ void QskInputContext::setInputPanel( QQuickItem* inputPanel ) if ( inputPanel ) { + if ( inputPanel->parent() == nullptr ) + inputPanel->setParent( this ); + connect( inputPanel, &QQuickItem::visibleChanged, this, &QPlatformInputContext::emitInputPanelVisibleChanged ); - // maybe using a QQuickItemChangeListener instead -#if 1 - - connect( inputPanel, &QQuickItem::xChanged, - this, &QPlatformInputContext::emitKeyboardRectChanged ); - - connect( inputPanel, &QQuickItem::yChanged, - this, &QPlatformInputContext::emitKeyboardRectChanged ); - - connect( inputPanel, &QQuickItem::widthChanged, - this, &QPlatformInputContext::emitKeyboardRectChanged ); - - connect( inputPanel, &QQuickItem::heightChanged, - this, &QPlatformInputContext::emitKeyboardRectChanged ); -#endif - if ( auto control = qobject_cast< QskControl* >( inputPanel ) ) { connect( control, &QskControl::localeChanged, @@ -520,21 +598,41 @@ void QskInputContext::commit() } bool QskInputContext::eventFilter( QObject* object, QEvent* event ) -{ - if ( event->type() == QEvent::InputMethodQuery ) +{ + switch( static_cast< int >( event->type() ) ) { - /* - 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 ) ) + case QEvent::Move: + case QEvent::Resize: { - sendEventToInputItem( event ); - return true; + if ( m_data->inputPanel && object == m_data->inputPanel->window() ) + emitKeyboardRectChanged(); + + break; + } + case QskEvent::GeometryChange: + { + if ( object == m_data->inputPopup ) + emitKeyboardRectChanged(); + + 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; } } diff --git a/playground/inputpanel/main.cpp b/playground/inputpanel/main.cpp index c074285e..fe660e7f 100644 --- a/playground/inputpanel/main.cpp +++ b/playground/inputpanel/main.cpp @@ -26,35 +26,26 @@ #define STRINGIFY(x) #x #define STRING(x) STRINGIFY(x) -#define LOCAL_PANEL 1 - class InputBox : public QskLinearBox { public: InputBox( QQuickItem* parentItem = nullptr ): QskLinearBox( Qt::Vertical, parentItem ) { - setDefaultAlignment( Qt::AlignHCenter | Qt::AlignTop ); + setExtraSpacingAt( Qt::BottomEdge | Qt::RightEdge ); setMargins( 10 ); setSpacing( 10 ); - auto* textInput = new QskTextInput( this ); - textInput->setText( "I am a line edit. Press and edit Me." ); - textInput->setSelectByMouse( true ); - textInput->setSizePolicy( Qt::Horizontal, QskSizePolicy::Preferred ); + auto* textInput1 = new QskTextInput( this ); + textInput1->setText( "I am a line edit. Press and edit Me." ); + textInput1->setSelectByMouse( true ); + textInput1->setSizePolicy( Qt::Horizontal, QskSizePolicy::Preferred ); -#if LOCAL_PANEL - auto* inputPanel = new QskInputPanel( this ); - inputPanel->setVisible( false ); - - /* - QskInputContext is connected to QskSetup::inputPanelChanged, - making it the system input. If no input panel has been assigned - QskInputContext would create a window or subwindow on the fly. - */ - qskSetup->setInputPanel( inputPanel ); -#endif + auto* textInput2 = new QskTextInput( this ); + textInput2->setText( "Another text" ); + textInput2->setSelectByMouse( true ); + textInput2->setSizePolicy( Qt::Horizontal, QskSizePolicy::Preferred ); } }; @@ -169,11 +160,20 @@ int main( int argc, char* argv[] ) SkinnyFont::init( &app ); SkinnyShortcut::enable( SkinnyShortcut::AllShortcuts ); -#if !LOCAL_PANEL +#if 0 // We don't want to have a top level window. qskDialog->setPolicy( QskDialog::EmbeddedBox ); #endif +#if 0 + /* + QskInputContext is connected to QskSetup::inputPanelChanged, + making it the system input. If no input panel has been assigned + QskInputContext would create a window or subwindow on the fly. + */ + qskSetup->setInputPanel( new QskInputPanel() ); +#endif + auto box = new QskLinearBox( Qt::Horizontal ); box->setSpacing( 10 ); box->setMargins( 20 ); diff --git a/src/controls/QskControl.cpp b/src/controls/QskControl.cpp index 5b857a88..857105c8 100644 --- a/src/controls/QskControl.cpp +++ b/src/controls/QskControl.cpp @@ -1538,10 +1538,13 @@ void QskControl::cleanupNodes() d->dirtyAttributes |= QQuickItemPrivate::EffectReference; } - // putting the nodes on the cleanup list of the window to be deleteted - // in the next cycle of the scene graph + if ( d->window ) + { + // putting the nodes on the cleanup list of the window to be deleteted + // in the next cycle of the scene graph - QQuickWindowPrivate::get( window() )->cleanup( d->itemNodeInstance ); + QQuickWindowPrivate::get( d->window )->cleanup( d->itemNodeInstance ); + } // now we can forget about the nodes diff --git a/src/inputpanel/QskInputPanel.cpp b/src/inputpanel/QskInputPanel.cpp index 4e560d67..9b54dea1 100644 --- a/src/inputpanel/QskInputPanel.cpp +++ b/src/inputpanel/QskInputPanel.cpp @@ -111,11 +111,6 @@ QskInputPanel::QskInputPanel( QQuickItem* parent ): m_data( new PrivateData() ) { setAutoLayoutChildren( true ); - setFlag( ItemIsFocusScope, true ); -#if 0 - // TODO ... - setTabFence( true ); -#endif auto layout = new QskLinearBox( Qt::Vertical, this );