qskinny/src/inputpanel/QskVirtualKeyboard.cpp

550 lines
13 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-06 17:30:24 +02:00
#include "QskPushButton.h"
2018-04-01 12:47:44 +02:00
#include "QskTextOptions.h"
2017-07-21 18:21:34 +02:00
#include <QGuiApplication>
#include <QStyleHints>
namespace
{
2018-04-06 17:30:24 +02:00
enum
2017-07-21 18:21:34 +02:00
{
2018-04-06 17:30:24 +02:00
RowCount = 5,
2018-04-12 12:03:51 +02:00
ColumnCount = 12
2018-04-06 17:30:24 +02:00
};
2018-04-13 16:32:48 +02:00
using KeyRow = int[ ColumnCount ];
2018-04-06 17:30:24 +02:00
class Button final : public QskPushButton
{
public:
Button( int row, int column, QQuickItem* parent ):
QskPushButton( parent ),
m_row( row ),
m_column( column )
{
QskTextOptions options;
options.setFontSizeMode( QskTextOptions::VerticalFit );
setTextOptions( options );
2017-07-21 18:21:34 +02:00
2018-04-06 17:30:24 +02:00
setFocusPolicy( Qt::TabFocus );
}
virtual QskAspect::Subcontrol effectiveSubcontrol(
QskAspect::Subcontrol subControl ) const override
2017-07-21 18:21:34 +02:00
{
2018-04-06 17:30:24 +02:00
if( subControl == QskPushButton::Panel )
return QskVirtualKeyboard::ButtonPanel;
if( subControl == QskPushButton::Text )
return QskVirtualKeyboard::ButtonText;
return subControl;
2017-07-21 18:21:34 +02:00
}
2018-04-06 17:30:24 +02:00
int row() const { return m_row; }
int column() const { return m_column; }
private:
const int m_row;
const int m_column;
2017-07-21 18:21:34 +02:00
};
}
struct QskVirtualKeyboardLayouts
2017-07-21 18:21:34 +02:00
{
struct KeyCodes
{
2018-04-13 16:32:48 +02:00
using Row = int[ ColumnCount ];
2018-04-06 17:30:24 +02:00
Row data[ 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
};
2018-04-13 16:32:48 +02:00
#define LOWER(x) int(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
2018-04-13 16:32:48 +02:00
static qreal qskKeyStretch( int key )
2017-07-21 18:21:34 +02:00
{
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;
}
2018-04-06 17:30:24 +02:00
static qreal qskRowStretch( const 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 )
{
2018-04-12 12:03:51 +02:00
stretch = ColumnCount;
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;
}
2018-04-13 16:32:48 +02:00
static QString qskTextForKey( int key )
{
2018-04-06 17:30:24 +02:00
// Special cases
switch( key )
{
2018-04-06 17:30:24 +02:00
case Qt::Key_Backspace:
case Qt::Key_Muhenkan:
return QChar( 0x232B );
2018-04-06 17:30:24 +02:00
case Qt::Key_CapsLock:
case Qt::Key_Kana_Lock:
return QChar( 0x21E7 );
2018-04-06 17:30:24 +02:00
case Qt::Key_Shift:
case Qt::Key_Kana_Shift:
return QChar( 0x2B06 );
2018-04-06 17:30:24 +02:00
case Qt::Key_Mode_switch:
return QChar( 0x2026 );
2018-04-06 17:30:24 +02:00
case Qt::Key_Return:
case Qt::Key_Kanji:
return QChar( 0x23CE );
2018-03-15 10:31:50 +01:00
2018-04-06 17:30:24 +02:00
case Qt::Key_Left:
return QChar( 0x2190 );
2018-03-14 17:30:37 +01:00
2018-04-06 17:30:24 +02:00
case Qt::Key_Right:
return QChar( 0x2192 );
2018-03-23 07:59:26 +01:00
2018-04-06 17:30:24 +02:00
case Qt::Key_ApplicationLeft:
return QChar( 0x2B05 );
2018-04-06 17:30:24 +02:00
case Qt::Key_ApplicationRight:
return QChar( 0x27A1 );
2018-03-26 10:05:44 +02:00
2018-04-06 17:30:24 +02:00
default:
return QChar( key );
2018-03-26 10:05:44 +02:00
}
2018-03-14 17:30:37 +01:00
}
2018-04-06 17:30:24 +02:00
static bool qskIsAutorepeat( int key )
2018-03-14 17:30:37 +01:00
{
2018-04-06 17:30:24 +02:00
return ( key != Qt::Key_Return && key != Qt::Key_Enter
&& key != Qt::Key_Shift && key != Qt::Key_CapsLock
&& key != Qt::Key_Mode_switch );
2018-03-14 17:30:37 +01:00
}
2018-04-06 17:30:24 +02:00
QSK_SUBCONTROL( QskVirtualKeyboard, Panel )
QSK_SUBCONTROL( QskVirtualKeyboard, ButtonPanel )
QSK_SUBCONTROL( QskVirtualKeyboard, ButtonText )
2018-03-14 17:30:37 +01:00
class QskVirtualKeyboard::PrivateData
2017-07-21 18:21:34 +02:00
{
2018-04-01 12:47:44 +02:00
public:
PrivateData():
currentLayout( nullptr ),
2018-04-06 17:30:24 +02:00
mode( QskVirtualKeyboard::LowercaseMode )
2018-04-01 12:47:44 +02:00
{
}
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-06 17:30:24 +02:00
QVector< Button* > keyButtons;
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-04-06 17:30:24 +02:00
setPolishOnResize( true );
2018-04-12 12:03:51 +02:00
initSizePolicy( QskSizePolicy::Expanding, QskSizePolicy::Constrained );
2018-04-12 12:03:51 +02:00
m_data->keyButtons.reserve( RowCount * ColumnCount );
2018-04-06 17:30:24 +02:00
const auto autoRepeatInterval =
1000 / QGuiApplication::styleHints()->keyboardAutoRepeatRate();
2018-04-01 12:47:44 +02:00
2018-04-06 17:30:24 +02:00
for ( int row = 0; row < RowCount; row++ )
{
2018-04-12 12:03:51 +02:00
for ( int col = 0; col < ColumnCount; col++ )
{
2018-04-06 17:30:24 +02:00
auto button = new Button( row, col, this );
button->installEventFilter( this );
2018-04-06 17:30:24 +02:00
button->setAutoRepeat( false );
button->setAutoRepeatDelay( 500 );
button->setAutoRepeatInterval( autoRepeatInterval );
2018-04-01 12:47:44 +02:00
2018-04-06 17:30:24 +02:00
connect( button, &QskPushButton::pressed,
this, &QskVirtualKeyboard::buttonPressed );
2018-04-06 17:30:24 +02:00
m_data->keyButtons += button;
}
}
2018-04-06 17:30:24 +02:00
connect( this, &QskControl::localeChanged,
this, &QskVirtualKeyboard::updateLocale );
updateLocale( locale() );
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;
}
QSizeF QskVirtualKeyboard::contentsSizeHint() const
{
constexpr qreal ratio = qreal( RowCount ) / ColumnCount;
const qreal w = 600;
return QSizeF( w, ratio * w );
}
2018-04-12 12:03:51 +02:00
qreal QskVirtualKeyboard::heightForWidth( qreal width ) const
{
/*
Not necessarily correct, when
subControlRect( Panel ) != contentsRect: TODO ...
*/
2018-04-12 12:03:51 +02:00
constexpr qreal ratio = qreal( RowCount ) / ColumnCount;
const auto margins = this->margins();
width -= margins.left() + margins.right();
const auto padding = innerPadding(
Panel, QSizeF( width, width ) );
width -= padding.left() + padding.right();
2018-04-12 12:03:51 +02:00
qreal height = width * ratio;
height += padding.top() + padding.bottom();
2018-04-12 12:03:51 +02:00
height += margins.top() + margins.bottom();
return height;
}
qreal QskVirtualKeyboard::widthForHeight( qreal height ) const
{
constexpr qreal ratio = qreal( RowCount ) / ColumnCount;
const auto margins = this->margins();
height -= margins.top() + margins.bottom();
const auto padding = innerPadding(
Panel, QSizeF( height, height ) );
height -= padding.top() + padding.bottom();
2018-04-12 12:03:51 +02:00
qreal width = height / ratio;
2018-04-12 13:32:28 +02:00
width += padding.left() + padding.right();
width += padding.left() + padding.right();
return width;
2018-04-12 12:03:51 +02:00
}
2018-04-06 17:30:24 +02:00
void QskVirtualKeyboard::updateLayout()
2017-07-21 18:21:34 +02:00
{
2018-04-06 17:30:24 +02:00
const auto r = layoutRect();
if( r.isEmpty() )
return;
2017-10-17 19:02:02 +02:00
2018-04-06 18:07:12 +02:00
const auto spacing = metric( Panel | QskAspect::Spacing );
const auto totalVSpacing = ( RowCount - 1 ) * spacing;
2017-10-17 19:02:02 +02:00
const auto keyHeight = ( r.height() - totalVSpacing ) / RowCount;
2017-10-17 19:02:02 +02:00
2018-04-06 17:30:24 +02:00
const auto& keyCodes = ( *m_data->currentLayout )[ m_data->mode ];
2017-10-17 19:02:02 +02:00
2018-04-06 18:07:12 +02:00
qreal yPos = r.top();
2017-07-21 18:21:34 +02:00
2018-04-06 17:30:24 +02:00
for( int row = 0; row < RowCount; row++ )
2018-03-14 17:30:35 +01:00
{
2018-04-06 17:30:24 +02:00
const auto& keys = keyCodes.data[ row ];
2017-07-21 18:21:34 +02:00
2018-04-06 18:07:12 +02:00
#if 1
// there should be a better way
auto totalHSpacing = -spacing;
if ( spacing )
{
2018-04-12 12:03:51 +02:00
for ( int col = 0; col < ColumnCount; col++ )
2018-04-06 18:07:12 +02:00
{
2018-04-13 16:32:48 +02:00
if ( keys[ col ] != 0 )
2018-04-06 18:07:12 +02:00
totalHSpacing += spacing;
}
}
#endif
const auto baseKeyWidth = ( r.width() - totalHSpacing ) / qskRowStretch( keys );
qreal xPos = r.left();
2018-04-01 12:47:44 +02:00
2018-04-12 12:03:51 +02:00
for ( int col = 0; col < ColumnCount; col++ )
2018-03-14 17:30:35 +01:00
{
2018-04-13 16:32:48 +02:00
const int key = keys[ col ];
2018-04-12 12:03:51 +02:00
auto button = m_data->keyButtons[ row * ColumnCount + col ];
2017-07-21 18:21:34 +02:00
2018-04-06 17:30:24 +02:00
button->setVisible( key != Qt::Key( 0 ) );
2018-03-21 13:00:24 +01:00
2018-04-06 17:30:24 +02:00
if ( button->isVisible() )
{
const qreal keyWidth = baseKeyWidth * qskKeyStretch( key );
2018-03-21 13:00:24 +01:00
2018-04-06 17:30:24 +02:00
const QRectF rect( xPos, yPos, keyWidth, keyHeight );
2018-03-21 13:00:24 +01:00
2018-04-06 18:07:12 +02:00
button->setGeometry( rect );
2018-04-06 17:30:24 +02:00
button->setAutoRepeat( qskIsAutorepeat( key ) );
button->setText( qskTextForKey( key ) );
2018-03-21 13:00:24 +01:00
2018-04-06 18:07:12 +02:00
xPos += keyWidth + spacing;
2018-04-06 17:30:24 +02:00
}
2018-03-21 13:00:24 +01:00
}
2018-04-06 18:07:12 +02:00
yPos += keyHeight + spacing;
}
2018-03-14 17:30:37 +01:00
}
2018-04-06 17:30:24 +02:00
void QskVirtualKeyboard::buttonPressed()
2017-07-21 18:21:34 +02:00
{
2018-04-06 17:30:24 +02:00
const auto button = static_cast< const Button* >( sender() );
if ( button == nullptr )
return;
2018-04-01 12:47:44 +02:00
2018-04-06 17:30:24 +02:00
const auto& keyCodes = ( *m_data->currentLayout )[ m_data->mode ];
2018-04-13 16:32:48 +02:00
const int key = keyCodes.data[ button->row() ][ button->column() ];
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:
2018-04-06 09:00:09 +02:00
{
2017-07-21 18:21:34 +02:00
setMode( UppercaseMode ); // Lock caps
2018-04-06 09:00:09 +02:00
break;
}
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:
2018-04-06 09:00:09 +02:00
{
2017-07-21 18:21:34 +02:00
setMode( LowercaseMode ); // Unlock caps
2018-04-06 09:00:09 +02:00
break;
}
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
2018-04-06 09:00:09 +02:00
{
setMode( static_cast< QskVirtualKeyboard::Mode >(
2018-04-01 12:47:44 +02:00
m_data->mode ? ( ( m_data->mode + 1 ) % QskVirtualKeyboard::ModeCount )
: SpecialCharacterMode ) );
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
break;
2018-04-06 09:00:09 +02:00
}
default:
{
Q_EMIT keySelected( key );
2018-04-06 09:00:09 +02:00
}
}
}
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
}
setMode( LowercaseMode );
2018-04-06 17:30:24 +02:00
polish();
2017-07-21 18:21:34 +02:00
}
void QskVirtualKeyboard::setMode( QskVirtualKeyboard::Mode mode )
2017-07-21 18:21:34 +02:00
{
m_data->mode = mode;
2018-04-06 09:00:09 +02:00
polish();
2018-03-14 17:30:37 +01:00
Q_EMIT modeChanged( m_data->mode );
2017-07-21 18:21:34 +02:00
}
#include "moc_QskVirtualKeyboard.cpp"