QSkinny 0.8.0
C++/Qt UI toolkit
Loading...
Searching...
No Matches
QskVirtualKeyboard.cpp
1/******************************************************************************
2 * QSkinny - Copyright (C) The authors
3 * SPDX-License-Identifier: BSD-3-Clause
4 *****************************************************************************/
5
6#include "QskVirtualKeyboard.h"
7#include "QskPushButton.h"
8
9#include <qguiapplication.h>
10#include <qset.h>
11#include <qstylehints.h>
12
13namespace
14{
15 class Button : public QskPushButton
16 {
17 public:
18 Button( QskVirtualKeyboard* parent )
19 : QskPushButton( parent )
20 {
21 setFocusPolicy( Qt::TabFocus );
22
23 setSubcontrolProxy( QskPushButton::Panel, QskVirtualKeyboard::ButtonPanel );
24 setSubcontrolProxy( QskPushButton::Text, QskVirtualKeyboard::ButtonText );
25 }
26
27 int key() const
28 {
29 return m_key;
30 }
31
32 void setKey( int key )
33 {
34 m_key = key;
35 }
36
37 private:
38 int m_key = 0;
39 };
40
41 static bool qskIsAutorepeat( int key )
42 {
43 return (
44 ( key != Qt::Key_Return ) &&
45 ( key != Qt::Key_Enter ) &&
46 ( key != Qt::Key_Shift ) &&
47 ( key != Qt::Key_CapsLock ) &&
48 ( key != Qt::Key_Mode_switch ) );
49 }
50}
51
52
53QSK_SUBCONTROL( QskVirtualKeyboard, Panel )
54QSK_SUBCONTROL( QskVirtualKeyboard, ButtonPanel )
55QSK_SUBCONTROL( QskVirtualKeyboard, ButtonText )
56
57class QskVirtualKeyboard::PrivateData
58{
59 public:
60 int rowCount = 5;
61 int columnCount = 12;
62
64 const QskVirtualKeyboardLayouts::Layout* currentLayout = nullptr;
65 QskVirtualKeyboard::Mode mode = QskVirtualKeyboard::LowercaseMode;
66
67 QVector< Button* > keyButtons;
68 QSet< int > keyCodes;
69};
70
71QskVirtualKeyboard::QskVirtualKeyboard( QQuickItem* parent )
72 : Inherited( parent )
73 , m_data( new PrivateData )
74{
75 setPolishOnResize( true );
76 initSizePolicy( QskSizePolicy::Expanding, QskSizePolicy::Fixed );
77
78#define LOWER( x ) int( x + 32 ) // Convert an uppercase key to lowercase
79 m_data->layouts =
80 {
81#include "QskVirtualKeyboardLayouts.hpp"
82 };
83#undef LOWER
84
85 ensureButtons();
86
87 connect( this, &QskControl::localeChanged,
88 this, &QskVirtualKeyboard::updateLocale );
89
90 updateLocale( locale() );
91
92 setSubcontrolProxy( QskBox::Panel, Panel );
93}
94
95QskVirtualKeyboard::~QskVirtualKeyboard()
96{
97}
98
99void QskVirtualKeyboard::setMode( QskVirtualKeyboard::Mode mode )
100{
101 m_data->mode = mode;
102 polish();
103
104 Q_EMIT modeChanged( m_data->mode );
105}
106
107QskVirtualKeyboard::Mode QskVirtualKeyboard::mode() const
108{
109 return m_data->mode;
110}
111
112QSizeF QskVirtualKeyboard::layoutSizeHint(
113 Qt::SizeHint which, const QSizeF& constraint ) const
114{
115 if ( which != Qt::PreferredSize )
116 return QSizeF();
117
118 const qreal ratio = qreal( rowCount() ) / columnCount();
119
120 qreal w = constraint.width();
121 qreal h = constraint.height();
122
123 if ( h >= 0 )
124 {
125 const auto padding = innerPadding( Panel, QSizeF( h, h ) );
126 const auto dw = padding.left() + padding.right();
127 const auto dh = padding.top() + padding.bottom();
128
129 w = ( h - dh ) / ratio + dw;
130 }
131 else
132 {
133 if ( w < 0 )
134 w = 600;
135
136 const auto padding = innerPadding( Panel, QSizeF( w, w ) );
137 const auto dw = padding.left() + padding.right();
138 const auto dh = padding.top() + padding.bottom();
139
140 h = ( w - dw ) * ratio + dh;
141 }
142
143 return QSizeF( w, h );
144}
145
146qreal QskVirtualKeyboard::keyStretch( int key ) const
147{
148 switch ( key )
149 {
150 case Qt::Key_Backspace:
151 case Qt::Key_Shift:
152 case Qt::Key_CapsLock:
153 return 1.5;
154
155 case Qt::Key_Space:
156 return 3.5;
157
158 case Qt::Key_Return:
159 case Qt::Key_Mode_switch:
160
161 // Possibly smaller
162 default:
163 break;
164 }
165
166 return 1.0;
167}
168
169bool QskVirtualKeyboard::isKeyVisible( int key ) const
170{
171 return key != 0;
172}
173
174QString QskVirtualKeyboard::textForKey( int key ) const
175{
176 // Special cases
177 switch ( key )
178 {
179 case Qt::Key_Backspace:
180 case Qt::Key_Muhenkan:
181 return QChar( 0x232B );
182
183 case Qt::Key_CapsLock:
184 case Qt::Key_Kana_Lock:
185 return QChar( 0x21E7 );
186
187 case Qt::Key_Shift:
188 case Qt::Key_Kana_Shift:
189 return QChar( 0x2B06 );
190
191 case Qt::Key_Mode_switch:
192 return QChar( 0x2026 );
193
194 case Qt::Key_Return:
195 case Qt::Key_Kanji:
196 return QChar( 0x23CE );
197
198 case Qt::Key_Left:
199 return QChar( 0x2190 );
200
201 case Qt::Key_Right:
202 return QChar( 0x2192 );
203
204 case Qt::Key_ApplicationLeft:
205 return QChar( 0x2B05 );
206
207 case Qt::Key_ApplicationRight:
208 return QChar( 0x27A1 );
209
210 default:
211 return QChar( key );
212 }
213}
214
215QskVirtualKeyboard::KeyType QskVirtualKeyboard::typeForKey( int key ) const
216{
217 switch( key )
218 {
219 case Qt::Key_Return:
220 case Qt::Key_Enter:
221 return EnterType;
222
223 case Qt::Key_Backspace:
224 return BackspaceType;
225
226 case Qt::Key_Shift:
227 case Qt::Key_CapsLock:
228 return CapsSwitchType;
229
230 case Qt::Key_Mode_switch:
231 return ModeSwitchType;
232
233 case Qt::Key_Comma:
234 case Qt::Key_Period:
235 return SpecialCharacterType;
236
237 default:
238 return NormalType;
239 }
240}
241
242void QskVirtualKeyboard::updateLayout() // ### fill keyCodes here
243{
244 const auto r = layoutRect();
245 if ( r.isEmpty() )
246 return;
247
248 const auto spacing = spacingHint( Panel );
249 const auto totalVSpacing = ( rowCount() - 1 ) * spacing;
250
251 const auto keyHeight = ( r.height() - totalVSpacing ) / rowCount();
252
253 qreal yPos = r.top();
254
255 const auto& page = ( *m_data->currentLayout )[ mode() ];
256
257 for ( int i = 0; i < page.size(); i++ )
258 {
259 const QVector< int > row = page[ i ];
260#if 1
261 // there should be a better way
262 auto totalHSpacing = -spacing;
263 if ( spacing )
264 {
265 for ( int j = 0; j < row.size(); j++ )
266 {
267 if ( row[ j ] != 0 )
268 totalHSpacing += spacing;
269 }
270 }
271#endif
272 const auto baseKeyWidth = ( r.width() - totalHSpacing ) / rowStretch( row );
273 qreal xPos = r.left();
274
275 for ( int j = 0; j < columnCount(); j++ )
276 {
277 auto button = m_data->keyButtons[ i * columnCount() + j ];
278
279 if( j < row.size() )
280 {
281 const int key = row[ j ];
282 button->setVisible( isKeyVisible( key ) );
283
284 if ( button->isVisible() )
285 {
286 const qreal keyWidth = baseKeyWidth * keyStretch( key );
287
288 const QRectF rect( xPos, yPos, keyWidth, keyHeight );
289
290 button->setGeometry( rect );
291 button->setAutoRepeat( qskIsAutorepeat( key ) );
292 button->setKey( key );
293 button->setText( textForKey( key ) );
294
295 const auto type = typeForKey( key );
296 const auto emphasis = emphasisForType( type );
297 button->setEmphasis( emphasis );
298
299 xPos += keyWidth + spacing;
300 }
301 }
302 else
303 {
304 button->setVisible( false );
305 }
306 }
307
308 yPos += keyHeight + spacing;
309 }
310}
311
312bool QskVirtualKeyboard::hasKey( int keyCode ) const
313{
314 return m_data->keyCodes.contains( keyCode );
315}
316
317int QskVirtualKeyboard::rowCount() const
318{
319 return m_data->rowCount;
320}
321
322void QskVirtualKeyboard::setRowCount( int rowCount )
323{
324 m_data->rowCount = rowCount;
325 ensureButtons();
326}
327
328int QskVirtualKeyboard::columnCount() const
329{
330 return m_data->columnCount;
331}
332
333void QskVirtualKeyboard::setColumnCount( int columnCount )
334{
335 m_data->columnCount = columnCount;
336 ensureButtons();
337}
338
339QskVirtualKeyboardLayouts QskVirtualKeyboard::layouts() const
340{
341 return m_data->layouts;
342}
343
344void QskVirtualKeyboard::setLayouts( const QskVirtualKeyboardLayouts& layouts )
345{
346 m_data->layouts = layouts;
347}
348
349void QskVirtualKeyboard::ensureButtons()
350{
351 const int newButtonSize = rowCount() * columnCount();
352 const int oldButtonSize = m_data->keyButtons.size();
353
354 if( newButtonSize == oldButtonSize )
355 return;
356
357 const auto hints = QGuiApplication::styleHints();
358
359 auto autoRepeatInterval = 1000.0;
360#if QT_VERSION >= QT_VERSION_CHECK( 6, 5, 0 )
361 autoRepeatInterval /= hints->keyboardAutoRepeatRateF();
362#else
363 autoRepeatInterval /= hints->keyboardAutoRepeatRate();
364#endif
365
366 m_data->keyButtons.reserve( rowCount() * columnCount() );
367
368 for( int i = 0; i < rowCount(); i++ )
369 {
370 for( int j = 0; j < columnCount(); j++ )
371 {
372 const int index = i * columnCount() + j;
373
374 if( index >= m_data->keyButtons.size() )
375 {
376 auto button = new Button( this );
377 button->installEventFilter( this );
378
379 button->setAutoRepeat( false );
380 button->setAutoRepeatDelay( 500 );
381 button->setAutoRepeatInterval( autoRepeatInterval );
382
383 connect( button, &QskPushButton::pressed,
384 this, &QskVirtualKeyboard::buttonPressed );
385
386 m_data->keyButtons += button;
387 }
388 }
389 }
390
391 while( m_data->keyButtons.size() > newButtonSize )
392 {
393 auto* button = m_data->keyButtons.takeLast();
394 button->deleteLater();
395 }
396}
397
398void QskVirtualKeyboard::buttonPressed()
399{
400 const auto button = static_cast< const Button* >( sender() );
401 if ( button == nullptr )
402 return;
403
404 const int key = button->key();
405
406 // Mode-switching keys
407 switch ( key )
408 {
409 case Qt::Key_CapsLock:
410 case Qt::Key_Kana_Lock:
411 {
412 setMode( UppercaseMode ); // Lock caps
413 break;
414 }
415
416 case Qt::Key_Shift:
417 case Qt::Key_Kana_Shift:
418 {
419 setMode( LowercaseMode ); // Unlock caps
420 break;
421 }
422
423 case Qt::Key_Mode_switch: // Cycle through modes, but skip caps
424 {
425 setMode( static_cast< QskVirtualKeyboard::Mode >(
426 m_data->mode ? ( ( m_data->mode + 1 ) % QskVirtualKeyboard::ModeCount )
427 : SpecialCharacterMode ) );
428
429 break;
430 }
431 default:
432 {
433 Q_EMIT keySelected( key );
434 }
435 }
436}
437
438void QskVirtualKeyboard::updateKeyCodes()
439{
440 m_data->keyCodes = {};
441 m_data->keyCodes.reserve( rowCount() * columnCount() );
442
443 const auto layout = *m_data->currentLayout;
444
445 for ( int mode = 0; mode < layout.size(); mode++ )
446 {
447 const auto& page = layout[ mode ];
448
449 for ( int i = 0; i < page.size(); i++ )
450 {
451 const auto& row = page[ i ];
452
453 for ( int j = 0; j < row.size(); j++ )
454 {
455 m_data->keyCodes += row[ j ];
456 }
457 }
458 }
459}
460
461qreal QskVirtualKeyboard::rowStretch( const QVector< int >& row )
462{
463 qreal stretch = 0;
464
465 for ( const int& key : row )
466 {
467 if ( !key )
468 {
469 continue;
470 }
471
472 stretch += keyStretch( key );
473 }
474
475 if ( stretch == 0.0 )
476 {
477 stretch = columnCount();
478 }
479
480 return stretch;
481}
482
483void QskVirtualKeyboard::updateLocale( const QLocale& locale )
484{
485 const QskVirtualKeyboardLayouts::Layout* newLayout = nullptr;
486
487 switch ( locale.language() )
488 {
489 case QLocale::Bulgarian:
490 newLayout = &m_data->layouts.bg;
491 break;
492
493 case QLocale::Czech:
494 newLayout = &m_data->layouts.cs;
495 break;
496
497 case QLocale::German:
498 newLayout = &m_data->layouts.de;
499 break;
500
501 case QLocale::Danish:
502 newLayout = &m_data->layouts.da;
503 break;
504
505 case QLocale::Greek:
506 newLayout = &m_data->layouts.el;
507 break;
508
509 case QLocale::English:
510 {
511 const auto territory =
512#if QT_VERSION >= QT_VERSION_CHECK( 6, 2, 0 )
513 locale.territory();
514#else
515 locale.country();
516#endif
517
518 switch ( territory )
519 {
520 case QLocale::Canada:
521 case QLocale::UnitedStates:
522 case QLocale::UnitedStatesMinorOutlyingIslands:
523 case QLocale::UnitedStatesVirginIslands:
524 newLayout = &m_data->layouts.en_US;
525 break;
526
527 default:
528 newLayout = &m_data->layouts.en_GB;
529 break;
530 }
531
532 break;
533 }
534
535 case QLocale::Spanish:
536 newLayout = &m_data->layouts.es;
537 break;
538
539 case QLocale::Finnish:
540 newLayout = &m_data->layouts.fi;
541 break;
542
543 case QLocale::French:
544 newLayout = &m_data->layouts.fr;
545 break;
546
547 case QLocale::Hungarian:
548 newLayout = &m_data->layouts.hu;
549 break;
550
551 case QLocale::Italian:
552 newLayout = &m_data->layouts.it;
553 break;
554
555 case QLocale::Japanese:
556 newLayout = &m_data->layouts.ja;
557 break;
558
559 case QLocale::Latvian:
560 newLayout = &m_data->layouts.lv;
561 break;
562
563 case QLocale::Lithuanian:
564 newLayout = &m_data->layouts.lt;
565 break;
566
567 case QLocale::Dutch:
568 newLayout = &m_data->layouts.nl;
569 break;
570
571 case QLocale::Portuguese:
572 newLayout = &m_data->layouts.pt;
573 break;
574
575 case QLocale::Romanian:
576 newLayout = &m_data->layouts.ro;
577 break;
578
579 case QLocale::Russian:
580 newLayout = &m_data->layouts.ru;
581 break;
582
583 case QLocale::Slovenian:
584 newLayout = &m_data->layouts.sl;
585 break;
586
587 case QLocale::Slovak:
588 newLayout = &m_data->layouts.sk;
589 break;
590
591 case QLocale::Turkish:
592 newLayout = &m_data->layouts.tr;
593 break;
594
595 case QLocale::Chinese:
596 newLayout = &m_data->layouts.zh;
597 break;
598#if 1
599 case QLocale::C:
600 newLayout = &m_data->layouts.en_US;
601 break;
602#endif
603 default:
604 qWarning() << "QskVirtualKeyboard: unsupported locale:" << locale;
605 newLayout = &m_data->layouts.en_US;
606 }
607
608 if ( newLayout != m_data->currentLayout )
609 {
610 m_data->currentLayout = newLayout;
611 updateKeyCodes();
612
613 setMode( LowercaseMode );
614 polish();
615 Q_EMIT keyboardLayoutChanged();
616 }
617}
618
619QskPushButton::Emphasis QskVirtualKeyboard::emphasisForType( KeyType type )
620{
621 switch( type )
622 {
623 case EnterType:
624 return QskPushButton::VeryHighEmphasis;
625
626 case BackspaceType:
627 case CapsSwitchType:
628 return QskPushButton::HighEmphasis;
629
630 case ModeSwitchType:
631 return QskPushButton::LowEmphasis;
632
633 case SpecialCharacterType:
634 return QskPushButton::VeryLowEmphasis;
635
636 default:
637 return QskPushButton::NoEmphasis;
638 }
639}
640
641#include "moc_QskVirtualKeyboard.cpp"
void localeChanged(const QLocale &)
QLocale locale
Definition QskControl.h:27
QRectF layoutRect() const
QRectF rect
Definition QskItem.h:21
qreal spacingHint(QskAspect, QskSkinHintStatus *=nullptr) const
Retrieves a spacing hint.
QMarginsF innerPadding(QskAspect, const QSizeF &) const
Calculate the padding from attributes for the given aspect.