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
00223 if (m_isPassword)
00224 {
00225 QString obscured;
00226
00227 obscured.fill('*', m_Message.size());
00228 m_Text->SetText(obscured);
00229 }
00230 else
00231 m_Text->SetText(m_Message);
00232
00233 if (moveCursor)
00234 MoveCursor(MoveEnd);
00235
00236 emit valueChanged();
00237 }
00238
00239 void MythUITextEdit::InsertText(const QString &text)
00240 {
00241 if (!m_Text)
00242 return;
00243
00244 int i = 0;
00245
00246 for (; i < text.size(); ++i)
00247 {
00248 InsertCharacter(text.data()[i]);
00249 }
00250
00251 emit valueChanged();
00252 }
00253
00254 bool MythUITextEdit::InsertCharacter(const QString &character)
00255 {
00256 if (m_maxLength != 0 && m_Message.length() == m_maxLength)
00257 return false;
00258
00259 QString newmessage = m_Message;
00260
00261 const QChar *unichar = character.unicode();
00262
00263
00264 if (!unichar->isPrint())
00265 return false;
00266
00267 if ((m_Filter & FilterAlpha) && unichar->isLetter())
00268 return false;
00269
00270 if ((m_Filter & FilterNumeric) && unichar->isNumber())
00271 return false;
00272
00273 if ((m_Filter & FilterSymbols) && unichar->isSymbol())
00274 return false;
00275
00276 if ((m_Filter & FilterPunct) && unichar->isPunct())
00277 return false;
00278
00279 newmessage.insert(m_Position + 1, character);
00280 SetText(newmessage, false);
00281 MoveCursor(MoveRight);
00282
00283 return true;
00284 }
00285
00286 void MythUITextEdit::RemoveCharacter(int position)
00287 {
00288 if (m_Message.isEmpty() || position < 0 || position >= m_Message.size())
00289 return;
00290
00291 QString newmessage = m_Message;
00292
00293 newmessage.remove(position, 1);
00294 SetText(newmessage, false);
00295
00296 if (position == m_Position)
00297 MoveCursor(MoveLeft);
00298 }
00299
00300 bool MythUITextEdit::MoveCursor(MoveDirection moveDir)
00301 {
00302 if (!m_Text || !m_cursorImage)
00303 return false;
00304
00305 switch (moveDir)
00306 {
00307 case MoveLeft:
00308 if (m_Position < 0)
00309 return false;
00310 m_Position--;
00311 break;
00312 case MoveRight:
00313 if (m_Position == (m_Message.size() - 1))
00314 return false;
00315 m_Position++;
00316 break;
00317 case MoveEnd:
00318 m_Position = m_Message.size() - 1;
00319 break;
00320 }
00321
00322 m_cursorImage->SetPosition(m_Text->CursorPosition(m_Position + 1));
00323
00324 SetRedraw();
00325 return true;
00326 }
00327
00328 void MythUITextEdit::CutTextToClipboard()
00329 {
00330 CopyTextToClipboard();
00331 Reset();
00332 }
00333
00334 void MythUITextEdit::CopyTextToClipboard()
00335 {
00336 QClipboard *clipboard = QApplication::clipboard();
00337
00338 clipboard->setText(m_Message);
00339 }
00340
00341 void MythUITextEdit::PasteTextFromClipboard(QClipboard::Mode mode)
00342 {
00343 QClipboard *clipboard = QApplication::clipboard();
00344
00345 if (!clipboard->supportsSelection())
00346 mode = QClipboard::Clipboard;
00347
00348 InsertText(clipboard->text(mode));
00349 }
00350
00351 typedef QPair<int, int> keyCombo;
00352 static QMap<keyCombo, int> gDeadKeyMap;
00353
00354 static void LoadDeadKeys(QMap<QPair<int, int>, int> &map)
00355 {
00356
00357 map[keyCombo(Qt::Key_Dead_Grave, Qt::Key_A)] = Qt::Key_Agrave;
00358 map[keyCombo(Qt::Key_Dead_Acute, Qt::Key_A)] = Qt::Key_Aacute;
00359 map[keyCombo(Qt::Key_Dead_Circumflex, Qt::Key_A)] = Qt::Key_Acircumflex;
00360 map[keyCombo(Qt::Key_Dead_Tilde, Qt::Key_A)] = Qt::Key_Atilde;
00361 map[keyCombo(Qt::Key_Dead_Diaeresis, Qt::Key_A)] = Qt::Key_Adiaeresis;
00362 map[keyCombo(Qt::Key_Dead_Abovering, Qt::Key_A)] = Qt::Key_Aring;
00363
00364 map[keyCombo(Qt::Key_Dead_Cedilla, Qt::Key_C)] = Qt::Key_Ccedilla;
00365
00366 map[keyCombo(Qt::Key_Dead_Grave, Qt::Key_E)] = Qt::Key_Egrave;
00367 map[keyCombo(Qt::Key_Dead_Acute, Qt::Key_E)] = Qt::Key_Eacute;
00368 map[keyCombo(Qt::Key_Dead_Circumflex, Qt::Key_E)] = Qt::Key_Ecircumflex;
00369 map[keyCombo(Qt::Key_Dead_Diaeresis, Qt::Key_E)] = Qt::Key_Ediaeresis;
00370
00371 map[keyCombo(Qt::Key_Dead_Grave, Qt::Key_I)] = Qt::Key_Igrave;
00372 map[keyCombo(Qt::Key_Dead_Acute, Qt::Key_I)] = Qt::Key_Iacute;
00373 map[keyCombo(Qt::Key_Dead_Circumflex, Qt::Key_I)] = Qt::Key_Icircumflex;
00374 map[keyCombo(Qt::Key_Dead_Diaeresis, Qt::Key_I)] = Qt::Key_Idiaeresis;
00375
00376 map[keyCombo(Qt::Key_Dead_Tilde, Qt::Key_N)] = Qt::Key_Ntilde;
00377
00378 map[keyCombo(Qt::Key_Dead_Grave, Qt::Key_O)] = Qt::Key_Ograve;
00379 map[keyCombo(Qt::Key_Dead_Acute, Qt::Key_O)] = Qt::Key_Oacute;
00380 map[keyCombo(Qt::Key_Dead_Circumflex, Qt::Key_O)] = Qt::Key_Ocircumflex;
00381 map[keyCombo(Qt::Key_Dead_Tilde, Qt::Key_O)] = Qt::Key_Otilde;
00382 map[keyCombo(Qt::Key_Dead_Diaeresis, Qt::Key_O)] = Qt::Key_Odiaeresis;
00383
00384 map[keyCombo(Qt::Key_Dead_Grave, Qt::Key_U)] = Qt::Key_Ugrave;
00385 map[keyCombo(Qt::Key_Dead_Acute, Qt::Key_U)] = Qt::Key_Uacute;
00386 map[keyCombo(Qt::Key_Dead_Circumflex, Qt::Key_U)] = Qt::Key_Ucircumflex;
00387 map[keyCombo(Qt::Key_Dead_Diaeresis, Qt::Key_U)] = Qt::Key_Udiaeresis;
00388
00389 map[keyCombo(Qt::Key_Dead_Acute, Qt::Key_Y)] = Qt::Key_Yacute;
00390 map[keyCombo(Qt::Key_Dead_Diaeresis, Qt::Key_Y)] = Qt::Key_ydiaeresis;
00391
00392 return;
00393 }
00394
00395 bool MythUITextEdit::keyPressEvent(QKeyEvent *event)
00396 {
00397 m_lastKeyPress.restart();
00398
00399 QStringList actions;
00400 bool handled = false;
00401
00402 handled = GetMythMainWindow()->TranslateKeyPress("Global", event, actions,
00403 false);
00404
00405 Qt::KeyboardModifiers modifiers = event->modifiers();
00406 int keynum = event->key();
00407
00408 if (keynum >= Qt::Key_Shift && keynum <= Qt::Key_CapsLock)
00409 return false;
00410
00411 QString character;
00412
00413
00414 if ((modifiers & Qt::GroupSwitchModifier) &&
00415 (keynum >= Qt::Key_Dead_Grave) && (keynum <= Qt::Key_Dead_Horn))
00416 {
00417 m_composeKey = keynum;
00418 handled = true;
00419 }
00420 else if (m_composeKey > 0)
00421 {
00422 if (gDeadKeyMap.isEmpty())
00423 LoadDeadKeys(gDeadKeyMap);
00424
00425 LOG(VB_GUI, LOG_DEBUG, QString("Compose key: %1 Key: %2").arg(QString::number(m_composeKey, 16)).arg(QString::number(keynum, 16)));
00426
00427 if (gDeadKeyMap.contains(keyCombo(m_composeKey, keynum)))
00428 {
00429 int keycode = gDeadKeyMap.value(keyCombo(m_composeKey, keynum));
00430
00431
00432 character = QChar(keycode);
00433
00434 if (modifiers & Qt::ShiftModifier)
00435 character = character.toUpper();
00436 else
00437 character = character.toLower();
00438 LOG(VB_GUI, LOG_DEBUG, QString("Found match for dead-key combo - %1").arg(character));
00439 }
00440 m_composeKey = 0;
00441 }
00442
00443 if (character.isEmpty())
00444 character = event->text();
00445
00446 if (!handled && InsertCharacter(character))
00447 handled = true;
00448
00449 for (int i = 0; i < actions.size() && !handled; i++)
00450 {
00451
00452 QString action = actions[i];
00453 handled = true;
00454
00455 if (action == "LEFT")
00456 {
00457 MoveCursor(MoveLeft);
00458 }
00459 else if (action == "RIGHT")
00460 {
00461 MoveCursor(MoveRight);
00462 }
00463 else if (action == "DELETE")
00464 {
00465 RemoveCharacter(m_Position + 1);
00466 }
00467 else if (action == "BACKSPACE")
00468 {
00469 RemoveCharacter(m_Position);
00470 }
00471 else if (action == "SELECT" && keynum != Qt::Key_Space
00472 && GetMythDB()->GetNumSetting("UseVirtualKeyboard", 1) == 1)
00473 {
00474 MythScreenStack *popupStack = GetMythMainWindow()->GetStack("popup stack");
00475 MythUIVirtualKeyboard *kb = new MythUIVirtualKeyboard(popupStack, this);
00476
00477 if (kb->Create())
00478 {
00479
00480 popupStack->AddScreen(kb);
00481 }
00482 else
00483 delete kb;
00484 }
00485 else if (action == "CUT")
00486 {
00487 CutTextToClipboard();
00488 }
00489 else if (action == "COPY")
00490 {
00491 CopyTextToClipboard();
00492 }
00493 else if (action == "PASTE")
00494 {
00495 PasteTextFromClipboard();
00496 }
00497 else
00498 handled = false;
00499 }
00500
00501 return handled;
00502 }
00503
00510 bool MythUITextEdit::gestureEvent(MythGestureEvent *event)
00511 {
00512 bool handled = false;
00513
00514 if (event->gesture() == MythGestureEvent::Click &&
00515 event->GetButton() == MythGestureEvent::MiddleButton)
00516 {
00517 PasteTextFromClipboard(QClipboard::Selection);
00518 }
00519
00520 return handled;
00521 }
00522
00523 void MythUITextEdit::CopyFrom(MythUIType *base)
00524 {
00525 MythUITextEdit *textedit = dynamic_cast<MythUITextEdit *>(base);
00526
00527 if (!textedit)
00528 {
00529 LOG(VB_GENERAL, LOG_ERR, LOC + "ERROR, bad parsing");
00530 return;
00531 }
00532
00533 m_Message.clear();
00534 m_Position = -1;
00535
00536 m_blinkInterval = textedit->m_blinkInterval;
00537 m_cursorBlinkRate = textedit->m_cursorBlinkRate;
00538 m_maxLength = textedit->m_maxLength;
00539 m_Filter = textedit->m_Filter;
00540 m_keyboardPosition = textedit->m_keyboardPosition;
00541
00542 MythUIType::CopyFrom(base);
00543
00544 SetInitialStates();
00545 }
00546
00547 void MythUITextEdit::CreateCopy(MythUIType *parent)
00548 {
00549 MythUITextEdit *textedit = new MythUITextEdit(parent, objectName());
00550 textedit->CopyFrom(this);
00551 }