diff --git a/playground/inputpanel/main.cpp b/playground/inputpanel/main.cpp index 50b78146..3f6c611b 100644 --- a/playground/inputpanel/main.cpp +++ b/playground/inputpanel/main.cpp @@ -6,16 +6,15 @@ #include #include -#include #include #include #include #include #include #include +#include #include -#include #include #include @@ -258,17 +257,16 @@ int main( int argc, char* argv[] ) SkinnyShortcut::enable( SkinnyShortcut::AllShortcuts ); #if 1 - // We don't want to have a top level window. + // We don't want to have the input panel in 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. + If no input panel has been assigned QskInputContext creates + default panel if none has been assigned */ - qskSetup->setInputPanel( new QskInputPanel() ); + QskInputContext::setInputPanel( new QskInputPanel() ); #endif auto box = new QskLinearBox( Qt::Horizontal ); diff --git a/src/common/QskModule.cpp b/src/common/QskModule.cpp index 27a0ca2e..72f48b91 100644 --- a/src/common/QskModule.cpp +++ b/src/common/QskModule.cpp @@ -159,9 +159,6 @@ public: Q_PROPERTY( QStringList skinList READ skinList NOTIFY skinListChanged ) - Q_PRIVATE_PROPERTY( setup(), QQuickItem* inputPanel READ inputPanel - WRITE setInputPanel NOTIFY inputPanelChanged ) - Q_PRIVATE_PROPERTY( setup(), QskSetupFlagsProvider controlFlags READ controlFlags WRITE setControlFlags NOTIFY controlFlagsChanged ) @@ -177,8 +174,7 @@ public: connect( setup(), &QskSetup::skinChanged, this, &QskMain::skinChanged, Qt::QueuedConnection ); - connect( setup(), &QskSetup::inputPanelChanged, - this, &QskMain::inputPanelChanged ); + connect( setup(), &QskSetup::controlFlagsChanged, this, &QskMain::controlFlagsChanged, Qt::QueuedConnection ); } diff --git a/src/controls/QskControl.cpp b/src/controls/QskControl.cpp index 9cfb43bb..88bca8a4 100644 --- a/src/controls/QskControl.cpp +++ b/src/controls/QskControl.cpp @@ -172,34 +172,37 @@ void qskForceActiveFocus( QQuickItem* item, Qt::FocusReason reason ) void qskUpdateInputMethod( const QQuickItem* item, Qt::InputMethodQueries queries ) { + if ( ( item == nullptr ) || + !( item->flags() & QQuickItem::ItemAcceptsInputMethod ) ) + { + return; + } + auto inputMethod = QGuiApplication::inputMethod(); bool doUpdate = item->hasActiveFocus(); - if ( !doUpdate ) + /* + We could also get the inputContext from QInputMethodPrivate + but for some reason the gcc sanitizer reports errors + when using it. So let's go with QGuiApplicationPrivate. + */ + const auto inputContext = + QGuiApplicationPrivate::platformIntegration()->inputContext(); + + if ( inputContext && inputContext->isInputPanelVisible() ) { /* - We could also get the inputContext from QInputMethodPrivate - but for some reason the gcc sanitizer reports errors - when using it. So let's go with QGuiApplicationPrivate. + QskInputContext allows to navigate inside the input panel + without losing the connected input item */ - const auto inputContext = - QGuiApplicationPrivate::platformIntegration()->inputContext(); - if ( inputContext && inputContext->isInputPanelVisible() ) + QQuickItem* inputItem = nullptr; + + if ( QMetaObject::invokeMethod( inputContext, "inputItem", + Qt::DirectConnection, Q_RETURN_ARG( QQuickItem*, inputItem ) ) ) { - /* - 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 ); - } + doUpdate = ( item == inputItem ); } } diff --git a/src/controls/QskSetup.cpp b/src/controls/QskSetup.cpp index 40a67e63..9fe817d7 100644 --- a/src/controls/QskSetup.cpp +++ b/src/controls/QskSetup.cpp @@ -126,7 +126,6 @@ public: QskGraphicProviderMap graphicProviders; - QPointer< QQuickItem > inputPanel; QskSetup::Flags controlFlags; }; @@ -261,20 +260,6 @@ QskGraphicProvider* QskSetup::graphicProvider( const QString& providerId ) const return m_data->graphicProviders.provider( providerId ); } -void QskSetup::setInputPanel( QQuickItem* inputPanel ) -{ - if ( m_data->inputPanel == inputPanel ) - return; - - m_data->inputPanel = inputPanel; - Q_EMIT inputPanelChanged( m_data->inputPanel ); -} - -QQuickItem* QskSetup::inputPanel() -{ - return m_data->inputPanel; -} - QLocale QskSetup::inheritedLocale( const QObject* object ) { VisitorLocale visitor; diff --git a/src/controls/QskSetup.h b/src/controls/QskSetup.h index 2092ae9f..f1c2456d 100644 --- a/src/controls/QskSetup.h +++ b/src/controls/QskSetup.h @@ -58,9 +58,6 @@ public: QskSkin* skin(); - void setInputPanel( QQuickItem* ); - QQuickItem* inputPanel(); - void addGraphicProvider( const QString& providerId, QskGraphicProvider* ); QskGraphicProvider* graphicProvider( const QString& providerId ) const; @@ -74,7 +71,6 @@ public: Q_SIGNALS: void skinChanged( QskSkin* ); - void inputPanelChanged( QQuickItem* ); void controlFlagsChanged(); private: diff --git a/src/controls/QskTextInput.cpp b/src/controls/QskTextInput.cpp index 53d530b7..33344186 100644 --- a/src/controls/QskTextInput.cpp +++ b/src/controls/QskTextInput.cpp @@ -540,6 +540,7 @@ void QskTextInput::setReadOnly( bool on ) m_data->textInput->setReadOnly( on ); + // we are killing user settings here ? m_data->textInput->setFlag( QQuickItem::ItemAcceptsInputMethod, !on ); qskUpdateInputMethod( this, Qt::ImEnabled ); @@ -645,10 +646,13 @@ QskTextInput::EchoMode QskTextInput::echoMode() const void QskTextInput::setEchoMode( EchoMode mode ) { - m_data->textInput->setEchoMode( - static_cast< QQuickTextInput::EchoMode >( mode ) ); + if ( mode != echoMode() ) + { + m_data->textInput->setEchoMode( + static_cast< QQuickTextInput::EchoMode >( mode ) ); - qskUpdateInputMethod( this, Qt::ImHints ); + qskUpdateInputMethod( this, Qt::ImHints ); + } } QString QskTextInput::displayText() const diff --git a/src/inputpanel/QskInputContext.cpp b/src/inputpanel/QskInputContext.cpp index 44f1193e..6b54112d 100644 --- a/src/inputpanel/QskInputContext.cpp +++ b/src/inputpanel/QskInputContext.cpp @@ -4,21 +4,68 @@ *****************************************************************************/ #include "QskInputContext.h" +#include "QskInputPanel.h" #include "QskTextPredictor.h" #include "QskInputPanel.h" #include "QskInputEngine.h" -#include "QskLinearBox.h" +#include #include #include #include -#include #include -#include #include #include +QSK_QT_PRIVATE_BEGIN +#include +QSK_QT_PRIVATE_END + +#include + +static QPointer< QskInputPanel > qskInputPanel = nullptr; + +static void qskDeletePanel() +{ + delete qskInputPanel; +} + +static void qskInputPanelHook() +{ + qAddPostRoutine( qskDeletePanel ); +} + +Q_COREAPP_STARTUP_FUNCTION( qskInputPanelHook ) + +static void qskSetInputPanel( QskInputPanel* inputPanel ) +{ + if ( inputPanel == qskInputPanel ) + return; + + delete qskInputPanel; + qskInputPanel = inputPanel; +} + +void QskInputContext::setInputPanel( QskInputPanel* inputPanel ) +{ + if ( inputPanel == qskInputPanel ) + return; + + qskSetInputPanel( inputPanel ); + + const auto inputContext = + QGuiApplicationPrivate::platformIntegration()->inputContext(); + + if ( auto context = qobject_cast< QskInputContext* >( inputContext ) ) + context->hideInputPanel(); +} + +QskInputPanel* QskInputContext::inputPanel() +{ + return qskInputPanel; +} + static inline uint qskHashLocale( const QLocale& locale ) { return uint( locale.language() + ( uint( locale.country() ) << 16 ) ); @@ -77,32 +124,20 @@ public: // item receiving the input QPointer< QQuickItem > inputItem; - // item, wher the user enters texts/keys - QPointer< QQuickItem > inputPanel; - - // popup or window embedding the inputPanel + // popup or window embedding qskInputPanel QskPopup* inputPopup = nullptr; QskWindow* inputWindow = nullptr; PredictorTable predictorTable; QskInputEngine* engine = nullptr; - - // the input panel is embedded in a window - bool ownsInputPanelWindow : 1; }; QskInputContext::QskInputContext(): m_data( new PrivateData() ) { setObjectName( "InputContext" ); - m_data->engine = new QskInputEngine( this ); - - connect( qskSetup, &QskSetup::inputPanelChanged, - this, &QskInputContext::setInputPanel ); - - setInputPanel( qskSetup->inputPanel() ); } QskInputContext::~QskInputContext() @@ -125,37 +160,6 @@ QQuickItem* QskInputContext::inputItem() return m_data->inputItem; } -void QskInputContext::setInputItem( QQuickItem* item ) -{ - if ( m_data->inputItem == item ) - return; - - auto panel = qobject_cast< QskInputPanel* >( m_data->inputPanel ); - - if ( isInputPanelVisible() ) - { - if ( item == nullptr ) - { - hideInputPanel(); - } - else - { - if ( panel ) - panel->attachInputItem( item ); - - update( Qt::ImQueryAll ); - } - } - else - { - // no need for updates - if ( panel ) - panel->attachInputItem( nullptr ); - } - - m_data->inputItem = item; -} - void QskInputContext::update( Qt::InputMethodQueries queries ) { if ( queries & Qt::ImEnabled ) @@ -170,225 +174,172 @@ void QskInputContext::update( Qt::InputMethodQueries queries ) } } - if ( auto panel = qobject_cast< QskInputPanel* >( m_data->inputPanel ) ) - panel->processInputMethodQueries( queries ); + if ( qskInputPanel ) + qskInputPanel->processInputMethodQueries( queries ); } QRectF QskInputContext::keyboardRect() const { - if ( m_data->inputPanel - && QskDialog::instance()->policy() != QskDialog::TopLevelWindow ) - { - return qskItemGeometry( m_data->inputPanel ); - } + // is this correct and what is this good for ? + if ( m_data->inputPopup ) + return m_data->inputPopup->geometry(); return Inherited::keyboardRect(); } bool QskInputContext::isAnimating() const { + // can be implemented once we have some sliding/fading effects return false; } void QskInputContext::showInputPanel() { - auto& inputPanel = m_data->inputPanel; + auto focusItem = qobject_cast< QQuickItem* >( qGuiApp->focusObject() ); + + if ( focusItem == nullptr ) + return; + + if ( ( focusItem == qskInputPanel ) + || qskIsAncestorOf( qskInputPanel, focusItem ) ) + { + // ignore: usually the input proxy of the panel + return; + } + + m_data->inputItem = focusItem; + auto& inputPopup = m_data->inputPopup; auto& inputWindow = m_data->inputWindow; - if ( inputPanel == nullptr ) + if ( qskInputPanel == nullptr ) + qskSetInputPanel( new QskInputPanel() ); + + connect( qskInputPanel, &QQuickItem::visibleChanged, + this, &QPlatformInputContext::emitInputPanelVisibleChanged, + Qt::UniqueConnection ); + + connect( qskInputPanel, &QskControl::localeChanged, + this, &QPlatformInputContext::emitLocaleChanged, + Qt::UniqueConnection ); + + if ( QskDialog::instance()->policy() == QskDialog::TopLevelWindow ) { - auto panel = new QskInputPanel(); + // The input panel is embedded in a top level window - panel->setParent( this ); - panel->setInputProxy( true ); - - setInputPanel( panel ); - } - - const bool isPopupPanel = qobject_cast< QskPopup* >( inputPanel ); - - bool useWindow = false; - if ( !isPopupPanel ) - { - useWindow = ( QskDialog::instance()->policy() == QskDialog::TopLevelWindow ); - } - - if ( useWindow ) - { delete inputPopup; if ( inputWindow == nullptr ) { inputWindow = new QskWindow(); inputWindow->setDeleteOnClose( true ); +#if 0 inputWindow->setFlags( Qt::Tool | Qt::WindowDoesNotAcceptFocus ); +#endif - inputPanel->setParentItem( inputWindow->contentItem() ); - - QSizeF size; - if ( auto control = qobject_cast< const QskControl* >( inputPanel ) ) - size = control->sizeHint(); - - if ( size.isEmpty() ) - size = QSizeF( 800, 240 ); // ### what size? - - inputWindow->resize( size.toSize() ); - inputWindow->show(); - - inputWindow->installEventFilter( this ); + qskInputPanel->setParentItem( inputWindow->contentItem() ); } + + 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; if ( inputPopup == nullptr ) { - if ( isPopupPanel ) - { - inputPopup = qobject_cast< QskPopup* >( inputPanel ); - } - else - { - auto popup = new QskPopup( m_data->inputItem->window()->contentItem() ); + inputPopup = new QskPopup( m_data->inputItem->window()->contentItem() ); - popup->setAutoLayoutChildren( true ); - popup->setTransparentForPositioner( false ); - popup->setOverlay( false ); - popup->setModal( true ); + inputPopup->setAutoLayoutChildren( true ); + inputPopup->setTransparentForPositioner( false ); + inputPopup->setModal( true ); - auto box = new QskLinearBox( popup ); - box->addItem( inputPanel ); + auto box = new QskLinearBox( inputPopup ); + box->addItem( qskInputPanel ); - if ( auto panel = qobject_cast< QskInputPanel* >( inputPanel ) ) - { - if ( panel->hasInputProxy() ) - { - popup->setOverlay( true ); - } - } + /* + 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(); - if ( !popup->hasOverlay() ) - { - box->setExtraSpacingAt( Qt::TopEdge | Qt::LeftEdge | Qt::RightEdge ); - } + inputPopup->setOverlay( hasInputProxy ); - 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() ); - } + if ( !hasInputProxy ) + box->setExtraSpacingAt( Qt::TopEdge | Qt::LeftEdge | Qt::RightEdge ); } + inputPopup->setParentItem( m_data->inputItem->window()->contentItem() ); inputPopup->setVisible( true ); + inputPopup->installEventFilter( this ); } - update( Qt::ImQueryAll ); - -#if 1 - if ( auto panel = qobject_cast< QskInputPanel* >( m_data->inputPanel ) ) - panel->updateInputProxy( m_data->inputItem ); -#endif - - inputPanel->setVisible( true ); - -#if 0 - if ( auto focusItem = inputPanel->nextItemInFocusChain( true ) ) - qskForceActiveFocus( focusItem, Qt::OtherFocusReason ); -#endif - - connect( inputPanel->window(), &QskWindow::visibleChanged, - this, &QskInputContext::emitInputPanelVisibleChanged ); - - updateInputPanel( m_data->inputItem ); - m_data->engine->setPredictor( m_data->predictorTable.find( locale() ) ); + + qskInputPanel->setLocale( locale() ); + qskInputPanel->attachInputItem( m_data->inputItem ); + qskInputPanel->setEngine( m_data->engine ); } void QskInputContext::hideInputPanel() { - if ( m_data->inputPanel ) + if ( m_data->inputPopup ) { - // to get rid of the scene graph nodes - m_data->inputPanel->setVisible( false ); - if ( auto panel = qobject_cast< QskInputPanel* >( m_data->inputPanel ) ) - panel->setEngine( nullptr ); - } - - if ( m_data->inputPopup == m_data->inputPanel ) - { - m_data->inputPopup->removeEventFilter( this ); - m_data->inputPopup = nullptr; - } - else - { - if ( m_data->inputPopup ) - { #if 1 - if ( auto focusItem = m_data->inputPopup->scopedFocusItem() ) - { - /* - Qt bug: QQuickItem::hasFocus() is not cleared - when the corresponding focusScope gets deleted. - Usually no problem, but here the focusItem is no - child and will be reused with a different parent - later. - */ - focusItem->setFocus( false ); - } + if ( auto focusItem = m_data->inputPopup->scopedFocusItem() ) + { + /* + Qt bug: QQuickItem::hasFocus() is not cleared + when the corresponding focusScope gets deleted. + Usually no problem, but here the focusItem is no + child and will be reused with a different parent + later. + */ + focusItem->setFocus( false ); + } #endif - m_data->inputPopup->deleteLater(); - } + m_data->inputPopup->deleteLater(); } - QskWindow* window = m_data->inputWindow; - m_data->inputWindow = nullptr; - - if ( window ) + if ( m_data->inputWindow ) { + QskWindow* window = m_data->inputWindow; + m_data->inputWindow = nullptr; + window->removeEventFilter( this ); window->close(); // deleteOnClose is set } - qGuiApp->removeEventFilter( this ); + if ( qskInputPanel ) + { + //qskInputPanel->setVisible( false ); + qskInputPanel->setParentItem( nullptr ); + qskInputPanel->attachInputItem( nullptr ); + qskInputPanel->setEngine( nullptr ); + } - updateInputPanel( nullptr ); -} - -void QskInputContext::updateInputPanel( QQuickItem* inputItem ) -{ - auto panel = qobject_cast< QskInputPanel* >( m_data->inputPanel ); - if ( panel == nullptr ) - return; - - panel->setLocale( locale() ); - panel->attachInputItem( inputItem ); - - panel->setEngine( inputItem ? m_data->engine : nullptr ); + m_data->inputItem = nullptr; } bool QskInputContext::isInputPanelVisible() const { - auto panel = m_data->inputPanel; - - return panel && panel->isVisible() - && panel->window() && panel->window()->isVisible(); + return qskInputPanel && qskInputPanel->isVisible() + && qskInputPanel->window() && qskInputPanel->window()->isVisible(); } QLocale QskInputContext::locale() const @@ -411,48 +362,52 @@ Qt::LayoutDirection QskInputContext::inputDirection() const void QskInputContext::setFocusObject( QObject* focusObject ) { - auto focusItem = qobject_cast< QQuickItem* >( focusObject ); - - if ( focusItem == nullptr ) + if ( m_data->inputItem == nullptr || m_data->inputItem == focusObject ) { - if ( m_data->inputItem ) + // we don't care + return; + } + + bool doTerminate = true; + + if ( focusObject == nullptr && m_data->inputPopup ) + { + if ( const auto window = m_data->inputItem->window() ) { - if ( m_data->inputItem->window() == QGuiApplication::focusWindow() ) - setInputItem( nullptr ); + auto focusItem = window->contentItem()->scopedFocusItem(); + if ( focusItem == m_data->inputPopup ) + doTerminate = false; } } - else + + if ( doTerminate ) { - /* - Do not change the input item when - navigating to or inside the input popup/window - */ - - bool isAccepted = ( m_data->inputItem == nullptr ); - - if ( !isAccepted ) + if ( m_data->inputWindow ) { - if ( m_data->inputWindow ) + auto focusWindow = QGuiApplication::focusWindow(); + + if ( focusWindow == nullptr || + QGuiApplication::focusWindow() == 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; - } - } - else - { - isAccepted = true; + doTerminate = false; } } + else if ( m_data->inputPopup ) + { + auto focusItem = qobject_cast< QQuickItem* >( focusObject ); - if ( isAccepted ) - setInputItem( focusItem ); + if ( ( focusItem == m_data->inputPopup ) + || qskIsAncestorOf( m_data->inputPopup, focusItem ) ) + { + doTerminate = false; + } + } + } + + if ( doTerminate ) + { + hideInputPanel(); + m_data->inputItem = nullptr; } } @@ -484,51 +439,17 @@ void QskInputContext::invokeAction( QInputMethod::Action, int ) { } -void QskInputContext::setInputPanel( QQuickItem* inputPanel ) -{ - if ( m_data->inputPanel == inputPanel ) - return; - - if ( m_data->inputPanel ) - { - m_data->inputPanel->disconnect( this ); - - if ( m_data->inputPanel->parent() == this ) - { - delete m_data->inputPanel; - } - else - { - m_data->inputPanel->setParentItem( nullptr ); - } - } - - m_data->inputPanel = inputPanel; - m_data->ownsInputPanelWindow = false; - - if ( inputPanel ) - { - if ( inputPanel->parent() == nullptr ) - inputPanel->setParent( this ); - - connect( inputPanel, &QQuickItem::visibleChanged, - this, &QPlatformInputContext::emitInputPanelVisibleChanged ); - - if ( auto control = qobject_cast< QskControl* >( inputPanel ) ) - { - connect( control, &QskControl::localeChanged, - this, &QPlatformInputContext::emitLocaleChanged ); - } - } -} - void QskInputContext::reset() { } void QskInputContext::commit() { - // called on focus changes + /* + commit is called, when the input item loses the focus. + As it it should be possible to navigate inside of the + inputPanel this is no valid reason to hide the panel. + */ } bool QskInputContext::eventFilter( QObject* object, QEvent* event ) @@ -539,15 +460,15 @@ bool QskInputContext::eventFilter( QObject* object, QEvent* event ) { case QEvent::Move: { - if ( m_data->inputPanel ) + if ( qskInputPanel ) emitKeyboardRectChanged(); break; } case QEvent::Resize: { - if ( m_data->inputPanel ) - m_data->inputPanel->setSize( m_data->inputWindow->size() ); + if ( qskInputPanel ) + qskInputPanel->setSize( m_data->inputWindow->size() ); break; } diff --git a/src/inputpanel/QskInputContext.h b/src/inputpanel/QskInputContext.h index 663961c3..bf3169e8 100644 --- a/src/inputpanel/QskInputContext.h +++ b/src/inputpanel/QskInputContext.h @@ -11,6 +11,7 @@ #include class QskTextPredictor; +class QskInputPanel; class QQuickItem; class QSK_EXPORT QskInputContext : public QPlatformInputContext @@ -51,19 +52,16 @@ public: virtual bool filterEvent( const QEvent* ) override; + static void setInputPanel( QskInputPanel* ); + static QskInputPanel* inputPanel(); + protected: - virtual void updateInputPanel( QQuickItem* inputItem ); - -private Q_SLOTS: - void setInputPanel( QQuickItem* ); - virtual bool eventFilter( QObject*, QEvent* ) override; private: - void setInputItem( QQuickItem* ); - class PrivateData; std::unique_ptr< PrivateData > m_data; }; + #endif diff --git a/src/inputpanel/QskInputPanel.cpp b/src/inputpanel/QskInputPanel.cpp index 442d51f3..2c68ee48 100644 --- a/src/inputpanel/QskInputPanel.cpp +++ b/src/inputpanel/QskInputPanel.cpp @@ -18,10 +18,27 @@ #include #include -static inline void qskSendText( QQuickItem* inputItem, +static inline void qskSendReplaceText( QQuickItem* receiver, const QString& text ) +{ + if ( receiver == nullptr ) + return; + + QInputMethodEvent::Attribute attribute( + QInputMethodEvent::Selection, 0, 32767, QVariant() ); + + QInputMethodEvent event1( QString(), { attribute } ); + QCoreApplication::sendEvent( receiver, &event1 ); + + QInputMethodEvent event2; + event2.setCommitString( text ); + + QCoreApplication::sendEvent( receiver, &event2 ); +} + +static inline void qskSendText( QQuickItem* receiver, const QString& text, bool isFinal ) { - if ( inputItem == nullptr ) + if ( receiver == nullptr ) return; if ( isFinal ) @@ -29,7 +46,7 @@ static inline void qskSendText( QQuickItem* inputItem, QInputMethodEvent event; event.setCommitString( text ); - QCoreApplication::sendEvent( inputItem, &event ); + QCoreApplication::sendEvent( receiver, &event ); } else { @@ -41,20 +58,20 @@ static inline void qskSendText( QQuickItem* inputItem, QInputMethodEvent event( text, { attribute } ); - QCoreApplication::sendEvent( inputItem, &event ); + QCoreApplication::sendEvent( receiver, &event ); } } -static inline void qskSendKey( QQuickItem* inputItem, int key ) +static inline void qskSendKey( QQuickItem* receiver, int key ) { - if ( inputItem == nullptr ) + if ( receiver == nullptr ) return; QKeyEvent keyPress( QEvent::KeyPress, key, Qt::NoModifier ); - QCoreApplication::sendEvent( inputItem, &keyPress ); + QCoreApplication::sendEvent( receiver, &keyPress ); QKeyEvent keyRelease( QEvent::KeyRelease, key, Qt::NoModifier ); - QCoreApplication::sendEvent( inputItem, &keyRelease ); + QCoreApplication::sendEvent( receiver, &keyRelease ); } namespace @@ -65,6 +82,7 @@ namespace TextInput( QQuickItem* parentItem = nullptr ): QskTextInput( parentItem ) { + setObjectName( "InputPanelInputProxy" ); } }; } @@ -75,10 +93,18 @@ class QskInputPanel::PrivateData { public: PrivateData(): + inputHints( 0 ), + maxChars( -1 ), + hasPrediction( true ), hasInputProxy( true ) { } + QQuickItem* receiverItem() + { + return hasInputProxy ? inputProxy : inputItem; + } + QPointer< QskInputEngine > engine; QPointer< QQuickItem > inputItem; @@ -88,6 +114,10 @@ public: QskInputPredictionBar* predictionBar; QskVirtualKeyboard* keyboard; + Qt::InputMethodHints inputHints; + int maxChars; + + bool hasPrediction : 1; bool hasInputProxy : 1; }; @@ -146,7 +176,8 @@ void QskInputPanel::setEngine( QskInputEngine* engine ) this, &QskInputPanel::updatePredictionBar ); } - m_data->predictionBar->setVisible( engine && engine->predictor() ); + m_data->predictionBar->setVisible( + m_data->hasPrediction && engine && engine->predictor() ); } void QskInputPanel::attachInputItem( QQuickItem* item ) @@ -165,6 +196,9 @@ void QskInputPanel::attachInputItem( QQuickItem* item ) queries &= ~Qt::ImEnabled; processInputMethodQueries( queries ); + + if ( m_data->hasInputProxy ) + m_data->inputProxy->setEditing( true ); } } @@ -234,56 +268,6 @@ void QskInputPanel::setInputProxy( bool on ) prompt->setVisible( false ); } -void QskInputPanel::updateInputProxy( const QQuickItem* inputItem ) -{ - if ( inputItem == nullptr ) - return; - - QInputMethodQueryEvent event( Qt::ImQueryAll ); - QCoreApplication::sendEvent( const_cast< QQuickItem* >( inputItem ), &event ); - - const auto proxy = m_data->inputProxy; - - if ( event.queries() & Qt::ImHints ) - { - const auto hints = static_cast< Qt::InputMethodHints >( - event.value( Qt::ImHints ).toInt() ); - - const auto echoMode = ( hints & Qt::ImhHiddenText ) - ? QskTextInput::PasswordEchoOnEdit : QskTextInput::Normal; - - proxy->setEchoMode( echoMode ); - } - - if ( event.queries() & Qt::ImSurroundingText ) - { - const auto text = event.value( Qt::ImSurroundingText ).toString(); - proxy->setText( text ); - } - - if ( event.queries() & Qt::ImCursorPosition ) - { - const auto pos = event.value( Qt::ImCursorPosition ).toInt(); - proxy->setCursorPosition( pos ); - } - -#if 0 - if ( event.queries() & Qt::ImCurrentSelection ) - { - const auto text = event.value( Qt::ImCursorPosition ).toString(); - if ( !text.isEmpty() ) - { - } - } -#endif - - if ( event.queries() & Qt::ImMaximumTextLength ) - { - const auto length = event.value( Qt::ImMaximumTextLength ).toInt(); - proxy->setMaxLength( length ); - } -} - void QskInputPanel::commitPredictiveText( int index ) { m_data->predictionBar->setPrediction( QVector< QString >() ); @@ -302,34 +286,23 @@ void QskInputPanel::commitKey( int key ) if ( m_data->engine == nullptr || m_data->inputItem == nullptr ) return; - auto engine = m_data->engine; - auto inputItem = m_data->inputItem; - - QInputMethodQueryEvent event( Qt::ImHints ); - QCoreApplication::sendEvent( inputItem, &event ); - - const auto inputHints = static_cast< Qt::InputMethodHints >( - event.value( Qt::ImHints ).toInt() ); - int spaceLeft = -1; - if ( !( inputHints & Qt::ImhMultiLine ) ) + if ( !( m_data->inputHints & Qt::ImhMultiLine ) ) { - QInputMethodQueryEvent event( - Qt::ImSurroundingText | Qt::ImMaximumTextLength ); + auto receiver = m_data->receiverItem(); - QCoreApplication::sendEvent( inputItem, &event ); - - const int max = event.value( Qt::ImMaximumTextLength ).toInt(); - - if ( max > 0 ) + if ( m_data->maxChars >= 0 ) { + QInputMethodQueryEvent event( Qt::ImSurroundingText ); + QCoreApplication::sendEvent( receiver, &event ); + const auto text = event.value( Qt::ImSurroundingText ).toString(); - spaceLeft = max - text.length(); + spaceLeft = m_data->maxChars - text.length(); } } - processKey( key, inputHints, spaceLeft ); + processKey( key, m_data->inputHints, spaceLeft ); } void QskInputPanel::processKey( int key, @@ -338,16 +311,35 @@ void QskInputPanel::processKey( int key, const auto result = m_data->engine->processKey( key, inputHints, spaceLeft ); auto inputItem = m_data->inputItem; + auto inputProxy = m_data->inputProxy; if ( result.key ) { - // sending a control key - qskSendKey( inputItem, result.key ); + switch( result.key ) + { + case Qt::Key_Return: + { + if ( m_data->hasInputProxy ) + qskSendReplaceText( inputItem, inputProxy->text() ); + + qskSendKey( inputItem, result.key ); + break; + } + case Qt::Key_Escape: + { + qskSendKey( inputItem, result.key ); + break; + } + default: + { + qskSendKey( m_data->receiverItem(), result.key ); + } + } } else if ( !result.text.isEmpty() ) { // changing the current text - qskSendText( inputItem, result.text, result.isFinal ); + qskSendText( m_data->receiverItem(), result.text, result.isFinal ); } } @@ -356,73 +348,182 @@ void QskInputPanel::processInputMethodQueries( Qt::InputMethodQueries queries ) if ( m_data->inputItem == nullptr ) return; - /* - adjust the input panel to information provided from the input item - */ + QInputMethodQueryEvent event( queries ); + QCoreApplication::sendEvent( m_data->inputItem, &event ); - QInputMethodQueryEvent queryEvent( queries ); - QCoreApplication::sendEvent( m_data->inputItem, &queryEvent ); - - if ( queryEvent.queries() & Qt::ImHints ) + if ( 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, // not use predictive text + bool hasPrediction = true; + bool hasEchoMode = false; - 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() ); + event.value( Qt::ImHints ).toInt() ); -#endif + if ( hints & Qt::ImhHiddenText ) + { + hasEchoMode = true; + } + + if ( hints & Qt::ImhSensitiveData ) + { + } + + if ( hints & Qt::ImhNoAutoUppercase ) + { + } + + if ( hints & Qt::ImhPreferNumbers ) + { + // we should start with having the number keys being visible + } + + if ( hints & Qt::ImhPreferUppercase ) + { + // we should start with having the upper keys being visible + } + + if ( hints & Qt::ImhPreferLowercase ) + { + // we should start with having the upper keys being visible + } + + if ( hints & Qt::ImhNoPredictiveText ) + { + hasPrediction = false; + } + + if ( hints & Qt::ImhDate ) + { + // we should have a date/time input + } + + if ( hints & Qt::ImhTime ) + { + // we should have a date/time input + } + + if ( hints & Qt::ImhPreferLatin ) + { + // conflicts with our concept of using the locale + } + + if ( hints & Qt::ImhMultiLine ) + { + // we need an implementation of QskTextEdit for this + } + + if ( hints & Qt::ImhDigitsOnly ) + { + // using a numpad instead of our virtual keyboard + } + + if ( hints & Qt::ImhFormattedNumbersOnly ) + { + // a numpad with decimal point and minus sign + } + + if ( hints & Qt::ImhUppercaseOnly ) + { + // locking all other keys + } + + if ( hints & Qt::ImhLowercaseOnly ) + { + // locking all other keys + } + + if ( hints & Qt::ImhDialableCharactersOnly ) + { + // characters suitable for phone dialing + } + + if ( hints & Qt::ImhEmailCharactersOnly ) + { + // characters suitable for email addresses + } + + if ( hints & Qt::ImhUrlCharactersOnly ) + { + // characters suitable for URLs + } + + if ( hints & Qt::ImhLatinOnly ) + { + // locking all other keys + } + + m_data->hasPrediction = hasPrediction; + + m_data->predictionBar->setVisible( + hasPrediction && m_data->engine && m_data->engine->predictor() ); + + m_data->inputProxy->setEchoMode( + hasEchoMode ? QskTextInput::PasswordEchoOnEdit : QskTextInput::Normal ); + + m_data->inputHints = hints; } -#if 0 - if ( queryEvent.queries() & Qt::ImPreferredLanguage ) + if ( queries & Qt::ImPreferredLanguage ) { + // already handled by the input context } + + if ( queries & Qt::ImMaximumTextLength ) + { + // needs to be handled before Qt::ImCursorPosition ! + + m_data->maxChars = event.value( Qt::ImMaximumTextLength ).toInt(); +#if 1 + if ( m_data->maxChars >= 32767 ) + m_data->maxChars = -1; #endif + if ( m_data->hasInputProxy ) + m_data->inputProxy->setMaxLength( m_data->maxChars ); + } + + + if ( queries & Qt::ImSurroundingText ) + { + if ( m_data->hasInputProxy ) + { + const auto text = event.value( Qt::ImSurroundingText ).toString(); + m_data->inputProxy->setText( text ); + } + } + + if ( queries & Qt::ImCursorPosition ) + { + if ( m_data->hasInputProxy ) + { + const auto pos = event.value( Qt::ImCursorPosition ).toInt(); + m_data->inputProxy->setCursorPosition( pos ); + } + } + + if ( queries & Qt::ImCurrentSelection ) + { +#if 0 + const auto text = event.value( Qt::ImCurrentSelection ).toString(); + if ( !text.isEmpty() ) + { + } +#endif + } /* 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 Qt::ImAbsolutePosition - Qt::ImTextBeforeCursor // important for chinese - Qt::ImTextAfterCursor // important for chinese + Qt::ImTextBeforeCursor + Qt::ImTextAfterCursor Qt::ImPlatformData // hard to say... Qt::ImEnterKeyType Qt::ImAnchorRectangle - Qt::ImInputItemClipRectangle // could be used for the geometry of the panel + Qt::ImInputItemClipRectangle */ - } void QskInputPanel::keyPressEvent( QKeyEvent* event ) diff --git a/src/inputpanel/QskInputPanel.h b/src/inputpanel/QskInputPanel.h index 3c005022..0cffc876 100644 --- a/src/inputpanel/QskInputPanel.h +++ b/src/inputpanel/QskInputPanel.h @@ -16,7 +16,7 @@ class QLocale; template class QVector< QString >; -class QSK_EXPORT QskInputPanel: public QskBox +class QSK_EXPORT QskInputPanel : public QskBox { Q_OBJECT @@ -49,7 +49,6 @@ public: virtual QskAspect::Subcontrol effectiveSubcontrol( QskAspect::Subcontrol ) const override; - void updateInputProxy( const QQuickItem* ); virtual void processInputMethodQueries( Qt::InputMethodQueries ); Q_SIGNALS: