qskinny/src/controls/QskInputPanel.cpp
2018-01-03 09:24:23 +01:00

967 lines
25 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/******************************************************************************
* Qsk Controls
* Copyright (C) 2016 Uwe Rathmann
*
* This file may be used under the terms of the 3-clause BSD License
*****************************************************************************/
#include "QskInputPanel.h"
#include "QskAspect.h"
#include <QskDialog.h>
#include <QFocusEvent>
#include <QGuiApplication>
#include <QStyleHints>
#include <cmath>
#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
{
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] ) );
}
};
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 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 =
{
#include "QskInputPanelLayouts.cpp"
};
#undef LOWER
QSK_DECLARE_OPERATORS_FOR_FLAGS( Qt::Key ) // Must appear after the LOWER macro
static const Qt::Key KeyPressed = static_cast< Qt::Key >( Qt::ShiftModifier );
static const Qt::Key KeyLocked = static_cast< Qt::Key >( Qt::ControlModifier );
static const Qt::Key KeyFocused = static_cast< Qt::Key >( Qt::MetaModifier );
static const Qt::Key KeyStates = static_cast< Qt::Key >( Qt::KeyboardModifierMask );
static qreal qskKeyStretch( Qt::Key key )
{
switch ( key )
{
case Qt::Key_Backspace:
case Qt::Key_Shift:
case Qt::Key_CapsLock:
return 1.5;
case Qt::Key_Space:
return 3.5;
case Qt::Key_Return:
case Qt::Key_Mode_switch:
// Possibly smaller
default:
break;
}
return 1.0;
}
static qreal qskRowStretch( const QskInputPanel::KeyRow& keyRow )
{
qreal stretch = 0;
for ( const auto& key : keyRow )
{
if ( !key )
continue;
stretch += qskKeyStretch( key );
}
if ( stretch == 0.0 )
stretch = QskInputPanel::KeyCount;
return stretch;
}
namespace
{
struct KeyCounter
{
int keyIndex;
int count;
};
}
class QskInputPanel::PrivateData
{
public:
PrivateData():
currentLayout( nullptr ),
mode( QskInputPanel::LowercaseMode ),
focusKeyIndex( -1 ),
selectedGroup( -1 ),
candidateOffset( 0 ),
repeatKeyTimerId( -1 )
{
}
public:
const QskInputPanelLayouts::Layout* currentLayout;
QskInputPanel::Mode mode;
qint16 focusKeyIndex;
qint16 selectedGroup;
qint32 candidateOffset;
int repeatKeyTimerId;
QLocale locale;
QVector< Qt::Key > groups;
QVector< Qt::Key > candidates;
std::unordered_map< int, KeyCounter > activeKeys;
KeyTable keyTable[ ModeCount ];
};
QskInputPanel::QskInputPanel( QQuickItem* parent ):
QskControl( parent ),
m_data( new PrivateData )
{
setFlag( ItemHasContents );
setAcceptedMouseButtons( Qt::LeftButton );
initSizePolicy( QskSizePolicy::Expanding, QskSizePolicy::Expanding );
updateLocale( locale() );
QObject::connect( this, &QskControl::localeChanged,
this, &QskInputPanel::updateLocale );
}
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
switch ( key )
{
case Qt::Key_Backspace:
case Qt::Key_Muhenkan:
return QChar( 0x232B );
case Qt::Key_CapsLock:
case Qt::Key_Kana_Lock:
return QChar( 0x21E7 );
case Qt::Key_Shift:
case Qt::Key_Kana_Shift:
return QChar( 0x2B06 );
case Qt::Key_Mode_switch:
return QChar( 0x2026 );
case Qt::Key_Return:
case Qt::Key_Kanji:
return QChar( 0x23CE );
case Qt::Key_Left:
return QChar( 0x2190 );
case Qt::Key_Right:
return QChar( 0x2192 );
case Qt::Key_ApplicationLeft:
return QChar( 0x2B05 );
case Qt::Key_ApplicationRight:
return QChar( 0x27A1 );
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();
switch ( locale.language() )
{
case QLocale::Bulgarian:
return QStringLiteral( "български език" );
case QLocale::Czech:
return QStringLiteral( "Čeština" );
case QLocale::German:
return QStringLiteral( "Deutsch" );
case QLocale::Danish:
return QStringLiteral( "Dansk" );
case QLocale::Greek:
return QStringLiteral( "Eλληνικά" );
case QLocale::English:
{
switch ( locale.country() )
{
case QLocale::Canada:
case QLocale::UnitedStates:
case QLocale::UnitedStatesMinorOutlyingIslands:
case QLocale::UnitedStatesVirginIslands:
return QStringLiteral( "English (US)" );
default:
return QStringLiteral( "English (UK)" );
}
}
case QLocale::Spanish:
return QStringLiteral( "Español" );
case QLocale::Finnish:
return QStringLiteral( "Suomi" );
case QLocale::French:
return QStringLiteral( "Français" );
case QLocale::Hungarian:
return QStringLiteral( "Magyar" );
case QLocale::Italian:
return QStringLiteral( "Italiano" );
case QLocale::Japanese:
return QStringLiteral( "日本語" );
case QLocale::Latvian:
return QStringLiteral( "Latviešu" );
case QLocale::Lithuanian:
return QStringLiteral( "Lietuvių" );
case QLocale::Dutch:
return QStringLiteral( "Nederlands" );
case QLocale::Portuguese:
return QStringLiteral( "Português" );
case QLocale::Romanian:
return QStringLiteral( "Română" );
case QLocale::Russia:
return QStringLiteral( "Русский" );
case QLocale::Slovenian:
return QStringLiteral( "Slovenščina" );
case QLocale::Slovak:
return QStringLiteral( "Slovenčina" );
case QLocale::Turkish:
return QStringLiteral( "Türkçe" );
case QLocale::Chinese:
return QStringLiteral( "中文" );
default:
break;
}
return QLocale::languageToString( locale.language() );
}
void QskInputPanel::setPreeditGroups( const QVector< Qt::Key >& groups )
{
auto& topRow = m_data->keyTable[ LowercaseMode ].data[ 0 ];
for ( const auto& group : groups )
{
auto& keyData = topRow[ &group - groups.data() ];
keyData.key = group;
}
m_data->groups = groups;
selectGroup( -1 );
if ( m_data->mode == LowercaseMode )
update();
}
void QskInputPanel::setPreeditCandidates( const QVector< Qt::Key >& candidates )
{
if ( m_data->candidates == candidates )
return;
m_data->candidates = candidates;
setCandidateOffset( 0 );
}
bool QskInputPanel::advanceFocus( bool forward )
{
deactivateFocusKey();
auto offset = forward ? 1 : -1;
auto focusKeyIndex = m_data->focusKeyIndex;
Q_FOREVER
{
focusKeyIndex += offset;
if ( focusKeyIndex < 0 || focusKeyIndex >= RowCount * KeyCount )
{
clearFocusKey();
return false;
}
const auto key = keyDataAt( focusKeyIndex ).key;
if ( key && key != Qt::Key_unknown )
break;
}
if ( m_data->focusKeyIndex >= 0 )
keyDataAt( m_data->focusKeyIndex ).key &= ~KeyFocused;
m_data->focusKeyIndex = focusKeyIndex;
keyDataAt( m_data->focusKeyIndex ).key |= KeyFocused;
update();
return true;
}
bool QskInputPanel::activateFocusKey()
{
if ( m_data->focusKeyIndex > 0 && m_data->focusKeyIndex < RowCount * KeyCount )
{
auto& keyData = keyDataAt( m_data->focusKeyIndex );
if ( keyData.key & KeyPressed )
handleKey( m_data->focusKeyIndex );
else
keyData.key |= KeyPressed;
update();
return true;
}
return false;
}
bool QskInputPanel::deactivateFocusKey()
{
if ( m_data->focusKeyIndex > 0 && m_data->focusKeyIndex < RowCount * KeyCount )
{
auto& keyData = keyDataAt( m_data->focusKeyIndex );
if ( keyData.key & KeyPressed )
{
keyData.key &= ~KeyPressed;
handleKey( m_data->focusKeyIndex );
}
update();
return true;
}
return true;
}
void QskInputPanel::clearFocusKey()
{
if ( m_data->focusKeyIndex > 0 && m_data->focusKeyIndex < RowCount * KeyCount )
{
keyDataAt( m_data->focusKeyIndex ).key &= ~KeyFocused;
update();
}
m_data->focusKeyIndex = -1;
}
void QskInputPanel::setCandidateOffset( int candidateOffset )
{
m_data->candidateOffset = candidateOffset;
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;
for ( int i = 0; i < count; ++i )
{
auto& keyData = topRow[ i + groupCount ];
if ( continueLeft && i == 0 )
keyData.key = Qt::Key_ApplicationLeft;
else if ( continueRight && ( i == KeyCount - groupCount - 1 ) )
keyData.key = Qt::Key_ApplicationRight;
else
keyData.key = m_data->candidates.at( i + m_data->candidateOffset );
}
for ( int i = count; i < KeyCount - groupCount; ++i )
{
auto& keyData = topRow[ i + groupCount ];
keyData.key = Qt::Key_unknown;
}
if ( m_data->mode == LowercaseMode )
update();
}
QRectF QskInputPanel::keyboardRect() const
{
auto keyboardRect = boundingRect(); // ### margins? would eliminate this thing below
if ( QskDialog::instance()->policy() != QskDialog::TopLevelWindow )
keyboardRect.adjust( 0, keyboardRect.height() * 0.5, 0, 0 );
return keyboardRect;
}
void QskInputPanel::geometryChanged(
const QRectF& newGeometry, const QRectF& oldGeometry )
{
Inherited::geometryChanged( newGeometry, oldGeometry );
Q_EMIT keyboardRectChanged();
}
void QskInputPanel::mousePressEvent( QMouseEvent* e )
{
if ( !keyboardRect().contains( e->pos() ) )
{
e->ignore();
return;
}
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 )
{
QTouchEvent::TouchPoint touchPoint( 0 );
touchPoint.setPos( e->pos() );
touchPoint.setState( Qt::TouchPointMoved );
QTouchEvent touchEvent( QTouchEvent::TouchUpdate, nullptr,
e->modifiers(), Qt::TouchPointMoved, { touchPoint } );
QCoreApplication::sendEvent( this, &touchEvent );
e->setAccepted( touchEvent.isAccepted() );
}
void QskInputPanel::mouseReleaseEvent( QMouseEvent* e )
{
QTouchEvent::TouchPoint touchPoint( 0 );
touchPoint.setPos( e->pos() );
touchPoint.setState( Qt::TouchPointReleased );
QTouchEvent touchEvent( QTouchEvent::TouchEnd, nullptr,
e->modifiers(), Qt::TouchPointReleased, { touchPoint } );
QCoreApplication::sendEvent( this, &touchEvent );
e->setAccepted( touchEvent.isAccepted() );
}
// Try to handle touch-specific details here; once touch is resolved, send to handleKey()
void QskInputPanel::touchEvent( QTouchEvent* e )
{
if ( e->type() == QEvent::TouchCancel )
{
for ( auto& it : m_data->activeKeys )
keyDataAt( it.second.keyIndex ).key &= ~KeyPressed;
m_data->activeKeys.clear();
return;
}
const auto rect = keyboardRect();
for ( const auto& tp : e->touchPoints() )
{
const auto pos = tp.pos();
const auto x = ( pos.x() - rect.x() ) / rect.width();
const auto y = ( pos.y() - rect.y() ) / rect.height();
auto keyData = qskKeyDataAt( m_data->keyTable[ m_data->mode ], x, y );
if ( !keyData || ( !keyData->key || keyData->key == Qt::Key_unknown ) )
{
auto it = m_data->activeKeys.find( tp.id() );
if ( it == m_data->activeKeys.cend() )
continue;
keyDataAt( it->second.keyIndex ).key &= ~KeyPressed;
m_data->activeKeys.erase( it );
continue;
}
const auto keyIndex = m_data->keyTable[ m_data->mode ].indexOf( keyData );
auto it = m_data->activeKeys.find( tp.id() );
if ( tp.state() == Qt::TouchPointReleased )
{
const int repeatCount = it->second.count;
auto it = m_data->activeKeys.find( tp.id() );
keyDataAt( it->second.keyIndex ).key &= ~KeyPressed;
m_data->activeKeys.erase( it );
if ( repeatCount < 0 )
continue; // Don't compose an accepted held key
handleKey( keyIndex );
continue;
}
if ( it == m_data->activeKeys.end() )
{
m_data->activeKeys.emplace( tp.id(), KeyCounter { keyIndex, 0 } );
}
else
{
if ( it->second.keyIndex != keyIndex )
{
keyDataAt( it->second.keyIndex ).key &= ~KeyPressed;
it->second.count = 0;
}
it->second.keyIndex = keyIndex;
}
keyDataAt( keyIndex ).key |= KeyPressed;
}
// Now start an update timer based on active keys
if ( m_data->activeKeys.empty() && m_data->repeatKeyTimerId >= 0 )
{
killTimer( m_data->repeatKeyTimerId );
m_data->repeatKeyTimerId = -1;
}
else if ( m_data->repeatKeyTimerId < 0 )
{
m_data->repeatKeyTimerId = startTimer( 1000
/ QGuiApplication::styleHints()->keyboardAutoRepeatRate() );
} /* else timer is already running as it should be */
update();
}
void QskInputPanel::timerEvent( QTimerEvent* e )
{
if ( e->timerId() == m_data->repeatKeyTimerId )
{
for ( auto it = m_data->activeKeys.begin(); it != m_data->activeKeys.end(); )
{
if ( it->second.count >= 0 && it->second.count++ > 20 ) // ### use platform long-press hint
{
const auto key = keyDataAt( it->second.keyIndex ).key & ~KeyStates;
if ( !key || key == Qt::Key_unknown )
{
it = m_data->activeKeys.erase( it );
continue;
}
if ( key == Qt::Key_ApplicationLeft || key == Qt::Key_ApplicationRight )
{
setCandidateOffset( m_data->candidateOffset
+ ( key == Qt::Key_ApplicationLeft ? -1 : 1 ) );
}
else if ( !( key & KeyLocked ) ) // do not repeat locked keys
{
// long press events could be emitted from here
compose( key & ~KeyStates );
}
}
++it;
}
}
}
QskInputPanel::KeyData& QskInputPanel::keyDataAt( int keyIndex )
{
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 )
{
const auto key = keyDataAt( keyIndex ).key & ~KeyStates;
// Preedit keys
const auto row = keyIndex / KeyCount;
const auto column = keyIndex % KeyCount;
if ( m_data->mode == LowercaseMode && !m_data->groups.isEmpty() && row == 0 )
{
if ( key == Qt::Key_ApplicationLeft
|| key == Qt::Key_ApplicationRight )
{
setCandidateOffset( m_data->candidateOffset
+ ( key == Qt::Key_ApplicationLeft ? -1 : 1 ) );
return;
}
const auto groupCount = m_data->groups.length();
if ( column < groupCount )
selectGroup( column );
else if ( column < KeyCount )
selectCandidate( column - groupCount + m_data->candidateOffset );
else
Q_UNREACHABLE(); // Handle the final key...
return;
}
// Mode-switching keys
switch ( key )
{
case Qt::Key_CapsLock:
case Qt::Key_Kana_Lock:
setMode( UppercaseMode ); // Lock caps
return;
case Qt::Key_Shift:
case Qt::Key_Kana_Shift:
setMode( LowercaseMode ); // Unlock caps
return;
case Qt::Key_Mode_switch: // Cycle through modes, but skip caps
setMode( static_cast< QskInputPanel::Mode >(
m_data->mode ? ( ( m_data->mode + 1 ) % QskInputPanel::ModeCount )
: SpecialCharacterMode ) );
return;
default:
break;
}
// Normal keys
compose( key );
}
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 ];
if ( m_data->selectedGroup >= 0 )
topRow[ m_data->selectedGroup ].key &= ~KeyLocked;
if ( m_data->selectedGroup == index )
index = -1; // clear selection
m_data->selectedGroup = index;
QGuiApplication::inputMethod()->invokeAction(
static_cast< QInputMethod::Action >( SelectGroup ), m_data->selectedGroup + 1 );
if ( m_data->selectedGroup < 0 )
return;
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 )
{
switch ( locale.language() )
{
case QLocale::Bulgarian:
m_data->currentLayout = &qskInputPanelLayouts.bg;
break;
case QLocale::Czech:
m_data->currentLayout = &qskInputPanelLayouts.cs;
break;
case QLocale::German:
m_data->currentLayout = &qskInputPanelLayouts.de;
break;
case QLocale::Danish:
m_data->currentLayout = &qskInputPanelLayouts.da;
break;
case QLocale::Greek:
m_data->currentLayout = &qskInputPanelLayouts.el;
break;
case QLocale::English:
{
switch ( locale.country() )
{
case QLocale::Canada:
case QLocale::UnitedStates:
case QLocale::UnitedStatesMinorOutlyingIslands:
case QLocale::UnitedStatesVirginIslands:
m_data->currentLayout = &qskInputPanelLayouts.en_US;
break;
default:
m_data->currentLayout = &qskInputPanelLayouts.en_GB;
break;
}
break;
}
case QLocale::Spanish:
m_data->currentLayout = &qskInputPanelLayouts.es;
break;
case QLocale::Finnish:
m_data->currentLayout = &qskInputPanelLayouts.fi;
break;
case QLocale::French:
m_data->currentLayout = &qskInputPanelLayouts.fr;
break;
case QLocale::Hungarian:
m_data->currentLayout = &qskInputPanelLayouts.hu;
break;
case QLocale::Italian:
m_data->currentLayout = &qskInputPanelLayouts.it;
break;
case QLocale::Japanese:
m_data->currentLayout = &qskInputPanelLayouts.ja;
break;
case QLocale::Latvian:
m_data->currentLayout = &qskInputPanelLayouts.lv;
break;
case QLocale::Lithuanian:
m_data->currentLayout = &qskInputPanelLayouts.lt;
break;
case QLocale::Dutch:
m_data->currentLayout = &qskInputPanelLayouts.nl;
break;
case QLocale::Portuguese:
m_data->currentLayout = &qskInputPanelLayouts.pt;
break;
case QLocale::Romanian:
m_data->currentLayout = &qskInputPanelLayouts.ro;
break;
case QLocale::Russia:
m_data->currentLayout = &qskInputPanelLayouts.ru;
break;
case QLocale::Slovenian:
m_data->currentLayout = &qskInputPanelLayouts.sl;
break;
case QLocale::Slovak:
m_data->currentLayout = &qskInputPanelLayouts.sk;
break;
case QLocale::Turkish:
m_data->currentLayout = &qskInputPanelLayouts.tr;
break;
case QLocale::Chinese:
m_data->currentLayout = &qskInputPanelLayouts.zh;
break;
case QLocale::C:
m_data->currentLayout = &qskInputPanelLayouts.en_US;
break;
default:
qWarning() << "QskInputPanel: unsupported locale:" << locale;
m_data->currentLayout = &qskInputPanelLayouts.en_US;
}
Q_EMIT displayLanguageNameChanged();
updateKeyData();
setMode( LowercaseMode );
}
void QskInputPanel::updateKeyData()
{
// Key data is in normalized coordinates
const auto keyHeight = 1.0f / RowCount;
for ( const auto& keyLayout : *m_data->currentLayout )
{
auto& keyDataLayout = m_data->keyTable[ &keyLayout - *m_data->currentLayout ];
qreal yPos = 0;
for ( int i = 0; i < RowCount; i++ )
{
auto& row = keyLayout.data[i];
auto& keyDataRow = keyDataLayout.data[ i ];
const auto baseKeyWidth = 1.0 / qskRowStretch( row );
qreal xPos = 0;
qreal keyWidth = baseKeyWidth;
for ( const auto& key : row )
{
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;
update();
}
#include "QskInputPanel.moc"
#include "moc_QskInputPanel.cpp"