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  int screenWidth = 0;
106  int screenHeight = 0;
107  float xmult = 0;
108  float ymult = 0;
109  GetMythUI()->GetScreenSettings(screenWidth, xmult, screenHeight, ymult);
110  MythRect editArea = m_parentEdit->GetArea();
111  MythRect area = GetArea();
112  MythPoint newPos;
113 
114  //FIXME this assumes the edit is a direct child of the parent screen
115  MythUIType *parentScreen = nullptr;
116  parentScreen = dynamic_cast<MythUIType *>(m_parentEdit->parent());
117  if (parentScreen)
118  {
119  editArea.moveTopLeft(QPoint(editArea.x() + parentScreen->GetArea().x(),
120  editArea.y() + parentScreen->GetArea().y()));
121  }
122 
123  switch (m_preferredPos)
124  {
125  case VK_POSABOVEEDIT:
126  if (editArea.y() - area.height() - 5 > 0)
127  {
128  newPos = QPoint(editArea.x() + editArea.width() / 2 - area.width() / 2,
129  editArea.y() - area.height() - 5);
130  }
131  else
132  {
133  newPos = QPoint(editArea.x() + editArea.width() / 2 - area.width() / 2,
134  editArea.y() + editArea.height() + 5);
135  }
136  break;
137 
138  case VK_POSTOPDIALOG:
139  newPos = QPoint(screenWidth / 2 - area.width() / 2, 5);
140  break;
141 
142  case VK_POSBOTTOMDIALOG:
143  newPos = QPoint(screenWidth / 2 - area.width() / 2, screenHeight - 5 - area.height());
144  break;
145 
146  case VK_POSCENTERDIALOG:
147  newPos = QPoint(screenWidth / 2 - area.width() / 2, screenHeight / 2 - area.height() / 2);
148  break;
149 
150  default:
151  // VK_POSBELOWEDIT
152  if (editArea.y() + editArea.height() + area.height() + 5 < screenHeight)
153  {
154  newPos = QPoint(editArea.x() + editArea.width() / 2 - area.width() / 2,
155  editArea.y() + editArea.height() + 5);
156  }
157  else
158  {
159  newPos = QPoint(editArea.x() + editArea.width() / 2 - area.width() / 2,
160  editArea.y() - area.height() - 5);
161  }
162  break;
163  }
164 
165  // make sure the popup doesn't go off screen
166  if (newPos.x() < 5)
167  newPos.setX(5);
168  if (newPos.x() + area.width() + 5 > screenWidth)
169  newPos.setX(screenWidth - area.width() - 5);
170  if (newPos.y() < 5)
171  newPos.setY(5);
172  if (newPos.y() + area.height() + 5 > screenHeight)
173  newPos.setY(screenHeight - area.height() - 5);
174 
175  SetPosition(newPos);
176 
177  return true;
178 }
179 
181 {
182  QString language = lang.toLower();
183 
184  QString defFile = QString("keyboard/%1.xml").arg(language);
185 
186  if (!GetMythUI()->FindThemeFile(defFile))
187  {
188  LOG(VB_GENERAL, LOG_ERR,
189  "No keyboard definition file found for: " + language);
190 
191  // default to US keyboard layout
192  defFile = "keyboard/en_us.xml";
193  if (!GetMythUI()->FindThemeFile(defFile))
194  {
195  LOG(VB_GENERAL, LOG_ERR,
196  "Cannot find definitions file: " + defFile);
197  return;
198  }
199  }
200 
201  LOG(VB_GENERAL, LOG_NOTICE, "Loading definitions from: " + defFile);
202 
203  QDomDocument doc("keydefinitions");
204  QFile file(defFile);
205  if (!file.open(QIODevice::ReadOnly))
206  {
207  LOG(VB_GENERAL, LOG_ERR, "Failed to open definitions file: " + defFile);
208  return;
209  }
210  if (!doc.setContent(&file))
211  {
212  LOG(VB_GENERAL, LOG_ERR,
213  "Failed to parse definitions file: " + defFile);
214  file.close();
215  return;
216  }
217  file.close();
218 
219  QDomElement docElem = doc.documentElement();
220  QDomNode n = docElem.firstChild();
221  while(!n.isNull())
222  {
223  QDomElement e = n.toElement();
224  if(!e.isNull())
225  {
226  if (e.tagName() == "key")
227  parseKey(e);
228  }
229  n = n.nextSibling();
230  }
231 }
232 
233 void MythUIVirtualKeyboard::parseKey(const QDomElement &element)
234 {
235  QString left;
236  QString right;
237  QString up;
238  QString down;
239  QString normal;
240  QString shift;
241  QString alt;
242  QString altshift;
243 
244  QString name = element.attribute("name");
245  QString type = element.attribute("type");
246 
247  QDomNode n = element.firstChild();
248  while(!n.isNull())
249  {
250  QDomElement e = n.toElement();
251  if(!e.isNull())
252  {
253  if (e.tagName() == "move")
254  {
255  left = e.attribute("left");
256  right = e.attribute("right");
257  up = e.attribute("up");
258  down = e.attribute("down");
259  }
260  else if (e.tagName() == "char")
261  {
262  normal = e.attribute("normal");
263  shift = e.attribute("shift");
264  alt = e.attribute("alt");
265  altshift = e.attribute("altshift");
266  }
267  else
268  LOG(VB_GENERAL, LOG_ERR, "Unknown element in key definition");
269  }
270  n = n.nextSibling();
271  }
272 
273  KeyDefinition key;
274  key.name = name;
275  key.type = type;
276  key.left = left;
277  key.right = right;
278  key.up = up;
279  key.down = down;
280  key.normal = decodeChar(normal);
281  key.alt = decodeChar(alt);
282  key.shift = decodeChar(shift);
283  key.altshift = decodeChar(altshift);
284 
285  m_keyMap[name] = key;
286 }
287 
288 void MythUIVirtualKeyboard::updateKeys(bool connectSignals)
289 {
290  QList<MythUIType *> *children = GetAllChildren();
291  for (int i = 0; i < children->size(); ++i)
292  {
293  auto *button = dynamic_cast<MythUIButton *>(children->at(i));
294  if (button)
295  {
296  if (m_keyMap.contains(button->objectName()))
297  {
298  KeyDefinition key = m_keyMap.value(button->objectName());
299  button->SetText(getKeyText(key));
300 
301  if (connectSignals)
302  {
303  if (key.type == "shift")
304  {
305  if (!m_shiftLButton)
306  m_shiftLButton = button;
307  else if (!m_shiftRButton)
308  m_shiftRButton = button;
309 
310  button->SetLockable(true);
311  connect(button, SIGNAL(Clicked()), SLOT(shiftClicked()));
312  }
313  else if (key.type == "char")
314  connect(button, SIGNAL(Clicked()), SLOT(charClicked()));
315  else if (key.type == "done")
316  connect(button, SIGNAL(Clicked()), SLOT(returnClicked()));
317  else if (key.type == "del")
318  connect(button, SIGNAL(Clicked()), SLOT(delClicked()));
319  else if (key.type == "lock")
320  {
321  m_lockButton = button;
322  m_lockButton->SetLockable(true);
323  connect(m_lockButton, SIGNAL(Clicked()), SLOT(lockClicked()));
324  }
325  else if (key.type == "alt")
326  {
327  m_altButton = button;
328  m_altButton->SetLockable(true);
329  connect(m_altButton, SIGNAL(Clicked()), SLOT(altClicked()));
330  }
331  else if (key.type == "comp")
332  {
333  m_compButton = button;
334  m_compButton->SetLockable(true);
335  connect(m_compButton, SIGNAL(Clicked()), SLOT(compClicked()));
336  }
337  else if (key.type == "moveleft")
338  connect(button, SIGNAL(Clicked()), SLOT(moveleftClicked()));
339  else if (key.type == "moveright")
340  connect(button, SIGNAL(Clicked()), SLOT(moverightClicked()));
341  else if (key.type == "back")
342  connect(button, SIGNAL(Clicked()), SLOT(backClicked()));
343  }
344  }
345  else
346  LOG(VB_GENERAL, LOG_WARNING,
347  QString("WARNING - Key '%1' not found in map")
348  .arg(button->objectName()));
349  }
350  }
351 }
352 
354 {
355  bool handled = false;
356  QStringList actions;
357  handled = GetMythMainWindow()->TranslateKeyPress("TV Frontend", e, actions);
358 
359  if (handled)
360  return true;
361 
362  bool keyFound = false;
363  KeyDefinition key;
364  if (GetFocusWidget())
365  {
366  if (m_keyMap.contains(GetFocusWidget()->objectName()))
367  {
368  key = m_keyMap.value(GetFocusWidget()->objectName());
369  keyFound = true;;
370  }
371  }
372 
373  for (int i = 0; i < actions.size() && !handled; i++)
374  {
375  QString action = actions[i];
376  handled = true;
377 
378  if (action == "UP")
379  {
380  if (keyFound)
381  SetFocusWidget(GetChild(key.up));
382  }
383  else if (action == "DOWN")
384  {
385  if (keyFound)
387  }
388  else if (action == "LEFT")
389  {
390  if (keyFound)
392  }
393  else if (action == "RIGHT")
394  {
395  if (keyFound)
397  }
398  else
399  handled = false;
400  }
401 
402  if (!handled && MythScreenType::keyPressEvent(e))
403  handled = true;
404 
405  return handled;
406 }
407 
409 {
410  if (!GetFocusWidget())
411  return;
412 
413  KeyDefinition key = m_keyMap.value(GetFocusWidget()->objectName());
414  QString c = getKeyText(key);
415 
416  if (m_composing)
417  {
418  if (m_composeStr.isEmpty())
419  m_composeStr = c;
420  else
421  {
422  // Produce the composed key.
423  for (int i = 0; i < numcomps; i++)
424  {
425  if ((m_composeStr == comps[i][0]) && (c == comps[i][1]))
426  {
427  c = comps[i][2];
428 
429  emit keyPressed(c);
430 
431  if (m_parentEdit)
432  {
433  auto *event = new QKeyEvent(QEvent::KeyPress, 0, Qt::NoModifier, c);
434  m_parentEdit->keyPressEvent(event);
435  }
436 
437  break;
438  }
439  }
440 
441  m_composeStr.clear();
442  m_composing = false;
443  if (m_compButton)
444  m_compButton->SetLocked(false);
445  }
446  }
447  else
448  {
449  emit keyPressed(c);
450 
451  if (m_parentEdit)
452  {
453  auto *event = new QKeyEvent(QEvent::KeyPress, 0, Qt::NoModifier, c);
454  m_parentEdit->keyPressEvent(event);
455  }
456 
457  if (m_shift && !m_lock)
458  {
459  m_shift = false;
460  if (m_shiftLButton)
461  m_shiftLButton->SetLocked(false);
462  if (m_shiftRButton)
463  m_shiftRButton->SetLocked(false);
464 
465  updateKeys();
466  }
467  }
468 }
469 
471 {
472  m_shift = !m_shift;
473 
474  if (m_shiftLButton)
476  if (m_shiftRButton)
478  if (m_lockButton && m_lock)
479  {
480  m_lockButton->SetLocked(false);
481  m_lock = false;
482  }
483 
484  updateKeys();
485 }
486 
488 {
489  emit keyPressed("{DELETE}");
490 
491  if (m_parentEdit)
492  {
493  //QKeyEvent *event = new QKeyEvent(QEvent::KeyPress, Qt::Key_Delete, Qt::NoModifier, "");
494  auto *event = new QKeyEvent(QEvent::KeyPress, Qt::Key_Backspace, Qt::NoModifier, "");
495  m_parentEdit->keyPressEvent(event);
496  }
497 }
498 
500 {
501  emit keyPressed("{BACK}");
502 
503  if (m_parentEdit)
504  {
505  auto *event = new QKeyEvent(QEvent::KeyPress, Qt::Key_Backspace, Qt::NoModifier, "");
506  m_parentEdit->keyPressEvent(event);
507  }
508 }
509 
511 {
512  m_lock = !m_lock;
513  m_shift = m_lock;
514 
515  if (m_shiftLButton)
517  if (m_shiftRButton)
519 
520  updateKeys();
521 }
522 
524 {
525  m_alt = !m_alt;
526 
527  updateKeys();
528 }
529 
531 {
533  m_composeStr.clear();
534 }
535 
537 {
538  if (m_shift)
539  {
540  emit keyPressed("{NEWLINE}");
541  auto *event = new QKeyEvent(QEvent::KeyPress, m_newlineKey.keyCode, m_newlineKey.modifiers, "");
542  m_parentEdit->keyPressEvent(event);
543  }
544  else
545  Close();
546 }
547 
549 {
550  if (m_parentEdit)
551  {
552  if (m_shift)
553  {
554  emit keyPressed("{MOVEUP}");
555  auto *event = new QKeyEvent(QEvent::KeyPress, m_upKey.keyCode, m_upKey.modifiers, "");
556  m_parentEdit->keyPressEvent(event);
557  }
558  else
559  {
560  emit keyPressed("{MOVELEFT}");
561  auto *event = new QKeyEvent(QEvent::KeyPress, m_leftKey.keyCode, m_leftKey.modifiers,"");
562  m_parentEdit->keyPressEvent(event);
563  }
564  }
565 }
566 
568 {
569  if (m_parentEdit)
570  {
571  if (m_shift)
572  {
573  emit keyPressed("{MOVEDOWN}");
574  auto *event = new QKeyEvent(QEvent::KeyPress, m_downKey.keyCode, m_downKey.modifiers, "");
575  m_parentEdit->keyPressEvent(event);
576  }
577  else
578  {
579  emit keyPressed("{MOVERIGHT}");
580  auto *event = new QKeyEvent(QEvent::KeyPress, m_rightKey.keyCode, m_rightKey.modifiers,"");
581  m_parentEdit->keyPressEvent(event);
582  }
583  }
584 }
585 
587 {
588  QString res;
589 
590  while (c.length() > 0)
591  {
592  if (c.startsWith("0x"))
593  {
594  QString sCode = c.left(6);
595  bool bOK = false;
596  short nCode = sCode.toShort(&bOK, 16);
597 
598  c = c.mid(6);
599 
600  if (bOK)
601  {
602  QChar uc(nCode);
603  res += QString(uc);
604  }
605  else
606  LOG(VB_GENERAL, LOG_ERR, QString("bad char code (%1)")
607  .arg(sCode));
608  }
609  else
610  {
611  res += c.left(1);
612  c = c.mid(1);
613  }
614  }
615 
616  return res;
617 }
618 
620 {
621 
622  if (m_shift)
623  {
624  if (m_alt)
625  return key.altshift;
626  return key.shift;
627  }
628 
629  if (m_alt)
630  return key.alt;
631 
632  return key.normal;
633 }
634 
636 {
637  QString keylist = MythMainWindow::GetKey("Global", action);
638  QStringList keys = keylist.split(',', QString::SkipEmptyParts);
639  if (keys.empty())
640  return;
641 
642  QKeySequence a(keys[0]);
643  if (a.isEmpty())
644  {
645  LOG(VB_GENERAL, LOG_ERR,
646  QString("loadEventKeyDefinitions bad key (%1)").arg(keys[0]));
647  return;
648  }
649 
650  keyDef->keyCode = a[0];
651 
652  Qt::KeyboardModifiers modifiers = Qt::NoModifier;
653  QStringList parts = keys[0].split('+');
654  for (int j = 0; j < parts.count(); j++)
655  {
656  if (parts[j].toUpper() == "CTRL")
657  modifiers |= Qt::ControlModifier;
658  if (parts[j].toUpper() == "SHIFT")
659  modifiers |= Qt::ShiftModifier;
660  if (parts[j].toUpper() == "ALT")
661  modifiers |= Qt::AltModifier;
662  if (parts[j].toUpper() == "META")
663  modifiers |= Qt::MetaModifier;
664  }
665 
666  keyDef->modifiers = modifiers;
667 }
QString GetLanguageAndVariant(void)
Returns the user-set language and variant.
QList< MythUIType * > * GetAllChildren(void)
Return a list of all child widgets.
Definition: mythuitype.cpp:196
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:450
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:519
bool keyPressEvent(QKeyEvent *) override
Key event handler.
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:866
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
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
void setY(const QString &sY)
Definition: mythrect.cpp:460
bool keyPressEvent(QKeyEvent *) override
Key event handler.
#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:252
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:86
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:132
QString getKeyText(const KeyDefinition &key)
PopupPosition GetKeyboardPosition(void)
MythUIButton * m_shiftRButton
void GetScreenSettings(float &wmult, float &hmult)