00001
00002 #include "mythuitextedit.h"
00003
00004
00005 #include <QApplication>
00006 #include <QRegExp>
00007 #include <QChar>
00008 #include <QKeyEvent>
00009 #include <QDomDocument>
00010 #include <Qt>
00011
00012
00013 #include "mythlogging.h"
00014 #include "mythdb.h"
00015
00016
00017 #include "mythpainter.h"
00018 #include "mythmainwindow.h"
00019 #include "mythfontproperties.h"
00020 #include "mythuihelper.h"
00021
00022 #define LOC QString("MythUITextEdit: ")
00023
00024 MythUITextEdit::MythUITextEdit(MythUIType *parent, const QString &name)
00025 : MythUIType(parent, name)
00026 {
00027 m_Message = "";
00028 m_Filter = FilterNone;
00029
00030 m_isPassword = false;
00031
00032 m_blinkInterval = 0;
00033 m_cursorBlinkRate = 40;
00034
00035 m_Position = -1;
00036
00037 m_maxLength = 255;
00038
00039 m_backgroundState = NULL;
00040 m_cursorImage = NULL;
00041 m_Text = NULL;
00042
00043 m_keyboardPosition = VK_POSBELOWEDIT;
00044
00045 connect(this, SIGNAL(TakingFocus()), SLOT(Select()));
00046 connect(this, SIGNAL(LosingFocus()), SLOT(Deselect()));
00047
00048 m_CanHaveFocus = true;
00049
00050 m_initialized = false;
00051
00052 m_lastKeyPress.start();
00053
00054 m_composeKey = 0;
00055 }
00056
00057 MythUITextEdit::~MythUITextEdit()
00058 {
00059 }
00060
00061 void MythUITextEdit::Select()
00062 {
00063 if (m_backgroundState && !m_backgroundState->DisplayState("selected"))
00064 LOG(VB_GENERAL, LOG_ERR, LOC + "selected state doesn't exist");
00065 }
00066
00067 void MythUITextEdit::Deselect()
00068 {
00069 if (m_backgroundState && !m_backgroundState->DisplayState("active"))
00070 LOG(VB_GENERAL, LOG_ERR, LOC + "active state doesn't exist");
00071 }
00072
00073 void MythUITextEdit::Reset()
00074 {
00075 SetText("");
00076 }
00077
00078 void MythUITextEdit::Pulse(void)
00079 {
00080 if (!m_cursorImage)
00081 return;
00082
00083 if (m_HasFocus)
00084 {
00085 if (m_lastKeyPress.elapsed() < 500)
00086 {
00087 m_cursorImage->SetVisible(true);
00088 m_blinkInterval = 0;
00089 }
00090 else if (m_blinkInterval > m_cursorBlinkRate)
00091 {
00092 m_blinkInterval = 0;
00093
00094 if (m_cursorImage->IsVisible())
00095 m_cursorImage->SetVisible(false);
00096 else
00097 m_cursorImage->SetVisible(true);
00098 }
00099
00100 m_blinkInterval++;
00101 }
00102 else
00103 m_cursorImage->SetVisible(false);
00104
00105 MythUIType::Pulse();
00106 }
00107
00108 bool MythUITextEdit::ParseElement(
00109 const QString &filename, QDomElement &element, bool showWarnings)
00110 {
00111 bool parsed = true;
00112
00113 if (element.tagName() == "area")
00114 {
00115 SetArea(parseRect(element));
00116 }
00117 else if (element.tagName() == "keyboardposition")
00118 {
00119 QString pos = getFirstText(element);
00120
00121 if (pos == "aboveedit")
00122 m_keyboardPosition = VK_POSABOVEEDIT;
00123 else if (pos == "belowedit")
00124 m_keyboardPosition = VK_POSBELOWEDIT;
00125 else if (pos == "screentop")
00126 m_keyboardPosition = VK_POSTOPDIALOG;
00127 else if (pos == "screenbottom")
00128 m_keyboardPosition = VK_POSBOTTOMDIALOG;
00129 else if (pos == "screencenter")
00130 m_keyboardPosition = VK_POSCENTERDIALOG;
00131 else
00132 {
00133 VERBOSE_XML(VB_GENERAL, LOG_ERR, filename, element,
00134 QString("Unknown popup position '%1'").arg(pos));
00135 m_keyboardPosition = VK_POSBELOWEDIT;
00136 }
00137 }
00138 else
00139 {
00140 return MythUIType::ParseElement(filename, element, showWarnings);
00141 }
00142
00143 return parsed;
00144 }
00145
00146 void MythUITextEdit::Finalize()
00147 {
00148 SetInitialStates();
00149
00150
00151
00152 if (m_Text)
00153 {
00154 m_Text->SetText(".");
00155 m_Text->SetText("");
00156 }
00157
00158 if (m_cursorImage && m_Text)
00159 m_cursorImage->SetPosition(m_Text->CursorPosition(0));
00160 }
00161
00162 void MythUITextEdit::SetInitialStates()
00163 {
00164 if (m_initialized)
00165 return;
00166
00167 m_initialized = true;
00168
00169 m_Text = dynamic_cast<MythUIText *>(GetChild("text"));
00170 m_cursorImage = dynamic_cast<MythUIImage *>(GetChild("cursor"));
00171 m_backgroundState =
00172 dynamic_cast<MythUIStateType *>(GetChild("background"));
00173
00174 if (!m_Text)
00175 LOG(VB_GENERAL, LOG_ERR, LOC + "Missing text element.");
00176
00177 if (!m_cursorImage)
00178 LOG(VB_GENERAL, LOG_ERR, LOC + "Missing cursor element.");
00179
00180 if (!m_backgroundState)
00181 LOG(VB_GENERAL, LOG_WARNING, LOC + "Missing background element.");
00182
00183 if (!m_Text || !m_cursorImage)
00184 {
00185 m_Text = NULL;
00186 m_cursorImage = NULL;
00187 m_backgroundState = NULL;
00188 return;
00189 }
00190
00191 if (m_backgroundState && !m_backgroundState->DisplayState("active"))
00192 LOG(VB_GENERAL, LOG_ERR, LOC + "active state doesn't exist");
00193 m_Text->SetCutDown(Qt::ElideNone);
00194
00195 QFontMetrics fm(m_Text->GetFontProperties()->face());
00196 int height = fm.height();
00197
00198 if (height > 0)
00199 {
00200 MythRect imageArea = m_cursorImage->GetFullArea();
00201 int width = int(((float)height / (float)imageArea.height())
00202 * (float)imageArea.width());
00203
00204 if (width <= 0)
00205 width = 1;
00206
00207 m_cursorImage->ForceSize(QSize(width, height));
00208 }
00209 }
00210
00211 void MythUITextEdit::SetMaxLength(const int length)
00212 {
00213 m_maxLength = length;
00214 }
00215
00216 void MythUITextEdit::SetText(const QString &text, bool moveCursor)
00217 {
00218 if (!m_Text || (m_Message == text))
00219 return;
00220
00221 m_Message = text;
00222 m_Message.detach();
00223
00224 if (m_isPassword)
00225 {
00226 QString obscured;
00227
00228 obscured.fill('*', m_Message.size());
00229 m_Text->SetText(obscured);
00230 }
00231 else
00232 m_Text->SetText(m_Message);
00233
00234 if (moveCursor)
00235 MoveCursor(MoveEnd);
00236
00237 emit valueChanged();
00238 }
00239
00240 QString MythUITextEdit::GetText(void) const
00241 {
00242 QString ret = m_Message;
00243 ret.detach();
00244 return ret;
00245 }
00246
00247 void MythUITextEdit::InsertText(const QString &text)
00248 {
00249 if (!m_Text)
00250 return;
00251
00252 int i = 0;
00253
00254 for (; i < text.size(); ++i)
00255 {
00256 InsertCharacter(text.data()[i]);
00257 }
00258
00259 emit valueChanged();
00260 }
00261
00262 bool MythUITextEdit::InsertCharacter(const QString &character)
00263 {
00264 if (m_maxLength != 0 && m_Message.length() == m_maxLength)
00265 return false;
00266
00267 QString newmessage = m_Message;
00268
00269 const QChar *unichar = character.unicode();
00270
00271
00272 if (!unichar->isPrint())
00273 return false;
00274
00275 if ((m_Filter & FilterAlpha) && unichar->isLetter())
00276 return false;
00277
00278 if ((m_Filter & FilterNumeric) && unichar->isNumber())
00279 return false;
00280
00281 if ((m_Filter & FilterSymbols) && unichar->isSymbol())
00282 return false;
00283
00284 if ((m_Filter & FilterPunct) && unichar->isPunct())
00285 return false;
00286
00287 newmessage.insert(m_Position + 1, character);
00288 SetText(newmessage, false);
00289 MoveCursor(MoveRight);
00290
00291 return true;
00292 }
00293
00294 void MythUITextEdit::RemoveCharacter(int position)
00295 {
00296 if (m_Message.isEmpty() || position < 0 || position >= m_Message.size())
00297 return;
00298
00299 QString newmessage = m_Message;
00300
00301 newmessage.remove(position, 1);
00302 SetText(newmessage, false);
00303
00304 if (position == m_Position)
00305 MoveCursor(MoveLeft);
00306 }
00307
00308 bool MythUITextEdit::MoveCursor(MoveDirection moveDir)
00309 {
00310 if (!m_Text || !m_cursorImage)
00311 return false;
00312
00313 switch (moveDir)
00314 {
00315 case MoveLeft:
00316 if (m_Position < 0)
00317 return false;
00318 m_Position--;
00319 break;
00320 case MoveRight:
00321 if (m_Position == (m_Message.size() - 1))
00322 return false;
00323 m_Position++;
00324 break;
00325 case MoveUp:
00326 {
00327 int newPos = m_Text->MoveCursor(-1);
00328 if (newPos == -1)
00329 return false;
00330 m_Position = newPos - 1;
00331 break;
00332 }
00333 case MoveDown:
00334 {
00335 int newPos = m_Text->MoveCursor(1);
00336 if (newPos == -1)
00337 return false;
00338 m_Position = newPos - 1;
00339 break;
00340 }
00341 case MovePageUp:
00342 {
00343 int lines = m_Text->m_Area.height() / (m_Text->m_lineHeight + m_Text->m_Leading);
00344 int newPos = m_Text->MoveCursor(-lines);
00345 if (newPos == -1)
00346 return false;
00347 m_Position = newPos - 1;
00348 break;
00349 }
00350 case MovePageDown:
00351 {
00352 int lines = m_Text->m_Area.height() / (m_Text->m_lineHeight + m_Text->m_Leading);
00353 int newPos = m_Text->MoveCursor(lines);
00354 if (newPos == -1)
00355 return false;
00356 m_Position = newPos - 1;
00357 break;
00358 }
00359 case MoveEnd:
00360 m_Position = m_Message.size() - 1;
00361 break;
00362 }
00363
00364 m_cursorImage->SetPosition(m_Text->CursorPosition(m_Position + 1));
00365
00366 SetRedraw();
00367 return true;
00368 }
00369
00370 void MythUITextEdit::CutTextToClipboard()
00371 {
00372 CopyTextToClipboard();
00373 Reset();
00374 }
00375
00376 void MythUITextEdit::CopyTextToClipboard()
00377 {
00378 QClipboard *clipboard = QApplication::clipboard();
00379
00380 clipboard->setText(m_Message);
00381 }
00382
00383 void MythUITextEdit::PasteTextFromClipboard(QClipboard::Mode mode)
00384 {
00385 QClipboard *clipboard = QApplication::clipboard();
00386
00387 if (!clipboard->supportsSelection())
00388 mode = QClipboard::Clipboard;
00389
00390 InsertText(clipboard->text(mode));
00391 }
00392
00393 typedef QPair<int, int> keyCombo;
00394 static QMap<keyCombo, int> gDeadKeyMap;
00395
00396 static void LoadDeadKeys(QMap<QPair<int, int>, int> &map)
00397 {
00398
00399 map[keyCombo(Qt::Key_Dead_Grave, Qt::Key_A)] = Qt::Key_Agrave;
00400 map[keyCombo(Qt::Key_Dead_Acute, Qt::Key_A)] = Qt::Key_Aacute;
00401 map[keyCombo(Qt::Key_Dead_Circumflex, Qt::Key_A)] = Qt::Key_Acircumflex;
00402 map[keyCombo(Qt::Key_Dead_Tilde, Qt::Key_A)] = Qt::Key_Atilde;
00403 map[keyCombo(Qt::Key_Dead_Diaeresis, Qt::Key_A)] = Qt::Key_Adiaeresis;
00404 map[keyCombo(Qt::Key_Dead_Abovering, Qt::Key_A)] = Qt::Key_Aring;
00405
00406 map[keyCombo(Qt::Key_Dead_Cedilla, Qt::Key_C)] = Qt::Key_Ccedilla;
00407
00408 map[keyCombo(Qt::Key_Dead_Grave, Qt::Key_E)] = Qt::Key_Egrave;
00409 map[keyCombo(Qt::Key_Dead_Acute, Qt::Key_E)] = Qt::Key_Eacute;
00410 map[keyCombo(Qt::Key_Dead_Circumflex, Qt::Key_E)] = Qt::Key_Ecircumflex;
00411 map[keyCombo(Qt::Key_Dead_Diaeresis, Qt::Key_E)] = Qt::Key_Ediaeresis;
00412
00413 map[keyCombo(Qt::Key_Dead_Grave, Qt::Key_I)] = Qt::Key_Igrave;
00414 map[keyCombo(Qt::Key_Dead_Acute, Qt::Key_I)] = Qt::Key_Iacute;
00415 map[keyCombo(Qt::Key_Dead_Circumflex, Qt::Key_I)] = Qt::Key_Icircumflex;
00416 map[keyCombo(Qt::Key_Dead_Diaeresis, Qt::Key_I)] = Qt::Key_Idiaeresis;
00417
00418 map[keyCombo(Qt::Key_Dead_Tilde, Qt::Key_N)] = Qt::Key_Ntilde;
00419
00420 map[keyCombo(Qt::Key_Dead_Grave, Qt::Key_O)] = Qt::Key_Ograve;
00421 map[keyCombo(Qt::Key_Dead_Acute, Qt::Key_O)] = Qt::Key_Oacute;
00422 map[keyCombo(Qt::Key_Dead_Circumflex, Qt::Key_O)] = Qt::Key_Ocircumflex;
00423 map[keyCombo(Qt::Key_Dead_Tilde, Qt::Key_O)] = Qt::Key_Otilde;
00424 map[keyCombo(Qt::Key_Dead_Diaeresis, Qt::Key_O)] = Qt::Key_Odiaeresis;
00425
00426 map[keyCombo(Qt::Key_Dead_Grave, Qt::Key_U)] = Qt::Key_Ugrave;
00427 map[keyCombo(Qt::Key_Dead_Acute, Qt::Key_U)] = Qt::Key_Uacute;
00428 map[keyCombo(Qt::Key_Dead_Circumflex, Qt::Key_U)] = Qt::Key_Ucircumflex;
00429 map[keyCombo(Qt::Key_Dead_Diaeresis, Qt::Key_U)] = Qt::Key_Udiaeresis;
00430
00431 map[keyCombo(Qt::Key_Dead_Acute, Qt::Key_Y)] = Qt::Key_Yacute;
00432 map[keyCombo(Qt::Key_Dead_Diaeresis, Qt::Key_Y)] = Qt::Key_ydiaeresis;
00433
00434 return;
00435 }
00436
00437 bool MythUITextEdit::keyPressEvent(QKeyEvent *event)
00438 {
00439 m_lastKeyPress.restart();
00440
00441 QStringList actions;
00442 bool handled = false;
00443
00444 handled = GetMythMainWindow()->TranslateKeyPress("Global", event, actions,
00445 false);
00446
00447 Qt::KeyboardModifiers modifiers = event->modifiers();
00448 int keynum = event->key();
00449
00450 if (keynum >= Qt::Key_Shift && keynum <= Qt::Key_CapsLock)
00451 return false;
00452
00453 QString character;
00454
00455
00456 if ((modifiers & Qt::GroupSwitchModifier) &&
00457 (keynum >= Qt::Key_Dead_Grave) && (keynum <= Qt::Key_Dead_Horn))
00458 {
00459 m_composeKey = keynum;
00460 handled = true;
00461 }
00462 else if (m_composeKey > 0)
00463 {
00464 if (gDeadKeyMap.isEmpty())
00465 LoadDeadKeys(gDeadKeyMap);
00466
00467 LOG(VB_GUI, LOG_DEBUG, QString("Compose key: %1 Key: %2").arg(QString::number(m_composeKey, 16)).arg(QString::number(keynum, 16)));
00468
00469 if (gDeadKeyMap.contains(keyCombo(m_composeKey, keynum)))
00470 {
00471 int keycode = gDeadKeyMap.value(keyCombo(m_composeKey, keynum));
00472
00473
00474 character = QChar(keycode);
00475
00476 if (modifiers & Qt::ShiftModifier)
00477 character = character.toUpper();
00478 else
00479 character = character.toLower();
00480 LOG(VB_GUI, LOG_DEBUG, QString("Found match for dead-key combo - %1").arg(character));
00481 }
00482 m_composeKey = 0;
00483 }
00484
00485 if (character.isEmpty())
00486 character = event->text();
00487
00488 if (!handled && InsertCharacter(character))
00489 handled = true;
00490
00491 for (int i = 0; i < actions.size() && !handled; i++)
00492 {
00493
00494 QString action = actions[i];
00495 handled = true;
00496
00497 if (action == "LEFT")
00498 {
00499 MoveCursor(MoveLeft);
00500 }
00501 else if (action == "RIGHT")
00502 {
00503 MoveCursor(MoveRight);
00504 }
00505 else if (action == "UP")
00506 {
00507 handled = MoveCursor(MoveUp);
00508 }
00509 else if (action == "DOWN")
00510 {
00511 handled = MoveCursor(MoveDown);
00512 }
00513 else if (action == "PAGEUP")
00514 {
00515 handled = MoveCursor(MovePageUp);
00516 }
00517 else if (action == "PAGEDOWN")
00518 {
00519 handled = MoveCursor(MovePageDown);
00520 }
00521 else if (action == "DELETE")
00522 {
00523 RemoveCharacter(m_Position + 1);
00524 }
00525 else if (action == "BACKSPACE")
00526 {
00527 RemoveCharacter(m_Position);
00528 }
00529 else if (action == "NEWLINE")
00530 {
00531 QString newmessage = m_Message;
00532 newmessage.insert(m_Position + 1, '\n');
00533 SetText(newmessage, false);
00534 MoveCursor(MoveRight);
00535 }
00536 else if (action == "SELECT" && keynum != Qt::Key_Space
00537 && GetMythDB()->GetNumSetting("UseVirtualKeyboard", 1) == 1)
00538 {
00539 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
00540 MythUIVirtualKeyboard *kb = new MythUIVirtualKeyboard(popupStack, this);
00541
00542 if (kb->Create())
00543 {
00544
00545 popupStack->AddScreen(kb);
00546 }
00547 else
00548 delete kb;
00549 }
00550 else if (action == "CUT")
00551 {
00552 CutTextToClipboard();
00553 }
00554 else if (action == "COPY")
00555 {
00556 CopyTextToClipboard();
00557 }
00558 else if (action == "PASTE")
00559 {
00560 PasteTextFromClipboard();
00561 }
00562 else
00563 handled = false;
00564 }
00565
00566 return handled;
00567 }
00568
00575 bool MythUITextEdit::gestureEvent(MythGestureEvent *event)
00576 {
00577 bool handled = false;
00578
00579 if (event->gesture() == MythGestureEvent::Click &&
00580 event->GetButton() == MythGestureEvent::MiddleButton)
00581 {
00582 PasteTextFromClipboard(QClipboard::Selection);
00583 }
00584
00585 return handled;
00586 }
00587
00588 void MythUITextEdit::CopyFrom(MythUIType *base)
00589 {
00590 MythUITextEdit *textedit = dynamic_cast<MythUITextEdit *>(base);
00591
00592 if (!textedit)
00593 {
00594 LOG(VB_GENERAL, LOG_ERR, LOC + "ERROR, bad parsing");
00595 return;
00596 }
00597
00598 m_Message.clear();
00599 m_Position = -1;
00600
00601 m_blinkInterval = textedit->m_blinkInterval;
00602 m_cursorBlinkRate = textedit->m_cursorBlinkRate;
00603 m_maxLength = textedit->m_maxLength;
00604 m_Filter = textedit->m_Filter;
00605 m_keyboardPosition = textedit->m_keyboardPosition;
00606
00607 MythUIType::CopyFrom(base);
00608
00609 SetInitialStates();
00610 }
00611
00612 void MythUITextEdit::CreateCopy(MythUIType *parent)
00613 {
00614 MythUITextEdit *textedit = new MythUITextEdit(parent, objectName());
00615 textedit->CopyFrom(this);
00616 }