MythTV  master
mythuitextedit.cpp
Go to the documentation of this file.
1 // Own header
2 #include "mythuitextedit.h"
3 
4 // QT headers
5 #include <QApplication>
6 #include <QRegExp>
7 #include <QChar>
8 #include <QKeyEvent>
9 #include <QDomDocument>
10 #include <Qt>
11 
12 // libmythbase headers
13 #include "mythlogging.h"
14 #include "mythdb.h"
15 
16 // MythUI headers
17 #include "mythpainter.h"
18 #include "mythmainwindow.h"
19 #include "mythfontproperties.h"
20 #include "mythuihelper.h"
21 #include "mythgesture.h"
22 #include "mythuitext.h"
23 #include "mythuistatetype.h"
24 #include "mythuiimage.h"
25 
26 #define LOC QString("MythUITextEdit: ")
27 
28 MythUITextEdit::MythUITextEdit(MythUIType *parent, const QString &name)
29  : MythUIType(parent, name)
30 {
31  m_Message = "";
33 
34  m_isPassword = false;
35 
36  m_blinkInterval = 0;
37  m_cursorBlinkRate = 40;
38 
39  m_Position = -1;
40 
41  m_maxLength = 255;
42 
43  m_backgroundState = nullptr;
44  m_cursorImage = nullptr;
45  m_Text = nullptr;
46 
48 
49  connect(this, SIGNAL(TakingFocus()), SLOT(Select()));
50  connect(this, SIGNAL(LosingFocus()), SLOT(Deselect()));
51 
52  m_CanHaveFocus = true;
53 
54  m_initialized = false;
55 
57 
58  m_composeKey = 0;
59 }
60 
62 {
64  LOG(VB_GENERAL, LOG_ERR, LOC + "selected state doesn't exist");
65 }
66 
68 {
70  LOG(VB_GENERAL, LOG_ERR, LOC + "active state doesn't exist");
71 }
72 
74 {
75  SetText("");
76 }
77 
79 {
80  if (!m_cursorImage)
81  return;
82 
83  if (m_HasFocus)
84  {
85  if (m_lastKeyPress.elapsed() < 500)
86  {
88  m_blinkInterval = 0;
89  }
91  {
92  m_blinkInterval = 0;
93 
94  if (m_cursorImage->IsVisible())
95  m_cursorImage->SetVisible(false);
96  else
98  }
99 
100  m_blinkInterval++;
101  }
102  else
103  m_cursorImage->SetVisible(false);
104 
106 }
107 
109  const QString &filename, QDomElement &element, bool showWarnings)
110 {
111  bool parsed = true;
112 
113  if (element.tagName() == "area")
114  {
115  SetArea(parseRect(element));
116  }
117  else if (element.tagName() == "keyboardposition")
118  {
119  QString pos = getFirstText(element);
120 
121  if (pos == "aboveedit")
123  else if (pos == "belowedit")
125  else if (pos == "screentop")
127  else if (pos == "screenbottom")
129  else if (pos == "screencenter")
131  else
132  {
133  VERBOSE_XML(VB_GENERAL, LOG_ERR, filename, element,
134  QString("Unknown popup position '%1'").arg(pos));
136  }
137  }
138  else
139  {
140  return MythUIType::ParseElement(filename, element, showWarnings);
141  }
142 
143  return parsed;
144 }
145 
147 {
149 
150  // Give it something to chew on, so it can position the initial
151  // cursor in the right place. Toggle text, to force an area recalc.
152  if (m_Text)
153  {
154  m_Text->SetText(".");
155  m_Text->SetText("");
156  }
157 
158  if (m_cursorImage && m_Text)
160 }
161 
163 {
164  if (m_initialized)
165  return;
166 
167  m_initialized = true;
168 
169  m_Text = dynamic_cast<MythUIText *>(GetChild("text"));
170  m_cursorImage = dynamic_cast<MythUIImage *>(GetChild("cursor"));
172  dynamic_cast<MythUIStateType *>(GetChild("background"));
173 
174  if (!m_Text)
175  LOG(VB_GENERAL, LOG_ERR, LOC + "Missing text element.");
176 
177  if (!m_cursorImage)
178  LOG(VB_GENERAL, LOG_ERR, LOC + "Missing cursor element.");
179 
180  if (!m_backgroundState)
181  LOG(VB_GENERAL, LOG_WARNING, LOC + "Missing background element.");
182 
183  if (!m_Text || !m_cursorImage)
184  {
185  m_Text = nullptr;
186  m_cursorImage = nullptr;
187  m_backgroundState = nullptr;
188  return;
189  }
190 
192  LOG(VB_GENERAL, LOG_ERR, LOC + "active state doesn't exist");
193  m_Text->SetCutDown(Qt::ElideNone);
194 
195  QFontMetrics fm(m_Text->GetFontProperties()->face());
196  int height = fm.height();
197 
198  if (height > 0)
199  {
200  MythRect imageArea = m_cursorImage->GetFullArea();
201  int width = int(((float)height / (float)imageArea.height())
202  * (float)imageArea.width());
203 
204  if (width <= 0)
205  width = 1;
206 
207  m_cursorImage->ForceSize(QSize(width, height));
208  }
209 }
210 
211 void MythUITextEdit::SetMaxLength(const int length)
212 {
213  m_maxLength = length;
214 }
215 
216 void MythUITextEdit::SetText(const QString &text, bool moveCursor)
217 {
218  if (!m_Text || (m_Message == text))
219  return;
220 
221  m_Message = text;
222 
223  if (m_isPassword)
224  {
225  QString obscured;
226 
227  obscured.fill('*', m_Message.size());
228  m_Text->SetText(obscured);
229  }
230  else
232 
233  if (moveCursor)
235 
236  emit valueChanged();
237 }
238 
239 void MythUITextEdit::InsertText(const QString &text)
240 {
241  if (!m_Text)
242  return;
243 
244  for (auto c : qAsConst(text))
245  InsertCharacter(c);
246 
247  emit valueChanged();
248 }
249 
250 bool MythUITextEdit::InsertCharacter(const QString &character)
251 {
252  if (m_maxLength != 0 && m_Message.length() == m_maxLength)
253  return false;
254 
255  QString newmessage = m_Message;
256 
257  const QChar *unichar = character.unicode();
258 
259  // Filter all non printable characters
260  if (!unichar->isPrint())
261  return false;
262 
263  if ((m_Filter & FilterAlpha) && unichar->isLetter())
264  return false;
265 
266  if ((m_Filter & FilterNumeric) && unichar->isNumber())
267  return false;
268 
269  if ((m_Filter & FilterSymbols) && unichar->isSymbol())
270  return false;
271 
272  if ((m_Filter & FilterPunct) && unichar->isPunct())
273  return false;
274 
275  newmessage.insert(m_Position + 1, character);
276  SetText(newmessage, false);
278 
279  return true;
280 }
281 
283 {
284  if (m_Message.isEmpty() || position < 0 || position >= m_Message.size())
285  return;
286 
287  QString newmessage = m_Message;
288 
289  newmessage.remove(position, 1);
290  SetText(newmessage, false);
291 
292  if (position == m_Position)
294 }
295 
297 {
298  if (!m_Text || !m_cursorImage)
299  return false;
300 
301  switch (moveDir)
302  {
303  case MoveLeft:
304  if (m_Position < 0)
305  return false;
306  m_Position--;
307  break;
308  case MoveRight:
309  if (m_Position == (m_Message.size() - 1))
310  return false;
311  m_Position++;
312  break;
313  case MoveUp:
314  {
315  int newPos = m_Text->MoveCursor(-1);
316  if (newPos == -1)
317  return false;
318  m_Position = newPos - 1;
319  break;
320  }
321  case MoveDown:
322  {
323  int newPos = m_Text->MoveCursor(1);
324  if (newPos == -1)
325  return false;
326  m_Position = newPos - 1;
327  break;
328  }
329  case MovePageUp:
330  {
331  int lines = m_Text->m_Area.height() / (m_Text->m_lineHeight + m_Text->m_Leading);
332  int newPos = m_Text->MoveCursor(-lines);
333  if (newPos == -1)
334  return false;
335  m_Position = newPos - 1;
336  break;
337  }
338  case MovePageDown:
339  {
340  int lines = m_Text->m_Area.height() / (m_Text->m_lineHeight + m_Text->m_Leading);
341  int newPos = m_Text->MoveCursor(lines);
342  if (newPos == -1)
343  return false;
344  m_Position = newPos - 1;
345  break;
346  }
347  case MoveEnd:
348  m_Position = m_Message.size() - 1;
349  break;
350  }
351 
353 
354  SetRedraw();
355  return true;
356 }
357 
359 {
361  Reset();
362 }
363 
365 {
366  QClipboard *clipboard = QApplication::clipboard();
367 
368  clipboard->setText(m_Message);
369 }
370 
371 void MythUITextEdit::PasteTextFromClipboard(QClipboard::Mode mode)
372 {
373  QClipboard *clipboard = QApplication::clipboard();
374 
375  if (!clipboard->supportsSelection())
376  mode = QClipboard::Clipboard;
377 
378  InsertText(clipboard->text(mode));
379 }
380 
381 using keyCombo = QPair<int, int>;
382 static QMap<keyCombo, int> gDeadKeyMap;
383 
384 static void LoadDeadKeys(QMap<QPair<int, int>, int> &map)
385 {
386  // Dead key // Key // Result
387  map[keyCombo(Qt::Key_Dead_Grave, Qt::Key_A)] = Qt::Key_Agrave;
388  map[keyCombo(Qt::Key_Dead_Acute, Qt::Key_A)] = Qt::Key_Aacute;
389  map[keyCombo(Qt::Key_Dead_Circumflex, Qt::Key_A)] = Qt::Key_Acircumflex;
390  map[keyCombo(Qt::Key_Dead_Tilde, Qt::Key_A)] = Qt::Key_Atilde;
391  map[keyCombo(Qt::Key_Dead_Diaeresis, Qt::Key_A)] = Qt::Key_Adiaeresis;
392  map[keyCombo(Qt::Key_Dead_Abovering, Qt::Key_A)] = Qt::Key_Aring;
393 
394  map[keyCombo(Qt::Key_Dead_Cedilla, Qt::Key_C)] = Qt::Key_Ccedilla;
395 
396  map[keyCombo(Qt::Key_Dead_Grave, Qt::Key_E)] = Qt::Key_Egrave;
397  map[keyCombo(Qt::Key_Dead_Acute, Qt::Key_E)] = Qt::Key_Eacute;
398  map[keyCombo(Qt::Key_Dead_Circumflex, Qt::Key_E)] = Qt::Key_Ecircumflex;
399  map[keyCombo(Qt::Key_Dead_Diaeresis, Qt::Key_E)] = Qt::Key_Ediaeresis;
400 
401  map[keyCombo(Qt::Key_Dead_Grave, Qt::Key_I)] = Qt::Key_Igrave;
402  map[keyCombo(Qt::Key_Dead_Acute, Qt::Key_I)] = Qt::Key_Iacute;
403  map[keyCombo(Qt::Key_Dead_Circumflex, Qt::Key_I)] = Qt::Key_Icircumflex;
404  map[keyCombo(Qt::Key_Dead_Diaeresis, Qt::Key_I)] = Qt::Key_Idiaeresis;
405 
406  map[keyCombo(Qt::Key_Dead_Tilde, Qt::Key_N)] = Qt::Key_Ntilde;
407 
408  map[keyCombo(Qt::Key_Dead_Grave, Qt::Key_O)] = Qt::Key_Ograve;
409  map[keyCombo(Qt::Key_Dead_Acute, Qt::Key_O)] = Qt::Key_Oacute;
410  map[keyCombo(Qt::Key_Dead_Circumflex, Qt::Key_O)] = Qt::Key_Ocircumflex;
411  map[keyCombo(Qt::Key_Dead_Tilde, Qt::Key_O)] = Qt::Key_Otilde;
412  map[keyCombo(Qt::Key_Dead_Diaeresis, Qt::Key_O)] = Qt::Key_Odiaeresis;
413 
414  map[keyCombo(Qt::Key_Dead_Grave, Qt::Key_U)] = Qt::Key_Ugrave;
415  map[keyCombo(Qt::Key_Dead_Acute, Qt::Key_U)] = Qt::Key_Uacute;
416  map[keyCombo(Qt::Key_Dead_Circumflex, Qt::Key_U)] = Qt::Key_Ucircumflex;
417  map[keyCombo(Qt::Key_Dead_Diaeresis, Qt::Key_U)] = Qt::Key_Udiaeresis;
418 
419  map[keyCombo(Qt::Key_Dead_Acute, Qt::Key_Y)] = Qt::Key_Yacute;
420  map[keyCombo(Qt::Key_Dead_Diaeresis, Qt::Key_Y)] = Qt::Key_ydiaeresis;
421 }
422 
423 bool MythUITextEdit::keyPressEvent(QKeyEvent *event)
424 {
426 
427  QStringList actions;
428  bool handled = false;
429 
430  handled = GetMythMainWindow()->TranslateKeyPress("Global", event, actions,
431  false);
432 
433  Qt::KeyboardModifiers modifiers = event->modifiers();
434  int keynum = event->key();
435 
436  if (keynum >= Qt::Key_Shift && keynum <= Qt::Key_CapsLock)
437  return false;
438 
439  QString character;
440  // Compose key handling
441  // Enter composition mode
442  if (((modifiers & Qt::GroupSwitchModifier) != 0U) &&
443  (keynum >= Qt::Key_Dead_Grave) && (keynum <= Qt::Key_Dead_Horn))
444  {
445  m_composeKey = keynum;
446  handled = true;
447  }
448  else if (m_composeKey > 0) // 'Compose' the key
449  {
450  if (gDeadKeyMap.isEmpty())
452 
453  LOG(VB_GUI, LOG_DEBUG, QString("Compose key: %1 Key: %2").arg(QString::number(m_composeKey, 16)).arg(QString::number(keynum, 16)));
454 
455  if (gDeadKeyMap.contains(keyCombo(m_composeKey, keynum)))
456  {
457  int keycode = gDeadKeyMap.value(keyCombo(m_composeKey, keynum));
458 
459  //QKeyEvent key(QEvent::KeyPress, keycode, modifiers);
460  character = QChar(keycode);
461 
462  if ((modifiers & Qt::ShiftModifier) != 0U)
463  character = character.toUpper();
464  else
465  character = character.toLower();
466  LOG(VB_GUI, LOG_DEBUG, QString("Found match for dead-key combo - %1").arg(character));
467  }
468  m_composeKey = 0;
469  }
470 
471  if (character.isEmpty())
472  character = event->text();
473 
474  if (!handled && InsertCharacter(character))
475  handled = true;
476 
477  for (int i = 0; i < actions.size() && !handled; i++)
478  {
479 
480  QString action = actions[i];
481  handled = true;
482 
483  if (action == "LEFT")
484  {
486  }
487  else if (action == "RIGHT")
488  {
490  }
491  else if (action == "UP")
492  {
493  handled = MoveCursor(MoveUp);
494  }
495  else if (action == "DOWN")
496  {
497  handled = MoveCursor(MoveDown);
498  }
499  else if (action == "PAGEUP")
500  {
501  handled = MoveCursor(MovePageUp);
502  }
503  else if (action == "PAGEDOWN")
504  {
505  handled = MoveCursor(MovePageDown);
506  }
507  else if (action == "DELETE")
508  {
510  }
511  else if (action == "BACKSPACE")
512  {
514  }
515  else if (action == "NEWLINE")
516  {
517  QString newmessage = m_Message;
518  newmessage.insert(m_Position + 1, '\n');
519  SetText(newmessage, false);
521  }
522  else if (action == "SELECT" && keynum != Qt::Key_Space
523  && GetMythDB()->GetNumSetting("UseVirtualKeyboard", 1) == 1)
524  {
525  MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
526  auto *kb = new MythUIVirtualKeyboard(popupStack, this);
527 
528  if (kb->Create())
529  {
530  //connect(kb, SIGNAL(keyPress(QString)), SLOT(keyPress(QString)));
531  popupStack->AddScreen(kb);
532  }
533  else
534  delete kb;
535  }
536  else if (action == "CUT")
537  {
539  }
540  else if (action == "COPY")
541  {
543  }
544  else if (action == "PASTE")
545  {
547  }
548  else
549  handled = false;
550  }
551 
552  return handled;
553 }
554 
561 {
562  bool handled = false;
563 
564  if (event->gesture() == MythGestureEvent::Click &&
565  event->GetButton() == MythGestureEvent::MiddleButton)
566  {
567  PasteTextFromClipboard(QClipboard::Selection);
568  }
569 
570  return handled;
571 }
572 
574 {
575  auto *textedit = dynamic_cast<MythUITextEdit *>(base);
576 
577  if (!textedit)
578  {
579  LOG(VB_GENERAL, LOG_ERR, LOC + "ERROR, bad parsing");
580  return;
581  }
582 
583  m_Message.clear();
584  m_Position = -1;
585 
586  m_blinkInterval = textedit->m_blinkInterval;
587  m_cursorBlinkRate = textedit->m_cursorBlinkRate;
588  m_maxLength = textedit->m_maxLength;
589  m_Filter = textedit->m_Filter;
590  m_keyboardPosition = textedit->m_keyboardPosition;
591 
592  MythUIType::CopyFrom(base);
593 
595 }
596 
598 {
599  auto *textedit = new MythUITextEdit(parent, objectName());
600  textedit->CopyFrom(this);
601 }
#define VERBOSE_XML(type, level, filename, element, msg)
Definition: xmlparsebase.h:14
int restart(void)
Returns milliseconds elapsed since last start() or restart() and resets the count.
Definition: mythtimer.cpp:62
void PasteTextFromClipboard(QClipboard::Mode mode=QClipboard::Clipboard)
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 LosingFocus()
static QMap< keyCombo, int > gDeadKeyMap
InputFilter m_Filter
bool MoveCursor(MoveDirection moveDir)
void SetRedraw(void)
Definition: mythuitype.cpp:293
bool IsVisible(bool recurse=false) const
Definition: mythuitype.cpp:882
void RemoveCharacter(int position)
virtual void SetText(const QString &text)
Definition: mythuitext.cpp:135
void CutTextToClipboard(void)
MythScreenStack * GetStack(const QString &stackname)
Gesture gesture(void) const
Get the gesture type.
Definition: mythgesture.h:109
int m_Leading
Definition: mythuitext.h:131
MythUIText * m_Text
The base class on which all widgets and screens are based.
Definition: mythuitype.h:63
void SetPosition(int x, int y)
Convenience method, calls SetPosition(const MythPoint&) Override that instead to change functionality...
Definition: mythuitype.cpp:517
MythUIImage * m_cursorImage
MythUITextEdit(MythUIType *parent, const QString &name)
MythTimer m_lastKeyPress
void Finalize(void) override
Perform any post-xml parsing initialisation tasks.
virtual void SetVisible(bool visible)
virtual void AddScreen(MythScreenType *screen, bool allowFade=true)
void CopyTextToClipboard(void)
QFont face(void) const
void SetMaxLength(int length)
void TakingFocus()
A C++ ripoff of the stroke library for MythTV.
void SetInitialStates(void)
void Reset(void) override
Reset the widget to it's original state, should not reset changes made by the theme.
static QString getFirstText(QDomElement &element)
bool m_CanHaveFocus
Definition: mythuitype.h:238
bool ParseElement(const QString &filename, QDomElement &element, bool showWarnings) override
Parse the xml definition of this widget setting the state of the object accordingly.
virtual void Pulse(void)
Pulse is called 70 times a second to trigger a single frame of an animation.
Definition: mythuitype.cpp:440
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.
MythRect m_Area
Definition: mythuitype.h:249
A custom event that represents a mouse gesture.
Definition: mythgesture.h:39
void CreateCopy(MythUIType *parent) override
Copy the state of this widget to the one given, it must be of the same type.
void SetCutDown(Qt::TextElideMode mode)
Definition: mythuitext.cpp:287
void SetText(const QString &text, bool moveCursor=true)
MythMainWindow * GetMythMainWindow(void)
static void LoadDeadKeys(QMap< QPair< int, int >, int > &map)
virtual void SetArea(const MythRect &rect)
Definition: mythuitype.cpp:589
MythUIStateType * m_backgroundState
int elapsed(void)
Returns milliseconds elapsed since last start() or restart()
Definition: mythtimer.cpp:90
virtual MythRect GetFullArea(void) const
Definition: mythuitype.cpp:872
bool gestureEvent(MythGestureEvent *event) override
Mouse click/movement handler, receives mouse gesture events from the QCoreApplication event loop.
virtual void CopyFrom(MythUIType *base)
Copy this widgets state from another.
bool InsertCharacter(const QString &character)
#define LOC
bool DisplayState(const QString &name)
void InsertText(const QString &text)
int m_lineHeight
Definition: mythuitext.h:133
static MythRect parseRect(const QString &text, bool normalize=true)
void CopyFrom(MythUIType *base) override
Copy this widgets state from another.
MythDB * GetMythDB(void)
Definition: mythdb.cpp:46
int MoveCursor(int lines)
const MythFontProperties * GetFontProperties()
Definition: mythuitext.h:79
void ForceSize(const QSize &size)
Force the dimensions of the widget and image to the given size.
PopupPosition m_keyboardPosition
A popup onscreen keyboard for easy alphanumeric and text entry using a remote control or mouse.
MythUIType * GetChild(const QString &name) const
Get a named child of this UIType.
Definition: mythuitype.cpp:130
void start(void)
starts measuring elapsed time.
Definition: mythtimer.cpp:47
void Pulse(void) override
Pulse is called 70 times a second to trigger a single frame of an animation.
QPoint CursorPosition(int text_offset)
virtual bool ParseElement(const QString &filename, QDomElement &element, bool showWarnings)
Parse the xml definition of this widget setting the state of the object accordingly.
bool m_HasFocus
Definition: mythuitype.h:237
void valueChanged()
QPair< int, int > keyCombo
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23