qskinny/src/controls/QskVirtualKeyboard.cpp

907 lines
24 KiB
C++
Raw Normal View History

2017-07-21 18:21:34 +02:00
/******************************************************************************
2018-02-06 14:55:35 +01:00
* QSkinny - Copyright (C) 2016 Uwe Rathmann
* This file may be used under the terms of the QSkinny License, Version 1.0
2017-07-21 18:21:34 +02:00
*****************************************************************************/
#include "QskVirtualKeyboard.h"
2018-04-01 12:47:44 +02:00
#include "QskTextOptions.h"
#include "QskLinearBox.h"
2017-07-21 18:21:34 +02:00
#include <QGuiApplication>
#include <QStyleHints>
2018-04-03 20:15:20 +02:00
QSK_QT_PRIVATE_BEGIN
#include <private/qguiapplication_p.h>
QSK_QT_PRIVATE_END
#include <qpa/qplatformintegration.h>
#include <qpa/qplatforminputcontext.h>
2017-07-21 18:21:34 +02:00
namespace
{
struct KeyTable
{
using Row = QskVirtualKeyboard::KeyData[ QskVirtualKeyboard::KeyCount ];
Row data[ QskVirtualKeyboard::RowCount ];
2017-07-21 18:21:34 +02:00
int indexOf( const QskVirtualKeyboard::KeyData* value ) const
2017-07-21 18:21:34 +02:00
{
return int( intptr_t( value - data[0] ) );
}
};
}
struct QskVirtualKeyboardLayouts
2017-07-21 18:21:34 +02:00
{
struct KeyCodes
{
using Row = Qt::Key[ QskVirtualKeyboard::KeyCount ];
Row data[ QskVirtualKeyboard::RowCount ];
2017-07-21 18:21:34 +02:00
};
using Layout = KeyCodes[ QskVirtualKeyboard::ModeCount ];
2017-07-21 18:21:34 +02:00
Layout bg; // Bulgarian
Layout cs; // Czech
Layout de; // German
Layout da; // Danish
Layout el; // Greek
Layout en_GB; // English (GB)
Layout en_US; // English (US)
Layout es; // Spanish
Layout fi; // Finnish
Layout fr; // French
Layout hu; // Hungarian
Layout it; // Italian
Layout ja; // Japanese
Layout lv; // Latvian
Layout lt; // Lithuanian
Layout nl; // Dutch
Layout pt; // Portuguese
Layout ro; // Romanian
Layout ru; // Russian
Layout sl; // Slovene
Layout sk; // Slovak
Layout tr; // Turkish
Layout zh; // Chinese
Q_GADGET
};
#define LOWER(x) Qt::Key(x + 32) // Convert an uppercase key to lowercase
static constexpr const QskVirtualKeyboardLayouts qskKeyboardLayouts =
2017-07-21 18:21:34 +02:00
{
#include "QskVirtualKeyboardLayouts.cpp"
2017-07-21 18:21:34 +02:00
};
#undef LOWER
QSK_DECLARE_OPERATORS_FOR_FLAGS( Qt::Key ) // Must appear after the LOWER macro
static const int KeyLocked = static_cast< int >( Qt::ControlModifier );
static const int KeyStates = static_cast< int >( Qt::KeyboardModifierMask );
2017-07-21 18:21:34 +02:00
static qreal qskKeyStretch( Qt::Key key )
{
2018-03-14 17:30:35 +01:00
switch( key )
2017-07-21 18:21:34 +02:00
{
case Qt::Key_Backspace:
case Qt::Key_Shift:
case Qt::Key_CapsLock:
return 1.5;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case Qt::Key_Space:
return 3.5;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case Qt::Key_Return:
case Qt::Key_Mode_switch:
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
// Possibly smaller
default:
break;
}
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
return 1.0;
}
static qreal qskRowStretch( const QskVirtualKeyboard::KeyRow& keyRow )
2017-07-21 18:21:34 +02:00
{
qreal stretch = 0;
2017-10-17 19:02:02 +02:00
2018-03-14 17:30:35 +01:00
for( const auto& key : keyRow )
2017-07-21 18:21:34 +02:00
{
2018-03-14 17:30:35 +01:00
if( !key )
{
2017-07-21 18:21:34 +02:00
continue;
2018-03-14 17:30:35 +01:00
}
2017-07-21 18:21:34 +02:00
stretch += qskKeyStretch( key );
}
2017-10-17 19:02:02 +02:00
2018-03-14 17:30:35 +01:00
if( stretch == 0.0 )
{
stretch = QskVirtualKeyboard::KeyCount;
2018-03-14 17:30:35 +01:00
}
2017-12-08 13:56:35 +01:00
2017-07-21 18:21:34 +02:00
return stretch;
}
static bool qskIsAutorepeat( int key )
2018-03-26 10:05:44 +02:00
{
return ( key != Qt::Key_Return && key != Qt::Key_Enter
2018-04-01 12:47:44 +02:00
&& key != Qt::Key_Shift && key != Qt::Key_CapsLock
&& key != Qt::Key_Mode_switch );
}
2017-07-21 18:21:34 +02:00
2018-04-04 10:15:59 +02:00
static inline QPlatformInputContext* qskInputContext()
{
return QGuiApplicationPrivate::platformIntegration()->inputContext();
}
QSK_SUBCONTROL( QskVirtualKeyboardCandidateButton, Panel )
QSK_SUBCONTROL( QskVirtualKeyboardCandidateButton, Text )
2018-04-01 12:47:44 +02:00
QskVirtualKeyboardCandidateButton::QskVirtualKeyboardCandidateButton(
QskVirtualKeyboard* inputPanel, QQuickItem* parent ) :
Inherited( parent ),
m_inputPanel( inputPanel ),
m_index( -1 )
{
setFlag( QQuickItem::ItemAcceptsInputMethod );
setText( QStringLiteral( " " ) ); // ###
2018-04-03 20:15:20 +02:00
connect( this, &QskVirtualKeyboardButton::pressed,
this, [ this ]() { m_inputPanel->handleCandidateKey( m_index, m_text ); } );
}
void QskVirtualKeyboardCandidateButton::setIndexAndText(int index, const QString& text )
{
m_index = index;
m_text = text;
setText( m_text );
}
2018-04-03 20:15:20 +02:00
QskAspect::Subcontrol QskVirtualKeyboardCandidateButton::effectiveSubcontrol(
QskAspect::Subcontrol subControl ) const
{
if( subControl == QskPushButton::Panel )
{
return QskVirtualKeyboardCandidateButton::Panel;
}
if( subControl == QskPushButton::Text )
{
return QskVirtualKeyboardCandidateButton::Text;
}
return subControl;
}
int QskVirtualKeyboardCandidateButton::maxCandidates()
{
return 12;
}
QSK_SUBCONTROL( QskVirtualKeyboard, Panel )
2018-03-15 10:31:50 +01:00
QSK_SUBCONTROL( QskVirtualKeyboardButton, Panel )
QSK_SUBCONTROL( QskVirtualKeyboardButton, Text )
2018-03-14 17:30:37 +01:00
2018-04-01 12:47:44 +02:00
QskVirtualKeyboardButton::QskVirtualKeyboardButton(
int keyIndex, QskVirtualKeyboard* inputPanel, QQuickItem* parent ) :
2018-03-14 17:30:37 +01:00
Inherited( parent ),
m_keyIndex( keyIndex ),
m_inputPanel( inputPanel )
{
2018-03-23 07:59:26 +01:00
QskTextOptions options;
options.setFontSizeMode( QskTextOptions::VerticalFit );
setTextOptions( options );
setFocusPolicy( Qt::TabFocus );
2018-03-26 10:05:44 +02:00
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() );
}
2018-03-14 17:30:37 +01:00
updateText();
2018-04-01 12:47:44 +02:00
connect( this, &QskVirtualKeyboardButton::pressed, this,
[ this ]() { m_inputPanel->handleKey( m_keyIndex ); } );
2018-03-14 17:30:37 +01:00
2018-04-01 12:47:44 +02:00
connect( m_inputPanel, &QskVirtualKeyboard::modeChanged,
this, &QskVirtualKeyboardButton::updateText );
2018-03-14 17:30:37 +01:00
}
2018-04-01 12:47:44 +02:00
QskAspect::Subcontrol QskVirtualKeyboardButton::effectiveSubcontrol(
QskAspect::Subcontrol subControl ) const
2018-03-14 17:30:37 +01:00
{
if( subControl == QskPushButton::Panel )
return QskVirtualKeyboardButton::Panel;
2018-03-14 17:30:37 +01:00
if( subControl == QskPushButton::Text )
2018-04-02 17:01:04 +02:00
return QskVirtualKeyboardButton::Text;
2018-03-14 17:30:37 +01:00
return subControl;
}
int QskVirtualKeyboardButton::keyIndex() const
2018-03-21 13:00:24 +01:00
{
return m_keyIndex;
}
void QskVirtualKeyboardButton::updateText()
2018-03-14 17:30:37 +01:00
{
QString text = m_inputPanel->currentTextForKeyIndex( m_keyIndex );
2018-03-28 10:26:33 +02:00
if( text.count() == 1 && text.at( 0 ) == QChar( 0 ) )
2018-03-14 17:30:37 +01:00
{
2018-03-28 10:26:33 +02:00
setVisible( false );
2018-03-14 17:30:37 +01:00
}
else
{
2018-03-28 10:26:33 +02:00
setVisible( true );
2018-03-14 17:30:37 +01:00
setText( text );
}
}
class QskVirtualKeyboard::PrivateData
2017-07-21 18:21:34 +02:00
{
2018-04-01 12:47:44 +02:00
public:
PrivateData():
currentLayout( nullptr ),
mode( QskVirtualKeyboard::LowercaseMode ),
selectedGroup( -1 ),
candidateOffset( 0 ),
candidateBox( nullptr ),
buttonsBox( nullptr ),
isUIInitialized( false ),
candidateBoxVisible( false )
{
}
2017-07-21 18:21:34 +02:00
2018-04-01 12:47:44 +02:00
public:
const QskVirtualKeyboardLayouts::Layout* currentLayout;
QskVirtualKeyboard::Mode mode;
2017-07-21 18:21:34 +02:00
2018-04-01 12:47:44 +02:00
qint16 selectedGroup;
qint32 candidateOffset;
2017-07-21 18:21:34 +02:00
2018-04-01 12:47:44 +02:00
QVector< QString > candidates;
2017-07-21 18:21:34 +02:00
2018-04-01 12:47:44 +02:00
KeyTable keyTable[ ModeCount ];
2018-03-14 17:30:37 +01:00
2018-04-01 12:47:44 +02:00
QList< QskVirtualKeyboardCandidateButton* > candidateButtons;
QskLinearBox* candidateBox;
QskLinearBox* buttonsBox;
QList< QskVirtualKeyboardButton* > keyButtons;
bool isUIInitialized;
bool candidateBoxVisible;
2017-07-21 18:21:34 +02:00
};
QskVirtualKeyboard::QskVirtualKeyboard( QQuickItem* parent ):
2018-03-28 15:31:44 +02:00
Inherited( parent ),
2017-07-21 18:21:34 +02:00
m_data( new PrivateData )
{
2018-03-14 17:30:37 +01:00
qRegisterMetaType< Qt::Key >();
2017-07-21 18:21:34 +02:00
setFlag( ItemHasContents );
2017-10-25 17:10:50 +02:00
setAcceptedMouseButtons( Qt::LeftButton );
initSizePolicy( QskSizePolicy::Expanding, QskSizePolicy::Expanding );
2017-07-21 18:21:34 +02:00
updateLocale( locale() );
2018-04-01 12:47:44 +02:00
connect( this, &QskControl::localeChanged,
this, &QskVirtualKeyboard::updateLocale );
2018-03-14 17:30:39 +01:00
setFlag( ItemIsFocusScope, true );
2018-03-26 15:48:03 +02:00
2018-03-28 14:01:26 +02:00
#if 0
setTabFence( true );
#endif
setAutoLayoutChildren( true );
m_data->buttonsBox = new QskLinearBox( Qt::Vertical, this );
m_data->buttonsBox->setAutoAddChildren( true );
2018-04-01 12:47:44 +02:00
const auto& panelKeyData = keyData();
for( const auto& keyRow : panelKeyData )
{
2018-04-01 12:47:44 +02:00
auto rowBox = new QskLinearBox( Qt::Horizontal, m_data->buttonsBox );
rowBox->setAutoAddChildren( true );
for( const auto& keyData : keyRow )
{
if( !keyData.key )
continue;
2018-04-01 12:47:44 +02:00
const int keyIndex = m_data->keyTable[ m_data->mode ].indexOf( &keyData );
auto button = new QskVirtualKeyboardButton( keyIndex, this, rowBox );
2018-04-03 20:15:20 +02:00
button->installEventFilter( this );
2018-03-28 10:26:33 +02:00
rowBox->setRetainSizeWhenHidden( button, true );
m_data->keyButtons.append( button );
}
}
2018-03-28 10:26:33 +02:00
2018-04-01 12:47:44 +02:00
connect( this, &QskVirtualKeyboard::modeChanged,
this, [ this ]() { updateLayout(); } );
2017-07-21 18:21:34 +02:00
}
QskVirtualKeyboard::~QskVirtualKeyboard()
2017-07-21 18:21:34 +02:00
{
}
2018-04-01 12:47:44 +02:00
QskAspect::Subcontrol QskVirtualKeyboard::effectiveSubcontrol(
QskAspect::Subcontrol subControl ) const
2018-03-22 15:48:29 +01:00
{
2018-03-28 15:31:44 +02:00
if( subControl == QskBox::Panel )
return QskVirtualKeyboard::Panel;
2018-03-22 15:48:29 +01:00
return subControl;
}
QskVirtualKeyboard::Mode QskVirtualKeyboard::mode() const
2017-07-21 18:21:34 +02:00
{
return m_data->mode;
}
const QskVirtualKeyboard::KeyDataSet& QskVirtualKeyboard::keyData( Mode mode ) const
2017-07-21 18:21:34 +02:00
{
mode = mode == CurrentMode ? m_data->mode : mode;
Q_ASSERT( mode >= 0 && mode < ModeCount );
return m_data->keyTable[ mode ].data;
}
QString QskVirtualKeyboard::textForKey( int key ) const
2017-07-21 18:21:34 +02:00
{
key &= ~KeyStates;
// Special cases
2018-03-14 17:30:35 +01:00
switch( key )
2017-10-17 19:02:02 +02:00
{
2017-07-21 18:21:34 +02:00
case Qt::Key_Backspace:
case Qt::Key_Muhenkan:
return QChar( 0x232B );
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case Qt::Key_CapsLock:
case Qt::Key_Kana_Lock:
return QChar( 0x21E7 );
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case Qt::Key_Shift:
case Qt::Key_Kana_Shift:
return QChar( 0x2B06 );
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case Qt::Key_Mode_switch:
return QChar( 0x2026 );
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case Qt::Key_Return:
case Qt::Key_Kanji:
return QChar( 0x23CE );
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case Qt::Key_Left:
return QChar( 0x2190 );
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case Qt::Key_Right:
return QChar( 0x2192 );
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case Qt::Key_ApplicationLeft:
return QChar( 0x2B05 );
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case Qt::Key_ApplicationRight:
return QChar( 0x27A1 );
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
default:
break;
}
// TODO: possibly route through locale for custom strings
// Default to direct key mapping
return QChar( key );
}
QString QskVirtualKeyboard::displayLanguageName() const
2017-07-21 18:21:34 +02:00
{
const auto locale = this->locale();
2017-10-17 19:02:02 +02:00
2018-03-14 17:30:35 +01:00
switch( locale.language() )
2017-07-21 18:21:34 +02:00
{
case QLocale::Bulgarian:
return QStringLiteral( "български език" );
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Czech:
return QStringLiteral( "Čeština" );
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::German:
return QStringLiteral( "Deutsch" );
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Danish:
return QStringLiteral( "Dansk" );
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Greek:
return QStringLiteral( "Eλληνικά" );
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::English:
2018-04-01 12:47:44 +02:00
{
switch( locale.country() )
2017-07-21 18:21:34 +02:00
{
2018-04-01 12:47:44 +02:00
case QLocale::Canada:
case QLocale::UnitedStates:
case QLocale::UnitedStatesMinorOutlyingIslands:
case QLocale::UnitedStatesVirginIslands:
return QStringLiteral( "English (US)" );
default:
return QStringLiteral( "English (UK)" );
2017-07-21 18:21:34 +02:00
}
2017-10-17 19:02:02 +02:00
2018-04-01 12:47:44 +02:00
break;
}
2017-07-21 18:21:34 +02:00
case QLocale::Spanish:
return QStringLiteral( "Español" );
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Finnish:
return QStringLiteral( "Suomi" );
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::French:
return QStringLiteral( "Français" );
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Hungarian:
return QStringLiteral( "Magyar" );
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Italian:
return QStringLiteral( "Italiano" );
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Japanese:
return QStringLiteral( "日本語" );
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Latvian:
return QStringLiteral( "Latviešu" );
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Lithuanian:
return QStringLiteral( "Lietuvių" );
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Dutch:
return QStringLiteral( "Nederlands" );
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Portuguese:
return QStringLiteral( "Português" );
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Romanian:
return QStringLiteral( "Română" );
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Russia:
return QStringLiteral( "Русский" );
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Slovenian:
return QStringLiteral( "Slovenščina" );
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Slovak:
return QStringLiteral( "Slovenčina" );
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Turkish:
return QStringLiteral( "Türkçe" );
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Chinese:
return QStringLiteral( "中文" );
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
default:
break;
}
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
return QLocale::languageToString( locale.language() );
}
void QskVirtualKeyboard::setPreeditCandidates( const QVector< QString >& candidates )
2017-07-21 18:21:34 +02:00
{
2018-03-14 17:30:35 +01:00
if( m_data->candidates == candidates )
{
2017-07-21 18:21:34 +02:00
return;
2018-03-14 17:30:35 +01:00
}
2017-07-21 18:21:34 +02:00
m_data->candidates = candidates;
setCandidateOffset( 0 );
}
void QskVirtualKeyboard::setCandidateOffset( int candidateOffset )
2017-07-21 18:21:34 +02:00
{
m_data->candidateOffset = candidateOffset;
const auto candidateCount = m_data->candidates.length();
const auto count = std::min( candidateCount, QskVirtualKeyboardCandidateButton::maxCandidates() );
2017-07-21 18:21:34 +02:00
const bool continueLeft = m_data->candidateOffset > 0;
const bool continueRight = ( candidateCount - m_data->candidateOffset ) > count;
2018-03-14 17:30:35 +01:00
for( int i = 0; i < count; ++i )
2017-07-21 18:21:34 +02:00
{
2018-04-01 12:47:44 +02:00
auto button = m_data->candidateButtons[i];
2018-03-14 17:30:35 +01:00
if( continueLeft && i == 0 )
{
2018-04-01 12:47:44 +02:00
button->setIndexAndText( i, textForKey( Qt::Key_ApplicationLeft ) );
2018-03-14 17:30:35 +01:00
}
2018-04-04 20:19:47 +02:00
else if( continueRight && ( i == KeyCount - 1 ) )
2018-03-14 17:30:35 +01:00
{
2018-04-01 12:47:44 +02:00
button->setIndexAndText( i, textForKey( Qt::Key_ApplicationRight ) );
2018-03-14 17:30:35 +01:00
}
2017-07-21 18:21:34 +02:00
else
2018-03-14 17:30:35 +01:00
{
2018-04-01 12:47:44 +02:00
const int index = i + m_data->candidateOffset;
button->setIndexAndText( index, m_data->candidates[index] );
2018-03-14 17:30:35 +01:00
}
2017-07-21 18:21:34 +02:00
}
for( int i = count; i < QskVirtualKeyboardCandidateButton::maxCandidates(); ++i )
2017-07-21 18:21:34 +02:00
{
2018-04-01 12:47:44 +02:00
m_data->candidateButtons[i]->setIndexAndText( -1, QString() );
2018-03-14 17:30:35 +01:00
}
2017-07-21 18:21:34 +02:00
}
void QskVirtualKeyboard::geometryChanged(
2018-03-14 17:30:37 +01:00
const QRectF& newGeometry, const QRectF& oldGeometry )
2017-07-21 18:21:34 +02:00
{
2018-03-14 17:30:37 +01:00
Inherited::geometryChanged( newGeometry, oldGeometry );
Q_EMIT keyboardRectChanged();
2017-07-21 18:21:34 +02:00
}
void QskVirtualKeyboard::updateLayout()
2018-03-21 13:00:24 +01:00
{
if( geometry().isNull() )
return; // no need to calculate anything, will be called again
2018-03-21 13:00:24 +01:00
2018-03-28 15:49:45 +02:00
QRectF rect = layoutRect();
2018-03-21 13:00:24 +01:00
qreal verticalSpacing = m_data->buttonsBox->spacing();
2018-03-28 15:49:45 +02:00
const auto& children = m_data->buttonsBox->childItems();
for( auto rowItem : children )
2018-03-21 13:00:24 +01:00
{
2018-03-28 15:49:45 +02:00
auto rowBox = qobject_cast< QskLinearBox* >( rowItem );
const qreal horizontalSpacing = rowBox->spacing();
2018-03-21 13:00:24 +01:00
2018-03-28 15:49:45 +02:00
const auto& rowChildren = rowBox->childItems();
for( auto keyItem : rowChildren )
2018-03-21 13:00:24 +01:00
{
auto button = qobject_cast< QskVirtualKeyboardButton* >( keyItem );
2018-03-21 13:00:24 +01:00
QRectF keyRect = keyDataAt( button->keyIndex() ).rect;
qreal width = keyRect.width() * rect.width() - horizontalSpacing;
qreal height = keyRect.height() * rect.height() - verticalSpacing;
button->setFixedSize( width, height );
}
}
}
void QskVirtualKeyboard::createUI()
{
setAutoLayoutChildren( true );
2018-03-30 15:04:26 +02:00
auto outerBox = new QskLinearBox( Qt::Vertical, this );
2018-03-30 15:04:26 +02:00
m_data->candidateBox = new QskLinearBox( Qt::Horizontal, outerBox );
#if 1
// should be skin hints TODO ...
QMarginsF margins( 0, 10, 0, 20 ); // ###
m_data->candidateBox->setMargins( margins );
2018-03-30 15:04:26 +02:00
#endif
// to determine suggestions buttons width
// (otherwise empty buttons would be too small when there are only a few suggestions):
// ### Can this be done with the layout engine or so?
QRectF rect = layoutRect();
auto candidateButtonWidth = rect.width() / QskVirtualKeyboardCandidateButton::maxCandidates()
2018-04-01 12:47:44 +02:00
- m_data->candidateBox->spacing() * QskVirtualKeyboardCandidateButton::maxCandidates();
for( int a = 0; a < QskVirtualKeyboardCandidateButton::maxCandidates(); ++a )
{
2018-04-01 12:47:44 +02:00
auto button = new QskVirtualKeyboardCandidateButton( this, m_data->candidateBox );
qreal height = button->sizeHint().height();
2018-03-30 15:04:26 +02:00
#if 1
2018-04-01 12:47:44 +02:00
// should be done by margins/paddings
button->setPreferredHeight( height + 10 );
2018-03-30 15:04:26 +02:00
#endif
2018-04-01 12:47:44 +02:00
button->setPreferredWidth( candidateButtonWidth );
button->installEventFilter( this );
2018-04-01 12:47:44 +02:00
m_data->candidateBox->setRetainSizeWhenHidden( button, true );
m_data->candidateButtons.append( button );
}
m_data->candidateBox->setVisible( m_data->candidateBoxVisible );
2018-03-30 15:04:26 +02:00
outerBox->setRetainSizeWhenHidden( m_data->candidateBox, true );
}
void QskVirtualKeyboard::updateUI()
2018-03-14 17:30:37 +01:00
{
2018-04-01 12:47:44 +02:00
for( auto button : qskAsConst( m_data->keyButtons ) )
2018-03-14 17:30:37 +01:00
button->updateText();
}
QskVirtualKeyboard::KeyData& QskVirtualKeyboard::keyDataAt( int keyIndex ) const
2017-07-21 18:21:34 +02:00
{
const auto row = keyIndex / KeyCount;
const auto col = keyIndex % KeyCount;
2018-04-01 12:47:44 +02:00
2017-07-21 18:21:34 +02:00
return m_data->keyTable[ m_data->mode ].data[ row ][ col ];
}
void QskVirtualKeyboard::handleKey( int keyIndex )
2017-07-21 18:21:34 +02:00
{
2018-03-14 17:30:37 +01:00
KeyData keyData = keyDataAt( keyIndex );
const auto key = keyData.key & ~KeyStates;
2017-07-21 18:21:34 +02:00
// Mode-switching keys
2018-03-14 17:30:35 +01:00
switch( key )
2017-07-21 18:21:34 +02:00
{
case Qt::Key_CapsLock:
case Qt::Key_Kana_Lock:
setMode( UppercaseMode ); // Lock caps
return;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case Qt::Key_Shift:
case Qt::Key_Kana_Shift:
setMode( LowercaseMode ); // Unlock caps
return;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case Qt::Key_Mode_switch: // Cycle through modes, but skip caps
setMode( static_cast< QskVirtualKeyboard::Mode >(
2018-04-01 12:47:44 +02:00
m_data->mode ? ( ( m_data->mode + 1 ) % QskVirtualKeyboard::ModeCount )
: SpecialCharacterMode ) );
2017-07-21 18:21:34 +02:00
return;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
default:
break;
}
compose( key );
}
void QskVirtualKeyboard::handleCandidateKey( int index, const QString& text )
{
if( text == textForKey( Qt::Key_ApplicationLeft ) )
2018-03-14 17:30:37 +01:00
{
setCandidateOffset( m_data->candidateOffset - 1 );
}
else if( text == textForKey( Qt::Key_ApplicationRight ) )
{
setCandidateOffset( m_data->candidateOffset + 1 );
2018-03-14 17:30:37 +01:00
}
else
{
selectCandidate( index );
2018-03-14 17:30:37 +01:00
}
}
void QskVirtualKeyboard::setCandidateBarVisible( bool visible )
{
// need to cache it until we have created the UI
m_data->candidateBoxVisible = visible;
if( m_data->isUIInitialized )
m_data->candidateBox->setVisible( m_data->candidateBoxVisible );
}
QString QskVirtualKeyboard::currentTextForKeyIndex( int keyIndex ) const
2018-03-14 17:30:37 +01:00
{
auto keyData = keyDataAt( keyIndex );
QString text = textForKey( keyData.key );
return text;
2017-07-21 18:21:34 +02:00
}
void QskVirtualKeyboard::compose( int key )
2017-07-21 18:21:34 +02:00
{
QGuiApplication::inputMethod()->invokeAction(
static_cast< QInputMethod::Action >( Compose ), key );
}
void QskVirtualKeyboard::selectCandidate( int index )
2017-07-21 18:21:34 +02:00
{
QGuiApplication::inputMethod()->invokeAction(
static_cast< QInputMethod::Action >( SelectCandidate ), index );
}
void QskVirtualKeyboard::updateLocale( const QLocale& locale )
2017-07-21 18:21:34 +02:00
{
2018-03-14 17:30:35 +01:00
switch( locale.language() )
2017-07-21 18:21:34 +02:00
{
case QLocale::Bulgarian:
m_data->currentLayout = &qskKeyboardLayouts.bg;
2017-07-21 18:21:34 +02:00
break;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Czech:
m_data->currentLayout = &qskKeyboardLayouts.cs;
2017-07-21 18:21:34 +02:00
break;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::German:
m_data->currentLayout = &qskKeyboardLayouts.de;
2017-07-21 18:21:34 +02:00
break;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Danish:
m_data->currentLayout = &qskKeyboardLayouts.da;
2017-07-21 18:21:34 +02:00
break;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Greek:
m_data->currentLayout = &qskKeyboardLayouts.el;
2017-07-21 18:21:34 +02:00
break;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::English:
2018-04-01 12:47:44 +02:00
{
switch( locale.country() )
2017-07-21 18:21:34 +02:00
{
2018-04-01 12:47:44 +02:00
case QLocale::Canada:
case QLocale::UnitedStates:
case QLocale::UnitedStatesMinorOutlyingIslands:
case QLocale::UnitedStatesVirginIslands:
m_data->currentLayout = &qskKeyboardLayouts.en_US;
break;
default:
m_data->currentLayout = &qskKeyboardLayouts.en_GB;
break;
2017-07-21 18:21:34 +02:00
}
2018-03-14 17:30:35 +01:00
2018-04-01 12:47:44 +02:00
break;
}
2017-07-21 18:21:34 +02:00
case QLocale::Spanish:
m_data->currentLayout = &qskKeyboardLayouts.es;
2017-07-21 18:21:34 +02:00
break;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Finnish:
m_data->currentLayout = &qskKeyboardLayouts.fi;
2017-07-21 18:21:34 +02:00
break;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::French:
m_data->currentLayout = &qskKeyboardLayouts.fr;
2017-07-21 18:21:34 +02:00
break;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Hungarian:
m_data->currentLayout = &qskKeyboardLayouts.hu;
2017-07-21 18:21:34 +02:00
break;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Italian:
m_data->currentLayout = &qskKeyboardLayouts.it;
2017-07-21 18:21:34 +02:00
break;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Japanese:
m_data->currentLayout = &qskKeyboardLayouts.ja;
2017-07-21 18:21:34 +02:00
break;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Latvian:
m_data->currentLayout = &qskKeyboardLayouts.lv;
2017-07-21 18:21:34 +02:00
break;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Lithuanian:
m_data->currentLayout = &qskKeyboardLayouts.lt;
2017-07-21 18:21:34 +02:00
break;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Dutch:
m_data->currentLayout = &qskKeyboardLayouts.nl;
2017-07-21 18:21:34 +02:00
break;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Portuguese:
m_data->currentLayout = &qskKeyboardLayouts.pt;
2017-07-21 18:21:34 +02:00
break;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Romanian:
m_data->currentLayout = &qskKeyboardLayouts.ro;
2017-07-21 18:21:34 +02:00
break;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Russia:
m_data->currentLayout = &qskKeyboardLayouts.ru;
2017-07-21 18:21:34 +02:00
break;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Slovenian:
m_data->currentLayout = &qskKeyboardLayouts.sl;
2017-07-21 18:21:34 +02:00
break;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Slovak:
m_data->currentLayout = &qskKeyboardLayouts.sk;
2017-07-21 18:21:34 +02:00
break;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Turkish:
m_data->currentLayout = &qskKeyboardLayouts.tr;
2017-07-21 18:21:34 +02:00
break;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Chinese:
m_data->currentLayout = &qskKeyboardLayouts.zh;
2017-07-21 18:21:34 +02:00
break;
#if 1
case QLocale::C:
m_data->currentLayout = &qskKeyboardLayouts.en_US;
break;
#endif
2017-12-11 08:58:57 +01:00
default:
qWarning() << "QskInputPanel: unsupported locale:" << locale;
m_data->currentLayout = &qskKeyboardLayouts.en_US;
2017-07-21 18:21:34 +02:00
}
2017-10-17 19:02:02 +02:00
Q_EMIT displayLanguageNameChanged();
2017-07-21 18:21:34 +02:00
updateKeyData();
setMode( LowercaseMode );
}
void QskVirtualKeyboard::updateKeyData()
2017-07-21 18:21:34 +02:00
{
// Key data is in normalized coordinates
const auto keyHeight = 1.0f / RowCount;
2018-03-14 17:30:35 +01:00
for( const auto& keyLayout : *m_data->currentLayout )
2017-07-21 18:21:34 +02:00
{
auto& keyDataLayout = m_data->keyTable[ &keyLayout - *m_data->currentLayout ];
qreal yPos = 0;
2018-03-14 17:30:35 +01:00
for( int i = 0; i < RowCount; i++ )
2017-07-21 18:21:34 +02:00
{
auto& row = keyLayout.data[i];
auto& keyDataRow = keyDataLayout.data[ i ];
2017-12-07 17:12:52 +01:00
const auto baseKeyWidth = 1.0 / qskRowStretch( row );
2017-07-21 18:21:34 +02:00
qreal xPos = 0;
qreal keyWidth = baseKeyWidth;
2018-03-14 17:30:35 +01:00
for( const auto& key : row )
2017-07-21 18:21:34 +02:00
{
auto& keyData = keyDataRow[ &key - row ];
keyData.key = key;
keyWidth = baseKeyWidth * qskKeyStretch( key );
keyData.rect = { xPos, yPos, keyWidth, keyHeight };
xPos += keyWidth;
}
yPos += keyHeight;
}
}
}
void QskVirtualKeyboard::setMode( QskVirtualKeyboard::Mode mode )
2017-07-21 18:21:34 +02:00
{
m_data->mode = mode;
2018-03-14 17:30:37 +01:00
Q_EMIT modeChanged( m_data->mode );
2017-07-21 18:21:34 +02:00
}
2018-04-03 20:15:20 +02:00
bool QskVirtualKeyboard::eventFilter( QObject* object, QEvent* event )
{
if ( event->type() == QEvent::InputMethodQuery )
{
/*
Qt/Quick expects that the item associated with the virtual keyboard
always has the focus. But you also find systems, where you have to
navigate and select inside the virtual keyboard.
So we have to fix the receiver.
*/
2018-04-04 10:15:59 +02:00
if ( const auto inputContext = qskInputContext() )
2018-04-03 20:15:20 +02:00
{
QQuickItem* item = nullptr;
if ( QMetaObject::invokeMethod( inputContext, "inputItem",
Qt::DirectConnection, Q_RETURN_ARG( QQuickItem*, item ) ) )
{
if ( item )
QGuiApplication::sendEvent( item, event );
}
}
return true;
}
return Inherited::eventFilter( object, event );
}
#include "moc_QskVirtualKeyboard.cpp"