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