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 
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  int i = 0;
245 
246  for (; i < text.size(); ++i)
247  {
248  InsertCharacter(text[i]);
249  }
250 
251  emit valueChanged();
252 }
253 
254 bool MythUITextEdit::InsertCharacter(const QString &character)
255 {
256  if (m_maxLength != 0 && m_Message.length() == m_maxLength)
257  return false;
258 
259  QString newmessage = m_Message;
260 
261  const QChar *unichar = character.unicode();
262 
263  // Filter all non printable characters
264  if (!unichar->isPrint())
265  return false;
266 
267  if ((m_Filter & FilterAlpha) && unichar->isLetter())
268  return false;
269 
270  if ((m_Filter & FilterNumeric) && unichar->isNumber())
271  return false;
272 
273  if ((m_Filter & FilterSymbols) && unichar->isSymbol())
274  return false;
275 
276  if ((m_Filter & FilterPunct) && unichar->isPunct())
277  return false;
278 
279  newmessage.insert(m_Position + 1, character);
280  SetText(newmessage, false);
282 
283  return true;
284 }
285 
287 {
288  if (m_Message.isEmpty() || position < 0 || position >= m_Message.size())
289  return;
290 
291  QString newmessage = m_Message;
292 
293  newmessage.remove(position, 1);
294  SetText(newmessage, false);
295 
296  if (position == m_Position)
298 }
299 
301 {
302  if (!m_Text || !m_cursorImage)
303  return false;
304 
305  switch (moveDir)
306  {
307  case MoveLeft:
308  if (m_Position < 0)
309  return false;
310  m_Position--;
311  break;
312  case MoveRight:
313  if (m_Position == (m_Message.size() - 1))
314  return false;
315  m_Position++;
316  break;
317  case MoveUp:
318  {
319  int newPos = m_Text->MoveCursor(-1);
320  if (newPos == -1)
321  return false;
322  m_Position = newPos - 1;
323  break;
324  }
325  case MoveDown:
326  {
327  int newPos = m_Text->MoveCursor(1);
328  if (newPos == -1)
329  return false;
330  m_Position = newPos - 1;
331  break;
332  }
333  case MovePageUp:
334  {
335  int lines = m_Text->m_Area.height() / (m_Text->m_lineHeight + m_Text->m_Leading);
336  int newPos = m_Text->MoveCursor(-lines);
337  if (newPos == -1)
338  return false;
339  m_Position = newPos - 1;
340  break;
341  }
342  case MovePageDown:
343  {
344  int lines = m_Text->m_Area.height() / (m_Text->m_lineHeight + m_Text->m_Leading);
345  int newPos = m_Text->MoveCursor(lines);
346  if (newPos == -1)
347  return false;
348  m_Position = newPos - 1;
349  break;
350  }
351  case MoveEnd:
352  m_Position = m_Message.size() - 1;
353  break;
354  }
355 
357 
358  SetRedraw();
359  return true;
360 }
361 
363 {
365  Reset();
366 }
367 
369 {
370  QClipboard *clipboard = QApplication::clipboard();
371 
372  clipboard->setText(m_Message);
373 }
374 
375 void MythUITextEdit::PasteTextFromClipboard(QClipboard::Mode mode)
376 {
377  QClipboard *clipboard = QApplication::clipboard();
378 
379  if (!clipboard->supportsSelection())
380  mode = QClipboard::Clipboard;
381 
382  InsertText(clipboard->text(mode));
383 }
384 
385 typedef QPair<int, int> keyCombo;
386 static QMap<keyCombo, int> gDeadKeyMap;
387 
388 static void LoadDeadKeys(QMap<QPair<int, int>, int> &map)
389 {
390  // Dead key // Key // Result
391  map[keyCombo(Qt::Key_Dead_Grave, Qt::Key_A)] = Qt::Key_Agrave;
392  map[keyCombo(Qt::Key_Dead_Acute, Qt::Key_A)] = Qt::Key_Aacute;
393  map[keyCombo(Qt::Key_Dead_Circumflex, Qt::Key_A)] = Qt::Key_Acircumflex;
394  map[keyCombo(Qt::Key_Dead_Tilde, Qt::Key_A)] = Qt::Key_Atilde;
395  map[keyCombo(Qt::Key_Dead_Diaeresis, Qt::Key_A)] = Qt::Key_Adiaeresis;
396  map[keyCombo(Qt::Key_Dead_Abovering, Qt::Key_A)] = Qt::Key_Aring;
397 
398  map[keyCombo(Qt::Key_Dead_Cedilla, Qt::Key_C)] = Qt::Key_Ccedilla;
399 
400  map[keyCombo(Qt::Key_Dead_Grave, Qt::Key_E)] = Qt::Key_Egrave;
401  map[keyCombo(Qt::Key_Dead_Acute, Qt::Key_E)] = Qt::Key_Eacute;
402  map[keyCombo(Qt::Key_Dead_Circumflex, Qt::Key_E)] = Qt::Key_Ecircumflex;
403  map[keyCombo(Qt::Key_Dead_Diaeresis, Qt::Key_E)] = Qt::Key_Ediaeresis;
404 
405  map[keyCombo(Qt::Key_Dead_Grave, Qt::Key_I)] = Qt::Key_Igrave;
406  map[keyCombo(Qt::Key_Dead_Acute, Qt::Key_I)] = Qt::Key_Iacute;
407  map[keyCombo(Qt::Key_Dead_Circumflex, Qt::Key_I)] = Qt::Key_Icircumflex;
408  map[keyCombo(Qt::Key_Dead_Diaeresis, Qt::Key_I)] = Qt::Key_Idiaeresis;
409 
410  map[keyCombo(Qt::Key_Dead_Tilde, Qt::Key_N)] = Qt::Key_Ntilde;
411 
412  map[keyCombo(Qt::Key_Dead_Grave, Qt::Key_O)] = Qt::Key_Ograve;
413  map[keyCombo(Qt::Key_Dead_Acute, Qt::Key_O)] = Qt::Key_Oacute;
414  map[keyCombo(Qt::Key_Dead_Circumflex, Qt::Key_O)] = Qt::Key_Ocircumflex;
415  map[keyCombo(Qt::Key_Dead_Tilde, Qt::Key_O)] = Qt::Key_Otilde;
416  map[keyCombo(Qt::Key_Dead_Diaeresis, Qt::Key_O)] = Qt::Key_Odiaeresis;
417 
418  map[keyCombo(Qt::Key_Dead_Grave, Qt::Key_U)] = Qt::Key_Ugrave;
419  map[keyCombo(Qt::Key_Dead_Acute, Qt::Key_U)] = Qt::Key_Uacute;
420  map[keyCombo(Qt::Key_Dead_Circumflex, Qt::Key_U)] = Qt::Key_Ucircumflex;
421  map[keyCombo(Qt::Key_Dead_Diaeresis, Qt::Key_U)] = Qt::Key_Udiaeresis;
422 
423  map[keyCombo(Qt::Key_Dead_Acute, Qt::Key_Y)] = Qt::Key_Yacute;
424  map[keyCombo(Qt::Key_Dead_Diaeresis, Qt::Key_Y)] = Qt::Key_ydiaeresis;
425 }
426 
427 bool MythUITextEdit::keyPressEvent(QKeyEvent *event)
428 {
430 
431  QStringList actions;
432  bool handled = false;
433 
434  handled = GetMythMainWindow()->TranslateKeyPress("Global", event, actions,
435  false);
436 
437  Qt::KeyboardModifiers modifiers = event->modifiers();
438  int keynum = event->key();
439 
440  if (keynum >= Qt::Key_Shift && keynum <= Qt::Key_CapsLock)
441  return false;
442 
443  QString character;
444  // Compose key handling
445  // Enter composition mode
446  if (((modifiers & Qt::GroupSwitchModifier) != 0U) &&
447  (keynum >= Qt::Key_Dead_Grave) && (keynum <= Qt::Key_Dead_Horn))
448  {
449  m_composeKey = keynum;
450  handled = true;
451  }
452  else if (m_composeKey > 0) // 'Compose' the key
453  {
454  if (gDeadKeyMap.isEmpty())
456 
457  LOG(VB_GUI, LOG_DEBUG, QString("Compose key: %1 Key: %2").arg(QString::number(m_composeKey, 16)).arg(QString::number(keynum, 16)));
458 
459  if (gDeadKeyMap.contains(keyCombo(m_composeKey, keynum)))
460  {
461  int keycode = gDeadKeyMap.value(keyCombo(m_composeKey, keynum));
462 
463  //QKeyEvent key(QEvent::KeyPress, keycode, modifiers);
464  character = QChar(keycode);
465 
466  if ((modifiers & Qt::ShiftModifier) != 0U)
467  character = character.toUpper();
468  else
469  character = character.toLower();
470  LOG(VB_GUI, LOG_DEBUG, QString("Found match for dead-key combo - %1").arg(character));
471  }
472  m_composeKey = 0;
473  }
474 
475  if (character.isEmpty())
476  character = event->text();
477 
478  if (!handled && InsertCharacter(character))
479  handled = true;
480 
481  for (int i = 0; i < actions.size() && !handled; i++)
482  {
483 
484  QString action = actions[i];
485  handled = true;
486 
487  if (action == "LEFT")
488  {
490  }
491  else if (action == "RIGHT")
492  {
494  }
495  else if (action == "UP")
496  {
497  handled = MoveCursor(MoveUp);
498  }
499  else if (action == "DOWN")
500  {
501  handled = MoveCursor(MoveDown);
502  }
503  else if (action == "PAGEUP")
504  {
505  handled = MoveCursor(MovePageUp);
506  }
507  else if (action == "PAGEDOWN")
508  {
509  handled = MoveCursor(MovePageDown);
510  }
511  else if (action == "DELETE")
512  {
514  }
515  else if (action == "BACKSPACE")
516  {
518  }
519  else if (action == "NEWLINE")
520  {
521  QString newmessage = m_Message;
522  newmessage.insert(m_Position + 1, '\n');
523  SetText(newmessage, false);
525  }
526  else if (action == "SELECT" && keynum != Qt::Key_Space
527  && GetMythDB()->GetNumSetting("UseVirtualKeyboard", 1) == 1)
528  {
529  MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
530  MythUIVirtualKeyboard *kb = new MythUIVirtualKeyboard(popupStack, this);
531 
532  if (kb->Create())
533  {
534  //connect(kb, SIGNAL(keyPress(QString)), SLOT(keyPress(QString)));
535  popupStack->AddScreen(kb);
536  }
537  else
538  delete kb;
539  }
540  else if (action == "CUT")
541  {
543  }
544  else if (action == "COPY")
545  {
547  }
548  else if (action == "PASTE")
549  {
551  }
552  else
553  handled = false;
554  }
555 
556  return handled;
557 }
558 
565 {
566  bool handled = false;
567 
568  if (event->gesture() == MythGestureEvent::Click &&
569  event->GetButton() == MythGestureEvent::MiddleButton)
570  {
571  PasteTextFromClipboard(QClipboard::Selection);
572  }
573 
574  return handled;
575 }
576 
578 {
579  MythUITextEdit *textedit = dynamic_cast<MythUITextEdit *>(base);
580 
581  if (!textedit)
582  {
583  LOG(VB_GENERAL, LOG_ERR, LOC + "ERROR, bad parsing");
584  return;
585  }
586 
587  m_Message.clear();
588  m_Position = -1;
589 
590  m_blinkInterval = textedit->m_blinkInterval;
592  m_maxLength = textedit->m_maxLength;
593  m_Filter = textedit->m_Filter;
595 
596  MythUIType::CopyFrom(base);
597 
599 }
600 
602 {
603  MythUITextEdit *textedit = new MythUITextEdit(parent, objectName());
604  textedit->CopyFrom(this);
605 }
#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
void SetRedraw(void)
Definition: mythuitype.cpp:295
bool IsVisible(bool recurse=false) const
Definition: mythuitype.cpp:881
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:107
bool gestureEvent(MythGestureEvent *) override
Mouse click/movement handler, receives mouse gesture events from the QCoreApplication event loop.
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:519
MythUIImage * m_cursorImage
bool keyPressEvent(QKeyEvent *) override
Key event handler.
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 SetMaxLength(const int length)
void CopyTextToClipboard(void)
QFont face(void) const
void TakingFocus()
QPair< int, int > keyCombo
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)
A text entry and edit widget.
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:442
Wrapper around QRect allowing us to handle percentage and other relative values for areas in mythui.
Definition: mythrect.h:17
MythRect m_Area
Definition: mythuitype.h:249
A custom event that represents a mouse gesture.
Definition: mythgesture.h:39
const char * name
Definition: ParseText.cpp:328
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)
int elapsed(void) const
Returns milliseconds elapsed since last start() or restart()
Definition: mythtimer.cpp:90
static void LoadDeadKeys(QMap< QPair< int, int >, int > &map)
virtual void SetArea(const MythRect &rect)
Definition: mythuitype.cpp:591
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
MythUIStateType * m_backgroundState
virtual MythRect GetFullArea(void) const
Definition: mythuitype.cpp:871
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
bool MoveCursor(MoveDirection)
static MythRect parseRect(const QString &text, bool normalize=true)
bool Create(void) override
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:132
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()