Merge branch 'peter-ha-qsktextinput'

This commit is contained in:
Uwe 2018-03-28 15:52:26 +02:00
commit 63795d21b0
16 changed files with 513 additions and 813 deletions

View File

@ -37,26 +37,26 @@ static QString qskKeyString( int code )
class QskInputCompositionModel::PrivateData class QskInputCompositionModel::PrivateData
{ {
public: public:
PrivateData() :
inputItem( nullptr ),
groupIndex( 0 )
{
}
// QInputMethod // QInputMethod
QString preedit; QString preedit;
QTextCharFormat preeditFormat; QTextCharFormat preeditFormat;
QList< QInputMethodEvent::Attribute > preeditAttributes; QList< QInputMethodEvent::Attribute > preeditAttributes;
QObject* inputItem;
int groupIndex; int groupIndex;
}; };
static inline void sendCompositionEvent( QInputMethodEvent* e )
{
if ( auto focusObject = QGuiApplication::focusObject() )
QCoreApplication::sendEvent( focusObject, e );
}
QskInputCompositionModel::QskInputCompositionModel(): QskInputCompositionModel::QskInputCompositionModel():
m_data( new PrivateData ) m_data( new PrivateData )
{ {
m_data->groupIndex = 0; m_data->groupIndex = 0;
m_data->preeditFormat.setFontUnderline(true); m_data->preeditFormat.setFontUnderline( true );
m_data->preeditAttributes.append( QInputMethodEvent::Attribute( m_data->preeditAttributes.append( QInputMethodEvent::Attribute(
QInputMethodEvent::TextFormat, 0, 0, m_data->preeditFormat ) ); QInputMethodEvent::TextFormat, 0, 0, m_data->preeditFormat ) );
} }
@ -71,13 +71,12 @@ void QskInputCompositionModel::composeKey( Qt::Key key )
if ( !inputMethod ) if ( !inputMethod )
return; return;
auto focusObject = QGuiApplication::focusObject(); if ( !m_data->inputItem )
if ( !focusObject )
return; return;
QInputMethodQueryEvent queryEvent( QInputMethodQueryEvent queryEvent(
Qt::ImSurroundingText | Qt::ImMaximumTextLength | Qt::ImHints ); Qt::ImSurroundingText | Qt::ImMaximumTextLength | Qt::ImHints );
QCoreApplication::sendEvent( focusObject, &queryEvent ); QCoreApplication::sendEvent( m_data->inputItem, &queryEvent );
const auto hints = static_cast< Qt::InputMethodHints >( const auto hints = static_cast< Qt::InputMethodHints >(
queryEvent.value( Qt::ImHints ).toInt() ); queryEvent.value( Qt::ImHints ).toInt() );
const int maxLength = queryEvent.value( Qt::ImMaximumTextLength ).toInt(); const int maxLength = queryEvent.value( Qt::ImMaximumTextLength ).toInt();
@ -224,8 +223,7 @@ void QskInputCompositionModel::commitCandidate( int index )
void QskInputCompositionModel::backspace() void QskInputCompositionModel::backspace()
{ {
auto focusWindow = QGuiApplication::focusWindow(); if ( !m_data->inputItem )
if ( !focusWindow )
return; return;
if ( !m_data->preedit.isEmpty() ) if ( !m_data->preedit.isEmpty() )
@ -237,8 +235,8 @@ void QskInputCompositionModel::backspace()
// Backspace one character only if preedit was inactive // Backspace one character only if preedit was inactive
QKeyEvent keyPress( QEvent::KeyPress, Qt::Key_Backspace, Qt::NoModifier ); QKeyEvent keyPress( QEvent::KeyPress, Qt::Key_Backspace, Qt::NoModifier );
QKeyEvent keyRelease( QEvent::KeyRelease, Qt::Key_Backspace, Qt::NoModifier ); QKeyEvent keyRelease( QEvent::KeyRelease, Qt::Key_Backspace, Qt::NoModifier );
QCoreApplication::sendEvent( focusWindow, &keyPress ); QCoreApplication::sendEvent( m_data->inputItem, &keyPress );
QCoreApplication::sendEvent( focusWindow, &keyRelease ); QCoreApplication::sendEvent( m_data->inputItem, &keyRelease );
return; return;
} }
@ -253,18 +251,28 @@ void QskInputCompositionModel::moveCursor( Qt::Key key )
if ( key != Qt::Key_Left && key != Qt::Key_Right ) if ( key != Qt::Key_Left && key != Qt::Key_Right )
return; return;
auto focusWindow = QGuiApplication::focusWindow(); if ( !m_data->inputItem )
if ( !focusWindow )
return; return;
// Moving cursor is disabled when preedit is active. // Moving cursor is disabled when preedit is active.
if ( !m_data->preedit.isEmpty() ) if ( !m_data->preedit.isEmpty() )
return; return;
// ### this should be in the panel:
QKeyEvent moveCursorPress( QEvent::KeyPress, key, Qt::NoModifier ); QKeyEvent moveCursorPress( QEvent::KeyPress, key, Qt::NoModifier );
QKeyEvent moveCursorRelease( QEvent::KeyRelease, key, Qt::NoModifier ); QKeyEvent moveCursorRelease( QEvent::KeyRelease, key, Qt::NoModifier );
QCoreApplication::sendEvent( focusWindow, &moveCursorPress ); #if 1
QCoreApplication::sendEvent( focusWindow, &moveCursorRelease ); 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 bool QskInputCompositionModel::hasIntermediate() const
@ -300,6 +308,11 @@ QVector< Qt::Key > QskInputCompositionModel::groups() const
return QVector< Qt::Key >(); return QVector< Qt::Key >();
} }
void QskInputCompositionModel::setInputItem( QObject *inputItem )
{
m_data->inputItem = inputItem;
}
bool QskInputCompositionModel::nextGroupIndex(int& index, bool forward) const bool QskInputCompositionModel::nextGroupIndex(int& index, bool forward) const
{ {
Q_UNUSED(index); Q_UNUSED(index);

View File

@ -10,6 +10,7 @@
#include <memory> #include <memory>
class QInputMethodEvent;
class QStringList; class QStringList;
class QskInputCompositionModel : public QObject class QskInputCompositionModel : public QObject
@ -37,6 +38,8 @@ public:
virtual QVector< Qt::Key > groups() const; virtual QVector< Qt::Key > groups() const;
void setInputItem( QObject* inputItem );
protected: protected:
// Used for text composition // Used for text composition
virtual bool hasIntermediate() const; virtual bool hasIntermediate() const;
@ -50,6 +53,7 @@ Q_SIGNALS:
private: private:
void backspace(); void backspace();
void moveCursor( Qt::Key key ); void moveCursor( Qt::Key key );
void sendCompositionEvent( QInputMethodEvent* e );
class PrivateData; class PrivateData;
std::unique_ptr< PrivateData > m_data; std::unique_ptr< PrivateData > m_data;

View File

@ -21,11 +21,15 @@
#include <QRectF> #include <QRectF>
QskInputContext::QskInputContext(): QskInputContext::QskInputContext():
Inherited() Inherited(),
m_inputCompositionModel( new QskInputCompositionModel )
{ {
connect( qskSetup, &QskSetup::inputPanelChanged, connect( qskSetup, &QskSetup::inputPanelChanged,
this, &QskInputContext::setInputPanel ); this, &QskInputContext::setInputPanel );
setInputPanel( qskSetup->inputPanel() ); setInputPanel( qskSetup->inputPanel() );
// We could connect candidatesChanged() here, but we don't emit
// the signal in the normal composition model anyhow
} }
QskInputContext::~QskInputContext() QskInputContext::~QskInputContext()
@ -41,11 +45,11 @@ bool QskInputContext::isValid() const
void QskInputContext::update( Qt::InputMethodQueries queries ) void QskInputContext::update( Qt::InputMethodQueries queries )
{ {
if ( !m_focusObject ) if ( !m_inputItem )
return; return;
QInputMethodQueryEvent queryEvent( queries ); QInputMethodQueryEvent queryEvent( queries );
if ( !QCoreApplication::sendEvent( m_focusObject, &queryEvent ) ) if ( !QCoreApplication::sendEvent( m_inputItem, &queryEvent ) )
return; return;
// Qt::ImCursorRectangle // Qt::ImCursorRectangle
@ -204,75 +208,44 @@ QLocale QskInputContext::locale() const
void QskInputContext::setFocusObject( QObject* focusObject ) void QskInputContext::setFocusObject( QObject* focusObject )
{ {
if ( m_focusObject )
m_focusObject->removeEventFilter( this );
m_focusObject = focusObject; m_focusObject = focusObject;
if ( !m_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; 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 ) ); 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 ) void QskInputContext::invokeAction( QInputMethod::Action action, int cursorPosition )
{ {
Q_UNUSED(cursorPosition); Q_UNUSED(cursorPosition);

View File

@ -8,6 +8,7 @@
#include <qpa/qplatforminputcontext.h> #include <qpa/qplatforminputcontext.h>
#include <QPointer> #include <QPointer>
#include <QQuickItem>
#include <memory> #include <memory>
@ -22,7 +23,7 @@ class QskInputContext : public QPlatformInputContext
public: public:
QskInputContext(); QskInputContext();
~QskInputContext(); ~QskInputContext() override;
bool isValid() const override; bool isValid() const override;
void update( Qt::InputMethodQueries ) override; void update( Qt::InputMethodQueries ) override;
@ -35,9 +36,6 @@ public:
QLocale locale() const override; QLocale locale() const override;
void setFocusObject( QObject* ) override; void setFocusObject( QObject* ) override;
protected:
bool eventFilter( QObject*, QEvent* ) override;
private Q_SLOTS: private Q_SLOTS:
void emitAnimatingChanged(); void emitAnimatingChanged();
void handleCandidatesChanged(); void handleCandidatesChanged();
@ -45,6 +43,7 @@ private Q_SLOTS:
private: private:
QPointer< QObject > m_focusObject; QPointer< QObject > m_focusObject;
QPointer< QQuickItem > m_inputItem;
QPointer< QskInputPanel > m_inputPanel; QPointer< QskInputPanel > m_inputPanel;
std::unique_ptr< QskInputCompositionModel > m_inputCompositionModel; std::unique_ptr< QskInputCompositionModel > m_inputCompositionModel;
}; };

View File

@ -0,0 +1,17 @@
#include "TextInput.h"
TextInput::TextInput( QQuickItem* parent )
: QQuickTextInput( parent )
{
}
TextInput::~TextInput()
{
}
void TextInput::inputMethodEvent(QInputMethodEvent *event)
{
QQuickTextInput::inputMethodEvent(event);
}

View File

@ -0,0 +1,16 @@
#ifndef TEXTINPUT_H
#define TEXTINPUT_H
#include <private/qquicktextinput_p.h>
class TextInput : public QQuickTextInput
{
public:
TextInput( QQuickItem* parent );
virtual ~TextInput() override;
protected:
void inputMethodEvent(QInputMethodEvent *) Q_DECL_OVERRIDE;
};
#endif // TEXTINPUT_H

View File

@ -5,4 +5,8 @@ TARGET = inputpanel
DEFINES += PLUGIN_PATH=$$clean_path( $$QSK_OUT_ROOT/plugins ) DEFINES += PLUGIN_PATH=$$clean_path( $$QSK_OUT_ROOT/plugins )
SOURCES += \ SOURCES += \
main.cpp main.cpp \
TextInput.cpp
HEADERS += \
TextInput.h

View File

@ -3,6 +3,8 @@
* This file may be used under the terms of the 3-clause BSD License * This file may be used under the terms of the 3-clause BSD License
*****************************************************************************/ *****************************************************************************/
#include "TextInput.h"
#include <SkinnyFont.h> #include <SkinnyFont.h>
#include <SkinnyShortcut.h> #include <SkinnyShortcut.h>
@ -21,8 +23,6 @@
#include <QGuiApplication> #include <QGuiApplication>
#include <QFontMetricsF> #include <QFontMetricsF>
#include <private/qquicktextinput_p.h>
#define STRINGIFY(x) #x #define STRINGIFY(x) #x
#define STRING(x) STRINGIFY(x) #define STRING(x) STRINGIFY(x)

View File

@ -521,31 +521,24 @@ void QskMaterialSkin::initTabViewHints()
void QskMaterialSkin::initInputPanelHints() void QskMaterialSkin::initInputPanelHints()
{ {
using namespace QskAspect; using namespace QskAspect;
using Q = QskInputPanel; using Q = QskKeyButton;
const ColorPalette& pal = m_data->palette; const ColorPalette& pal = m_data->palette;
// key panel // key panel
setMargins( Q::KeyPanel | Margin, 2 ); setMargins( QskInputPanel::Panel | Margin, 2 );
setBoxShape( Q::KeyPanel, 20.0, Qt::RelativeSize ); setBoxShape( Q::Panel, 20.0, Qt::RelativeSize );
setBoxBorderMetrics( Q::KeyPanel, 2 ); setBoxBorderMetrics( Q::Panel, 2 );
setGradient( Q::KeyPanel, pal.darker125 ); setGradient( Q::Panel, pal.darker125 );
setBoxBorderColors( Q::KeyPanel, pal.baseColor ); setBoxBorderColors( Q::Panel, pal.baseColor );
for ( auto state : { NoState, Q::Focused } ) 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::Panel | Color, qskDuration );
setAnimation( Q::KeyPanel | Metric, qskDuration ); setAnimation( Q::Panel | 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 );
// panel // panel
setBoxShape( Q::Panel, 0 ); setBoxShape( Q::Panel, 0 );

View File

@ -554,36 +554,21 @@ void QskSquiekSkin::initTabViewHints()
void QskSquiekSkin::initInputPanelHints() void QskSquiekSkin::initInputPanelHints()
{ {
using namespace QskAspect; using namespace QskAspect;
using Q = QskInputPanel; using Q = QskKeyButton;
const ColorPalette& pal = m_data->palette; const ColorPalette& pal = m_data->palette;
// key panel // 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::Panel, Raised );
setButton( Q::KeyPanel | Q::Pressed, Sunken ); setButton( Q::Panel | Q::Pressed, Sunken );
setAnimation( Q::KeyPanel | Color, qskDuration ); setAnimation( Q::Panel | Color, qskDuration );
#if 0
// crashes because animations are started from updateNode
// TODO ...
setAnimation( Q::KeyPanel | Metric, qskDuration ); setColor( Q::Text, pal.themeForeground );
#endif setColor( Q::Text | Q::Disabled, pal.darker200 );
// 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 );
} }
void QskSquiekSkin::initScrollViewHints() void QskSquiekSkin::initScrollViewHints()

View File

@ -7,21 +7,19 @@
#include "QskAspect.h" #include "QskAspect.h"
#include <QskPushButton.h>
#include <QskTextLabel.h>
#include <QskDialog.h> #include <QskDialog.h>
#include <QFocusEvent>
#include <QGuiApplication> #include <QGuiApplication>
#include <QStyleHints> #include <QStyleHints>
#include <QskLinearBox.h>
#include <QskTextOptions.h>
#include <QTimer>
#include <cmath> #include <cmath>
#include <unordered_map> #include <unordered_map>
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 namespace
{ {
struct KeyTable struct KeyTable
@ -34,60 +32,6 @@ namespace
return int( intptr_t( value - data[0] ) ); 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 struct QskInputPanelLayouts
@ -130,20 +74,18 @@ struct QskInputPanelLayouts
#define LOWER(x) Qt::Key(x + 32) // Convert an uppercase key to lowercase #define LOWER(x) Qt::Key(x + 32) // Convert an uppercase key to lowercase
static constexpr const QskInputPanelLayouts qskInputPanelLayouts = static constexpr const QskInputPanelLayouts qskInputPanelLayouts =
{ {
#include "QskInputPanelLayouts.cpp" #include "QskInputPanelLayouts.cpp"
}; };
#undef LOWER #undef LOWER
QSK_DECLARE_OPERATORS_FOR_FLAGS( Qt::Key ) // Must appear after the LOWER macro 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 int KeyLocked = static_cast< int >( Qt::ControlModifier );
static const Qt::Key KeyLocked = static_cast< Qt::Key >( Qt::ControlModifier ); static const int KeyStates = static_cast< int >( Qt::KeyboardModifierMask );
static const Qt::Key KeyFocused = static_cast< Qt::Key >( Qt::MetaModifier );
static const Qt::Key KeyStates = static_cast< Qt::Key >( Qt::KeyboardModifierMask );
static qreal qskKeyStretch( Qt::Key key ) static qreal qskKeyStretch( Qt::Key key )
{ {
switch ( key ) switch( key )
{ {
case Qt::Key_Backspace: case Qt::Key_Backspace:
case Qt::Key_Shift: case Qt::Key_Shift:
@ -168,20 +110,32 @@ static qreal qskRowStretch( const QskInputPanel::KeyRow& keyRow )
{ {
qreal stretch = 0; qreal stretch = 0;
for ( const auto& key : keyRow ) for( const auto& key : keyRow )
{ {
if ( !key ) if( !key )
{
continue; continue;
}
stretch += qskKeyStretch( key ); stretch += qskKeyStretch( key );
} }
if ( stretch == 0.0 ) if( stretch == 0.0 )
{
stretch = QskInputPanel::KeyCount; stretch = QskInputPanel::KeyCount;
}
return stretch; 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 namespace
{ {
struct KeyCounter 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: QskTextOptions options;
PrivateData(): options.setFontSizeMode( QskTextOptions::VerticalFit );
currentLayout( nullptr ), setTextOptions( options );
mode( QskInputPanel::LowercaseMode ),
focusKeyIndex( -1 ), setFocusPolicy( Qt::TabFocus );
selectedGroup( -1 ),
candidateOffset( 0 ), auto keyData = m_inputPanel->keyDataAt( m_keyIndex );
repeatKeyTimerId( -1 ) const auto key = keyData.key & ~KeyStates;
if ( qskIsAutorepeat( key ) )
{ {
setAutoRepeat( true );
setAutoRepeatDelay( 500 );
setAutoRepeatInterval( 1000 / QGuiApplication::styleHints()->keyboardAutoRepeatRate() );
} }
public: updateText();
const QskInputPanelLayouts::Layout* currentLayout;
QskInputPanel::Mode mode;
qint16 focusKeyIndex; connect( this, &QskKeyButton::pressed, this, [ this ]()
qint16 selectedGroup; {
qint32 candidateOffset; 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; if( subControl == QskPushButton::Text )
QVector< Qt::Key > candidates; {
// ### we could also introduce a state to not always query the button
return isCancelButton() ? QskKeyButton::TextCancelButton : QskKeyButton::Text;
}
std::unordered_map< int, KeyCounter > activeKeys; return subControl;
KeyTable keyTable[ ModeCount ]; }
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 ): QskInputPanel::QskInputPanel( QQuickItem* parent ):
QskControl( parent ), Inherited( parent ),
m_data( new PrivateData ) m_data( new PrivateData )
{ {
qRegisterMetaType< Qt::Key >();
setFlag( ItemHasContents ); setFlag( ItemHasContents );
setAcceptedMouseButtons( Qt::LeftButton ); setAcceptedMouseButtons( Qt::LeftButton );
initSizePolicy( QskSizePolicy::Expanding, QskSizePolicy::Expanding ); initSizePolicy( QskSizePolicy::Expanding, QskSizePolicy::Expanding );
setAutoFillBackground( true );
auto margins = marginsHint( Panel | QskAspect::Margin );
setMargins( margins );
updateLocale( locale() ); updateLocale( locale() );
QObject::connect( this, &QskControl::localeChanged, 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() QskInputPanel::~QskInputPanel()
{ {
} }
QskAspect::Subcontrol QskInputPanel::effectiveSubcontrol( QskAspect::Subcontrol subControl ) const
{
if( subControl == QskBox::Panel )
{
return QskInputPanel::Panel;
}
return subControl;
}
QskInputPanel::Mode QskInputPanel::mode() const QskInputPanel::Mode QskInputPanel::mode() const
{ {
return m_data->mode; return m_data->mode;
@ -254,12 +345,12 @@ const QskInputPanel::KeyDataSet& QskInputPanel::keyData( Mode mode ) const
return m_data->keyTable[ mode ].data; return m_data->keyTable[ mode ].data;
} }
QString QskInputPanel::textForKey( Qt::Key key ) const QString QskInputPanel::textForKey( int key ) const
{ {
key &= ~KeyStates; key &= ~KeyStates;
// Special cases // Special cases
switch ( key ) switch( key )
{ {
case Qt::Key_Backspace: case Qt::Key_Backspace:
case Qt::Key_Muhenkan: case Qt::Key_Muhenkan:
@ -306,7 +397,7 @@ QString QskInputPanel::displayLanguageName() const
{ {
const auto locale = this->locale(); const auto locale = this->locale();
switch ( locale.language() ) switch( locale.language() )
{ {
case QLocale::Bulgarian: case QLocale::Bulgarian:
return QStringLiteral( "български език" ); return QStringLiteral( "български език" );
@ -324,19 +415,21 @@ QString QskInputPanel::displayLanguageName() const
return QStringLiteral( "Eλληνικά" ); return QStringLiteral( "Eλληνικά" );
case QLocale::English: case QLocale::English:
{
switch ( locale.country() )
{ {
case QLocale::Canada: switch( locale.country() )
case QLocale::UnitedStates: {
case QLocale::UnitedStatesMinorOutlyingIslands: case QLocale::Canada:
case QLocale::UnitedStatesVirginIslands: case QLocale::UnitedStates:
return QStringLiteral( "English (US)" ); case QLocale::UnitedStatesMinorOutlyingIslands:
case QLocale::UnitedStatesVirginIslands:
return QStringLiteral( "English (US)" );
default: default:
return QStringLiteral( "English (UK)" ); return QStringLiteral( "English (UK)" );
}
break;
} }
}
case QLocale::Spanish: case QLocale::Spanish:
return QStringLiteral( "Español" ); return QStringLiteral( "Español" );
@ -397,7 +490,7 @@ void QskInputPanel::setPreeditGroups( const QVector< Qt::Key >& groups )
{ {
auto& topRow = m_data->keyTable[ LowercaseMode ].data[ 0 ]; auto& topRow = m_data->keyTable[ LowercaseMode ].data[ 0 ];
for ( const auto& group : groups ) for( const auto& group : groups )
{ {
auto& keyData = topRow[ &group - groups.data() ]; auto& keyData = topRow[ &group - groups.data() ];
keyData.key = group; keyData.key = group;
@ -406,96 +499,23 @@ void QskInputPanel::setPreeditGroups( const QVector< Qt::Key >& groups )
m_data->groups = groups; m_data->groups = groups;
selectGroup( -1 ); selectGroup( -1 );
if ( m_data->mode == LowercaseMode ) if( m_data->mode == LowercaseMode )
{
update(); update();
}
} }
void QskInputPanel::setPreeditCandidates( const QVector< Qt::Key >& candidates ) void QskInputPanel::setPreeditCandidates( const QVector< Qt::Key >& candidates )
{ {
if ( m_data->candidates == candidates ) if( m_data->candidates == candidates )
{
return; return;
}
m_data->candidates = candidates; m_data->candidates = candidates;
setCandidateOffset( 0 ); 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 ) void QskInputPanel::setCandidateOffset( int candidateOffset )
{ {
m_data->candidateOffset = candidateOffset; m_data->candidateOffset = candidateOffset;
@ -508,202 +528,83 @@ void QskInputPanel::setCandidateOffset( int candidateOffset )
const bool continueLeft = m_data->candidateOffset > 0; const bool continueLeft = m_data->candidateOffset > 0;
const bool continueRight = ( candidateCount - m_data->candidateOffset ) > count; 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 ]; auto& keyData = topRow[ i + groupCount ];
if ( continueLeft && i == 0 ) if( continueLeft && i == 0 )
{
keyData.key = Qt::Key_ApplicationLeft; keyData.key = Qt::Key_ApplicationLeft;
else if ( continueRight && ( i == KeyCount - groupCount - 1 ) ) }
else if( continueRight && ( i == KeyCount - groupCount - 1 ) )
{
keyData.key = Qt::Key_ApplicationRight; keyData.key = Qt::Key_ApplicationRight;
}
else else
{
keyData.isSuggestionKey = true; // ### reset when switching layouts etc.!
keyData.key = m_data->candidates.at( i + m_data->candidateOffset ); 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 ]; auto& keyData = topRow[ i + groupCount ];
keyData.key = Qt::Key_unknown; keyData.key = Qt::Key_unknown;
} }
if ( m_data->mode == LowercaseMode ) if( m_data->mode == LowercaseMode )
update(); {
updateUI();
}
} }
QRectF QskInputPanel::keyboardRect() const void QskInputPanel::registerCompositionModelForLocale( const QLocale& locale,
QskInputCompositionModel* model )
{ {
auto keyboardRect = rect(); // ### margins? would eliminate this thing below Q_EMIT inputMethodRegistered( locale, model );
if ( QskDialog::instance()->policy() != QskDialog::TopLevelWindow )
keyboardRect.adjust( 0, keyboardRect.height() * 0.5, 0, 0 );
return keyboardRect;
} }
void QskInputPanel::geometryChanged( void QskInputPanel::geometryChanged(
const QRectF& newGeometry, const QRectF& oldGeometry ) const QRectF& newGeometry, const QRectF& oldGeometry )
{ {
Inherited::geometryChanged( newGeometry, oldGeometry ); Inherited::geometryChanged( newGeometry, oldGeometry );
Q_EMIT keyboardRectChanged(); 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(); auto rowBox = qobject_cast< QskLinearBox* >( rowItem );
return; 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 ); for( QskKeyButton* button : qskAsConst( m_data->keyButtons ) )
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 ( auto& it : m_data->activeKeys ) button->updateText();
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;
}
} }
} }
@ -716,35 +617,43 @@ QskInputPanel::KeyData& QskInputPanel::keyDataAt( int keyIndex ) const
void QskInputPanel::handleKey( int keyIndex ) void QskInputPanel::handleKey( int keyIndex )
{ {
const auto key = keyDataAt( keyIndex ).key & ~KeyStates; KeyData keyData = keyDataAt( keyIndex );
const auto key = keyData.key & ~KeyStates;
// Preedit keys // Preedit keys
const auto row = keyIndex / KeyCount; const auto row = keyIndex / KeyCount;
const auto column = 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 if( key == Qt::Key_ApplicationLeft
|| key == Qt::Key_ApplicationRight ) || key == Qt::Key_ApplicationRight )
{ {
setCandidateOffset( m_data->candidateOffset setCandidateOffset( m_data->candidateOffset
+ ( key == Qt::Key_ApplicationLeft ? -1 : 1 ) ); + ( key == Qt::Key_ApplicationLeft ? -1 : 1 ) );
return; return;
} }
const auto groupCount = m_data->groups.length(); const auto groupCount = m_data->groups.length();
if ( column < groupCount )
if( column < groupCount )
{
selectGroup( column ); selectGroup( column );
else if ( column < KeyCount ) }
else if( column < KeyCount )
{
selectCandidate( column - groupCount + m_data->candidateOffset ); selectCandidate( column - groupCount + m_data->candidateOffset );
}
else else
Q_UNREACHABLE(); // Handle the final key... {
Q_UNREACHABLE(); // Handle the final key...
}
return; return;
} }
// Mode-switching keys // Mode-switching keys
switch ( key ) switch( key )
{ {
case Qt::Key_CapsLock: case Qt::Key_CapsLock:
case Qt::Key_Kana_Lock: 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 case Qt::Key_Mode_switch: // Cycle through modes, but skip caps
setMode( static_cast< QskInputPanel::Mode >( setMode( static_cast< QskInputPanel::Mode >(
m_data->mode ? ( ( m_data->mode + 1 ) % QskInputPanel::ModeCount ) m_data->mode ? ( ( m_data->mode + 1 ) % QskInputPanel::ModeCount )
: SpecialCharacterMode ) ); : SpecialCharacterMode ) );
return;
// This is (one of) the cancel symbol, not Qt::Key_Cancel:
case Qt::Key( 10006 ):
Q_EMIT cancelPressed();
return; return;
default: default:
break; break;
} }
// Normal keys if( keyData.isSuggestionKey )
compose( key ); {
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( QGuiApplication::inputMethod()->invokeAction(
static_cast< QInputMethod::Action >( Compose ), key ); static_cast< QInputMethod::Action >( Compose ), key );
@ -779,18 +706,26 @@ void QskInputPanel::compose( Qt::Key key )
void QskInputPanel::selectGroup( int index ) void QskInputPanel::selectGroup( int index )
{ {
auto& topRow = m_data->keyTable[ m_data->mode ].data[ 0 ]; 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 ) if( m_data->selectedGroup >= 0 )
index = -1; // clear selection {
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; m_data->selectedGroup = index;
QGuiApplication::inputMethod()->invokeAction( QGuiApplication::inputMethod()->invokeAction(
static_cast< QInputMethod::Action >( SelectGroup ), m_data->selectedGroup + 1 ); static_cast< QInputMethod::Action >( SelectGroup ), m_data->selectedGroup + 1 );
if ( m_data->selectedGroup < 0 ) if( m_data->selectedGroup < 0 )
{
return; return;
}
topRow[ m_data->selectedGroup ].key |= KeyLocked; topRow[ m_data->selectedGroup ].key |= KeyLocked;
} }
@ -803,7 +738,7 @@ void QskInputPanel::selectCandidate( int index )
void QskInputPanel::updateLocale( const QLocale& locale ) void QskInputPanel::updateLocale( const QLocale& locale )
{ {
switch ( locale.language() ) switch( locale.language() )
{ {
case QLocale::Bulgarian: case QLocale::Bulgarian:
m_data->currentLayout = &qskInputPanelLayouts.bg; m_data->currentLayout = &qskInputPanelLayouts.bg;
@ -826,21 +761,24 @@ void QskInputPanel::updateLocale( const QLocale& locale )
break; break;
case QLocale::English: case QLocale::English:
{
switch ( locale.country() )
{ {
case QLocale::Canada: switch( locale.country() )
case QLocale::UnitedStates: {
case QLocale::UnitedStatesMinorOutlyingIslands: case QLocale::Canada:
case QLocale::UnitedStatesVirginIslands: case QLocale::UnitedStates:
m_data->currentLayout = &qskInputPanelLayouts.en_US; case QLocale::UnitedStatesMinorOutlyingIslands:
break; case QLocale::UnitedStatesVirginIslands:
default: m_data->currentLayout = &qskInputPanelLayouts.en_US;
m_data->currentLayout = &qskInputPanelLayouts.en_GB; break;
break;
default:
m_data->currentLayout = &qskInputPanelLayouts.en_GB;
break;
}
break;
} }
break;
}
case QLocale::Spanish: case QLocale::Spanish:
m_data->currentLayout = &qskInputPanelLayouts.es; m_data->currentLayout = &qskInputPanelLayouts.es;
break; break;
@ -905,10 +843,6 @@ void QskInputPanel::updateLocale( const QLocale& locale )
m_data->currentLayout = &qskInputPanelLayouts.zh; m_data->currentLayout = &qskInputPanelLayouts.zh;
break; break;
case QLocale::C:
m_data->currentLayout = &qskInputPanelLayouts.en_US;
break;
default: default:
qWarning() << "QskInputPanel: unsupported locale:" << locale; qWarning() << "QskInputPanel: unsupported locale:" << locale;
m_data->currentLayout = &qskInputPanelLayouts.en_US; m_data->currentLayout = &qskInputPanelLayouts.en_US;
@ -925,12 +859,12 @@ void QskInputPanel::updateKeyData()
// Key data is in normalized coordinates // Key data is in normalized coordinates
const auto keyHeight = 1.0f / RowCount; 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 ]; auto& keyDataLayout = m_data->keyTable[ &keyLayout - *m_data->currentLayout ];
qreal yPos = 0; qreal yPos = 0;
for ( int i = 0; i < RowCount; i++ ) for( int i = 0; i < RowCount; i++ )
{ {
auto& row = keyLayout.data[i]; auto& row = keyLayout.data[i];
auto& keyDataRow = keyDataLayout.data[ i ]; auto& keyDataRow = keyDataLayout.data[ i ];
@ -940,7 +874,7 @@ void QskInputPanel::updateKeyData()
qreal xPos = 0; qreal xPos = 0;
qreal keyWidth = baseKeyWidth; qreal keyWidth = baseKeyWidth;
for ( const auto& key : row ) for( const auto& key : row )
{ {
auto& keyData = keyDataRow[ &key - row ]; auto& keyData = keyDataRow[ &key - row ];
keyData.key = key; keyData.key = key;
@ -958,7 +892,7 @@ void QskInputPanel::updateKeyData()
void QskInputPanel::setMode( QskInputPanel::Mode mode ) void QskInputPanel::setMode( QskInputPanel::Mode mode )
{ {
m_data->mode = mode; m_data->mode = mode;
update(); Q_EMIT modeChanged( m_data->mode );
} }
#include "QskInputPanel.moc" #include "QskInputPanel.moc"

View File

@ -6,28 +6,55 @@
#ifndef QSK_INPUT_PANEL_H #ifndef QSK_INPUT_PANEL_H
#define QSK_INPUT_PANEL_H #define QSK_INPUT_PANEL_H
#include "QskControl.h" #include "QskBox.h"
#include "QskPushButton.h"
#include <QRectF> #include <QRectF>
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_OBJECT
Q_PROPERTY( QRectF keyboardRect READ keyboardRect NOTIFY keyboardRectChanged )
Q_PROPERTY( QString displayLanguageName READ displayLanguageName Q_PROPERTY( QString displayLanguageName READ displayLanguageName
NOTIFY displayLanguageNameChanged ) NOTIFY displayLanguageNameChanged )
using Inherited = QskControl; using Inherited = QskBox;
public: public:
QSK_SUBCONTROLS( Panel, KeyPanel, KeyGlyph )
QSK_STATES( Checked, Pressed ) QSK_SUBCONTROLS( Panel )
struct KeyData struct KeyData
{ {
Qt::Key key = Qt::Key( 0 ); int key = 0;
bool isSuggestionKey = false;
QRectF rect; QRectF rect;
}; };
@ -60,7 +87,9 @@ public:
using KeyDataSet = KeyDataRow[RowCount]; using KeyDataSet = KeyDataRow[RowCount];
QskInputPanel( QQuickItem* parent = nullptr ); QskInputPanel( QQuickItem* parent = nullptr );
virtual ~QskInputPanel(); virtual ~QskInputPanel() override;
virtual QskAspect::Subcontrol effectiveSubcontrol( QskAspect::Subcontrol subControl ) const override;
void updateLocale( const QLocale& locale ); void updateLocale( const QLocale& locale );
@ -69,33 +98,32 @@ public:
const KeyDataSet& keyData( QskInputPanel::Mode = CurrentMode ) const; const KeyDataSet& keyData( QskInputPanel::Mode = CurrentMode ) const;
QString textForKey( Qt::Key ) const; QString textForKey( int ) const;
QString displayLanguageName() const; QString displayLanguageName() const;
QRectF keyboardRect() const; QRectF keyboardRect() const;
// takes ownership:
void registerCompositionModelForLocale( const QLocale& locale,
QskInputCompositionModel* model );
public Q_SLOTS: public Q_SLOTS:
void setPreeditGroups( const QVector< Qt::Key >& ); void setPreeditGroups( const QVector< Qt::Key >& );
void setPreeditCandidates( const QVector< Qt::Key >& ); void setPreeditCandidates( const QVector< Qt::Key >& );
bool advanceFocus( bool = true );
bool activateFocusKey(); void handleKey( int keyIndex );
bool deactivateFocusKey(); KeyData& keyDataAt( int ) const;
void clearFocusKey(); QString currentTextForKeyIndex( int keyIndex ) const;
protected: protected:
virtual void geometryChanged( const QRectF&, const QRectF& ) override; virtual void geometryChanged( const QRectF&, const QRectF& ) override;
virtual void updateLayout() 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;
private: private:
KeyData& keyDataAt( int ) const; void createUI();
void updateUI(); // e.g. called when updating Pinyin suggestions
void handleKey( int ); void compose( int );
void compose( Qt::Key );
void selectGroup( int ); void selectGroup( int );
void selectCandidate( int ); void selectCandidate( int );
void setCandidateOffset( int ); void setCandidateOffset( int );
@ -105,6 +133,11 @@ private:
Q_SIGNALS: Q_SIGNALS:
void keyboardRectChanged(); void keyboardRectChanged();
void displayLanguageNameChanged(); 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: public:
class PrivateData; class PrivateData;

View File

@ -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"

View File

@ -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

View File

@ -39,9 +39,6 @@ QSK_QT_PRIVATE_END
#include "QskGraphicLabel.h" #include "QskGraphicLabel.h"
#include "QskGraphicLabelSkinlet.h" #include "QskGraphicLabelSkinlet.h"
#include "QskInputPanel.h"
#include "QskInputPanelSkinlet.h"
#include "QskListView.h" #include "QskListView.h"
#include "QskListViewSkinlet.h" #include "QskListViewSkinlet.h"
@ -130,7 +127,6 @@ QskSkin::QskSkin( QObject* parent ):
declareSkinlet< QskBox, QskBoxSkinlet >(); declareSkinlet< QskBox, QskBoxSkinlet >();
declareSkinlet< QskFocusIndicator, QskFocusIndicatorSkinlet >(); declareSkinlet< QskFocusIndicator, QskFocusIndicatorSkinlet >();
declareSkinlet< QskGraphicLabel, QskGraphicLabelSkinlet >(); declareSkinlet< QskGraphicLabel, QskGraphicLabelSkinlet >();
declareSkinlet< QskInputPanel, QskInputPanelSkinlet >();
declareSkinlet< QskListView, QskListViewSkinlet >(); declareSkinlet< QskListView, QskListViewSkinlet >();
declareSkinlet< QskPageIndicator, QskPageIndicatorSkinlet >(); declareSkinlet< QskPageIndicator, QskPageIndicatorSkinlet >();
declareSkinlet< QskPopup, QskPopupSkinlet >(); declareSkinlet< QskPopup, QskPopupSkinlet >();

View File

@ -136,7 +136,6 @@ HEADERS += \
controls/QskGraphicLabelSkinlet.h \ controls/QskGraphicLabelSkinlet.h \
controls/QskHintAnimator.h \ controls/QskHintAnimator.h \
controls/QskInputPanel.h \ controls/QskInputPanel.h \
controls/QskInputPanelSkinlet.h \
controls/QskListView.h \ controls/QskListView.h \
controls/QskListViewSkinlet.h \ controls/QskListViewSkinlet.h \
controls/QskObjectTree.h \ controls/QskObjectTree.h \
@ -201,7 +200,6 @@ SOURCES += \
controls/QskGraphicLabelSkinlet.cpp \ controls/QskGraphicLabelSkinlet.cpp \
controls/QskHintAnimator.cpp \ controls/QskHintAnimator.cpp \
controls/QskInputPanel.cpp \ controls/QskInputPanel.cpp \
controls/QskInputPanelSkinlet.cpp \
controls/QskListView.cpp \ controls/QskListView.cpp \
controls/QskListViewSkinlet.cpp \ controls/QskListViewSkinlet.cpp \
controls/QskObjectTree.cpp \ controls/QskObjectTree.cpp \