qskinny/src/controls/QskInputPanel.cpp

991 lines
25 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 "QskInputPanel.h"
#include "QskAspect.h"
2018-03-14 17:30:37 +01:00
#include <QskPushButton.h>
#include <QskTextLabel.h>
2017-07-21 18:21:34 +02:00
#include <QskDialog.h>
#include <QFocusEvent>
#include <QGuiApplication>
#include <QStyleHints>
2018-03-14 17:30:37 +01:00
#include <QskLinearBox.h>
#include <QTimer>
2017-07-21 18:21:34 +02:00
#include <cmath>
#include <unordered_map>
namespace
{
struct KeyTable
{
using Row = QskInputPanel::KeyData[ QskInputPanel::KeyCount ];
Row data[ QskInputPanel::RowCount ];
int indexOf( const QskInputPanel::KeyData* value ) const
{
return int( intptr_t( value - data[0] ) );
}
};
}
struct QskInputPanelLayouts
{
struct KeyCodes
{
using Row = Qt::Key[ QskInputPanel::KeyCount ];
Row data[ QskInputPanel::RowCount ];
};
using Layout = KeyCodes[ QskInputPanel::ModeCount ];
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 QskInputPanelLayouts qskInputPanelLayouts =
{
2018-03-14 17:30:35 +01:00
#include "QskInputPanelLayouts.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 Qt::Key KeyPressed = static_cast< Qt::Key >( Qt::ShiftModifier );
static const Qt::Key KeyLocked = static_cast< Qt::Key >( Qt::ControlModifier );
static const Qt::Key KeyFocused = static_cast< Qt::Key >( Qt::MetaModifier );
static const Qt::Key KeyStates = static_cast< Qt::Key >( Qt::KeyboardModifierMask );
static 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 QskInputPanel::KeyRow& keyRow )
{
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 )
{
2017-12-08 13:56:35 +01:00
stretch = QskInputPanel::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;
}
namespace
2017-07-21 18:21:34 +02:00
{
struct KeyCounter
{
int keyIndex;
int count;
};
}
2017-07-21 18:21:34 +02:00
2018-03-14 17:30:37 +01:00
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 )
{
updateText();
connect( this, &QskKeyButton::pressed, this, [ this ]()
{
m_inputPanel->handleKey( m_keyIndex );
} );
connect( m_inputPanel, &QskInputPanel::modeChanged, this, &QskKeyButton::updateText );
}
QskAspect::Subcontrol QskKeyButton::effectiveSubcontrol( QskAspect::Subcontrol subControl ) const
{
if( subControl == QskPushButton::Panel )
{
return QskKeyButton::Panel;
}
if( subControl == QskPushButton::Text )
{
// ### we could also introduce a state to not always query the button
return isCancelButton() ? QskKeyButton::TextCancelButton : QskKeyButton::Text;
}
return subControl;
}
void QskKeyButton::updateText()
{
QString text = m_inputPanel->currentTextForKeyIndex( m_keyIndex );
if( text.count() == 1 && text.at( 0 ) == Qt::Key_unknown )
{
setText( QStringLiteral( "" ) );
}
else
{
setText( text );
}
}
bool QskKeyButton::isCancelButton() const
{
auto keyData = m_inputPanel->keyDataAt( m_keyIndex );
bool isCancel = ( keyData.key == 0x2716 );
return isCancel;
}
2017-07-21 18:21:34 +02:00
class QskInputPanel::PrivateData
{
2018-03-14 17:30:35 +01:00
public:
PrivateData():
currentLayout( nullptr ),
mode( QskInputPanel::LowercaseMode ),
focusKeyIndex( -1 ),
selectedGroup( -1 ),
candidateOffset( 0 ),
2018-03-14 17:30:37 +01:00
repeatKeyTimerId( -1 ),
isUIInitialized( false )
2018-03-14 17:30:35 +01:00
{
}
2017-07-21 18:21:34 +02:00
2018-03-14 17:30:35 +01:00
public:
const QskInputPanelLayouts::Layout* currentLayout;
QskInputPanel::Mode mode;
2017-07-21 18:21:34 +02:00
2018-03-14 17:30:35 +01:00
qint16 focusKeyIndex;
qint16 selectedGroup;
qint32 candidateOffset;
2017-07-21 18:21:34 +02:00
2018-03-14 17:30:35 +01:00
int repeatKeyTimerId;
2017-07-21 18:21:34 +02:00
2018-03-14 17:30:35 +01:00
QLocale locale;
2017-07-21 18:21:34 +02:00
2018-03-14 17:30:35 +01:00
QVector< Qt::Key > groups;
QVector< Qt::Key > candidates;
2017-07-21 18:21:34 +02:00
2018-03-14 17:30:35 +01:00
std::unordered_map< int, KeyCounter > activeKeys;
KeyTable keyTable[ ModeCount ];
2018-03-14 17:30:37 +01:00
QList< QskKeyButton* > keyButtons;
bool isUIInitialized;
2017-07-21 18:21:34 +02:00
};
QskInputPanel::QskInputPanel( QQuickItem* parent ):
QskControl( parent ),
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() );
QObject::connect( this, &QskControl::localeChanged,
2018-03-14 17:30:35 +01:00
this, &QskInputPanel::updateLocale );
2018-03-14 17:30:39 +01:00
setFlag( ItemIsFocusScope, true );
setTabFence( true );
2017-07-21 18:21:34 +02:00
}
QskInputPanel::~QskInputPanel()
{
}
QskInputPanel::Mode QskInputPanel::mode() const
{
return m_data->mode;
}
const QskInputPanel::KeyDataSet& QskInputPanel::keyData( Mode mode ) const
{
mode = mode == CurrentMode ? m_data->mode : mode;
Q_ASSERT( mode >= 0 && mode < ModeCount );
return m_data->keyTable[ mode ].data;
}
QString QskInputPanel::textForKey( Qt::Key key ) const
{
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 QskInputPanel::displayLanguageName() const
{
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-03-14 17:30:35 +01:00
switch( locale.country() )
{
case QLocale::Canada:
case QLocale::UnitedStates:
case QLocale::UnitedStatesMinorOutlyingIslands:
case QLocale::UnitedStatesVirginIslands:
return QStringLiteral( "English (US)" );
default:
return QStringLiteral( "English (UK)" );
}
2018-03-14 17:30:37 +01:00
break;
2017-07-21 18:21:34 +02:00
}
2017-10-17 19:02:02 +02:00
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 QskInputPanel::setPreeditGroups( const QVector< Qt::Key >& groups )
{
auto& topRow = m_data->keyTable[ LowercaseMode ].data[ 0 ];
2017-10-17 19:02:02 +02:00
2018-03-14 17:30:35 +01:00
for( const auto& group : groups )
2017-07-21 18:21:34 +02:00
{
auto& keyData = topRow[ &group - groups.data() ];
keyData.key = group;
}
m_data->groups = groups;
selectGroup( -1 );
2018-03-14 17:30:35 +01:00
if( m_data->mode == LowercaseMode )
{
2017-07-21 18:21:34 +02:00
update();
2018-03-14 17:30:35 +01:00
}
2017-07-21 18:21:34 +02:00
}
void QskInputPanel::setPreeditCandidates( const QVector< Qt::Key >& candidates )
{
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 );
}
bool QskInputPanel::advanceFocus( bool forward )
{
deactivateFocusKey();
auto offset = forward ? 1 : -1;
auto focusKeyIndex = m_data->focusKeyIndex;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
Q_FOREVER
{
focusKeyIndex += offset;
2018-03-14 17:30:35 +01:00
if( focusKeyIndex < 0 || focusKeyIndex >= RowCount * KeyCount )
2017-07-21 18:21:34 +02:00
{
clearFocusKey();
return false;
}
const auto key = keyDataAt( focusKeyIndex ).key;
2018-03-14 17:30:35 +01:00
if( key && key != Qt::Key_unknown )
{
2017-07-21 18:21:34 +02:00
break;
2018-03-14 17:30:35 +01:00
}
2017-07-21 18:21:34 +02:00
}
2018-03-14 17:30:35 +01:00
if( m_data->focusKeyIndex >= 0 )
{
2017-07-21 18:21:34 +02:00
keyDataAt( m_data->focusKeyIndex ).key &= ~KeyFocused;
2018-03-14 17:30:35 +01:00
}
2017-07-21 18:21:34 +02:00
m_data->focusKeyIndex = focusKeyIndex;
keyDataAt( m_data->focusKeyIndex ).key |= KeyFocused;
update();
return true;
}
bool QskInputPanel::activateFocusKey()
{
2018-03-14 17:30:35 +01:00
if( m_data->focusKeyIndex > 0 && m_data->focusKeyIndex < RowCount * KeyCount )
2017-07-21 18:21:34 +02:00
{
auto& keyData = keyDataAt( m_data->focusKeyIndex );
2017-10-17 19:02:02 +02:00
2018-03-14 17:30:35 +01:00
if( keyData.key & KeyPressed )
{
2017-07-21 18:21:34 +02:00
handleKey( m_data->focusKeyIndex );
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
{
2017-07-21 18:21:34 +02:00
keyData.key |= KeyPressed;
2018-03-14 17:30:35 +01:00
}
2017-07-21 18:21:34 +02:00
update();
return true;
}
return false;
}
bool QskInputPanel::deactivateFocusKey()
{
2018-03-14 17:30:35 +01:00
if( m_data->focusKeyIndex > 0 && m_data->focusKeyIndex < RowCount * KeyCount )
2017-07-21 18:21:34 +02:00
{
auto& keyData = keyDataAt( m_data->focusKeyIndex );
2018-03-14 17:30:35 +01:00
if( keyData.key & KeyPressed )
2017-07-21 18:21:34 +02:00
{
keyData.key &= ~KeyPressed;
handleKey( m_data->focusKeyIndex );
}
update();
return true;
}
return true;
}
void QskInputPanel::clearFocusKey()
{
2018-03-14 17:30:35 +01:00
if( m_data->focusKeyIndex > 0 && m_data->focusKeyIndex < RowCount * KeyCount )
2017-07-21 18:21:34 +02:00
{
keyDataAt( m_data->focusKeyIndex ).key &= ~KeyFocused;
update();
}
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
m_data->focusKeyIndex = -1;
}
void QskInputPanel::setCandidateOffset( int candidateOffset )
{
m_data->candidateOffset = candidateOffset;
auto& topRow = m_data->keyTable[ LowercaseMode ].data[ 0 ];
const auto groupCount = m_data->groups.length();
const auto candidateCount = m_data->candidates.length();
const auto count = std::min( candidateCount, KeyCount - groupCount );
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
{
auto& keyData = topRow[ i + groupCount ];
2017-10-17 19:02:02 +02:00
2018-03-14 17:30:35 +01:00
if( continueLeft && i == 0 )
{
2017-07-21 18:21:34 +02:00
keyData.key = Qt::Key_ApplicationLeft;
2018-03-14 17:30:35 +01:00
}
else if( continueRight && ( i == KeyCount - groupCount - 1 ) )
{
2017-07-21 18:21:34 +02:00
keyData.key = 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-03-14 17:30:37 +01:00
keyData.isSuggestionKey = true; // ### reset when switching layouts etc.!
2017-07-21 18:21:34 +02:00
keyData.key = m_data->candidates.at( i + m_data->candidateOffset );
2018-03-14 17:30:35 +01:00
}
2017-07-21 18:21:34 +02:00
}
2018-03-14 17:30:35 +01:00
for( int i = count; i < KeyCount - groupCount; ++i )
2017-07-21 18:21:34 +02:00
{
auto& keyData = topRow[ i + groupCount ];
keyData.key = Qt::Key_unknown;
}
2018-03-14 17:30:35 +01:00
if( m_data->mode == LowercaseMode )
{
2018-03-14 17:30:37 +01:00
updateUI();
2018-03-14 17:30:35 +01:00
}
2017-07-21 18:21:34 +02:00
}
QRectF QskInputPanel::keyboardRect() const
{
auto keyboardRect = rect(); // ### margins? would eliminate this thing below
2018-03-14 17:30:37 +01:00
// if ( QskDialog::instance()->policy() != QskDialog::TopLevelWindow )
// keyboardRect.adjust( 0, keyboardRect.height() * 0.5, 0, 0 );
2017-07-21 18:21:34 +02:00
return keyboardRect;
}
2018-03-14 17:30:37 +01:00
void QskInputPanel::registerCompositionModelForLocale( const QLocale& locale,
QskInputCompositionModel* model )
2017-07-21 18:21:34 +02:00
{
2018-03-14 17:30:37 +01:00
Q_EMIT inputMethodRegistered( locale, model );
2017-07-21 18:21:34 +02:00
}
2018-03-14 17:30:37 +01:00
void QskInputPanel::geometryChanged(
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 );
2017-07-21 18:21:34 +02:00
2018-03-14 17:30:37 +01:00
if( newGeometry != oldGeometry && !newGeometry.size().isNull() && !m_data->isUIInitialized )
2017-07-21 18:21:34 +02:00
{
2018-03-14 17:30:37 +01:00
createUI();
m_data->isUIInitialized = true;
2017-07-21 18:21:34 +02:00
}
2018-03-14 17:30:37 +01:00
Q_EMIT keyboardRectChanged();
2017-07-21 18:21:34 +02:00
}
void QskInputPanel::timerEvent( QTimerEvent* e )
{
2018-03-14 17:30:35 +01:00
if( e->timerId() == m_data->repeatKeyTimerId )
2017-07-21 18:21:34 +02:00
{
2018-03-14 17:30:35 +01:00
for( auto it = m_data->activeKeys.begin(); it != m_data->activeKeys.end(); )
2017-07-21 18:21:34 +02:00
{
2018-03-14 17:30:35 +01:00
if( it->second.count >= 0 && it->second.count++ > 20 ) // ### use platform long-press hint
2017-07-21 18:21:34 +02:00
{
const auto key = keyDataAt( it->second.keyIndex ).key & ~KeyStates;
2018-03-14 17:30:35 +01:00
if( !key || key == Qt::Key_unknown )
2017-07-21 18:21:34 +02:00
{
it = m_data->activeKeys.erase( it );
continue;
}
2018-03-14 17:30:35 +01:00
if( key == Qt::Key_ApplicationLeft || key == Qt::Key_ApplicationRight )
2017-07-21 18:21:34 +02:00
{
setCandidateOffset( m_data->candidateOffset
2018-03-14 17:30:35 +01:00
+ ( key == Qt::Key_ApplicationLeft ? -1 : 1 ) );
2017-07-21 18:21:34 +02:00
}
2018-03-14 17:30:35 +01:00
else if( !( key & KeyLocked ) ) // do not repeat locked keys
2017-07-21 18:21:34 +02:00
{
// long press events could be emitted from here
compose( key & ~KeyStates );
}
}
2018-03-14 17:30:35 +01:00
2017-07-21 18:21:34 +02:00
++it;
}
}
}
2018-03-14 17:30:37 +01:00
bool QskInputPanel::eventFilter( QObject* object, QEvent* event )
{
if( event->type() == QEvent::InputMethod )
{
QInputMethodEvent* inputMethodEvent = static_cast< QInputMethodEvent* >( event );
Q_EMIT inputMethodEventReceived( inputMethodEvent );
return true;
}
else if( event->type() == QEvent::KeyPress )
{
// Return, Backspace and others are covered here (maybe because they don't carry a 'commit string'):
QKeyEvent* keyEvent = static_cast< QKeyEvent* >( event );
Q_EMIT keyEventReceived( keyEvent );
return true;
}
else
{
return Inherited::eventFilter( object, event );
}
}
void QskInputPanel::createUI()
{
// deferring the UI creation until we are visible so that the contentsRect() returns the proper value
setAutoLayoutChildren( true );
auto& panelKeyData = keyData();
const auto contentsRect = keyboardRect();
const qreal sx = contentsRect.size().width();
const qreal sy = contentsRect.size().height();
QskLinearBox* outterBox = new QskLinearBox( Qt::Vertical, this );
outterBox->setAutoAddChildren( true );
for( const auto& keyRow : panelKeyData )
{
QskLinearBox* rowBox = new QskLinearBox( Qt::Horizontal, outterBox );
rowBox->setAutoAddChildren( true );
for( const auto& keyData : keyRow )
{
if( !keyData.key )
{
continue;
}
const QSizeF buttonSize( keyData.rect.width() * sx, keyData.rect.height() * sy );
int keyIndex = m_data->keyTable[ m_data->mode ].indexOf( &keyData );
QskKeyButton* button = new QskKeyButton( keyIndex, this, rowBox );
button->installEventFilter( this );
button->setMaximumWidth( buttonSize.width() ); // ### set min width as well?
button->setFixedHeight( buttonSize.height() );
m_data->keyButtons.append( button );
}
}
}
void QskInputPanel::updateUI()
{
for( QskKeyButton* button : m_data->keyButtons )
{
button->updateText();
}
}
2018-03-25 16:17:46 +02:00
QskInputPanel::KeyData& QskInputPanel::keyDataAt( int keyIndex ) const
2017-07-21 18:21:34 +02:00
{
const auto row = keyIndex / KeyCount;
const auto col = keyIndex % KeyCount;
return m_data->keyTable[ m_data->mode ].data[ row ][ col ];
}
void QskInputPanel::handleKey( int keyIndex )
{
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
// Preedit keys
const auto row = keyIndex / KeyCount;
const auto column = keyIndex % KeyCount;
2017-10-17 19:02:02 +02:00
2018-03-14 17:30:35 +01:00
if( m_data->mode == LowercaseMode && !m_data->groups.isEmpty() && row == 0 )
2017-07-21 18:21:34 +02:00
{
2018-03-14 17:30:35 +01:00
if( key == Qt::Key_ApplicationLeft
|| key == Qt::Key_ApplicationRight )
2017-07-21 18:21:34 +02:00
{
setCandidateOffset( m_data->candidateOffset
2018-03-14 17:30:35 +01:00
+ ( key == Qt::Key_ApplicationLeft ? -1 : 1 ) );
2017-07-21 18:21:34 +02:00
return;
}
const auto groupCount = m_data->groups.length();
2018-03-14 17:30:35 +01:00
if( column < groupCount )
{
2017-07-21 18:21:34 +02:00
selectGroup( column );
2018-03-14 17:30:35 +01:00
}
else if( column < KeyCount )
{
2017-07-21 18:21:34 +02:00
selectCandidate( column - groupCount + m_data->candidateOffset );
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
{
Q_UNREACHABLE(); // Handle the final key...
}
2017-07-21 18:21:34 +02:00
return;
}
// 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< QskInputPanel::Mode >(
2018-03-14 17:30:35 +01:00
m_data->mode ? ( ( m_data->mode + 1 ) % QskInputPanel::ModeCount )
: SpecialCharacterMode ) );
2017-07-21 18:21:34 +02:00
return;
2017-10-17 19:02:02 +02:00
2018-03-14 17:30:37 +01:00
// This is (one of) the cancel symbol, not Qt::Key_Cancel:
case Qt::Key( 10006 ):
Q_EMIT cancelPressed();
return;
2017-07-21 18:21:34 +02:00
default:
break;
}
2018-03-14 17:30:37 +01:00
if( keyData.isSuggestionKey )
{
selectCandidate( keyIndex );
}
else
{
compose( key );
}
}
QString QskInputPanel::currentTextForKeyIndex( int keyIndex ) const
{
auto keyData = keyDataAt( keyIndex );
QString text = textForKey( keyData.key );
return text;
2017-07-21 18:21:34 +02:00
}
void QskInputPanel::compose( Qt::Key key )
{
QGuiApplication::inputMethod()->invokeAction(
static_cast< QInputMethod::Action >( Compose ), key );
}
void QskInputPanel::selectGroup( int index )
{
auto& topRow = m_data->keyTable[ m_data->mode ].data[ 0 ];
2018-03-14 17:30:35 +01:00
if( m_data->selectedGroup >= 0 )
{
2017-07-21 18:21:34 +02:00
topRow[ m_data->selectedGroup ].key &= ~KeyLocked;
2018-03-14 17:30:35 +01:00
}
2017-07-21 18:21:34 +02:00
2018-03-14 17:30:35 +01:00
if( m_data->selectedGroup == index )
{
index = -1; // clear selection
}
2017-07-21 18:21:34 +02:00
m_data->selectedGroup = index;
QGuiApplication::inputMethod()->invokeAction(
static_cast< QInputMethod::Action >( SelectGroup ), m_data->selectedGroup + 1 );
2018-03-14 17:30:35 +01:00
if( m_data->selectedGroup < 0 )
{
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
topRow[ m_data->selectedGroup ].key |= KeyLocked;
}
void QskInputPanel::selectCandidate( int index )
{
QGuiApplication::inputMethod()->invokeAction(
static_cast< QInputMethod::Action >( SelectCandidate ), index );
}
void QskInputPanel::updateLocale( const QLocale& locale )
{
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 = &qskInputPanelLayouts.bg;
break;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Czech:
m_data->currentLayout = &qskInputPanelLayouts.cs;
break;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::German:
m_data->currentLayout = &qskInputPanelLayouts.de;
break;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Danish:
m_data->currentLayout = &qskInputPanelLayouts.da;
break;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Greek:
m_data->currentLayout = &qskInputPanelLayouts.el;
break;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::English:
{
2018-03-14 17:30:35 +01:00
switch( locale.country() )
{
case QLocale::Canada:
case QLocale::UnitedStates:
case QLocale::UnitedStatesMinorOutlyingIslands:
case QLocale::UnitedStatesVirginIslands:
m_data->currentLayout = &qskInputPanelLayouts.en_US;
break;
default:
m_data->currentLayout = &qskInputPanelLayouts.en_GB;
break;
}
break;
2017-07-21 18:21:34 +02:00
}
2018-03-14 17:30:35 +01:00
2017-07-21 18:21:34 +02:00
case QLocale::Spanish:
m_data->currentLayout = &qskInputPanelLayouts.es;
break;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Finnish:
m_data->currentLayout = &qskInputPanelLayouts.fi;
break;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::French:
m_data->currentLayout = &qskInputPanelLayouts.fr;
break;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Hungarian:
m_data->currentLayout = &qskInputPanelLayouts.hu;
break;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Italian:
m_data->currentLayout = &qskInputPanelLayouts.it;
break;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Japanese:
m_data->currentLayout = &qskInputPanelLayouts.ja;
break;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Latvian:
m_data->currentLayout = &qskInputPanelLayouts.lv;
break;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Lithuanian:
m_data->currentLayout = &qskInputPanelLayouts.lt;
break;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Dutch:
m_data->currentLayout = &qskInputPanelLayouts.nl;
break;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Portuguese:
m_data->currentLayout = &qskInputPanelLayouts.pt;
break;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Romanian:
m_data->currentLayout = &qskInputPanelLayouts.ro;
break;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Russia:
m_data->currentLayout = &qskInputPanelLayouts.ru;
break;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Slovenian:
m_data->currentLayout = &qskInputPanelLayouts.sl;
break;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Slovak:
m_data->currentLayout = &qskInputPanelLayouts.sk;
break;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Turkish:
m_data->currentLayout = &qskInputPanelLayouts.tr;
break;
2017-10-17 19:02:02 +02:00
2017-07-21 18:21:34 +02:00
case QLocale::Chinese:
m_data->currentLayout = &qskInputPanelLayouts.zh;
break;
2017-10-17 19:02:02 +02:00
2017-12-11 08:58:57 +01:00
default:
qWarning() << "QskInputPanel: unsupported locale:" << locale;
m_data->currentLayout = &qskInputPanelLayouts.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 QskInputPanel::updateKeyData()
{
// 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 QskInputPanel::setMode( QskInputPanel::Mode mode )
{
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
}
#include "QskInputPanel.moc"
#include "moc_QskInputPanel.cpp"