MythTV master
mythvirtualkeyboard.cpp
Go to the documentation of this file.
1
2// Own header
4
5// c++
6#include <array>
7#include <iostream>
8
9// qt
10#include <QKeyEvent>
11#include <QDomDocument>
12#include <QFile>
13
14// libmythbase
17
18// libmythui
19#include "mythmainwindow.h"
20#include "mythfontproperties.h"
21#include "mythuihelper.h"
22#include "mythuibutton.h"
23#include "mythuitextedit.h"
24
25
26#define LOC QString("MythUIVirtualKeyboard: ")
27
28static const std::array<std::array<QString,3>,95> comps {{
29 {"!", "!", (QChar)0xa1}, {"c", "/", (QChar)0xa2},
30 {"l", "-", (QChar)0xa3}, {"o", "x", (QChar)0xa4},
31 {"y", "-", (QChar)0xa5}, {"|", "|", (QChar)0xa6},
32 {"s", "o", (QChar)0xa7}, {"\"", "\"", (QChar)0xa8},
33 {"c", "o", (QChar)0xa9}, {"-", "a", (QChar)0xaa},
34 {"<", "<", (QChar)0xab}, {"-", "|", (QChar)0xac},
35 {"-", "-", (QChar)0xad}, {"r", "o", (QChar)0xae},
36 {"^", "-", (QChar)0xaf}, {"^", "0", (QChar)0xb0},
37 {"+", "-", (QChar)0xb1}, {"^", "2", (QChar)0xb2},
38 {"^", "3", (QChar)0xb3}, {"/", "/", (QChar)0xb4},
39 {"/", "u", (QChar)0xb5}, {"P", "!", (QChar)0xb6},
40 {"^", ".", (QChar)0xb7}, {",", ",", (QChar)0xb8},
41 {"^", "1", (QChar)0xb9}, {"_", "o", (QChar)0xba},
42 {">", ">", (QChar)0xbb}, {"1", "4", (QChar)0xbc},
43 {"1", "2", (QChar)0xbd}, {"3", "4", (QChar)0xbe},
44 {"?", "?", (QChar)0xbf}, {"A", "`", (QChar)0xc0},
45 {"A", "'", (QChar)0xc1}, {"A", "^", (QChar)0xc2},
46 {"A", "~", (QChar)0xc3}, {"A", "\"", (QChar)0xc4},
47 {"A", "*", (QChar)0xc5}, {"A", "E", (QChar)0xc6},
48 {"C", ",", (QChar)0xc7}, {"E", "`", (QChar)0xc8},
49 {"E", "'", (QChar)0xc9}, {"E", "^", (QChar)0xca},
50 {"E", "\"", (QChar)0xcb}, {"I", "`", (QChar)0xcc},
51 {"I", "'", (QChar)0xcd}, {"I", "^", (QChar)0xce},
52 {"I", "\"", (QChar)0xcf}, {"D", "-", (QChar)0xd0},
53 {"N", "~", (QChar)0xd1}, {"O", "`", (QChar)0xd2},
54 {"O", "'", (QChar)0xd3}, {"O", "^", (QChar)0xd4},
55 {"O", "~", (QChar)0xd5}, {"O", "\"", (QChar)0xd6},
56 {"x", "x", (QChar)0xd7}, {"O", "/", (QChar)0xd8},
57 {"U", "`", (QChar)0xd9}, {"U", "'", (QChar)0xda},
58 {"U", "^", (QChar)0xdb}, {"U", "\"", (QChar)0xdc},
59 {"Y", "'", (QChar)0xdd}, {"T", "H", (QChar)0xde},
60 {"s", "s", (QChar)0xdf}, {"a", "`", (QChar)0xe0},
61 {"a", "'", (QChar)0xe1}, {"a", "^", (QChar)0xe2},
62 {"a", "~", (QChar)0xe3}, {"a", "\"", (QChar)0xe4},
63 {"a", "*", (QChar)0xe5}, {"a", "e", (QChar)0xe6},
64 {"c", ",", (QChar)0xe7}, {"e", "`", (QChar)0xe8},
65 {"e", "'", (QChar)0xe9}, {"e", "^", (QChar)0xea},
66 {"e", "\"", (QChar)0xeb}, {"i", "`", (QChar)0xec},
67 {"i", "'", (QChar)0xed}, {"i", "^", (QChar)0xee},
68 {"i", "\"", (QChar)0xef}, {"d", "-", (QChar)0xf0},
69 {"n", "~", (QChar)0xf1}, {"o", "`", (QChar)0xf2},
70 {"o", "'", (QChar)0xf3}, {"o", "^", (QChar)0xf4},
71 {"o", "~", (QChar)0xf5}, {"o", "\"", (QChar)0xf6},
72 {"-", ":", (QChar)0xf7}, {"o", "/", (QChar)0xf8},
73 {"u", "`", (QChar)0xf9}, {"u", "'", (QChar)0xfa},
74 {"u", "^", (QChar)0xfb}, {"u", "\"", (QChar)0xfc},
75 {"y", "'", (QChar)0xfd}, {"t", "h", (QChar)0xfe},
76 {"y", "\"", (QChar)0xff}
77}};
78
80 : MythScreenType(parentStack, "MythUIVirtualKeyboard"),
81 m_parentEdit(parentEdit)
82{
83 if (m_parentEdit)
85 else
87
93}
94
96{
97 if (!LoadWindowFromXML("keyboard/keyboard.xml", "keyboard", this))
98 return false;
99
101
103 updateKeys(true);
104
105 QSize screensize = GetMythMainWindow()->GetScreenRect().size();
106 MythRect editArea = m_parentEdit->GetArea();
107 MythRect area = GetArea();
108 MythPoint newPos;
109
110 //FIXME this assumes the edit is a direct child of the parent screen
111 MythUIType *parentScreen = nullptr;
112 parentScreen = qobject_cast<MythUIType *>(m_parentEdit->parent());
113 if (parentScreen)
114 {
115 editArea.moveTopLeft(QPoint(editArea.x() + parentScreen->GetArea().x(),
116 editArea.y() + parentScreen->GetArea().y()));
117 }
118
119 switch (m_preferredPos)
120 {
121 case VK_POSABOVEEDIT:
122 if (editArea.y() - area.height() - 5 > 0)
123 {
124 newPos = QPoint(editArea.x() + (editArea.width() / 2) - (area.width() / 2),
125 editArea.y() - area.height() - 5);
126 }
127 else
128 {
129 newPos = QPoint(editArea.x() + (editArea.width() / 2) - (area.width() / 2),
130 editArea.y() + editArea.height() + 5);
131 }
132 break;
133
134 case VK_POSTOPDIALOG:
135 newPos = QPoint((screensize.width() / 2) - (area.width() / 2), 5);
136 break;
137
139 newPos = QPoint((screensize.width() / 2) - (area.width() / 2), screensize.height() - 5 - area.height());
140 break;
141
143 newPos = QPoint((screensize.width() / 2) - (area.width() / 2), (screensize.height() / 2) - (area.height() / 2));
144 break;
145
146 default:
147 // VK_POSBELOWEDIT
148 if (editArea.y() + editArea.height() + area.height() + 5 < screensize.height())
149 {
150 newPos = QPoint(editArea.x() + (editArea.width() / 2) - (area.width() / 2),
151 editArea.y() + editArea.height() + 5);
152 }
153 else
154 {
155 newPos = QPoint(editArea.x() + (editArea.width() / 2) - (area.width() / 2),
156 editArea.y() - area.height() - 5);
157 }
158 break;
159 }
160
161 // make sure the popup doesn't go off screen
162 if (newPos.x() < 5)
163 newPos.setX(5);
164 if (newPos.x() + area.width() + 5 > screensize.width())
165 newPos.setX(screensize.width() - area.width() - 5);
166 if (newPos.y() < 5)
167 newPos.setY(5);
168 if (newPos.y() + area.height() + 5 > screensize.width())
169 newPos.setY(screensize.width() - area.height() - 5);
170
171 SetPosition(newPos);
172
173 return true;
174}
175
177{
178 QString language = lang.toLower();
179
180 QString defFile = QString("keyboard/%1.xml").arg(language);
181
182 if (!GetMythUI()->FindThemeFile(defFile))
183 {
184 LOG(VB_GENERAL, LOG_ERR,
185 "No keyboard definition file found for: " + language);
186
187 // default to US keyboard layout
188 defFile = "keyboard/en_us.xml";
189 if (!GetMythUI()->FindThemeFile(defFile))
190 {
191 LOG(VB_GENERAL, LOG_ERR,
192 "Cannot find definitions file: " + defFile);
193 return;
194 }
195 }
196
197 LOG(VB_GENERAL, LOG_NOTICE, "Loading definitions from: " + defFile);
198
199 QDomDocument doc("keydefinitions");
200 QFile file(defFile);
201 if (!file.open(QIODevice::ReadOnly))
202 {
203 LOG(VB_GENERAL, LOG_ERR, "Failed to open definitions file: " + defFile);
204 return;
205 }
206 if (!doc.setContent(&file))
207 {
208 LOG(VB_GENERAL, LOG_ERR,
209 "Failed to parse definitions file: " + defFile);
210 file.close();
211 return;
212 }
213 file.close();
214
215 QDomElement docElem = doc.documentElement();
216 QDomNode n = docElem.firstChild();
217 while(!n.isNull())
218 {
219 QDomElement e = n.toElement();
220 if(!e.isNull())
221 {
222 if (e.tagName() == "key")
223 parseKey(e);
224 }
225 n = n.nextSibling();
226 }
227}
228
229void MythUIVirtualKeyboard::parseKey(const QDomElement &element)
230{
231 QString left;
232 QString right;
233 QString up;
234 QString down;
235 QString normal;
236 QString shift;
237 QString alt;
238 QString altshift;
239
240 QString name = element.attribute("name");
241 QString type = element.attribute("type");
242
243 QDomNode n = element.firstChild();
244 while(!n.isNull())
245 {
246 QDomElement e = n.toElement();
247 if(!e.isNull())
248 {
249 if (e.tagName() == "move")
250 {
251 left = e.attribute("left");
252 right = e.attribute("right");
253 up = e.attribute("up");
254 down = e.attribute("down");
255 }
256 else if (e.tagName() == "char")
257 {
258 normal = e.attribute("normal");
259 shift = e.attribute("shift");
260 alt = e.attribute("alt");
261 altshift = e.attribute("altshift");
262 }
263 else
264 {
265 LOG(VB_GENERAL, LOG_ERR, "Unknown element in key definition");
266 }
267 }
268 n = n.nextSibling();
269 }
270
271 KeyDefinition key;
272 key.name = name;
273 key.type = type;
274 key.left = left;
275 key.right = right;
276 key.up = up;
277 key.down = down;
278 key.normal = decodeChar(normal);
279 key.alt = decodeChar(alt);
280 key.shift = decodeChar(shift);
281 key.altshift = decodeChar(altshift);
282
283 m_keyMap[name] = key;
284}
285
286void MythUIVirtualKeyboard::updateKeys(bool connectSignals)
287{
288 QList<MythUIType *> *children = GetAllChildren();
289 for (auto *child : std::as_const(*children))
290 {
291 auto *button = dynamic_cast<MythUIButton *>(child);
292 if (button)
293 {
294 if (m_keyMap.contains(button->objectName()))
295 {
296 KeyDefinition key = m_keyMap.value(button->objectName());
297 button->SetText(getKeyText(key));
298
299 if (connectSignals)
300 {
301 if (key.type == "shift")
302 {
303 if (!m_shiftLButton)
304 m_shiftLButton = button;
305 else if (!m_shiftRButton)
306 m_shiftRButton = button;
307
308 button->SetLockable(true);
310 }
311 else if (key.type == "char")
312 {
314 }
315 else if (key.type == "done")
316 {
318 }
319 else if (key.type == "del")
320 {
322 }
323 else if (key.type == "lock")
324 {
325 m_lockButton = button;
328 }
329 else if (key.type == "alt")
330 {
331 m_altButton = button;
334 }
335 else if (key.type == "comp")
336 {
337 m_compButton = button;
340 }
341 else if (key.type == "moveleft")
342 {
344 }
345 else if (key.type == "moveright")
346 {
348 }
349 else if (key.type == "back")
350 {
352 }
353 }
354 }
355 else
356 {
357 LOG(VB_GENERAL, LOG_WARNING,
358 QString("WARNING - Key '%1' not found in map")
359 .arg(button->objectName()));
360 }
361 }
362 }
363}
364
366{
367 bool handled = false;
368 QStringList actions;
369 handled = GetMythMainWindow()->TranslateKeyPress("TV Frontend", e, actions);
370
371 if (handled)
372 return true;
373
374 bool keyFound = false;
375 KeyDefinition key;
376 if (GetFocusWidget())
377 {
378 if (m_keyMap.contains(GetFocusWidget()->objectName()))
379 {
380 key = m_keyMap.value(GetFocusWidget()->objectName());
381 keyFound = true;;
382 }
383 }
384
385 for (int i = 0; i < actions.size() && !handled; i++)
386 {
387 const QString& action = actions[i];
388 handled = true;
389
390 if (action == "UP")
391 {
392 if (keyFound)
394 }
395 else if (action == "DOWN")
396 {
397 if (keyFound)
399 }
400 else if (action == "LEFT")
401 {
402 if (keyFound)
404 }
405 else if (action == "RIGHT")
406 {
407 if (keyFound)
409 }
410 else
411 {
412 handled = false;
413 }
414 }
415
416 if (!handled && MythScreenType::keyPressEvent(e))
417 handled = true;
418
419 return handled;
420}
421
423{
424 if (!GetFocusWidget())
425 return;
426
427 KeyDefinition key = m_keyMap.value(GetFocusWidget()->objectName());
428 QString c = getKeyText(key);
429
430 if (m_composing)
431 {
432 if (m_composeStr.isEmpty())
433 m_composeStr = c;
434 else
435 {
436 // Produce the composed key.
437 for (const auto & comp : comps)
438 {
439 if ((m_composeStr == comp[0]) && (c == comp[1]))
440 {
441 c = comp[2];
442
443 emit keyPressed(c);
444
445 if (m_parentEdit)
446 {
447 auto *event = new QKeyEvent(QEvent::KeyPress, 0, Qt::NoModifier, c);
449 }
450
451 break;
452 }
453 }
454
455 m_composeStr.clear();
456 m_composing = false;
457 if (m_compButton)
458 m_compButton->SetLocked(false);
459 }
460 }
461 else
462 {
463 emit keyPressed(c);
464
465 if (m_parentEdit)
466 {
467 auto *event = new QKeyEvent(QEvent::KeyPress, 0, Qt::NoModifier, c);
469 }
470
471 if (m_shift && !m_lock)
472 {
473 m_shift = false;
474 if (m_shiftLButton)
476 if (m_shiftRButton)
478
479 updateKeys();
480 }
481 }
482}
483
485{
486 m_shift = !m_shift;
487
488 if (m_shiftLButton)
490 if (m_shiftRButton)
492 if (m_lockButton && m_lock)
493 {
494 m_lockButton->SetLocked(false);
495 m_lock = false;
496 }
497
498 updateKeys();
499}
500
502{
503 emit keyPressed("{DELETE}");
504
505 if (m_parentEdit)
506 {
507 //QKeyEvent *event = new QKeyEvent(QEvent::KeyPress, Qt::Key_Delete, Qt::NoModifier, "");
508 auto *event = new QKeyEvent(QEvent::KeyPress, Qt::Key_Backspace, Qt::NoModifier, "");
510 }
511}
512
514{
515 emit keyPressed("{BACK}");
516
517 if (m_parentEdit)
518 {
519 auto *event = new QKeyEvent(QEvent::KeyPress, Qt::Key_Backspace, Qt::NoModifier, "");
521 }
522}
523
525{
526 m_lock = !m_lock;
527 m_shift = m_lock;
528
529 if (m_shiftLButton)
531 if (m_shiftRButton)
533
534 updateKeys();
535}
536
538{
539 m_alt = !m_alt;
540
541 updateKeys();
542}
543
545{
547 m_composeStr.clear();
548}
549
551{
552 if (m_shift)
553 {
554 emit keyPressed("{NEWLINE}");
555 auto *event = new QKeyEvent(QEvent::KeyPress, m_newlineKey.key(),
556 m_newlineKey.keyboardModifiers(), "");
558 }
559 else
560 {
561 Close();
562 }
563}
564
566{
567 if (m_parentEdit)
568 {
569 if (m_shift)
570 {
571 emit keyPressed("{MOVEUP}");
572 auto *event = new QKeyEvent(QEvent::KeyPress, m_upKey.key(),
573 m_upKey.keyboardModifiers(), "");
575 }
576 else
577 {
578 emit keyPressed("{MOVELEFT}");
579 auto *event = new QKeyEvent(QEvent::KeyPress, m_leftKey.key(),
580 m_leftKey.keyboardModifiers(), "");
582 }
583 }
584}
585
587{
588 if (m_parentEdit)
589 {
590 if (m_shift)
591 {
592 emit keyPressed("{MOVEDOWN}");
593 auto *event = new QKeyEvent(QEvent::KeyPress, m_downKey.key(),
594 m_downKey.keyboardModifiers(), "");
596 }
597 else
598 {
599 emit keyPressed("{MOVERIGHT}");
600 auto *event = new QKeyEvent(QEvent::KeyPress, m_rightKey.key(),
601 m_rightKey.keyboardModifiers(), "");
603 }
604 }
605}
606
608{
609 QString res;
610
611 while (c.length() > 0)
612 {
613 if (c.startsWith("0x"))
614 {
615 QString sCode = c.left(6);
616 bool bOK = false;
617 short nCode = sCode.toShort(&bOK, 16);
618
619 c = c.mid(6);
620
621 if (bOK)
622 {
623 QChar uc(nCode);
624 res += QString(uc);
625 }
626 else
627 {
628 LOG(VB_GENERAL, LOG_ERR, QString("bad char code (%1)")
629 .arg(sCode));
630 }
631 }
632 else
633 {
634 res += c.at(0);
635 c = c.mid(1);
636 }
637 }
638
639 return res;
640}
641
643{
644
645 if (m_shift)
646 {
647 if (m_alt)
648 return key.altshift;
649 return key.shift;
650 }
651
652 if (m_alt)
653 return key.alt;
654
655 return key.normal;
656}
657
659#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
660 KeyEventDefinition *keyDef,
661#else
662 QKeyCombination *keyDef,
663#endif
664 const QString &action)
665{
666 QString keylist = MythMainWindow::GetKey("Global", action);
667 QStringList keys = keylist.split(',', Qt::SkipEmptyParts);
668 if (keys.empty())
669 return;
670
671 QKeySequence a(keys[0]);
672 if (a.isEmpty())
673 {
674 LOG(VB_GENERAL, LOG_ERR,
675 QString("loadEventKeyDefinitions bad key (%1)").arg(keys[0]));
676 return;
677 }
678
679#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
680 keyDef->keyCode = a[0];
681
682 Qt::KeyboardModifiers modifiers = Qt::NoModifier;
683 QStringList parts = keys[0].split('+');
684 for (int j = 0; j < parts.count(); j++)
685 {
686 if (parts[j].toUpper() == "CTRL")
687 modifiers |= Qt::ControlModifier;
688 if (parts[j].toUpper() == "SHIFT")
689 modifiers |= Qt::ShiftModifier;
690 if (parts[j].toUpper() == "ALT")
691 modifiers |= Qt::AltModifier;
692 if (parts[j].toUpper() == "META")
693 modifiers |= Qt::MetaModifier;
694 }
695
696 keyDef->modifiers = modifiers;
697#else
698 *keyDef = a[0];
699#endif
700}
QString GetLanguageAndVariant(void)
Returns the user-set language and variant.
bool TranslateKeyPress(const QString &Context, QKeyEvent *Event, QStringList &Actions, bool AllowJumps=true)
Get a list of actions for a keypress in the given context.
static QString GetKey(const QString &Context, const QString &Action)
Wrapper around QPoint allowing us to handle percentage and other relative values for positioning in m...
Definition: mythrect.h:89
void setY(const QString &sY)
Definition: mythrect.cpp:540
void setX(const QString &sX)
Definition: mythrect.cpp:530
Wrapper around QRect allowing us to handle percentage and other relative values for areas in mythui.
Definition: mythrect.h:18
void moveTopLeft(QPoint point)
Definition: mythrect.cpp:296
Screen in which all other widgets are contained and rendered.
void BuildFocusList(void)
MythUIType * GetFocusWidget(void) const
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
bool SetFocusWidget(MythUIType *widget=nullptr)
virtual void Close()
A single button widget.
Definition: mythuibutton.h:22
void SetLocked(bool locked)
void Clicked()
void SetLockable(bool lockable)
Definition: mythuibutton.h:39
A text entry and edit widget.
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
PopupPosition GetKeyboardPosition(void)
The base class on which all widgets and screens are based.
Definition: mythuitype.h:86
QList< MythUIType * > * GetAllChildren(void)
Return a list of all child widgets.
Definition: mythuitype.cpp:202
virtual MythRect GetArea(void) const
If the object has a minimum area defined, return it, other wise return the default area.
Definition: mythuitype.cpp:885
void SetPosition(int x, int y)
Convenience method, calls SetPosition(const MythPoint&) Override that instead to change functionality...
Definition: mythuitype.cpp:533
MythUIType * GetChild(const QString &name) const
Get a named child of this UIType.
Definition: mythuitype.cpp:138
void keyPressed(QString key)
void updateKeys(bool connectSignals=false)
bool keyPressEvent(QKeyEvent *e) override
Key event handler.
bool Create(void) override
QString getKeyText(const KeyDefinition &key) const
static void loadEventKeyDefinitions(QKeyCombination *keyDef, const QString &action)
QMap< QString, KeyDefinition > m_keyMap
MythUIButton * m_shiftRButton
MythUITextEdit * m_parentEdit
void parseKey(const QDomElement &element)
static QString decodeChar(QString c)
QKeyCombination m_newlineKey
MythUIButton * m_shiftLButton
MythUIVirtualKeyboard(MythScreenStack *parentStack, MythUITextEdit *m_parentEdit)
void loadKeyDefinitions(const QString &lang)
static bool LoadWindowFromXML(const QString &xmlfile, const QString &windowname, MythUIType *parent)
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MythMainWindow * GetMythMainWindow(void)
MythUIHelper * GetMythUI()
static const std::array< std::array< QString, 3 >, 95 > comps
@ VK_POSBOTTOMDIALOG
@ VK_POSCENTERDIALOG
@ VK_POSBELOWEDIT
@ VK_POSABOVEEDIT
@ VK_POSTOPDIALOG