diff --git a/inputcontext/QskInputCompositionModel.cpp b/inputcontext/QskInputCompositionModel.cpp index 601b7aef..3a9b8961 100644 --- a/inputcontext/QskInputCompositionModel.cpp +++ b/inputcontext/QskInputCompositionModel.cpp @@ -37,26 +37,26 @@ static QString qskKeyString( int code ) class QskInputCompositionModel::PrivateData { public: + PrivateData() : + inputItem( nullptr ), + groupIndex( 0 ) + { + } // QInputMethod QString preedit; QTextCharFormat preeditFormat; QList< QInputMethodEvent::Attribute > preeditAttributes; + QObject* inputItem; int groupIndex; }; -static inline void sendCompositionEvent( QInputMethodEvent* e ) -{ - if ( auto focusObject = QGuiApplication::focusObject() ) - QCoreApplication::sendEvent( focusObject, e ); -} - QskInputCompositionModel::QskInputCompositionModel(): m_data( new PrivateData ) { m_data->groupIndex = 0; - m_data->preeditFormat.setFontUnderline(true); + m_data->preeditFormat.setFontUnderline( true ); m_data->preeditAttributes.append( QInputMethodEvent::Attribute( QInputMethodEvent::TextFormat, 0, 0, m_data->preeditFormat ) ); } @@ -71,13 +71,12 @@ void QskInputCompositionModel::composeKey( Qt::Key key ) if ( !inputMethod ) return; - auto focusObject = QGuiApplication::focusObject(); - if ( !focusObject ) + if ( !m_data->inputItem ) return; QInputMethodQueryEvent queryEvent( Qt::ImSurroundingText | Qt::ImMaximumTextLength | Qt::ImHints ); - QCoreApplication::sendEvent( focusObject, &queryEvent ); + QCoreApplication::sendEvent( m_data->inputItem, &queryEvent ); const auto hints = static_cast< Qt::InputMethodHints >( queryEvent.value( Qt::ImHints ).toInt() ); const int maxLength = queryEvent.value( Qt::ImMaximumTextLength ).toInt(); @@ -224,8 +223,7 @@ void QskInputCompositionModel::commitCandidate( int index ) void QskInputCompositionModel::backspace() { - auto focusWindow = QGuiApplication::focusWindow(); - if ( !focusWindow ) + if ( !m_data->inputItem ) return; if ( !m_data->preedit.isEmpty() ) @@ -237,8 +235,8 @@ void QskInputCompositionModel::backspace() // Backspace one character only if preedit was inactive QKeyEvent keyPress( QEvent::KeyPress, Qt::Key_Backspace, Qt::NoModifier ); QKeyEvent keyRelease( QEvent::KeyRelease, Qt::Key_Backspace, Qt::NoModifier ); - QCoreApplication::sendEvent( focusWindow, &keyPress ); - QCoreApplication::sendEvent( focusWindow, &keyRelease ); + QCoreApplication::sendEvent( m_data->inputItem, &keyPress ); + QCoreApplication::sendEvent( m_data->inputItem, &keyRelease ); return; } @@ -253,18 +251,28 @@ void QskInputCompositionModel::moveCursor( Qt::Key key ) if ( key != Qt::Key_Left && key != Qt::Key_Right ) return; - auto focusWindow = QGuiApplication::focusWindow(); - if ( !focusWindow ) + if ( !m_data->inputItem ) return; // Moving cursor is disabled when preedit is active. if ( !m_data->preedit.isEmpty() ) return; + // ### this should be in the panel: QKeyEvent moveCursorPress( QEvent::KeyPress, key, Qt::NoModifier ); QKeyEvent moveCursorRelease( QEvent::KeyRelease, key, Qt::NoModifier ); - QCoreApplication::sendEvent( focusWindow, &moveCursorPress ); - QCoreApplication::sendEvent( focusWindow, &moveCursorRelease ); +#if 1 + QFocusEvent focusIn( QEvent::FocusIn ); // hack to display the cursor +#endif + QCoreApplication::sendEvent( m_data->inputItem, &focusIn ); + QCoreApplication::sendEvent( m_data->inputItem, &moveCursorPress ); + QCoreApplication::sendEvent( m_data->inputItem, &moveCursorRelease ); +} + +void QskInputCompositionModel::sendCompositionEvent( QInputMethodEvent* e ) +{ + if ( m_data->inputItem ) + QCoreApplication::sendEvent( m_data->inputItem, e ); } bool QskInputCompositionModel::hasIntermediate() const @@ -300,6 +308,11 @@ QVector< Qt::Key > QskInputCompositionModel::groups() const return QVector< Qt::Key >(); } +void QskInputCompositionModel::setInputItem( QObject *inputItem ) +{ + m_data->inputItem = inputItem; +} + bool QskInputCompositionModel::nextGroupIndex(int& index, bool forward) const { Q_UNUSED(index); diff --git a/inputcontext/QskInputCompositionModel.h b/inputcontext/QskInputCompositionModel.h index e96eecd2..94e5be53 100644 --- a/inputcontext/QskInputCompositionModel.h +++ b/inputcontext/QskInputCompositionModel.h @@ -10,6 +10,7 @@ #include +class QInputMethodEvent; class QStringList; class QskInputCompositionModel : public QObject @@ -37,6 +38,8 @@ public: virtual QVector< Qt::Key > groups() const; + void setInputItem( QObject* inputItem ); + protected: // Used for text composition virtual bool hasIntermediate() const; @@ -50,6 +53,7 @@ Q_SIGNALS: private: void backspace(); void moveCursor( Qt::Key key ); + void sendCompositionEvent( QInputMethodEvent* e ); class PrivateData; std::unique_ptr< PrivateData > m_data; diff --git a/inputcontext/QskInputContext.cpp b/inputcontext/QskInputContext.cpp index 20a24c20..c516740b 100644 --- a/inputcontext/QskInputContext.cpp +++ b/inputcontext/QskInputContext.cpp @@ -21,11 +21,15 @@ #include QskInputContext::QskInputContext(): - Inherited() + Inherited(), + m_inputCompositionModel( new QskInputCompositionModel ) { connect( qskSetup, &QskSetup::inputPanelChanged, this, &QskInputContext::setInputPanel ); setInputPanel( qskSetup->inputPanel() ); + + // We could connect candidatesChanged() here, but we don't emit + // the signal in the normal composition model anyhow } QskInputContext::~QskInputContext() @@ -41,11 +45,11 @@ bool QskInputContext::isValid() const void QskInputContext::update( Qt::InputMethodQueries queries ) { - if ( !m_focusObject ) + if ( !m_inputItem ) return; QInputMethodQueryEvent queryEvent( queries ); - if ( !QCoreApplication::sendEvent( m_focusObject, &queryEvent ) ) + if ( !QCoreApplication::sendEvent( m_inputItem, &queryEvent ) ) return; // Qt::ImCursorRectangle @@ -204,75 +208,44 @@ QLocale QskInputContext::locale() const void QskInputContext::setFocusObject( QObject* focusObject ) { - if ( m_focusObject ) - m_focusObject->removeEventFilter( this ); - m_focusObject = focusObject; if ( !m_focusObject ) - return; - - QInputMethodQueryEvent queryEvent( Qt::ImEnabled ); - if ( !QCoreApplication::sendEvent( m_focusObject, &queryEvent ) ) - return; - - if ( !queryEvent.value( Qt::ImEnabled ).toBool() ) { - hideInputPanel(); + m_inputItem = nullptr; + m_inputCompositionModel->setInputItem( nullptr ); return; } - m_focusObject->installEventFilter( this ); + bool inputItemChanged = false; + + auto focusQuickItem = qobject_cast< QQuickItem* >( focusObject ); + if( focusQuickItem ) + { + // Do not change the input item when panel buttons get the focus: + if( qskNearestFocusScope( focusQuickItem ) != m_inputPanel ) + { + m_inputItem = focusQuickItem; + m_inputCompositionModel->setInputItem( m_inputItem ); // ### use a signal/slot connection + inputItemChanged = true; + } + } + + if( inputItemChanged ) + { + QInputMethodQueryEvent queryEvent( Qt::ImEnabled ); + if ( !QCoreApplication::sendEvent( m_inputItem, &queryEvent ) ) + return; + + if ( !queryEvent.value( Qt::ImEnabled ).toBool() ) + { + hideInputPanel(); + return; + } + } + update( Qt::InputMethodQuery( Qt::ImQueryAll & ~Qt::ImEnabled ) ); } -bool QskInputContext::eventFilter( QObject* object, QEvent* event ) -{ - if ( m_inputPanel && event->type() == QEvent::KeyPress ) - { - auto keyEvent = static_cast< QKeyEvent* >( event ); - const auto key = keyEvent->key(); - switch ( key ) - { - case Qt::Key_Tab: - case Qt::Key_Backtab: - if ( m_inputPanel->advanceFocus( key == Qt::Key_Tab ) ) - { - keyEvent->accept(); - return true; - } - break; - case Qt::Key_Select: - case Qt::Key_Space: - // if there is a focused key, treat the key like a push button - if ( m_inputPanel->activateFocusKey() ) - { - keyEvent->accept(); - return true; - } - break; - } - } - - if ( m_inputPanel && event->type() == QEvent::KeyRelease ) - { - auto keyEvent = static_cast< QKeyEvent* >( event ); - const auto key = keyEvent->key(); - switch ( key ) - { - case Qt::Key_Select: - case Qt::Key_Space: - if ( m_inputPanel->deactivateFocusKey() ) - { - keyEvent->accept(); - return true; - } - break; - } - } - - return Inherited::eventFilter( object, event ); -} - void QskInputContext::invokeAction( QInputMethod::Action action, int cursorPosition ) { Q_UNUSED(cursorPosition); diff --git a/inputcontext/QskInputContext.h b/inputcontext/QskInputContext.h index 1a167f86..b7694bfb 100644 --- a/inputcontext/QskInputContext.h +++ b/inputcontext/QskInputContext.h @@ -8,6 +8,7 @@ #include #include +#include #include @@ -22,7 +23,7 @@ class QskInputContext : public QPlatformInputContext public: QskInputContext(); - ~QskInputContext(); + ~QskInputContext() override; bool isValid() const override; void update( Qt::InputMethodQueries ) override; @@ -35,9 +36,6 @@ public: QLocale locale() const override; void setFocusObject( QObject* ) override; -protected: - bool eventFilter( QObject*, QEvent* ) override; - private Q_SLOTS: void emitAnimatingChanged(); void handleCandidatesChanged(); @@ -45,6 +43,7 @@ private Q_SLOTS: private: QPointer< QObject > m_focusObject; + QPointer< QQuickItem > m_inputItem; QPointer< QskInputPanel > m_inputPanel; std::unique_ptr< QskInputCompositionModel > m_inputCompositionModel; }; diff --git a/playground/inputpanel/TextInput.cpp b/playground/inputpanel/TextInput.cpp new file mode 100644 index 00000000..6069ffdc --- /dev/null +++ b/playground/inputpanel/TextInput.cpp @@ -0,0 +1,17 @@ +#include "TextInput.h" + +TextInput::TextInput( QQuickItem* parent ) + : QQuickTextInput( parent ) +{ + +} + +TextInput::~TextInput() +{ + +} + +void TextInput::inputMethodEvent(QInputMethodEvent *event) +{ + QQuickTextInput::inputMethodEvent(event); +} diff --git a/playground/inputpanel/TextInput.h b/playground/inputpanel/TextInput.h new file mode 100644 index 00000000..86ff4f3f --- /dev/null +++ b/playground/inputpanel/TextInput.h @@ -0,0 +1,16 @@ +#ifndef TEXTINPUT_H +#define TEXTINPUT_H + +#include + +class TextInput : public QQuickTextInput +{ + public: + TextInput( QQuickItem* parent ); + virtual ~TextInput() override; + +protected: + void inputMethodEvent(QInputMethodEvent *) Q_DECL_OVERRIDE; +}; + +#endif // TEXTINPUT_H diff --git a/playground/inputpanel/inputpanel.pro b/playground/inputpanel/inputpanel.pro index 499ddb20..3b9b2535 100644 --- a/playground/inputpanel/inputpanel.pro +++ b/playground/inputpanel/inputpanel.pro @@ -5,4 +5,8 @@ TARGET = inputpanel DEFINES += PLUGIN_PATH=$$clean_path( $$QSK_OUT_ROOT/plugins ) SOURCES += \ - main.cpp + main.cpp \ + TextInput.cpp + +HEADERS += \ + TextInput.h diff --git a/playground/inputpanel/main.cpp b/playground/inputpanel/main.cpp index defd4ce4..0e485801 100644 --- a/playground/inputpanel/main.cpp +++ b/playground/inputpanel/main.cpp @@ -3,6 +3,8 @@ * This file may be used under the terms of the 3-clause BSD License *****************************************************************************/ +#include "TextInput.h" + #include #include @@ -21,8 +23,6 @@ #include #include -#include - #define STRINGIFY(x) #x #define STRING(x) STRINGIFY(x) diff --git a/skins/material/QskMaterialSkin.cpp b/skins/material/QskMaterialSkin.cpp index 37a4b8b0..aac89c7e 100644 --- a/skins/material/QskMaterialSkin.cpp +++ b/skins/material/QskMaterialSkin.cpp @@ -521,31 +521,24 @@ void QskMaterialSkin::initTabViewHints() void QskMaterialSkin::initInputPanelHints() { using namespace QskAspect; - using Q = QskInputPanel; + using Q = QskKeyButton; const ColorPalette& pal = m_data->palette; // key panel - setMargins( Q::KeyPanel | Margin, 2 ); + setMargins( QskInputPanel::Panel | Margin, 2 ); - setBoxShape( Q::KeyPanel, 20.0, Qt::RelativeSize ); - setBoxBorderMetrics( Q::KeyPanel, 2 ); + setBoxShape( Q::Panel, 20.0, Qt::RelativeSize ); + setBoxBorderMetrics( Q::Panel, 2 ); - setGradient( Q::KeyPanel, pal.darker125 ); - setBoxBorderColors( Q::KeyPanel, pal.baseColor ); + setGradient( Q::Panel, pal.darker125 ); + setBoxBorderColors( Q::Panel, pal.baseColor ); for ( auto state : { NoState, Q::Focused } ) - setBoxBorderColors( Q::KeyPanel | Q::Pressed | state, pal.accentColor ); + setBoxBorderColors( Q::Panel | Q::Pressed | state, pal.accentColor ); - setAnimation( Q::KeyPanel | Color, qskDuration ); - setAnimation( Q::KeyPanel | Metric, qskDuration ); - - // glyph - setSkinHint( Q::KeyGlyph | Alignment, Qt::AlignCenter ); - setFontRole( Q::KeyGlyph, QskSkin::TinyFont ); - - setColor( Q::KeyGlyph, pal.textColor ); - setColor( Q::KeyGlyph | Q::Disabled, pal.darker200 ); + setAnimation( Q::Panel | Color, qskDuration ); + setAnimation( Q::Panel | Metric, qskDuration ); // panel setBoxShape( Q::Panel, 0 ); diff --git a/skins/squiek/QskSquiekSkin.cpp b/skins/squiek/QskSquiekSkin.cpp index 4d199662..ed700ba2 100644 --- a/skins/squiek/QskSquiekSkin.cpp +++ b/skins/squiek/QskSquiekSkin.cpp @@ -554,36 +554,21 @@ void QskSquiekSkin::initTabViewHints() void QskSquiekSkin::initInputPanelHints() { using namespace QskAspect; - using Q = QskInputPanel; + using Q = QskKeyButton; const ColorPalette& pal = m_data->palette; // key panel - setMargins( Q::KeyPanel | Margin, 2 ); // should be Panel | Spacing + setMargins( QskInputPanel::Panel | Padding, 5 ); + setPanel( QskInputPanel::Panel, Raised ); - setButton( Q::KeyPanel, Raised ); - setButton( Q::KeyPanel | Q::Pressed, Sunken ); + setButton( Q::Panel, Raised ); + setButton( Q::Panel | Q::Pressed, Sunken ); - setAnimation( Q::KeyPanel | Color, qskDuration ); -#if 0 - // crashes because animations are started from updateNode - // TODO ... + setAnimation( Q::Panel | Color, qskDuration ); - setAnimation( Q::KeyPanel | Metric, qskDuration ); -#endif - - // glyph - setSkinHint( Q::KeyGlyph | Alignment, Qt::AlignCenter ); - setFontRole( Q::KeyGlyph, QskSkin::TinyFont ); - - setColor( Q::KeyGlyph, pal.themeForeground ); - setColor( Q::KeyGlyph | Q::Disabled, pal.darker200 ); - - // panel - - setMargins( Q::Panel | Padding, 5 ); - setMargins( Q::Panel | Spacing, 5 ); - setPanel( Q::Panel, Raised ); + setColor( Q::Text, pal.themeForeground ); + setColor( Q::Text | Q::Disabled, pal.darker200 ); } void QskSquiekSkin::initScrollViewHints() diff --git a/src/controls/QskInputPanel.cpp b/src/controls/QskInputPanel.cpp index e1d0d324..99c55199 100644 --- a/src/controls/QskInputPanel.cpp +++ b/src/controls/QskInputPanel.cpp @@ -7,21 +7,19 @@ #include "QskAspect.h" +#include +#include #include -#include #include #include +#include +#include + +#include #include #include -QSK_SUBCONTROL( QskInputPanel, Panel ) -QSK_SUBCONTROL( QskInputPanel, KeyPanel ) -QSK_SUBCONTROL( QskInputPanel, KeyGlyph ) - -QSK_STATE( QskInputPanel, Checked, QskAspect::LastSystemState >> 3 ) -QSK_STATE( QskInputPanel, Pressed, QskAspect::LastSystemState >> 2 ) - namespace { struct KeyTable @@ -34,60 +32,6 @@ namespace return int( intptr_t( value - data[0] ) ); } }; - - QskInputPanel::KeyData* qskKeyDataAt( KeyTable& table, qreal x, qreal y ) - { - if ( x < 0 || x > 1 || y < 0 || y > 1 ) - return nullptr; - - auto rowIndex = size_t( std::floor( y * QskInputPanel::RowCount ) ); - auto columnIndex = size_t( std::floor( x * QskInputPanel::KeyCount ) ); - - Q_FOREVER - { - const auto rect = table.data[ rowIndex ][ columnIndex ].rect; - if ( rect.contains( x, y ) ) - return &table.data[ rowIndex ][ columnIndex ]; - - // Look up/down - if ( y < rect.top() ) - { - if ( rowIndex == 0 ) - break; - - rowIndex -= 1; - continue; - } - else if ( y > rect.bottom() ) - { - if ( rowIndex == QskInputPanel::RowCount - 1 ) - break; - - rowIndex += 1; - continue; - } - - // Look left/right - if ( x < rect.left() ) - { - if ( columnIndex == 0 ) - break; - - columnIndex -= 1; - continue; - } - else if ( x > rect.right() ) - { - if ( columnIndex == QskInputPanel::KeyCount - 1 ) - break; - - columnIndex += 1; - continue; - } - } - - return nullptr; - } } struct QskInputPanelLayouts @@ -130,20 +74,18 @@ struct QskInputPanelLayouts #define LOWER(x) Qt::Key(x + 32) // Convert an uppercase key to lowercase static constexpr const QskInputPanelLayouts qskInputPanelLayouts = { - #include "QskInputPanelLayouts.cpp" +#include "QskInputPanelLayouts.cpp" }; #undef LOWER QSK_DECLARE_OPERATORS_FOR_FLAGS( Qt::Key ) // Must appear after the LOWER macro -static const Qt::Key KeyPressed = static_cast< Qt::Key >( Qt::ShiftModifier ); -static const Qt::Key KeyLocked = static_cast< Qt::Key >( Qt::ControlModifier ); -static const Qt::Key KeyFocused = static_cast< Qt::Key >( Qt::MetaModifier ); -static const Qt::Key KeyStates = static_cast< Qt::Key >( Qt::KeyboardModifierMask ); +static const int KeyLocked = static_cast< int >( Qt::ControlModifier ); +static const int KeyStates = static_cast< int >( Qt::KeyboardModifierMask ); static qreal qskKeyStretch( Qt::Key key ) { - switch ( key ) + switch( key ) { case Qt::Key_Backspace: case Qt::Key_Shift: @@ -168,20 +110,32 @@ static qreal qskRowStretch( const QskInputPanel::KeyRow& keyRow ) { qreal stretch = 0; - for ( const auto& key : keyRow ) + for( const auto& key : keyRow ) { - if ( !key ) + if( !key ) + { continue; + } stretch += qskKeyStretch( key ); } - if ( stretch == 0.0 ) + if( stretch == 0.0 ) + { stretch = QskInputPanel::KeyCount; + } return stretch; } +static bool qskIsAutorepeat( int key ) +{ + return ( key != Qt::Key_Return && key != Qt::Key_Enter + && key != Qt::Key_Shift && key!= Qt::Key_CapsLock + && key != Qt::Key_Mode_switch ); + +} + namespace { struct KeyCounter @@ -191,57 +145,194 @@ namespace }; } -class QskInputPanel::PrivateData +QSK_SUBCONTROL( QskInputPanel, Panel ) + +QSK_SUBCONTROL( QskKeyButton, Panel ) +QSK_SUBCONTROL( QskKeyButton, Text ) +QSK_SUBCONTROL( QskKeyButton, TextCancelButton ) + +QskKeyButton::QskKeyButton( int keyIndex, QskInputPanel* inputPanel, QQuickItem* parent ) : + Inherited( parent ), + m_keyIndex( keyIndex ), + m_inputPanel( inputPanel ) { -public: - PrivateData(): - currentLayout( nullptr ), - mode( QskInputPanel::LowercaseMode ), - focusKeyIndex( -1 ), - selectedGroup( -1 ), - candidateOffset( 0 ), - repeatKeyTimerId( -1 ) + QskTextOptions options; + options.setFontSizeMode( QskTextOptions::VerticalFit ); + setTextOptions( options ); + + setFocusPolicy( Qt::TabFocus ); + + auto keyData = m_inputPanel->keyDataAt( m_keyIndex ); + const auto key = keyData.key & ~KeyStates; + + if ( qskIsAutorepeat( key ) ) { + setAutoRepeat( true ); + setAutoRepeatDelay( 500 ); + setAutoRepeatInterval( 1000 / QGuiApplication::styleHints()->keyboardAutoRepeatRate() ); } -public: - const QskInputPanelLayouts::Layout* currentLayout; - QskInputPanel::Mode mode; + updateText(); - qint16 focusKeyIndex; - qint16 selectedGroup; - qint32 candidateOffset; + connect( this, &QskKeyButton::pressed, this, [ this ]() + { + m_inputPanel->handleKey( m_keyIndex ); + } ); - int repeatKeyTimerId; + connect( m_inputPanel, &QskInputPanel::modeChanged, this, &QskKeyButton::updateText ); +} - QLocale locale; +QskAspect::Subcontrol QskKeyButton::effectiveSubcontrol( QskAspect::Subcontrol subControl ) const +{ + if( subControl == QskPushButton::Panel ) + { + return QskKeyButton::Panel; + } - QVector< Qt::Key > groups; - QVector< Qt::Key > candidates; + if( subControl == QskPushButton::Text ) + { + // ### we could also introduce a state to not always query the button + return isCancelButton() ? QskKeyButton::TextCancelButton : QskKeyButton::Text; + } - std::unordered_map< int, KeyCounter > activeKeys; - KeyTable keyTable[ ModeCount ]; + return subControl; +} + +int QskKeyButton::keyIndex() const +{ + return m_keyIndex; +} + +void QskKeyButton::updateText() +{ + QString text = m_inputPanel->currentTextForKeyIndex( m_keyIndex ); + + if( text.count() == 1 && text.at( 0 ) == QChar( 0 ) ) + { + setVisible( false ); + } + else + { + setVisible( true ); + setText( text ); + } +} + +bool QskKeyButton::isCancelButton() const +{ + auto keyData = m_inputPanel->keyDataAt( m_keyIndex ); + bool isCancel = ( keyData.key == 0x2716 ); + return isCancel; +} + +class QskInputPanel::PrivateData +{ + public: + PrivateData(): + currentLayout( nullptr ), + mode( QskInputPanel::LowercaseMode ), + selectedGroup( -1 ), + candidateOffset( 0 ), + buttonsBox( nullptr ), + isUIInitialized( false ) + { + } + + public: + const QskInputPanelLayouts::Layout* currentLayout; + QskInputPanel::Mode mode; + + qint16 selectedGroup; + qint32 candidateOffset; + + QLocale locale; + + QVector< Qt::Key > groups; + QVector< Qt::Key > candidates; + + std::unordered_map< int, KeyCounter > activeKeys; + KeyTable keyTable[ ModeCount ]; + + QskLinearBox* buttonsBox; + QList< QskKeyButton* > keyButtons; + bool isUIInitialized; }; QskInputPanel::QskInputPanel( QQuickItem* parent ): - QskControl( parent ), + Inherited( parent ), m_data( new PrivateData ) { + qRegisterMetaType< Qt::Key >(); + setFlag( ItemHasContents ); setAcceptedMouseButtons( Qt::LeftButton ); initSizePolicy( QskSizePolicy::Expanding, QskSizePolicy::Expanding ); + setAutoFillBackground( true ); + + auto margins = marginsHint( Panel | QskAspect::Margin ); + setMargins( margins ); + updateLocale( locale() ); QObject::connect( this, &QskControl::localeChanged, - this, &QskInputPanel::updateLocale ); + this, &QskInputPanel::updateLocale ); + + setFlag( ItemIsFocusScope, true ); + +#if 0 + setTabFence( true ); +#endif + + setAutoLayoutChildren( true ); + + auto& panelKeyData = keyData(); + + m_data->buttonsBox = new QskLinearBox( Qt::Vertical, this ); + m_data->buttonsBox->setAutoAddChildren( true ); + + for( const auto& keyRow : panelKeyData ) + { + QskLinearBox* rowBox = new QskLinearBox( Qt::Horizontal, m_data->buttonsBox ); + rowBox->setAutoAddChildren( true ); + + for( const auto& keyData : keyRow ) + { + if( !keyData.key ) + { + continue; + } + + int keyIndex = m_data->keyTable[ m_data->mode ].indexOf( &keyData ); + QskKeyButton* button = new QskKeyButton( keyIndex, this, rowBox ); + rowBox->setRetainSizeWhenHidden( button, true ); + + m_data->keyButtons.append( button ); + } + } + + connect( this, &QskInputPanel::modeChanged, this, [ this ]() { + updateLayout(); + }); } QskInputPanel::~QskInputPanel() { } + +QskAspect::Subcontrol QskInputPanel::effectiveSubcontrol( QskAspect::Subcontrol subControl ) const +{ + if( subControl == QskBox::Panel ) + { + return QskInputPanel::Panel; + } + + return subControl; +} + + QskInputPanel::Mode QskInputPanel::mode() const { return m_data->mode; @@ -254,12 +345,12 @@ const QskInputPanel::KeyDataSet& QskInputPanel::keyData( Mode mode ) const return m_data->keyTable[ mode ].data; } -QString QskInputPanel::textForKey( Qt::Key key ) const +QString QskInputPanel::textForKey( int key ) const { key &= ~KeyStates; // Special cases - switch ( key ) + switch( key ) { case Qt::Key_Backspace: case Qt::Key_Muhenkan: @@ -306,7 +397,7 @@ QString QskInputPanel::displayLanguageName() const { const auto locale = this->locale(); - switch ( locale.language() ) + switch( locale.language() ) { case QLocale::Bulgarian: return QStringLiteral( "български език" ); @@ -324,19 +415,21 @@ QString QskInputPanel::displayLanguageName() const return QStringLiteral( "Eλληνικά" ); case QLocale::English: - { - switch ( locale.country() ) { - case QLocale::Canada: - case QLocale::UnitedStates: - case QLocale::UnitedStatesMinorOutlyingIslands: - case QLocale::UnitedStatesVirginIslands: - return QStringLiteral( "English (US)" ); + switch( locale.country() ) + { + case QLocale::Canada: + case QLocale::UnitedStates: + case QLocale::UnitedStatesMinorOutlyingIslands: + case QLocale::UnitedStatesVirginIslands: + return QStringLiteral( "English (US)" ); - default: - return QStringLiteral( "English (UK)" ); + default: + return QStringLiteral( "English (UK)" ); + } + + break; } - } case QLocale::Spanish: return QStringLiteral( "Español" ); @@ -397,7 +490,7 @@ void QskInputPanel::setPreeditGroups( const QVector< Qt::Key >& groups ) { auto& topRow = m_data->keyTable[ LowercaseMode ].data[ 0 ]; - for ( const auto& group : groups ) + for( const auto& group : groups ) { auto& keyData = topRow[ &group - groups.data() ]; keyData.key = group; @@ -406,96 +499,23 @@ void QskInputPanel::setPreeditGroups( const QVector< Qt::Key >& groups ) m_data->groups = groups; selectGroup( -1 ); - if ( m_data->mode == LowercaseMode ) + if( m_data->mode == LowercaseMode ) + { update(); + } } void QskInputPanel::setPreeditCandidates( const QVector< Qt::Key >& candidates ) { - if ( m_data->candidates == candidates ) + if( m_data->candidates == candidates ) + { return; + } m_data->candidates = candidates; setCandidateOffset( 0 ); } -bool QskInputPanel::advanceFocus( bool forward ) -{ - deactivateFocusKey(); - auto offset = forward ? 1 : -1; - - auto focusKeyIndex = m_data->focusKeyIndex; - - Q_FOREVER - { - focusKeyIndex += offset; - if ( focusKeyIndex < 0 || focusKeyIndex >= RowCount * KeyCount ) - { - clearFocusKey(); - return false; - } - - const auto key = keyDataAt( focusKeyIndex ).key; - if ( key && key != Qt::Key_unknown ) - break; - } - - if ( m_data->focusKeyIndex >= 0 ) - keyDataAt( m_data->focusKeyIndex ).key &= ~KeyFocused; - - m_data->focusKeyIndex = focusKeyIndex; - keyDataAt( m_data->focusKeyIndex ).key |= KeyFocused; - update(); - return true; -} - -bool QskInputPanel::activateFocusKey() -{ - if ( m_data->focusKeyIndex > 0 && m_data->focusKeyIndex < RowCount * KeyCount ) - { - auto& keyData = keyDataAt( m_data->focusKeyIndex ); - - if ( keyData.key & KeyPressed ) - handleKey( m_data->focusKeyIndex ); - else - keyData.key |= KeyPressed; - - update(); - return true; - } - - return false; -} - -bool QskInputPanel::deactivateFocusKey() -{ - if ( m_data->focusKeyIndex > 0 && m_data->focusKeyIndex < RowCount * KeyCount ) - { - auto& keyData = keyDataAt( m_data->focusKeyIndex ); - if ( keyData.key & KeyPressed ) - { - keyData.key &= ~KeyPressed; - handleKey( m_data->focusKeyIndex ); - } - - update(); - return true; - } - - return true; -} - -void QskInputPanel::clearFocusKey() -{ - if ( m_data->focusKeyIndex > 0 && m_data->focusKeyIndex < RowCount * KeyCount ) - { - keyDataAt( m_data->focusKeyIndex ).key &= ~KeyFocused; - update(); - } - - m_data->focusKeyIndex = -1; -} - void QskInputPanel::setCandidateOffset( int candidateOffset ) { m_data->candidateOffset = candidateOffset; @@ -508,202 +528,83 @@ void QskInputPanel::setCandidateOffset( int candidateOffset ) const bool continueLeft = m_data->candidateOffset > 0; const bool continueRight = ( candidateCount - m_data->candidateOffset ) > count; - for ( int i = 0; i < count; ++i ) + for( int i = 0; i < count; ++i ) { auto& keyData = topRow[ i + groupCount ]; - if ( continueLeft && i == 0 ) + if( continueLeft && i == 0 ) + { keyData.key = Qt::Key_ApplicationLeft; - else if ( continueRight && ( i == KeyCount - groupCount - 1 ) ) + } + else if( continueRight && ( i == KeyCount - groupCount - 1 ) ) + { keyData.key = Qt::Key_ApplicationRight; + } else + { + keyData.isSuggestionKey = true; // ### reset when switching layouts etc.! keyData.key = m_data->candidates.at( i + m_data->candidateOffset ); + } } - for ( int i = count; i < KeyCount - groupCount; ++i ) + for( int i = count; i < KeyCount - groupCount; ++i ) { auto& keyData = topRow[ i + groupCount ]; keyData.key = Qt::Key_unknown; } - if ( m_data->mode == LowercaseMode ) - update(); + if( m_data->mode == LowercaseMode ) + { + updateUI(); + } } -QRectF QskInputPanel::keyboardRect() const +void QskInputPanel::registerCompositionModelForLocale( const QLocale& locale, + QskInputCompositionModel* model ) { - auto keyboardRect = rect(); // ### margins? would eliminate this thing below - - if ( QskDialog::instance()->policy() != QskDialog::TopLevelWindow ) - keyboardRect.adjust( 0, keyboardRect.height() * 0.5, 0, 0 ); - - return keyboardRect; + Q_EMIT inputMethodRegistered( locale, model ); } void QskInputPanel::geometryChanged( const QRectF& newGeometry, const QRectF& oldGeometry ) { Inherited::geometryChanged( newGeometry, oldGeometry ); + Q_EMIT keyboardRectChanged(); } -void QskInputPanel::mousePressEvent( QMouseEvent* e ) +void QskInputPanel::updateLayout() { - if ( !keyboardRect().contains( e->pos() ) ) + if( geometry().isNull() ) + return; // no need to calculate anything, will be called again + + QRectF rect = layoutRect(); + qreal verticalSpacing = m_data->buttonsBox->spacing(); + + const auto& children = m_data->buttonsBox->childItems(); + for( auto rowItem : children ) { - e->ignore(); - return; + auto rowBox = qobject_cast< QskLinearBox* >( rowItem ); + const qreal horizontalSpacing = rowBox->spacing(); + + const auto& rowChildren = rowBox->childItems(); + for( auto keyItem : rowChildren ) + { + auto button = qobject_cast< QskKeyButton* >( keyItem ); + QRectF keyRect = keyDataAt( button->keyIndex() ).rect; + qreal width = keyRect.width() * rect.width() - horizontalSpacing; + qreal height = keyRect.height() * rect.height() - verticalSpacing; + + button->setFixedSize( width, height ); + } } - - QTouchEvent::TouchPoint touchPoint( 0 ); - touchPoint.setPos( e->pos() ); - touchPoint.setState( Qt::TouchPointPressed ); - - QTouchEvent touchEvent( QTouchEvent::TouchBegin, nullptr, - e->modifiers(), Qt::TouchPointPressed, { touchPoint } ); - QCoreApplication::sendEvent( this, &touchEvent ); - - e->setAccepted( touchEvent.isAccepted() ); } -void QskInputPanel::mouseMoveEvent( QMouseEvent* e ) +void QskInputPanel::updateUI() { - QTouchEvent::TouchPoint touchPoint( 0 ); - touchPoint.setPos( e->pos() ); - touchPoint.setState( Qt::TouchPointMoved ); - - QTouchEvent touchEvent( QTouchEvent::TouchUpdate, nullptr, - e->modifiers(), Qt::TouchPointMoved, { touchPoint } ); - QCoreApplication::sendEvent( this, &touchEvent ); - - e->setAccepted( touchEvent.isAccepted() ); -} - -void QskInputPanel::mouseReleaseEvent( QMouseEvent* e ) -{ - QTouchEvent::TouchPoint touchPoint( 0 ); - touchPoint.setPos( e->pos() ); - touchPoint.setState( Qt::TouchPointReleased ); - - QTouchEvent touchEvent( QTouchEvent::TouchEnd, nullptr, - e->modifiers(), Qt::TouchPointReleased, { touchPoint } ); - QCoreApplication::sendEvent( this, &touchEvent ); - - e->setAccepted( touchEvent.isAccepted() ); -} - -// Try to handle touch-specific details here; once touch is resolved, send to handleKey() -void QskInputPanel::touchEvent( QTouchEvent* e ) -{ - if ( e->type() == QEvent::TouchCancel ) + for( QskKeyButton* button : qskAsConst( m_data->keyButtons ) ) { - for ( auto& it : m_data->activeKeys ) - keyDataAt( it.second.keyIndex ).key &= ~KeyPressed; - - m_data->activeKeys.clear(); - return; - } - - const auto rect = keyboardRect(); - for ( const auto& tp : e->touchPoints() ) - { - const auto pos = tp.pos(); - - const auto x = ( pos.x() - rect.x() ) / rect.width(); - const auto y = ( pos.y() - rect.y() ) / rect.height(); - - auto keyData = qskKeyDataAt( m_data->keyTable[ m_data->mode ], x, y ); - if ( !keyData || ( !keyData->key || keyData->key == Qt::Key_unknown ) ) - { - auto it = m_data->activeKeys.find( tp.id() ); - if ( it == m_data->activeKeys.cend() ) - continue; - - keyDataAt( it->second.keyIndex ).key &= ~KeyPressed; - m_data->activeKeys.erase( it ); - continue; - } - - const auto keyIndex = m_data->keyTable[ m_data->mode ].indexOf( keyData ); - auto it = m_data->activeKeys.find( tp.id() ); - - if ( tp.state() == Qt::TouchPointReleased ) - { - const int repeatCount = it->second.count; - - auto it = m_data->activeKeys.find( tp.id() ); - keyDataAt( it->second.keyIndex ).key &= ~KeyPressed; - m_data->activeKeys.erase( it ); - - if ( repeatCount < 0 ) - continue; // Don't compose an accepted held key - - handleKey( keyIndex ); - continue; - } - - if ( it == m_data->activeKeys.end() ) - { - m_data->activeKeys.emplace( tp.id(), KeyCounter { keyIndex, 0 } ); - } - else - { - if ( it->second.keyIndex != keyIndex ) - { - keyDataAt( it->second.keyIndex ).key &= ~KeyPressed; - it->second.count = 0; - } - - it->second.keyIndex = keyIndex; - } - - keyDataAt( keyIndex ).key |= KeyPressed; - } - - // Now start an update timer based on active keys - if ( m_data->activeKeys.empty() && m_data->repeatKeyTimerId >= 0 ) - { - killTimer( m_data->repeatKeyTimerId ); - m_data->repeatKeyTimerId = -1; - } - else if ( m_data->repeatKeyTimerId < 0 ) - { - m_data->repeatKeyTimerId = startTimer( 1000 - / QGuiApplication::styleHints()->keyboardAutoRepeatRate() ); - } /* else timer is already running as it should be */ - - update(); -} - -void QskInputPanel::timerEvent( QTimerEvent* e ) -{ - if ( e->timerId() == m_data->repeatKeyTimerId ) - { - for ( auto it = m_data->activeKeys.begin(); it != m_data->activeKeys.end(); ) - { - if ( it->second.count >= 0 && it->second.count++ > 20 ) // ### use platform long-press hint - { - const auto key = keyDataAt( it->second.keyIndex ).key & ~KeyStates; - - if ( !key || key == Qt::Key_unknown ) - { - it = m_data->activeKeys.erase( it ); - continue; - } - - if ( key == Qt::Key_ApplicationLeft || key == Qt::Key_ApplicationRight ) - { - setCandidateOffset( m_data->candidateOffset - + ( key == Qt::Key_ApplicationLeft ? -1 : 1 ) ); - } - else if ( !( key & KeyLocked ) ) // do not repeat locked keys - { - // long press events could be emitted from here - compose( key & ~KeyStates ); - } - } - ++it; - } + button->updateText(); } } @@ -716,35 +617,43 @@ QskInputPanel::KeyData& QskInputPanel::keyDataAt( int keyIndex ) const void QskInputPanel::handleKey( int keyIndex ) { - const auto key = keyDataAt( keyIndex ).key & ~KeyStates; + KeyData keyData = keyDataAt( keyIndex ); + const auto key = keyData.key & ~KeyStates; // Preedit keys const auto row = keyIndex / KeyCount; const auto column = keyIndex % KeyCount; - if ( m_data->mode == LowercaseMode && !m_data->groups.isEmpty() && row == 0 ) + if( m_data->mode == LowercaseMode && !m_data->groups.isEmpty() && row == 0 ) { - if ( key == Qt::Key_ApplicationLeft - || key == Qt::Key_ApplicationRight ) + if( key == Qt::Key_ApplicationLeft + || key == Qt::Key_ApplicationRight ) { setCandidateOffset( m_data->candidateOffset - + ( key == Qt::Key_ApplicationLeft ? -1 : 1 ) ); + + ( key == Qt::Key_ApplicationLeft ? -1 : 1 ) ); return; } const auto groupCount = m_data->groups.length(); - if ( column < groupCount ) + + if( column < groupCount ) + { selectGroup( column ); - else if ( column < KeyCount ) + } + else if( column < KeyCount ) + { selectCandidate( column - groupCount + m_data->candidateOffset ); + } else - Q_UNREACHABLE(); // Handle the final key... + { + Q_UNREACHABLE(); // Handle the final key... + } return; } // Mode-switching keys - switch ( key ) + switch( key ) { case Qt::Key_CapsLock: case Qt::Key_Kana_Lock: @@ -758,19 +667,37 @@ void QskInputPanel::handleKey( int keyIndex ) case Qt::Key_Mode_switch: // Cycle through modes, but skip caps setMode( static_cast< QskInputPanel::Mode >( - m_data->mode ? ( ( m_data->mode + 1 ) % QskInputPanel::ModeCount ) - : SpecialCharacterMode ) ); + m_data->mode ? ( ( m_data->mode + 1 ) % QskInputPanel::ModeCount ) + : SpecialCharacterMode ) ); + return; + + // This is (one of) the cancel symbol, not Qt::Key_Cancel: + case Qt::Key( 10006 ): + Q_EMIT cancelPressed(); return; default: break; } - // Normal keys - compose( key ); + if( keyData.isSuggestionKey ) + { + selectCandidate( keyIndex ); + } + else + { + compose( key ); + } } -void QskInputPanel::compose( Qt::Key key ) +QString QskInputPanel::currentTextForKeyIndex( int keyIndex ) const +{ + auto keyData = keyDataAt( keyIndex ); + QString text = textForKey( keyData.key ); + return text; +} + +void QskInputPanel::compose( int key ) { QGuiApplication::inputMethod()->invokeAction( static_cast< QInputMethod::Action >( Compose ), key ); @@ -779,18 +706,26 @@ void QskInputPanel::compose( Qt::Key key ) void QskInputPanel::selectGroup( int index ) { auto& topRow = m_data->keyTable[ m_data->mode ].data[ 0 ]; - if ( m_data->selectedGroup >= 0 ) - topRow[ m_data->selectedGroup ].key &= ~KeyLocked; - if ( m_data->selectedGroup == index ) - index = -1; // clear selection + if( m_data->selectedGroup >= 0 ) + { + auto group = static_cast< int >( m_data->selectedGroup ); + topRow[ group ].key &= ~KeyLocked; + } + + if( m_data->selectedGroup == index ) + { + index = -1; // clear selection + } m_data->selectedGroup = index; QGuiApplication::inputMethod()->invokeAction( static_cast< QInputMethod::Action >( SelectGroup ), m_data->selectedGroup + 1 ); - if ( m_data->selectedGroup < 0 ) + if( m_data->selectedGroup < 0 ) + { return; + } topRow[ m_data->selectedGroup ].key |= KeyLocked; } @@ -803,7 +738,7 @@ void QskInputPanel::selectCandidate( int index ) void QskInputPanel::updateLocale( const QLocale& locale ) { - switch ( locale.language() ) + switch( locale.language() ) { case QLocale::Bulgarian: m_data->currentLayout = &qskInputPanelLayouts.bg; @@ -826,21 +761,24 @@ void QskInputPanel::updateLocale( const QLocale& locale ) break; case QLocale::English: - { - switch ( locale.country() ) { - case QLocale::Canada: - case QLocale::UnitedStates: - case QLocale::UnitedStatesMinorOutlyingIslands: - case QLocale::UnitedStatesVirginIslands: - m_data->currentLayout = &qskInputPanelLayouts.en_US; - break; - default: - m_data->currentLayout = &qskInputPanelLayouts.en_GB; - break; + switch( locale.country() ) + { + case QLocale::Canada: + case QLocale::UnitedStates: + case QLocale::UnitedStatesMinorOutlyingIslands: + case QLocale::UnitedStatesVirginIslands: + m_data->currentLayout = &qskInputPanelLayouts.en_US; + break; + + default: + m_data->currentLayout = &qskInputPanelLayouts.en_GB; + break; + } + + break; } - break; - } + case QLocale::Spanish: m_data->currentLayout = &qskInputPanelLayouts.es; break; @@ -905,10 +843,6 @@ void QskInputPanel::updateLocale( const QLocale& locale ) m_data->currentLayout = &qskInputPanelLayouts.zh; break; - case QLocale::C: - m_data->currentLayout = &qskInputPanelLayouts.en_US; - break; - default: qWarning() << "QskInputPanel: unsupported locale:" << locale; m_data->currentLayout = &qskInputPanelLayouts.en_US; @@ -925,12 +859,12 @@ void QskInputPanel::updateKeyData() // Key data is in normalized coordinates const auto keyHeight = 1.0f / RowCount; - for ( const auto& keyLayout : *m_data->currentLayout ) + for( const auto& keyLayout : *m_data->currentLayout ) { auto& keyDataLayout = m_data->keyTable[ &keyLayout - *m_data->currentLayout ]; qreal yPos = 0; - for ( int i = 0; i < RowCount; i++ ) + for( int i = 0; i < RowCount; i++ ) { auto& row = keyLayout.data[i]; auto& keyDataRow = keyDataLayout.data[ i ]; @@ -940,7 +874,7 @@ void QskInputPanel::updateKeyData() qreal xPos = 0; qreal keyWidth = baseKeyWidth; - for ( const auto& key : row ) + for( const auto& key : row ) { auto& keyData = keyDataRow[ &key - row ]; keyData.key = key; @@ -958,7 +892,7 @@ void QskInputPanel::updateKeyData() void QskInputPanel::setMode( QskInputPanel::Mode mode ) { m_data->mode = mode; - update(); + Q_EMIT modeChanged( m_data->mode ); } #include "QskInputPanel.moc" diff --git a/src/controls/QskInputPanel.h b/src/controls/QskInputPanel.h index 963e08a2..16a5fa75 100644 --- a/src/controls/QskInputPanel.h +++ b/src/controls/QskInputPanel.h @@ -6,28 +6,55 @@ #ifndef QSK_INPUT_PANEL_H #define QSK_INPUT_PANEL_H -#include "QskControl.h" +#include "QskBox.h" +#include "QskPushButton.h" #include -class QSK_EXPORT QskInputPanel : public QskControl +class QskInputCompositionModel; +class QskInputPanel; + +class QskKeyButton : public QskPushButton // ### rename to QskInputButton or so? +{ + Q_OBJECT + + using Inherited = QskPushButton; + + public: + QSK_SUBCONTROLS( Panel, Text, TextCancelButton ) + QskKeyButton( int keyIndex, QskInputPanel* inputPanel, QQuickItem* parent = nullptr ); + + virtual QskAspect::Subcontrol effectiveSubcontrol( QskAspect::Subcontrol subControl ) const override; + + int keyIndex() const; + + public Q_SLOTS: + void updateText(); + + private: + bool isCancelButton() const; + + const int m_keyIndex; + QskInputPanel* m_inputPanel; +}; + +class QSK_EXPORT QskInputPanel : public QskBox { Q_OBJECT - Q_PROPERTY( QRectF keyboardRect READ keyboardRect NOTIFY keyboardRectChanged ) - Q_PROPERTY( QString displayLanguageName READ displayLanguageName - NOTIFY displayLanguageNameChanged ) + NOTIFY displayLanguageNameChanged ) - using Inherited = QskControl; + using Inherited = QskBox; public: - QSK_SUBCONTROLS( Panel, KeyPanel, KeyGlyph ) - QSK_STATES( Checked, Pressed ) + + QSK_SUBCONTROLS( Panel ) struct KeyData { - Qt::Key key = Qt::Key( 0 ); + int key = 0; + bool isSuggestionKey = false; QRectF rect; }; @@ -60,7 +87,9 @@ public: using KeyDataSet = KeyDataRow[RowCount]; QskInputPanel( QQuickItem* parent = nullptr ); - virtual ~QskInputPanel(); + virtual ~QskInputPanel() override; + + virtual QskAspect::Subcontrol effectiveSubcontrol( QskAspect::Subcontrol subControl ) const override; void updateLocale( const QLocale& locale ); @@ -69,33 +98,32 @@ public: const KeyDataSet& keyData( QskInputPanel::Mode = CurrentMode ) const; - QString textForKey( Qt::Key ) const; + QString textForKey( int ) const; QString displayLanguageName() const; QRectF keyboardRect() const; + // takes ownership: + void registerCompositionModelForLocale( const QLocale& locale, + QskInputCompositionModel* model ); + public Q_SLOTS: void setPreeditGroups( const QVector< Qt::Key >& ); void setPreeditCandidates( const QVector< Qt::Key >& ); - bool advanceFocus( bool = true ); - bool activateFocusKey(); - bool deactivateFocusKey(); - void clearFocusKey(); + + void handleKey( int keyIndex ); + KeyData& keyDataAt( int ) const; + QString currentTextForKeyIndex( int keyIndex ) const; protected: virtual void geometryChanged( const QRectF&, const QRectF& ) override; - - virtual void mousePressEvent( QMouseEvent* ) override; - virtual void mouseMoveEvent( QMouseEvent* ) override; - virtual void mouseReleaseEvent( QMouseEvent* ) override; - virtual void touchEvent( QTouchEvent* ) override; - virtual void timerEvent( QTimerEvent* ) override; + virtual void updateLayout() override; private: - KeyData& keyDataAt( int ) const; + void createUI(); + void updateUI(); // e.g. called when updating Pinyin suggestions - void handleKey( int ); - void compose( Qt::Key ); + void compose( int ); void selectGroup( int ); void selectCandidate( int ); void setCandidateOffset( int ); @@ -105,6 +133,11 @@ private: Q_SIGNALS: void keyboardRectChanged(); void displayLanguageNameChanged(); + void inputMethodRegistered( const QLocale& locale, QskInputCompositionModel* model ); + void inputMethodEventReceived( QInputMethodEvent* inputMethodEvent ); + void keyEventReceived( QKeyEvent* keyEvent ); + void modeChanged( QskInputPanel::Mode mode ); + void cancelPressed(); public: class PrivateData; diff --git a/src/controls/QskInputPanelSkinlet.cpp b/src/controls/QskInputPanelSkinlet.cpp deleted file mode 100644 index 56acd7a4..00000000 --- a/src/controls/QskInputPanelSkinlet.cpp +++ /dev/null @@ -1,222 +0,0 @@ -/****************************************************************************** - * QSkinny - Copyright (C) 2016 Uwe Rathmann - * This file may be used under the terms of the QSkinny License, Version 1.0 - *****************************************************************************/ - -#include "QskInputPanelSkinlet.h" -#include "QskInputPanel.h" - -#include "QskAspect.h" -#include "QskSkin.h" -#include "QskTextOptions.h" -#include "QskTextNode.h" -#include "QskBoxNode.h" - -static constexpr const QSGNode::Flag IsSubtreeBlocked = - static_cast< QSGNode::Flag >( 0x100000 ); -static constexpr const QSGNode::Flag KeyFrameNode = - static_cast< QSGNode::Flag >( 0x200000 ); -static constexpr const QSGNode::Flag KeyGlyphNode = - static_cast< QSGNode::Flag >( 0x400000 ); - -namespace -{ - class KeySkinnable: public QskSkinnable - { - public: - KeySkinnable( QskInputPanel* panel ): - m_panel( panel ) - { - } - - void setKey( Qt::Key key ) - { - setSkinStateFlag( QskInputPanel::Pressed, key & Qt::ShiftModifier ); - setSkinStateFlag( QskInputPanel::Checked, key & Qt::ControlModifier ); - setSkinStateFlag( QskInputPanel::Focused, key & Qt::MetaModifier ); - } - - const QMetaObject* metaObject() const override final - { - // Use the parent skinlet - return &QskInputPanelSkinlet::staticMetaObject; - } - - protected: - virtual const QskSkinlet* effectiveSkinlet() const override final - { - return m_panel->effectiveSkinlet(); - } - - private: - virtual QskControl* owningControl() const override final - { - return const_cast< QskInputPanel* >( m_panel ); - } - - private: - QskInputPanel* m_panel; - }; - - class FrameNode final : public QskBoxNode, public KeySkinnable - { - public: - FrameNode( QskInputPanel* panel ): - KeySkinnable( panel ) - { - setFlag( KeyFrameNode ); - } - }; - - class TextNode final : public QskTextNode, public KeySkinnable - { - public: - TextNode( QskInputPanel* panel ): - KeySkinnable( panel ) - { - setFlag( KeyGlyphNode ); - } - }; - - class InputPanelNode final : public QskBoxNode - { - public: - InputPanelNode() - { - } - - virtual bool isSubtreeBlocked() const override final - { - return flags() & IsSubtreeBlocked; - } - - using Row = QSGNode*[ QskInputPanel::KeyCount ]; - Row frames[ QskInputPanel::RowCount ] = {}; - Row glyphs[ QskInputPanel::RowCount ] = {}; - }; -} - -static_assert( QskInputPanel::RowCount * QskInputPanel::KeyCount < 255, - "The number of keys must fit into an unsigned byte." ); - -QskInputPanelSkinlet::QskInputPanelSkinlet( QskSkin* skin ): - Inherited( skin ) -{ - setNodeRoles( { Panel0, Panel1, Panel2 } ); -} - -QskInputPanelSkinlet::~QskInputPanelSkinlet() -{ -} - -QSGNode* QskInputPanelSkinlet::updateSubNode( - const QskSkinnable* control, quint8 nodeRole, QSGNode* node ) const -{ - auto inputPanel = static_cast< const QskInputPanel* >( control ); - const bool blockSubtree = inputPanel->mode() != nodeRole; - - auto panelNode = static_cast< InputPanelNode* >( node ); - if ( panelNode && panelNode->isSubtreeBlocked() != blockSubtree ) - { - panelNode->setFlag( IsSubtreeBlocked, blockSubtree ); - panelNode->markDirty( QSGNode::DirtySubtreeBlocked ); - } - - if ( !blockSubtree ) - node = updatePanelNode( inputPanel, panelNode ); - - return node; -} - -QSGNode* QskInputPanelSkinlet::updatePanelNode( - const QskInputPanel* panel, QSGNode* node ) const -{ - auto panelNode = static_cast< InputPanelNode* >( node ); - if ( panelNode == nullptr ) - panelNode = new InputPanelNode; - - const auto contentsRect = panel->keyboardRect(); - - auto& panelKeyData = panel->keyData(); - for ( const auto& keyRow : panelKeyData ) - { - const auto rowIndex = &keyRow - panelKeyData; - auto& frames = panelNode->frames[ rowIndex ]; - auto& glyphs = panelNode->glyphs[ rowIndex ]; - - for ( const auto& keyData : keyRow ) - { - const auto colIndex = &keyData - keyRow; - auto& frame = frames[ colIndex ]; - auto& glyph = glyphs[ colIndex ]; - - if ( !keyData.key || keyData.key == Qt::Key_unknown ) - { - delete frame; - frame = nullptr; - - delete glyph; - glyph = nullptr; - - continue; - } - - const qreal sx = contentsRect.size().width(); - const qreal sy = contentsRect.size().height(); - - const QRectF keyRect( - contentsRect.x() + keyData.rect.x() * sx, - contentsRect.y() + keyData.rect.y() * sy, - keyData.rect.width() * sx, - keyData.rect.height() * sy ); - - frame = updateKeyFrameNode( panel, frame, keyRect, keyData.key ); - if ( frame->parent() != panelNode ) - panelNode->appendChildNode( frame ); - - glyph = updateKeyGlyphNode( panel, glyph, keyRect, keyData.key ); - if ( glyph->parent() != panelNode ) - panelNode->appendChildNode( glyph ); - } - } - - updateBoxNode( panel, panelNode, contentsRect, QskInputPanel::Panel ); - - return panelNode; -} - -QSGNode* QskInputPanelSkinlet::updateKeyFrameNode( - const QskInputPanel* panel, QSGNode* node, - const QRectF& rect, Qt::Key key ) const -{ - auto frameNode = static_cast< FrameNode* >( node ); - if ( frameNode == nullptr ) - frameNode = new FrameNode( const_cast< QskInputPanel* >( panel ) ); - - frameNode->setKey( key ); - updateBoxNode( frameNode, frameNode, rect, QskInputPanel::KeyPanel ); - - return frameNode; -} - -QSGNode* QskInputPanelSkinlet::updateKeyGlyphNode( - const QskInputPanel* panel, QSGNode* node, - const QRectF& rect, Qt::Key key ) const -{ - auto textNode = static_cast< TextNode* >( node ); - if ( textNode == nullptr ) - textNode = new TextNode( const_cast< QskInputPanel* >( panel ) ); - - textNode->setKey( key ); - - QskTextOptions options; - options.setFontSizeMode( QskTextOptions::VerticalFit ); - - const auto alignment = textNode->flagHint< Qt::Alignment >( - QskInputPanel::KeyGlyph | QskAspect::Alignment, Qt::AlignCenter ); - - return updateTextNode( panel, textNode, rect, alignment, - panel->textForKey( key ), options, QskInputPanel::KeyGlyph ); -} - -#include "moc_QskInputPanelSkinlet.cpp" diff --git a/src/controls/QskInputPanelSkinlet.h b/src/controls/QskInputPanelSkinlet.h deleted file mode 100644 index 02202069..00000000 --- a/src/controls/QskInputPanelSkinlet.h +++ /dev/null @@ -1,43 +0,0 @@ -/****************************************************************************** - * QSkinny - Copyright (C) 2016 Uwe Rathmann - * This file may be used under the terms of the QSkinny License, Version 1.0 - *****************************************************************************/ - -#ifndef QSK_INPUT_PANEL_SKINLET_H -#define QSK_INPUT_PANEL_SKINLET_H - -#include "QskSkinlet.h" - -class QskInputPanel; - -class QSK_EXPORT QskInputPanelSkinlet : public QskSkinlet -{ - Q_GADGET - - using Inherited = QskSkinlet; - - enum NodeRole - { - Panel0, - Panel1, - Panel2 - }; - -public: - Q_INVOKABLE QskInputPanelSkinlet( QskSkin* = nullptr ); - virtual ~QskInputPanelSkinlet(); - -protected: - virtual QSGNode* updateSubNode( const QskSkinnable*, - quint8, QSGNode* ) const override; - - virtual QSGNode* updatePanelNode( const QskInputPanel*, QSGNode* ) const; - - virtual QSGNode* updateKeyFrameNode( const QskInputPanel*, - QSGNode*, const QRectF&, Qt::Key ) const; - - virtual QSGNode* updateKeyGlyphNode( const QskInputPanel*, - QSGNode*, const QRectF&, Qt::Key ) const; -}; - -#endif diff --git a/src/controls/QskSkin.cpp b/src/controls/QskSkin.cpp index 8b262531..427878ea 100644 --- a/src/controls/QskSkin.cpp +++ b/src/controls/QskSkin.cpp @@ -39,9 +39,6 @@ QSK_QT_PRIVATE_END #include "QskGraphicLabel.h" #include "QskGraphicLabelSkinlet.h" -#include "QskInputPanel.h" -#include "QskInputPanelSkinlet.h" - #include "QskListView.h" #include "QskListViewSkinlet.h" @@ -130,7 +127,6 @@ QskSkin::QskSkin( QObject* parent ): declareSkinlet< QskBox, QskBoxSkinlet >(); declareSkinlet< QskFocusIndicator, QskFocusIndicatorSkinlet >(); declareSkinlet< QskGraphicLabel, QskGraphicLabelSkinlet >(); - declareSkinlet< QskInputPanel, QskInputPanelSkinlet >(); declareSkinlet< QskListView, QskListViewSkinlet >(); declareSkinlet< QskPageIndicator, QskPageIndicatorSkinlet >(); declareSkinlet< QskPopup, QskPopupSkinlet >(); diff --git a/src/src.pro b/src/src.pro index 828b7a41..c92375ce 100644 --- a/src/src.pro +++ b/src/src.pro @@ -136,7 +136,6 @@ HEADERS += \ controls/QskGraphicLabelSkinlet.h \ controls/QskHintAnimator.h \ controls/QskInputPanel.h \ - controls/QskInputPanelSkinlet.h \ controls/QskListView.h \ controls/QskListViewSkinlet.h \ controls/QskObjectTree.h \ @@ -201,7 +200,6 @@ SOURCES += \ controls/QskGraphicLabelSkinlet.cpp \ controls/QskHintAnimator.cpp \ controls/QskInputPanel.cpp \ - controls/QskInputPanelSkinlet.cpp \ controls/QskListView.cpp \ controls/QskListViewSkinlet.cpp \ controls/QskObjectTree.cpp \