From 3ffd9b3a714f7397df6fe883995c4db35cadf64a Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Mon, 30 Apr 2018 10:03:51 +0200 Subject: [PATCH] input panel improved --- skins/squiek/QskSquiekSkin.cpp | 20 +++ skins/squiek/QskSquiekSkin.h | 1 + src/inputpanel/QskInputContext.cpp | 205 ++++++++++++++++------------- src/inputpanel/QskInputContext.h | 5 + src/inputpanel/QskInputEngine.cpp | 7 +- src/inputpanel/QskInputPanel.cpp | 28 +++- 6 files changed, 170 insertions(+), 96 deletions(-) diff --git a/skins/squiek/QskSquiekSkin.cpp b/skins/squiek/QskSquiekSkin.cpp index d2ee85f4..1a57f8f5 100644 --- a/skins/squiek/QskSquiekSkin.cpp +++ b/skins/squiek/QskSquiekSkin.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -243,6 +244,7 @@ void QskSquiekSkin::initHints() initDialogButtonHints(); initFocusIndicatorHints(); initInputPanelHints(); + initInputPredictionBar(); initVirtualKeyboardHints(); initListViewHints(); initPageIndicatorHints(); @@ -622,6 +624,24 @@ void QskSquiekSkin::initInputPanelHints() setPanel( Q::Panel, Raised ); } +void QskSquiekSkin::initInputPredictionBar() +{ + using namespace QskAspect; + using Q = QskInputPredictionBar; + + const ColorPalette& pal = m_data->palette; + + setMargins( Q::Panel | Padding, 5 ); + setPanel( Q::Panel, Flat ); + + setButton( Q::ButtonPanel, Flat ); + setButton( Q::ButtonPanel | QskPushButton::Pressed, Sunken ); + setMetric( Q::ButtonPanel | MinimumWidth, qskDpiScaled( 30.0 ) ); + + setColor( Q::ButtonText, pal.themeForeground ); + setColor( Q::ButtonText | QskPushButton::Disabled, pal.darker200 ); +} + void QskSquiekSkin::initVirtualKeyboardHints() { using namespace QskAspect; diff --git a/skins/squiek/QskSquiekSkin.h b/skins/squiek/QskSquiekSkin.h index 7d2cf30a..f76626f4 100644 --- a/skins/squiek/QskSquiekSkin.h +++ b/skins/squiek/QskSquiekSkin.h @@ -30,6 +30,7 @@ private: void initDialogButtonBoxHints(); void initFocusIndicatorHints(); void initInputPanelHints(); + void initInputPredictionBar(); void initVirtualKeyboardHints(); void initListViewHints(); void initPageIndicatorHints(); diff --git a/src/inputpanel/QskInputContext.cpp b/src/inputpanel/QskInputContext.cpp index ced9a773..51b080fa 100644 --- a/src/inputpanel/QskInputContext.cpp +++ b/src/inputpanel/QskInputContext.cpp @@ -193,6 +193,50 @@ bool QskInputContext::isAnimating() const return false; } +QskPopup* QskInputContext::createEmbeddingPopup( QskInputPanel* panel ) +{ + auto popup = new QskPopup(); + + popup->setAutoLayoutChildren( true ); + popup->setTransparentForPositioner( false ); + popup->setModal( true ); + + auto box = new QskLinearBox( popup ); + box->addItem( panel ); + + /* + When the panel has an input proxy ( usually a local text input ) + we don't need to see the input item and display the overlay + and align in the center of the window. + */ + const bool hasInputProxy = panel->hasInputProxy(); + + popup->setOverlay( hasInputProxy ); + + if ( hasInputProxy ) + box->setMargins( QMarginsF( 5, 5, 5, 5 ) ); + else + box->setExtraSpacingAt( Qt::TopEdge | Qt::LeftEdge | Qt::RightEdge ); + + return popup; +} + +QskWindow* QskInputContext::createEmbeddingWindow( QskInputPanel* panel ) +{ + auto window = new QskWindow(); + + window->setFlags( window->flags() & Qt::Dialog ); + //window->setModality( Qt::ApplicationModal ); + window->setAutoLayoutChildren( true ); +#if 0 + window->setFlags( Qt::Tool | Qt::WindowDoesNotAcceptFocus ); +#endif + + panel->setParentItem( window->contentItem() ); + + return window; +} + void QskInputContext::showInputPanel() { auto focusItem = qobject_cast< QQuickItem* >( qGuiApp->focusObject() ); @@ -209,9 +253,6 @@ void QskInputContext::showInputPanel() m_data->inputItem = focusItem; - auto& inputPopup = m_data->inputPopup; - auto& inputWindow = m_data->inputWindow; - if ( qskInputPanel == nullptr ) qskSetInputPanel( new QskInputPanel() ); @@ -227,66 +268,53 @@ void QskInputContext::showInputPanel() { // The input panel is embedded in a top level window - delete inputPopup; + delete m_data->inputPopup; - if ( inputWindow == nullptr ) + if ( m_data->inputWindow == nullptr ) { - inputWindow = new QskWindow(); - inputWindow->setDeleteOnClose( true ); -#if 0 - inputWindow->setFlags( Qt::Tool | Qt::WindowDoesNotAcceptFocus ); -#endif + auto window = createEmbeddingWindow( qskInputPanel ); - qskInputPanel->setParentItem( inputWindow->contentItem() ); + if ( window ) + { + QSize size = window->effectivePreferredSize(); + if ( size.isEmpty() ) + { + // no idea, may be something based on the screen size + size = QSize( 800, 240 ); + } + + window->resize( size ); + window->show(); + + window->setDeleteOnClose( true ); + window->installEventFilter( this ); + } + + m_data->inputWindow = window; } - - QSize size = qskInputPanel->sizeHint().toSize(); - if ( size.isEmpty() ) - { - // no idea, may be something based on the screen size - size = QSize( 800, 240 ); - } - - inputWindow->resize( size ); - inputWindow->show(); - - inputWindow->installEventFilter( this ); } else { // The input panel is embedded in a popup - delete inputWindow; + delete m_data->inputWindow; - if ( inputPopup == nullptr ) + if ( m_data->inputPopup == nullptr ) { - inputPopup = new QskPopup( m_data->inputItem->window()->contentItem() ); + auto popup = createEmbeddingPopup( qskInputPanel ); - inputPopup->setAutoLayoutChildren( true ); - inputPopup->setTransparentForPositioner( false ); - inputPopup->setModal( true ); + if ( popup ) + { + popup->setParentItem( m_data->inputItem->window()->contentItem() ); + if ( popup->parent() == nullptr ) + popup->setParent( this ); - auto box = new QskLinearBox( inputPopup ); - box->addItem( qskInputPanel ); - - /* - When the panel has an input proxy ( usually a local text input ) - we don't need to see the input item and display the overlay - and align in the center of the window. - */ - const bool hasInputProxy = qskInputPanel->hasInputProxy(); - - inputPopup->setOverlay( hasInputProxy ); - - if ( hasInputProxy ) - box->setMargins( QMarginsF( 5, 5, 5, 5 ) ); - else - box->setExtraSpacingAt( Qt::TopEdge | Qt::LeftEdge | Qt::RightEdge ); + popup->setVisible( true ); + popup->installEventFilter( this ); + } + + m_data->inputPopup = popup; } - - inputPopup->setParentItem( m_data->inputItem->window()->contentItem() ); - inputPopup->setVisible( true ); - inputPopup->installEventFilter( this ); } m_data->engine->setPredictor( @@ -314,7 +342,17 @@ void QskInputContext::hideInputPanel() focusItem->setFocus( false ); } #endif + } + if ( qskInputPanel ) + { + qskInputPanel->setParentItem( nullptr ); + qskInputPanel->attachInputItem( nullptr ); + qskInputPanel->setEngine( nullptr ); + } + + if ( m_data->inputPopup ) + { m_data->inputPopup->deleteLater(); } @@ -327,14 +365,6 @@ void QskInputContext::hideInputPanel() window->close(); // deleteOnClose is set } - if ( qskInputPanel ) - { - //qskInputPanel->setVisible( false ); - qskInputPanel->setParentItem( nullptr ); - qskInputPanel->attachInputItem( nullptr ); - qskInputPanel->setEngine( nullptr ); - } - m_data->inputItem = nullptr; } @@ -370,47 +400,46 @@ void QskInputContext::setFocusObject( QObject* focusObject ) return; } - bool doTerminate = true; + const auto w = m_data->inputItem->window(); + if ( w == nullptr ) + return; - if ( focusObject == nullptr && m_data->inputPopup ) + if ( m_data->inputWindow ) { - if ( const auto window = m_data->inputItem->window() ) + if ( focusObject == nullptr ) { - auto focusItem = window->contentItem()->scopedFocusItem(); - if ( focusItem == m_data->inputPopup ) - doTerminate = false; - } - } - - if ( doTerminate ) - { - if ( m_data->inputWindow ) - { - auto focusWindow = QGuiApplication::focusWindow(); - - if ( focusWindow == nullptr || - QGuiApplication::focusWindow() == m_data->inputWindow ) + if ( m_data->inputItem->hasFocus() ) { - doTerminate = false; + /* + As long as the focus is noewhere and + the local focus stay on the input item + we don't care + */ + + return; } } - else if ( m_data->inputPopup ) + else { - auto focusItem = qobject_cast< QQuickItem* >( focusObject ); - - if ( ( focusItem == m_data->inputPopup ) - || qskIsAncestorOf( m_data->inputPopup, focusItem ) ) - { - doTerminate = false; - } + const auto focusItem = qobject_cast< QQuickItem* >( focusObject ); + if ( focusItem && focusItem->window() == m_data->inputWindow ) + return; + } + } + else if ( m_data->inputPopup ) + { + if ( w->contentItem()->scopedFocusItem() == m_data->inputPopup ) + { + /* + As long as the focus stays inside the inputPopup + we don't care + */ + return; } } - if ( doTerminate ) - { - hideInputPanel(); - m_data->inputItem = nullptr; - } + hideInputPanel(); + m_data->inputItem = nullptr; } void QskInputContext::registerPredictor( diff --git a/src/inputpanel/QskInputContext.h b/src/inputpanel/QskInputContext.h index bf3169e8..247b45ed 100644 --- a/src/inputpanel/QskInputContext.h +++ b/src/inputpanel/QskInputContext.h @@ -12,6 +12,8 @@ class QskTextPredictor; class QskInputPanel; +class QskPopup; +class QskWindow; class QQuickItem; class QSK_EXPORT QskInputContext : public QPlatformInputContext @@ -58,6 +60,9 @@ public: protected: virtual bool eventFilter( QObject*, QEvent* ) override; + virtual QskPopup* createEmbeddingPopup( QskInputPanel* ); + virtual QskWindow* createEmbeddingWindow( QskInputPanel* ); + private: class PrivateData; std::unique_ptr< PrivateData > m_data; diff --git a/src/inputpanel/QskInputEngine.cpp b/src/inputpanel/QskInputEngine.cpp index 19e07fed..df28f93d 100644 --- a/src/inputpanel/QskInputEngine.cpp +++ b/src/inputpanel/QskInputEngine.cpp @@ -240,8 +240,11 @@ QskInputEngine::Result QskInputEngine::processKey( int key, QString QskInputEngine::predictiveText( int index ) const { - if ( m_data->predictor ) - return m_data->predictor->candidate( index ); + if ( const QskTextPredictor* predictor = m_data->predictor ) + { + if ( index >= 0 && index < predictor->candidateCount() ) + return predictor->candidate( index ); + } return QString(); } diff --git a/src/inputpanel/QskInputPanel.cpp b/src/inputpanel/QskInputPanel.cpp index d7483951..37ad0997 100644 --- a/src/inputpanel/QskInputPanel.cpp +++ b/src/inputpanel/QskInputPanel.cpp @@ -110,14 +110,16 @@ static inline void qskSyncInputProxy( namespace { - class TextInput final : public QskTextInput + class TextInputProxy final : public QskTextInput { public: - TextInput( QQuickItem* parentItem = nullptr ): + TextInputProxy( QQuickItem* parentItem = nullptr ): QskTextInput( parentItem ) { setObjectName( "InputPanelInputProxy" ); +#if 1 setFocusPolicy( Qt::NoFocus ); +#endif } protected: @@ -154,7 +156,7 @@ public: QskLinearBox* layout; QskTextLabel* prompt; - TextInput* inputProxy; + TextInputProxy* inputProxy; QskInputPredictionBar* predictionBar; QskVirtualKeyboard* keyboard; @@ -175,7 +177,7 @@ QskInputPanel::QskInputPanel( QQuickItem* parent ): m_data->prompt = new QskTextLabel(); m_data->prompt->setVisible( false ); - m_data->inputProxy = new TextInput(); + m_data->inputProxy = new TextInputProxy(); m_data->inputProxy->setVisible( m_data->hasInputProxy ); m_data->predictionBar = new QskInputPredictionBar(); @@ -331,9 +333,11 @@ void QskInputPanel::commitPredictiveText( int index ) if ( m_data->engine ) { const QString text = m_data->engine->predictiveText( index ); - m_data->engine->reset(); - qskSendText( m_data->inputItem, text, true ); + m_data->engine->reset(); + m_data->predictionBar->setPrediction( QVector< QString >() ); + + qskSendText( m_data->receiverItem(), text, true ); } } @@ -599,6 +603,18 @@ void QskInputPanel::keyPressEvent( QKeyEvent* event ) break; } + case Qt::Key_Shift: + case Qt::Key_Control: + case Qt::Key_Meta: + case Qt::Key_Alt: + case Qt::Key_AltGr: + case Qt::Key_CapsLock: + case Qt::Key_NumLock: + case Qt::Key_ScrollLock: + { + break; + } + default: { const auto text = event->text();