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 <QChar>
7#include <QKeyEvent>
8#include <QDomDocument>
9#include <QInputMethodEvent>
10#include <Qt>
11
12// libmythbase headers
13#include "libmythbase/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
28MythUITextEdit::MythUITextEdit(MythUIType *parent, const QString &name)
29 : MythUIType(parent, name),
30 m_message("")
31{
32 connect(this, &MythUIType::TakingFocus, this, &MythUITextEdit::Select);
34
35 m_canHaveFocus = true;
36
38 m_messageBak.clear();
39}
40
42{
44 LOG(VB_GENERAL, LOG_ERR, LOC + "selected state doesn't exist");
45}
46
48{
50 LOG(VB_GENERAL, LOG_ERR, LOC + "active state doesn't exist");
51}
52
54{
55 SetText("");
56}
57
59{
60 if (!m_cursorImage)
61 return;
62
63 if (m_hasFocus)
64 {
65 if (m_lastKeyPress.elapsed() < 500ms)
66 {
69 }
71 {
73
76 else
78 }
79
81 }
82 else
83 {
85 }
86
88}
89
91 const QString &filename, QDomElement &element, bool showWarnings)
92{
93 bool parsed = true;
94
95 if (element.tagName() == "area")
96 {
97 SetArea(parseRect(element));
98 }
99 else if (element.tagName() == "keyboardposition")
100 {
101 QString pos = getFirstText(element);
102
103 if (pos == "aboveedit") {
105 } else if (pos == "belowedit") {
107 } else if (pos == "screentop") {
109 } else if (pos == "screenbottom") {
111 } else if (pos == "screencenter") {
113 } else {
114 VERBOSE_XML(VB_GENERAL, LOG_ERR, filename, element,
115 QString("Unknown popup position '%1'").arg(pos));
117 }
118 }
119 else
120 {
121 return MythUIType::ParseElement(filename, element, showWarnings);
122 }
123
124 return parsed;
125}
126
128{
130
131 // Give it something to chew on, so it can position the initial
132 // cursor in the right place. Toggle text, to force an area recalc.
133 if (m_text)
134 {
135 m_text->SetText(".");
136 m_text->SetText("");
137 }
138
139 if (m_cursorImage && m_text)
141}
142
144{
145 if (m_initialized)
146 return;
147
148 m_initialized = true;
149
150 m_text = dynamic_cast<MythUIText *>(GetChild("text"));
151 m_cursorImage = dynamic_cast<MythUIImage *>(GetChild("cursor"));
153 dynamic_cast<MythUIStateType *>(GetChild("background"));
154
155 if (!m_text)
156 LOG(VB_GENERAL, LOG_ERR, LOC + "Missing text element.");
157
158 if (!m_cursorImage)
159 LOG(VB_GENERAL, LOG_ERR, LOC + "Missing cursor element.");
160
162 LOG(VB_GENERAL, LOG_WARNING, LOC + "Missing background element.");
163
164 if (!m_text || !m_cursorImage)
165 {
166 m_text = nullptr;
167 m_cursorImage = nullptr;
168 m_backgroundState = nullptr;
169 return;
170 }
171
173 LOG(VB_GENERAL, LOG_ERR, LOC + "active state doesn't exist");
174 m_text->SetCutDown(Qt::ElideNone);
175
176 QFontMetrics fm(m_text->GetFontProperties()->face());
177 int height = fm.height();
178
179 if (height > 0)
180 {
181 MythRect imageArea = m_cursorImage->GetFullArea();
182 int width = int(((float)height / (float)imageArea.height())
183 * (float)imageArea.width());
184
185 if (width <= 0)
186 width = 1;
187
188 m_cursorImage->ForceSize(QSize(width, height));
189 }
190}
191
192void MythUITextEdit::SetMaxLength(const int length)
193{
194 m_maxLength = length;
195}
196
197void MythUITextEdit::SetText(const QString &text, bool moveCursor)
198{
199 if (!m_text || (m_message == text))
200 return;
201
202 m_message = text;
203
204 if (m_isPassword)
205 {
206 QString obscured;
207
208 obscured.fill('*', m_message.size());
209 m_text->SetText(obscured);
210 }
211 else
212 {
214 }
215
216 if (moveCursor)
218
219 emit valueChanged();
220}
221
222void MythUITextEdit::InsertText(const QString &text)
223{
224 if (!m_text)
225 return;
226
227 for (const auto& c : std::as_const(text))
229
230 emit valueChanged();
231}
232
233bool MythUITextEdit::InsertCharacter(const QString &character)
234{
235 if (m_maxLength != 0 && m_message.length() == m_maxLength)
236 return false;
237
238 QString newmessage = m_message;
239
240 const QChar *unichar = character.unicode();
241
242 // Filter all non printable characters
243 if (!unichar->isPrint())
244 return false;
245
246 if ((m_filter & FilterAlpha) && unichar->isLetter())
247 return false;
248
249 if ((m_filter & FilterNumeric) && unichar->isNumber())
250 return false;
251
252 if ((m_filter & FilterSymbols) && unichar->isSymbol())
253 return false;
254
255 if ((m_filter & FilterPunct) && unichar->isPunct())
256 return false;
257
258 newmessage.insert(m_position + 1, character);
259 SetText(newmessage, false);
261
262 return true;
263}
264
265// This is used for updating IME.
266bool MythUITextEdit::UpdateTmpString(const QString &str)
267{
268 if (!m_text)
269 return false;
270
271 if (str.isEmpty())
272 return false;
273 QString newmessage = m_message;
274 newmessage.append(str);
275 SetText(newmessage, false);
276 return true;
277}
278
280{
281 if (m_message.isEmpty() || position < 0 || position >= m_message.size())
282 return;
283
284 QString newmessage = m_message;
285
286 newmessage.remove(position, 1);
287 SetText(newmessage, false);
288
289 if (position == m_position)
291}
292
294{
295 if (!m_text || !m_cursorImage)
296 return false;
297
298 switch (moveDir)
299 {
300 case MoveLeft:
301 if (m_position < 0)
302 return false;
303 m_position--;
304 break;
305 case MoveRight:
306 if (m_position == (m_message.size() - 1))
307 return false;
308 m_position++;
309 break;
310 case MoveUp:
311 {
312 int newPos = m_text->MoveCursor(-1);
313 if (newPos == -1)
314 return false;
315 m_position = newPos - 1;
316 break;
317 }
318 case MoveDown:
319 {
320 int newPos = m_text->MoveCursor(1);
321 if (newPos == -1)
322 return false;
323 m_position = newPos - 1;
324 break;
325 }
326 case MovePageUp:
327 {
328 int lines = m_text->m_area.height() / (m_text->m_lineHeight + m_text->m_leading);
329 int newPos = m_text->MoveCursor(-lines);
330 if (newPos == -1)
331 return false;
332 m_position = newPos - 1;
333 break;
334 }
335 case MovePageDown:
336 {
337 int lines = m_text->m_area.height() / (m_text->m_lineHeight + m_text->m_leading);
338 int newPos = m_text->MoveCursor(lines);
339 if (newPos == -1)
340 return false;
341 m_position = newPos - 1;
342 break;
343 }
344 case MoveEnd:
345 m_position = m_message.size() - 1;
346 break;
347 }
348
350
351 SetRedraw();
352 return true;
353}
354
356{
358 Reset();
359}
360
362{
363 QClipboard *clipboard = QApplication::clipboard();
364
365 clipboard->setText(m_message);
366}
367
369{
370 QClipboard *clipboard = QApplication::clipboard();
371
372 if (!clipboard->supportsSelection())
373 mode = QClipboard::Clipboard;
374
375 InsertText(clipboard->text(mode));
376}
377
378using keyCombo = QPair<int, int>;
379static QMap<keyCombo, int> gDeadKeyMap;
380
381static void LoadDeadKeys(QMap<QPair<int, int>, int> &map)
382{
383 // Dead key // Key // Result
384 map[keyCombo(Qt::Key_Dead_Grave, Qt::Key_A)] = Qt::Key_Agrave;
385 map[keyCombo(Qt::Key_Dead_Acute, Qt::Key_A)] = Qt::Key_Aacute;
386 map[keyCombo(Qt::Key_Dead_Circumflex, Qt::Key_A)] = Qt::Key_Acircumflex;
387 map[keyCombo(Qt::Key_Dead_Tilde, Qt::Key_A)] = Qt::Key_Atilde;
388 map[keyCombo(Qt::Key_Dead_Diaeresis, Qt::Key_A)] = Qt::Key_Adiaeresis;
389 map[keyCombo(Qt::Key_Dead_Abovering, Qt::Key_A)] = Qt::Key_Aring;
390
391 map[keyCombo(Qt::Key_Dead_Cedilla, Qt::Key_C)] = Qt::Key_Ccedilla;
392
393 map[keyCombo(Qt::Key_Dead_Grave, Qt::Key_E)] = Qt::Key_Egrave;
394 map[keyCombo(Qt::Key_Dead_Acute, Qt::Key_E)] = Qt::Key_Eacute;
395 map[keyCombo(Qt::Key_Dead_Circumflex, Qt::Key_E)] = Qt::Key_Ecircumflex;
396 map[keyCombo(Qt::Key_Dead_Diaeresis, Qt::Key_E)] = Qt::Key_Ediaeresis;
397
398 map[keyCombo(Qt::Key_Dead_Grave, Qt::Key_I)] = Qt::Key_Igrave;
399 map[keyCombo(Qt::Key_Dead_Acute, Qt::Key_I)] = Qt::Key_Iacute;
400 map[keyCombo(Qt::Key_Dead_Circumflex, Qt::Key_I)] = Qt::Key_Icircumflex;
401 map[keyCombo(Qt::Key_Dead_Diaeresis, Qt::Key_I)] = Qt::Key_Idiaeresis;
402
403 map[keyCombo(Qt::Key_Dead_Tilde, Qt::Key_N)] = Qt::Key_Ntilde;
404
405 map[keyCombo(Qt::Key_Dead_Grave, Qt::Key_O)] = Qt::Key_Ograve;
406 map[keyCombo(Qt::Key_Dead_Acute, Qt::Key_O)] = Qt::Key_Oacute;
407 map[keyCombo(Qt::Key_Dead_Circumflex, Qt::Key_O)] = Qt::Key_Ocircumflex;
408 map[keyCombo(Qt::Key_Dead_Tilde, Qt::Key_O)] = Qt::Key_Otilde;
409 map[keyCombo(Qt::Key_Dead_Diaeresis, Qt::Key_O)] = Qt::Key_Odiaeresis;
410
411 map[keyCombo(Qt::Key_Dead_Grave, Qt::Key_U)] = Qt::Key_Ugrave;
412 map[keyCombo(Qt::Key_Dead_Acute, Qt::Key_U)] = Qt::Key_Uacute;
413 map[keyCombo(Qt::Key_Dead_Circumflex, Qt::Key_U)] = Qt::Key_Ucircumflex;
414 map[keyCombo(Qt::Key_Dead_Diaeresis, Qt::Key_U)] = Qt::Key_Udiaeresis;
415
416 map[keyCombo(Qt::Key_Dead_Acute, Qt::Key_Y)] = Qt::Key_Yacute;
417 map[keyCombo(Qt::Key_Dead_Diaeresis, Qt::Key_Y)] = Qt::Key_ydiaeresis;
418}
419
420bool MythUITextEdit::inputMethodEvent(QInputMethodEvent *event)
421{
422 // 1st test.
423 if (m_isPassword)
424 return false;
425
426 bool _bak = m_isIMEinput;
427 if (!m_isIMEinput && (event->commitString().isEmpty() || event->preeditString().isEmpty()))
428 {
429 m_isIMEinput = true;
431 }
432#if 0
433 printf("IME: %s->%s PREEDIT=\"%s\" COMMIT=\"%s\"\n"
434 , (_bak) ? "ON" : "OFF"
435 , (m_isIMEinput) ? "ON" : "OFF"
436 , event->preeditString().toUtf8().constData()
437 , event->commitString().toUtf8().constData());
438#endif
439 if (!event->commitString().isEmpty() && m_isIMEinput)
440 {
442 m_messageBak.clear();
443 InsertText(event->commitString());
444 m_isIMEinput = false;
445 return true; // commited
446 }
447 if (m_isIMEinput && !event->preeditString().isEmpty())
448 {
450 UpdateTmpString(event->preeditString());
451 return true; // preedited
452 }
453 if (m_isIMEinput && _bak)
454 { // Abort?
455 m_isIMEinput = false;
456 QString newmessage= m_messageBak;
457 m_messageBak.clear();
458 SetText(newmessage, true);
459 return true;
460 }
461 return true; // Not commited
462}
463
464bool MythUITextEdit::keyPressEvent(QKeyEvent *event)
465{
466 if (m_isIMEinput) // Prefer IME then keyPress.
467 return true;
469
470 QStringList actions;
471 bool handled = false;
472
473 handled = GetMythMainWindow()->TranslateKeyPress("Global", event, actions,
474 false);
475
476 Qt::KeyboardModifiers modifiers = event->modifiers();
477 int keynum = event->key();
478
479 if (keynum >= Qt::Key_Shift && keynum <= Qt::Key_CapsLock)
480 return false;
481
482 QString character;
483 // Compose key handling
484 // Enter composition mode
485 if (((modifiers & Qt::GroupSwitchModifier) != 0U) &&
486 (keynum >= Qt::Key_Dead_Grave) && (keynum <= Qt::Key_Dead_Horn))
487 {
488 m_composeKey = keynum;
489 handled = true;
490 }
491 else if (m_composeKey > 0) // 'Compose' the key
492 {
493 if (gDeadKeyMap.isEmpty())
495
496 LOG(VB_GUI, LOG_DEBUG, QString("Compose key: %1 Key: %2")
497 .arg(QString::number(m_composeKey, 16), QString::number(keynum, 16)));
498
499 if (gDeadKeyMap.contains(keyCombo(m_composeKey, keynum)))
500 {
501 int keycode = gDeadKeyMap.value(keyCombo(m_composeKey, keynum));
502
503 //QKeyEvent key(QEvent::KeyPress, keycode, modifiers);
504 character = QChar(keycode);
505
506 if ((modifiers & Qt::ShiftModifier) != 0U)
507 character = character.toUpper();
508 else
509 character = character.toLower();
510 LOG(VB_GUI, LOG_DEBUG, QString("Found match for dead-key combo - %1").arg(character));
511 }
512 m_composeKey = 0;
513 }
514
515 if (character.isEmpty())
516 character = event->text();
517
518 if (!handled && InsertCharacter(character))
519 handled = true;
520
521 for (int i = 0; i < actions.size() && !handled; i++)
522 {
523
524 const QString& action = actions[i];
525 handled = true;
526
527 if (action == "LEFT")
528 {
530 }
531 else if (action == "RIGHT")
532 {
534 }
535 else if (action == "UP")
536 {
537 handled = MoveCursor(MoveUp);
538 }
539 else if (action == "DOWN")
540 {
541 handled = MoveCursor(MoveDown);
542 }
543 else if (action == "PAGEUP")
544 {
545 handled = MoveCursor(MovePageUp);
546 }
547 else if (action == "PAGEDOWN")
548 {
549 handled = MoveCursor(MovePageDown);
550 }
551 else if (action == "DELETE")
552 {
554 }
555 else if (action == "BACKSPACE")
556 {
558 }
559 else if (action == "NEWLINE")
560 {
561 QString newmessage = m_message;
562 newmessage.insert(m_position + 1, '\n');
563 SetText(newmessage, false);
565 }
566 else if (action == "SELECT" && keynum != Qt::Key_Space
567 && GetMythDB()->GetNumSetting("UseVirtualKeyboard", 1) == 1)
568 {
569 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
570 auto *kb = new MythUIVirtualKeyboard(popupStack, this);
571
572 if (kb->Create())
573 {
574 popupStack->AddScreen(kb);
575 }
576 else
577 {
578 delete kb;
579 }
580 }
581 else if (action == "CUT")
582 {
584 }
585 else if (action == "COPY")
586 {
588 }
589 else if (action == "PASTE")
590 {
592 }
593 else
594 {
595 handled = false;
596 }
597 }
598
599 return handled;
600}
601
608{
609 bool handled = false;
610
611 if (event->GetGesture() == MythGestureEvent::Click &&
612 event->GetButton() == Qt::MiddleButton)
613 {
614 PasteTextFromClipboard(QClipboard::Selection);
615 }
616
617 return handled;
618}
619
621{
622 auto *textedit = dynamic_cast<MythUITextEdit *>(base);
623
624 if (!textedit)
625 {
626 LOG(VB_GENERAL, LOG_ERR, LOC + "ERROR, bad parsing");
627 return;
628 }
629
630 m_message.clear();
631 m_position = -1;
632
633 m_blinkInterval = textedit->m_blinkInterval;
634 m_cursorBlinkRate = textedit->m_cursorBlinkRate;
635 m_maxLength = textedit->m_maxLength;
636 m_filter = textedit->m_filter;
637 m_keyboardPosition = textedit->m_keyboardPosition;
638
640
642}
643
645{
646 auto *textedit = new MythUITextEdit(parent, objectName());
647 textedit->CopyFrom(this);
648}
QFont face(void) const
A custom event that represents a mouse gesture.
Definition: mythgesture.h:40
Gesture GetGesture() const
Definition: mythgesture.h:85
Qt::MouseButton GetButton() const
Definition: mythgesture.h:88
bool TranslateKeyPress(const QString &Context, QKeyEvent *Event, QStringList &Actions, bool AllowJumps=true)
Get a list of actions for a keypress in the given context.
MythScreenStack * GetStack(const QString &Stackname)
Wrapper around QRect allowing us to handle percentage and other relative values for areas in mythui.
Definition: mythrect.h:18
virtual void AddScreen(MythScreenType *screen, bool allowFade=true)
std::chrono::milliseconds restart(void)
Returns milliseconds elapsed since last start() or restart() and resets the count.
Definition: mythtimer.cpp:62
std::chrono::milliseconds elapsed(void)
Returns milliseconds elapsed since last start() or restart()
Definition: mythtimer.cpp:91
void start(void)
starts measuring elapsed time.
Definition: mythtimer.cpp:47
Image widget, displays a single image or multiple images in sequence.
Definition: mythuiimage.h:98
void ForceSize(QSize size)
Force the dimensions of the widget and image to the given size.
This widget is used for grouping other widgets for display when a particular named state is called.
bool DisplayState(const QString &name)
A text entry and edit widget.
bool gestureEvent(MythGestureEvent *event) override
Mouse click/movement handler, receives mouse gesture events from the QCoreApplication event loop.
MythUIImage * m_cursorImage
bool InsertCharacter(const QString &character)
bool keyPressEvent(QKeyEvent *event) override
Key event handler.
void SetInitialStates(void)
MythUIText * m_text
void PasteTextFromClipboard(QClipboard::Mode mode=QClipboard::Clipboard)
bool UpdateTmpString(const QString &str)
void SetText(const QString &text, bool moveCursor=true)
void RemoveCharacter(int position)
InputFilter m_filter
MythUITextEdit(MythUIType *parent, const QString &name)
PopupPosition m_keyboardPosition
void SetMaxLength(int length)
void CopyTextToClipboard(void)
void CreateCopy(MythUIType *parent) override
Copy the state of this widget to the one given, it must be of the same type.
MythTimer m_lastKeyPress
void valueChanged()
MythUIStateType * m_backgroundState
void Reset(void) override
Reset the widget to it's original state, should not reset changes made by the theme.
void InsertText(const QString &text)
bool ParseElement(const QString &filename, QDomElement &element, bool showWarnings) override
Parse the xml definition of this widget setting the state of the object accordingly.
void Pulse(void) override
Pulse is called 70 times a second to trigger a single frame of an animation.
void CutTextToClipboard(void)
void Finalize(void) override
Perform any post-xml parsing initialisation tasks.
bool MoveCursor(MoveDirection moveDir)
void CopyFrom(MythUIType *base) override
Copy this widgets state from another.
QString m_messageBak
bool inputMethodEvent(QInputMethodEvent *event) override
Input Method event handler.
All purpose text widget, displays a text string.
Definition: mythuitext.h:29
int MoveCursor(int lines)
int m_leading
Definition: mythuitext.h:131
QPoint CursorPosition(int text_offset)
const MythFontProperties * GetFontProperties()
Definition: mythuitext.h:81
int m_lineHeight
Definition: mythuitext.h:133
void SetCutDown(Qt::TextElideMode mode)
Definition: mythuitext.cpp:265
virtual void SetText(const QString &text)
Definition: mythuitext.cpp:115
The base class on which all widgets and screens are based.
Definition: mythuitype.h:97
bool IsVisible(bool recurse=false) const
Definition: mythuitype.cpp:889
void TakingFocus(void)
virtual void SetVisible(bool visible)
virtual void SetArea(const MythRect &rect)
Definition: mythuitype.cpp:596
virtual void CopyFrom(MythUIType *base)
Copy this widgets state from another.
void SetRedraw(void)
Definition: mythuitype.cpp:299
virtual void Pulse(void)
Pulse is called 70 times a second to trigger a single frame of an animation.
Definition: mythuitype.cpp:442
bool m_canHaveFocus
Definition: mythuitype.h:276
virtual MythRect GetFullArea(void) const
Definition: mythuitype.cpp:879
bool m_hasFocus
Definition: mythuitype.h:275
void SetPosition(int x, int y)
Convenience method, calls SetPosition(const MythPoint&) Override that instead to change functionality...
Definition: mythuitype.cpp:519
MythUIType * GetChild(const QString &name) const
Get a named child of this UIType.
Definition: mythuitype.cpp:130
virtual bool ParseElement(const QString &filename, QDomElement &element, bool showWarnings)
Parse the xml definition of this widget setting the state of the object accordingly.
MythRect m_area
Definition: mythuitype.h:288
void LosingFocus(void)
A popup onscreen keyboard for easy alphanumeric and text entry using a remote control or mouse.
static MythRect parseRect(const QString &text, bool normalize=true)
static QString getFirstText(QDomElement &element)
MythDB * GetMythDB(void)
Definition: mythdb.cpp:50
A C++ ripoff of the stroke library for MythTV.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MythMainWindow * GetMythMainWindow(void)
#define LOC
QPair< int, int > keyCombo
static QMap< keyCombo, int > gDeadKeyMap
static void LoadDeadKeys(QMap< QPair< int, int >, int > &map)
@ FilterAlpha
@ FilterPunct
@ FilterSymbols
@ FilterNumeric
@ VK_POSBOTTOMDIALOG
@ VK_POSCENTERDIALOG
@ VK_POSBELOWEDIT
@ VK_POSABOVEEDIT
@ VK_POSTOPDIALOG
Mode
Definition: synaesthesia.h:20
#define VERBOSE_XML(type, level, filename, element, msg)
Definition: xmlparsebase.h:15