MythTV  master
mythvirtualkeyboard.cpp
Go to the documentation of this file.
1 
2 // Own header
3 #include "mythvirtualkeyboard.h"
4 
5 // c++
6 #include <iostream>
7 
8 // qt
9 #include <QKeyEvent>
10 #include <QDomDocument>
11 #include <QFile>
12 
13 // myth
14 #include "mythmainwindow.h"
15 #include "mythlogging.h"
16 #include "mythfontproperties.h"
17 #include "mythuihelper.h"
18 #include "mythuibutton.h"
19 #include "mythuitextedit.h"
20 #include "mythcorecontext.h"
21 
22 
23 #define LOC QString("MythUIVirtualKeyboard: ")
24 
25 static const int numcomps = 95;
26 
27 static const QString comps[numcomps][3] = {
28  {"!", "!", (QChar)0xa1}, {"c", "/", (QChar)0xa2},
29  {"l", "-", (QChar)0xa3}, {"o", "x", (QChar)0xa4},
30  {"y", "-", (QChar)0xa5}, {"|", "|", (QChar)0xa6},
31  {"s", "o", (QChar)0xa7}, {"\"", "\"", (QChar)0xa8},
32  {"c", "o", (QChar)0xa9}, {"-", "a", (QChar)0xaa},
33  {"<", "<", (QChar)0xab}, {"-", "|", (QChar)0xac},
34  {"-", "-", (QChar)0xad}, {"r", "o", (QChar)0xae},
35  {"^", "-", (QChar)0xaf}, {"^", "0", (QChar)0xb0},
36  {"+", "-", (QChar)0xb1}, {"^", "2", (QChar)0xb2},
37  {"^", "3", (QChar)0xb3}, {"/", "/", (QChar)0xb4},
38  {"/", "u", (QChar)0xb5}, {"P", "!", (QChar)0xb6},
39  {"^", ".", (QChar)0xb7}, {",", ",", (QChar)0xb8},
40  {"^", "1", (QChar)0xb9}, {"_", "o", (QChar)0xba},
41  {">", ">", (QChar)0xbb}, {"1", "4", (QChar)0xbc},
42  {"1", "2", (QChar)0xbd}, {"3", "4", (QChar)0xbe},
43  {"?", "?", (QChar)0xbf}, {"A", "`", (QChar)0xc0},
44  {"A", "'", (QChar)0xc1}, {"A", "^", (QChar)0xc2},
45  {"A", "~", (QChar)0xc3}, {"A", "\"", (QChar)0xc4},
46  {"A", "*", (QChar)0xc5}, {"A", "E", (QChar)0xc6},
47  {"C", ",", (QChar)0xc7}, {"E", "`", (QChar)0xc8},
48  {"E", "'", (QChar)0xc9}, {"E", "^", (QChar)0xca},
49  {"E", "\"", (QChar)0xcb}, {"I", "`", (QChar)0xcc},
50  {"I", "'", (QChar)0xcd}, {"I", "^", (QChar)0xce},
51  {"I", "\"", (QChar)0xcf}, {"D", "-", (QChar)0xd0},
52  {"N", "~", (QChar)0xd1}, {"O", "`", (QChar)0xd2},
53  {"O", "'", (QChar)0xd3}, {"O", "^", (QChar)0xd4},
54  {"O", "~", (QChar)0xd5}, {"O", "\"", (QChar)0xd6},
55  {"x", "x", (QChar)0xd7}, {"O", "/", (QChar)0xd8},
56  {"U", "`", (QChar)0xd9}, {"U", "'", (QChar)0xda},
57  {"U", "^", (QChar)0xdb}, {"U", "\"", (QChar)0xdc},
58  {"Y", "'", (QChar)0xdd}, {"T", "H", (QChar)0xde},
59  {"s", "s", (QChar)0xdf}, {"a", "`", (QChar)0xe0},
60  {"a", "'", (QChar)0xe1}, {"a", "^", (QChar)0xe2},
61  {"a", "~", (QChar)0xe3}, {"a", "\"", (QChar)0xe4},
62  {"a", "*", (QChar)0xe5}, {"a", "e", (QChar)0xe6},
63  {"c", ",", (QChar)0xe7}, {"e", "`", (QChar)0xe8},
64  {"e", "'", (QChar)0xe9}, {"e", "^", (QChar)0xea},
65  {"e", "\"", (QChar)0xeb}, {"i", "`", (QChar)0xec},
66  {"i", "'", (QChar)0xed}, {"i", "^", (QChar)0xee},
67  {"i", "\"", (QChar)0xef}, {"d", "-", (QChar)0xf0},
68  {"n", "~", (QChar)0xf1}, {"o", "`", (QChar)0xf2},
69  {"o", "'", (QChar)0xf3}, {"o", "^", (QChar)0xf4},
70  {"o", "~", (QChar)0xf5}, {"o", "\"", (QChar)0xf6},
71  {"-", ":", (QChar)0xf7}, {"o", "/", (QChar)0xf8},
72  {"u", "`", (QChar)0xf9}, {"u", "'", (QChar)0xfa},
73  {"u", "^", (QChar)0xfb}, {"u", "\"", (QChar)0xfc},
74  {"y", "'", (QChar)0xfd}, {"t", "h", (QChar)0xfe},
75  {"y", "\"", (QChar)0xff}
76 };
77 
79  : MythScreenType(parentStack, "MythUIVirtualKeyboard")
80 {
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 
100  BuildFocusList();
101 
103  updateKeys(true);
104 
105  QSize screensize = GetMythUI()->GetScreenSettings().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 = dynamic_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 
138  case VK_POSBOTTOMDIALOG:
139  newPos = QPoint(screensize.width() / 2 - area.width() / 2, screensize.height() - 5 - area.height());
140  break;
141 
142  case VK_POSCENTERDIALOG:
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 
229 void 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  LOG(VB_GENERAL, LOG_ERR, "Unknown element in key definition");
265  }
266  n = n.nextSibling();
267  }
268 
269  KeyDefinition key;
270  key.name = name;
271  key.type = type;
272  key.left = left;
273  key.right = right;
274  key.up = up;
275  key.down = down;
276  key.normal = decodeChar(normal);
277  key.alt = decodeChar(alt);
278  key.shift = decodeChar(shift);
279  key.altshift = decodeChar(altshift);
280 
281  m_keyMap[name] = key;
282 }
283 
284 void MythUIVirtualKeyboard::updateKeys(bool connectSignals)
285 {
286  QList<MythUIType *> *children = GetAllChildren();
287  foreach (auto child, *children)
288  {
289  auto *button = dynamic_cast<MythUIButton *>(child);
290  if (button)
291  {
292  if (m_keyMap.contains(button->objectName()))
293  {
294  KeyDefinition key = m_keyMap.value(button->objectName());
295  button->SetText(getKeyText(key));
296 
297  if (connectSignals)
298  {
299  if (key.type == "shift")
300  {
301  if (!m_shiftLButton)
302  m_shiftLButton = button;
303  else if (!m_shiftRButton)
304  m_shiftRButton = button;
305 
306  button->SetLockable(true);
307  connect(button, SIGNAL(Clicked()), SLOT(shiftClicked()));
308  }
309  else if (key.type == "char")
310  connect(button, SIGNAL(Clicked()), SLOT(charClicked()));
311  else if (key.type == "done")
312  connect(button, SIGNAL(Clicked()), SLOT(returnClicked()));
313  else if (key.type == "del")
314  connect(button, SIGNAL(Clicked()), SLOT(delClicked()));
315  else if (key.type == "lock")
316  {
317  m_lockButton = button;
318  m_lockButton->SetLockable(true);
319  connect(m_lockButton, SIGNAL(Clicked()), SLOT(lockClicked()));
320  }
321  else if (key.type == "alt")
322  {
323  m_altButton = button;
324  m_altButton->SetLockable(true);
325  connect(m_altButton, SIGNAL(Clicked()), SLOT(altClicked()));
326  }
327  else if (key.type == "comp")
328  {
329  m_compButton = button;
330  m_compButton->SetLockable(true);
331  connect(m_compButton, SIGNAL(Clicked()), SLOT(compClicked()));
332  }
333  else if (key.type == "moveleft")
334  connect(button, SIGNAL(Clicked()), SLOT(moveleftClicked()));
335  else if (key.type == "moveright")
336  connect(button, SIGNAL(Clicked()), SLOT(moverightClicked()));
337  else if (key.type == "back")
338  connect(button, SIGNAL(Clicked()), SLOT(backClicked()));
339  }
340  }
341  else
342  {
343  LOG(VB_GENERAL, LOG_WARNING,
344  QString("WARNING - Key '%1' not found in map")
345  .arg(button->objectName()));
346  }
347  }
348  }
349 }
350 
352 {
353  bool handled = false;
354  QStringList actions;
355  handled = GetMythMainWindow()->TranslateKeyPress("TV Frontend", e, actions);
356 
357  if (handled)
358  return true;
359 
360  bool keyFound = false;
361  KeyDefinition key;
362  if (GetFocusWidget())
363  {
364  if (m_keyMap.contains(GetFocusWidget()->objectName()))
365  {
366  key = m_keyMap.value(GetFocusWidget()->objectName());
367  keyFound = true;;
368  }
369  }
370 
371  for (int i = 0; i < actions.size() && !handled; i++)
372  {
373  QString action = actions[i];
374  handled = true;
375 
376  if (action == "UP")
377  {
378  if (keyFound)
379  SetFocusWidget(GetChild(key.up));
380  }
381  else if (action == "DOWN")
382  {
383  if (keyFound)
385  }
386  else if (action == "LEFT")
387  {
388  if (keyFound)
390  }
391  else if (action == "RIGHT")
392  {
393  if (keyFound)
395  }
396  else
397  handled = false;
398  }
399 
400  if (!handled && MythScreenType::keyPressEvent(e))
401  handled = true;
402 
403  return handled;
404 }
405 
407 {
408  if (!GetFocusWidget())
409  return;
410 
411  KeyDefinition key = m_keyMap.value(GetFocusWidget()->objectName());
412  QString c = getKeyText(key);
413 
414  if (m_composing)
415  {
416  if (m_composeStr.isEmpty())
417  m_composeStr = c;
418  else
419  {
420  // Produce the composed key.
421  for (const auto & comp : comps)
422  {
423  if ((m_composeStr == comp[0]) && (c == comp[1]))
424  {
425  c = comp[2];
426 
427  emit keyPressed(c);
428 
429  if (m_parentEdit)
430  {
431  auto *event = new QKeyEvent(QEvent::KeyPress, 0, Qt::NoModifier, c);
432  m_parentEdit->keyPressEvent(event);
433  }
434 
435  break;
436  }
437  }
438 
439  m_composeStr.clear();
440  m_composing = false;
441  if (m_compButton)
442  m_compButton->SetLocked(false);
443  }
444  }
445  else
446  {
447  emit keyPressed(c);
448 
449  if (m_parentEdit)
450  {
451  auto *event = new QKeyEvent(QEvent::KeyPress, 0, Qt::NoModifier, c);
452  m_parentEdit->keyPressEvent(event);
453  }
454 
455  if (m_shift && !m_lock)
456  {
457  m_shift = false;
458  if (m_shiftLButton)
459  m_shiftLButton->SetLocked(false);
460  if (m_shiftRButton)
461  m_shiftRButton->SetLocked(false);
462 
463  updateKeys();
464  }
465  }
466 }
467 
469 {
470  m_shift = !m_shift;
471 
472  if (m_shiftLButton)
474  if (m_shiftRButton)
476  if (m_lockButton && m_lock)
477  {
478  m_lockButton->SetLocked(false);
479  m_lock = false;
480  }
481 
482  updateKeys();
483 }
484 
486 {
487  emit keyPressed("{DELETE}");
488 
489  if (m_parentEdit)
490  {
491  //QKeyEvent *event = new QKeyEvent(QEvent::KeyPress, Qt::Key_Delete, Qt::NoModifier, "");
492  auto *event = new QKeyEvent(QEvent::KeyPress, Qt::Key_Backspace, Qt::NoModifier, "");
493  m_parentEdit->keyPressEvent(event);
494  }
495 }
496 
498 {
499  emit keyPressed("{BACK}");
500 
501  if (m_parentEdit)
502  {
503  auto *event = new QKeyEvent(QEvent::KeyPress, Qt::Key_Backspace, Qt::NoModifier, "");
504  m_parentEdit->keyPressEvent(event);
505  }
506 }
507 
509 {
510  m_lock = !m_lock;
511  m_shift = m_lock;
512 
513  if (m_shiftLButton)
515  if (m_shiftRButton)
517 
518  updateKeys();
519 }
520 
522 {
523  m_alt = !m_alt;
524 
525  updateKeys();
526 }
527 
529 {
531  m_composeStr.clear();
532 }
533 
535 {
536  if (m_shift)
537  {
538  emit keyPressed("{NEWLINE}");
539  auto *event = new QKeyEvent(QEvent::KeyPress, m_newlineKey.keyCode, m_newlineKey.modifiers, "");
540  m_parentEdit->keyPressEvent(event);
541  }
542  else
543  Close();
544 }
545 
547 {
548  if (m_parentEdit)
549  {
550  if (m_shift)
551  {
552  emit keyPressed("{MOVEUP}");
553  auto *event = new QKeyEvent(QEvent::KeyPress, m_upKey.keyCode, m_upKey.modifiers, "");
554  m_parentEdit->keyPressEvent(event);
555  }
556  else
557  {
558  emit keyPressed("{MOVELEFT}");
559  auto *event = new QKeyEvent(QEvent::KeyPress, m_leftKey.keyCode, m_leftKey.modifiers,"");
560  m_parentEdit->keyPressEvent(event);
561  }
562  }
563 }
564 
566 {
567  if (m_parentEdit)
568  {
569  if (m_shift)
570  {
571  emit keyPressed("{MOVEDOWN}");
572  auto *event = new QKeyEvent(QEvent::KeyPress, m_downKey.keyCode, m_downKey.modifiers, "");
573  m_parentEdit->keyPressEvent(event);
574  }
575  else
576  {
577  emit keyPressed("{MOVERIGHT}");
578  auto *event = new QKeyEvent(QEvent::KeyPress, m_rightKey.keyCode, m_rightKey.modifiers,"");
579  m_parentEdit->keyPressEvent(event);
580  }
581  }
582 }
583 
585 {
586  QString res;
587 
588  while (c.length() > 0)
589  {
590  if (c.startsWith("0x"))
591  {
592  QString sCode = c.left(6);
593  bool bOK = false;
594  short nCode = sCode.toShort(&bOK, 16);
595 
596  c = c.mid(6);
597 
598  if (bOK)
599  {
600  QChar uc(nCode);
601  res += QString(uc);
602  }
603  else
604  LOG(VB_GENERAL, LOG_ERR, QString("bad char code (%1)")
605  .arg(sCode));
606  }
607  else
608  {
609  res += c.left(1);
610  c = c.mid(1);
611  }
612  }
613 
614  return res;
615 }
616 
618 {
619 
620  if (m_shift)
621  {
622  if (m_alt)
623  return key.altshift;
624  return key.shift;
625  }
626 
627  if (m_alt)
628  return key.alt;
629 
630  return key.normal;
631 }
632 
634 {
635  QString keylist = MythMainWindow::GetKey("Global", action);
636  QStringList keys = keylist.split(',', QString::SkipEmptyParts);
637  if (keys.empty())
638  return;
639 
640  QKeySequence a(keys[0]);
641  if (a.isEmpty())
642  {
643  LOG(VB_GENERAL, LOG_ERR,
644  QString("loadEventKeyDefinitions bad key (%1)").arg(keys[0]));
645  return;
646  }
647 
648  keyDef->keyCode = a[0];
649 
650  Qt::KeyboardModifiers modifiers = Qt::NoModifier;
651  QStringList parts = keys[0].split('+');
652  for (int j = 0; j < parts.count(); j++)
653  {
654  if (parts[j].toUpper() == "CTRL")
655  modifiers |= Qt::ControlModifier;
656  if (parts[j].toUpper() == "SHIFT")
657  modifiers |= Qt::ShiftModifier;
658  if (parts[j].toUpper() == "ALT")
659  modifiers |= Qt::AltModifier;
660  if (parts[j].toUpper() == "META")
661  modifiers |= Qt::MetaModifier;
662  }
663 
664  keyDef->modifiers = modifiers;
665 }
QString GetLanguageAndVariant(void)
Returns the user-set language and variant.
QList< MythUIType * > * GetAllChildren(void)
Return a list of all child widgets.
Definition: mythuitype.cpp:194
KeyEventDefinition m_downKey
static const int numcomps
bool TranslateKeyPress(const QString &context, QKeyEvent *e, QStringList &actions, bool allowJumps=true)
Get a list of actions for a keypress in the given context.
void setX(const QString &sX)
Definition: mythrect.cpp:458
QRect GetScreenSettings(void)
MythUIVirtualKeyboard(MythScreenStack *parentStack, MythUITextEdit *m_parentEdit)
void keyPressed(QString key)
void parseKey(const QDomElement &element)
KeyEventDefinition m_leftKey
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
void updateKeys(bool connectSignals=false)
The base class on which all widgets and screens are based.
Definition: mythuitype.h:63
void BuildFocusList(void)
void SetPosition(int x, int y)
Convenience method, calls SetPosition(const MythPoint&) Override that instead to change functionality...
Definition: mythuitype.cpp:517
virtual void Close()
QMap< QString, KeyDefinition > m_keyMap
static QString decodeChar(QString c)
void SetLocked(bool locked)
virtual MythRect GetArea(void) const
If the object has a minimum area defined, return it, other wise return the default area.
Definition: mythuitype.cpp:864
A text entry and edit widget.
bool keyPressEvent(QKeyEvent *e) override
Key event handler.
Wrapper around QRect allowing us to handle percentage and other relative values for areas in mythui.
Definition: mythrect.h:17
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
void loadKeyDefinitions(const QString &lang)
MythUIHelper * GetMythUI()
static bool LoadWindowFromXML(const QString &xmlfile, const QString &windowname, MythUIType *parent)
MythUIType * GetFocusWidget(void) const
static QString GetKey(const QString &context, const QString &action)
MythMainWindow * GetMythMainWindow(void)
KeyEventDefinition m_rightKey
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
void setY(const QString &sY)
Definition: mythrect.cpp:468
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
MythUITextEdit * m_parentEdit
static void loadEventKeyDefinitions(KeyEventDefinition *keyDef, const QString &action)
Qt::KeyboardModifiers modifiers
void SetLockable(bool lockable)
Definition: mythuibutton.h:39
void moveTopLeft(const MythPoint &point)
Definition: mythrect.cpp:260
KeyEventDefinition m_upKey
bool Create(void) override
static const QString comps[numcomps][3]
bool SetFocusWidget(MythUIType *widget=nullptr)
Wrapper around QPoint allowing us to handle percentage and other relative values for positioning in m...
Definition: mythrect.h:85
Screen in which all other widgets are contained and rendered.
MythUIButton * m_shiftLButton
KeyEventDefinition m_newlineKey
MythUIType * GetChild(const QString &name) const
Get a named child of this UIType.
Definition: mythuitype.cpp:130
QString getKeyText(const KeyDefinition &key)
PopupPosition GetKeyboardPosition(void)
MythUIButton * m_shiftRButton