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 {
115 VERBOSE_XML(VB_GENERAL, LOG_ERR, filename, element,
116 QString("Unknown popup position '%1'").arg(pos));
118 }
119 }
120 else
121 {
122 return MythUIType::ParseElement(filename, element, showWarnings);
123 }
124
125 return parsed;
126}
127
129{
131
132 // Give it something to chew on, so it can position the initial
133 // cursor in the right place. Toggle text, to force an area recalc.
134 if (m_text)
135 {
136 m_text->SetText(".");
137 m_text->SetText("");
138 }
139
140 if (m_cursorImage && m_text)
142}
143
145{
146 if (m_initialized)
147 return;
148
149 m_initialized = true;
150
151 m_text = dynamic_cast<MythUIText *>(GetChild("text"));
152 m_cursorImage = dynamic_cast<MythUIImage *>(GetChild("cursor"));
154 dynamic_cast<MythUIStateType *>(GetChild("background"));
155
156 if (!m_text)
157 LOG(VB_GENERAL, LOG_ERR, LOC + "Missing text element.");
158
159 if (!m_cursorImage)
160 LOG(VB_GENERAL, LOG_ERR, LOC + "Missing cursor element.");
161
163 LOG(VB_GENERAL, LOG_WARNING, LOC + "Missing background element.");
164
165 if (!m_text || !m_cursorImage)
166 {
167 m_text = nullptr;
168 m_cursorImage = nullptr;
169 m_backgroundState = nullptr;
170 return;
171 }
172
174 LOG(VB_GENERAL, LOG_ERR, LOC + "active state doesn't exist");
175 m_text->SetCutDown(Qt::ElideNone);
176
177 QFontMetrics fm(m_text->GetFontProperties()->face());
178 int height = fm.height();
179
180 if (height > 0)
181 {
182 MythRect imageArea = m_cursorImage->GetFullArea();
183 int width = int(((float)height / (float)imageArea.height())
184 * (float)imageArea.width());
185
186 if (width <= 0)
187 width = 1;
188
189 m_cursorImage->ForceSize(QSize(width, height));
190 }
191}
192
193void MythUITextEdit::SetMaxLength(const int length)
194{
195 m_maxLength = length;
196}
197
198void MythUITextEdit::SetText(const QString &text, bool moveCursor)
199{
200 if (!m_text || (m_message == text))
201 return;
202
203 m_message = text;
204
205 if (m_isPassword)
206 {
207 QString obscured;
208
209 obscured.fill('*', m_message.size());
210 m_text->SetText(obscured);
211 }
212 else
213 {
215 }
216
217 if (moveCursor)
219
220 emit valueChanged();
221}
222
223void MythUITextEdit::InsertText(const QString &text)
224{
225 if (!m_text)
226 return;
227
228 for (const auto& c : std::as_const(text))
230
231 emit valueChanged();
232}
233
234bool MythUITextEdit::InsertCharacter(const QString &character)
235{
236 if (m_maxLength != 0 && m_message.length() == m_maxLength)
237 return false;
238
239 QString newmessage = m_message;
240
241 const QChar *unichar = character.unicode();
242
243 // Filter all non printable characters
244 if (!unichar->isPrint())
245 return false;
246
247 if ((m_filter & FilterAlpha) && unichar->isLetter())
248 return false;
249
250 if ((m_filter & FilterNumeric) && unichar->isNumber())
251 return false;
252
253 if ((m_filter & FilterSymbols) && unichar->isSymbol())
254 return false;
255
256 if ((m_filter & FilterPunct) && unichar->isPunct())
257 return false;
258
259 newmessage.insert(m_position + 1, character);
260 SetText(newmessage, false);
262
263 return true;
264}
265
266// This is used for updating IME.
267bool MythUITextEdit::UpdateTmpString(const QString &str)
268{
269 if (!m_text)
270 return false;
271
272 if (str.isEmpty())
273 return false;
274 QString newmessage = m_message;
275 newmessage.append(str);
276 SetText(newmessage, false);
277 return true;
278}
279
281{
282 if (m_message.isEmpty() || position < 0 || position >= m_message.size())
283 return;
284
285 QString newmessage = m_message;
286
287 newmessage.remove(position, 1);
288 SetText(newmessage, false);
289
290 if (position == m_position)
292}
293
295{
296 if (!m_text || !m_cursorImage)
297 return false;
298
299 switch (moveDir)
300 {
301 case MoveLeft:
302 if (m_position < 0)
303 return false;
304 m_position--;
305 break;
306 case MoveRight:
307 if (m_position == (m_message.size() - 1))
308 return false;
309 m_position++;
310 break;
311 case MoveUp:
312 {
313 int newPos = m_text->MoveCursor(-1);
314 if (newPos == -1)
315 return false;
316 m_position = newPos - 1;
317 break;
318 }
319 case MoveDown:
320 {
321 int newPos = m_text->MoveCursor(1);
322 if (newPos == -1)
323 return false;
324 m_position = newPos - 1;
325 break;
326 }
327 case MovePageUp:
328 {
329 int lines = m_text->m_area.height() / (m_text->m_lineHeight + m_text->m_leading);
330 int newPos = m_text->MoveCursor(-lines);
331 if (newPos == -1)
332 return false;
333 m_position = newPos - 1;
334 break;
335 }
336 case MovePageDown:
337 {
338 int lines = m_text->m_area.height() / (m_text->m_lineHeight + m_text->m_leading);
339 int newPos = m_text->MoveCursor(lines);
340 if (newPos == -1)
341 return false;
342 m_position = newPos - 1;
343 break;
344 }
345 case MoveEnd:
346 m_position = m_message.size() - 1;
347 break;
348 }
349
351
352 SetRedraw();
353 return true;
354}
355
357{
359 Reset();
360}
361
363{
364 QClipboard *clipboard = QApplication::clipboard();
365
366 clipboard->setText(m_message);
367}
368
370{
371 QClipboard *clipboard = QApplication::clipboard();
372
373 if (!clipboard->supportsSelection())
374 mode = QClipboard::Clipboard;
375
376 InsertText(clipboard->text(mode));
377}
378
379using keyCombo = QPair<int, int>;
380static QMap<keyCombo, int> gDeadKeyMap;
381
382static void LoadDeadKeys(QMap<QPair<int, int>, int> &map)
383{
384 // Dead key // Key // Result
385 map[keyCombo(Qt::Key_Dead_Grave, Qt::Key_A)] = Qt::Key_Agrave;
386 map[keyCombo(Qt::Key_Dead_Acute, Qt::Key_A)] = Qt::Key_Aacute;
387 map[keyCombo(Qt::Key_Dead_Circumflex, Qt::Key_A)] = Qt::Key_Acircumflex;
388 map[keyCombo(Qt::Key_Dead_Tilde, Qt::Key_A)] = Qt::Key_Atilde;
389 map[keyCombo(Qt::Key_Dead_Diaeresis, Qt::Key_A)] = Qt::Key_Adiaeresis;
390 map[keyCombo(Qt::Key_Dead_Abovering, Qt::Key_A)] = Qt::Key_Aring;
391
392 map[keyCombo(Qt::Key_Dead_Cedilla, Qt::Key_C)] = Qt::Key_Ccedilla;
393
394 map[keyCombo(Qt::Key_Dead_Grave, Qt::Key_E)] = Qt::Key_Egrave;
395 map[keyCombo(Qt::Key_Dead_Acute, Qt::Key_E)] = Qt::Key_Eacute;
396 map[keyCombo(Qt::Key_Dead_Circumflex, Qt::Key_E)] = Qt::Key_Ecircumflex;
397 map[keyCombo(Qt::Key_Dead_Diaeresis, Qt::Key_E)] = Qt::Key_Ediaeresis;
398
399 map[keyCombo(Qt::Key_Dead_Grave, Qt::Key_I)] = Qt::Key_Igrave;
400 map[keyCombo(Qt::Key_Dead_Acute, Qt::Key_I)] = Qt::Key_Iacute;
401 map[keyCombo(Qt::Key_Dead_Circumflex, Qt::Key_I)] = Qt::Key_Icircumflex;
402 map[keyCombo(Qt::Key_Dead_Diaeresis, Qt::Key_I)] = Qt::Key_Idiaeresis;
403
404 map[keyCombo(Qt::Key_Dead_Tilde, Qt::Key_N)] = Qt::Key_Ntilde;
405
406 map[keyCombo(Qt::Key_Dead_Grave, Qt::Key_O)] = Qt::Key_Ograve;
407 map[keyCombo(Qt::Key_Dead_Acute, Qt::Key_O)] = Qt::Key_Oacute;
408 map[keyCombo(Qt::Key_Dead_Circumflex, Qt::Key_O)] = Qt::Key_Ocircumflex;
409 map[keyCombo(Qt::Key_Dead_Tilde, Qt::Key_O)] = Qt::Key_Otilde;
410 map[keyCombo(Qt::Key_Dead_Diaeresis, Qt::Key_O)] = Qt::Key_Odiaeresis;
411
412 map[keyCombo(Qt::Key_Dead_Grave, Qt::Key_U)] = Qt::Key_Ugrave;
413 map[keyCombo(Qt::Key_Dead_Acute, Qt::Key_U)] = Qt::Key_Uacute;
414 map[keyCombo(Qt::Key_Dead_Circumflex, Qt::Key_U)] = Qt::Key_Ucircumflex;
415 map[keyCombo(Qt::Key_Dead_Diaeresis, Qt::Key_U)] = Qt::Key_Udiaeresis;
416
417 map[keyCombo(Qt::Key_Dead_Acute, Qt::Key_Y)] = Qt::Key_Yacute;
418 map[keyCombo(Qt::Key_Dead_Diaeresis, Qt::Key_Y)] = Qt::Key_ydiaeresis;
419}
420
421bool MythUITextEdit::inputMethodEvent(QInputMethodEvent *event)
422{
423 // 1st test.
424 if (m_isPassword)
425 return false;
426
427 bool _bak = m_isIMEinput;
428 if (!m_isIMEinput && (event->commitString().isEmpty() || event->preeditString().isEmpty()))
429 {
430 m_isIMEinput = true;
432 }
433#if 0
434 printf("IME: %s->%s PREEDIT=\"%s\" COMMIT=\"%s\"\n"
435 , (_bak) ? "ON" : "OFF"
436 , (m_isIMEinput) ? "ON" : "OFF"
437 , event->preeditString().toUtf8().constData()
438 , event->commitString().toUtf8().constData());
439#endif
440 if (!event->commitString().isEmpty() && m_isIMEinput)
441 {
443 m_messageBak.clear();
444 InsertText(event->commitString());
445 m_isIMEinput = false;
446 return true; // commited
447 }
448 if (m_isIMEinput && !event->preeditString().isEmpty())
449 {
451 UpdateTmpString(event->preeditString());
452 return true; // preedited
453 }
454 if (m_isIMEinput && _bak)
455 { // Abort?
456 m_isIMEinput = false;
457 QString newmessage= m_messageBak;
458 m_messageBak.clear();
459 SetText(newmessage, true);
460 return true;
461 }
462 return true; // Not commited
463}
464
465bool MythUITextEdit::keyPressEvent(QKeyEvent *event)
466{
467 if (m_isIMEinput) // Prefer IME then keyPress.
468 return true;
470
471 QStringList actions;
472 bool handled = false;
473
474 handled = GetMythMainWindow()->TranslateKeyPress("Global", event, actions,
475 false);
476
477 Qt::KeyboardModifiers modifiers = event->modifiers();
478 int keynum = event->key();
479
480 if (keynum >= Qt::Key_Shift && keynum <= Qt::Key_CapsLock)
481 return false;
482
483 QString character;
484 // Compose key handling
485 // Enter composition mode
486 if (((modifiers & Qt::GroupSwitchModifier) != 0U) &&
487 (keynum >= Qt::Key_Dead_Grave) && (keynum <= Qt::Key_Dead_Horn))
488 {
489 m_composeKey = keynum;
490 handled = true;
491 }
492 else if (m_composeKey > 0) // 'Compose' the key
493 {
494 if (gDeadKeyMap.isEmpty())
496
497 LOG(VB_GUI, LOG_DEBUG, QString("Compose key: %1 Key: %2")
498 .arg(QString::number(m_composeKey, 16), QString::number(keynum, 16)));
499
500 if (gDeadKeyMap.contains(keyCombo(m_composeKey, keynum)))
501 {
502 int keycode = gDeadKeyMap.value(keyCombo(m_composeKey, keynum));
503
504 //QKeyEvent key(QEvent::KeyPress, keycode, modifiers);
505 character = QChar(keycode);
506
507 if ((modifiers & Qt::ShiftModifier) != 0U)
508 character = character.toUpper();
509 else
510 character = character.toLower();
511 LOG(VB_GUI, LOG_DEBUG, QString("Found match for dead-key combo - %1").arg(character));
512 }
513 m_composeKey = 0;
514 }
515
516 if (character.isEmpty())
517 character = event->text();
518
519 if (!handled && InsertCharacter(character))
520 handled = true;
521
522 for (int i = 0; i < actions.size() && !handled; i++)
523 {
524
525 const QString& action = actions[i];
526 handled = true;
527
528 if (action == "LEFT")
529 {
531 }
532 else if (action == "RIGHT")
533 {
535 }
536 else if (action == "UP")
537 {
538 handled = MoveCursor(MoveUp);
539 }
540 else if (action == "DOWN")
541 {
542 handled = MoveCursor(MoveDown);
543 }
544 else if (action == "PAGEUP")
545 {
546 handled = MoveCursor(MovePageUp);
547 }
548 else if (action == "PAGEDOWN")
549 {
550 handled = MoveCursor(MovePageDown);
551 }
552 else if (action == "DELETE")
553 {
555 }
556 else if (action == "BACKSPACE")
557 {
559 }
560 else if (action == "NEWLINE")
561 {
562 QString newmessage = m_message;
563 newmessage.insert(m_position + 1, '\n');
564 SetText(newmessage, false);
566 }
567 else if (action == "SELECT" && keynum != Qt::Key_Space
568 && GetMythDB()->GetNumSetting("UseVirtualKeyboard", 1) == 1)
569 {
570 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
571 auto *kb = new MythUIVirtualKeyboard(popupStack, this);
572
573 if (kb->Create())
574 {
575 popupStack->AddScreen(kb);
576 }
577 else
578 {
579 delete kb;
580 }
581 }
582 else if (action == "CUT")
583 {
585 }
586 else if (action == "COPY")
587 {
589 }
590 else if (action == "PASTE")
591 {
593 }
594 else
595 {
596 handled = false;
597 }
598 }
599
600 return handled;
601}
602
609{
610 bool handled = false;
611
612 if (event->GetGesture() == MythGestureEvent::Click &&
613 event->GetButton() == Qt::MiddleButton)
614 {
615 PasteTextFromClipboard(QClipboard::Selection);
616 }
617
618 return handled;
619}
620
622{
623 auto *textedit = dynamic_cast<MythUITextEdit *>(base);
624
625 if (!textedit)
626 {
627 LOG(VB_GENERAL, LOG_ERR, LOC + "ERROR, bad parsing");
628 return;
629 }
630
631 m_message.clear();
632 m_position = -1;
633
634 m_blinkInterval = textedit->m_blinkInterval;
635 m_cursorBlinkRate = textedit->m_cursorBlinkRate;
636 m_maxLength = textedit->m_maxLength;
637 m_filter = textedit->m_filter;
638 m_keyboardPosition = textedit->m_keyboardPosition;
639
641
643}
644
646{
647 auto *textedit = new MythUITextEdit(parent, objectName());
648 textedit->CopyFrom(this);
649}
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:79
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:86
bool IsVisible(bool recurse=false) const
Definition: mythuitype.cpp:903
void TakingFocus(void)
virtual void SetVisible(bool visible)
virtual void SetArea(const MythRect &rect)
Definition: mythuitype.cpp:610
virtual void CopyFrom(MythUIType *base)
Copy this widgets state from another.
void SetRedraw(void)
Definition: mythuitype.cpp:313
virtual void Pulse(void)
Pulse is called 70 times a second to trigger a single frame of an animation.
Definition: mythuitype.cpp:456
bool m_canHaveFocus
Definition: mythuitype.h:265
virtual MythRect GetFullArea(void) const
Definition: mythuitype.cpp:893
bool m_hasFocus
Definition: mythuitype.h:264
void SetPosition(int x, int y)
Convenience method, calls SetPosition(const MythPoint&) Override that instead to change functionality...
Definition: mythuitype.cpp:533
MythUIType * GetChild(const QString &name) const
Get a named child of this UIType.
Definition: mythuitype.cpp:138
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:277
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:51
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:23
#define VERBOSE_XML(type, level, filename, element, msg)
Definition: xmlparsebase.h:15