00001
00002 #include "mythuitext.h"
00003
00004 #include <QCoreApplication>
00005 #include <QtGlobal>
00006 #include <QDomDocument>
00007 #include <QFontMetrics>
00008 #include <QString>
00009 #include <QHash>
00010
00011 #include "mythlogging.h"
00012
00013 #include "mythuihelper.h"
00014 #include "mythpainter.h"
00015 #include "mythmainwindow.h"
00016 #include "mythfontproperties.h"
00017 #include "mythcorecontext.h"
00018
00019 #include "compat.h"
00020
00021 MythUIText::MythUIText(MythUIType *parent, const QString &name)
00022 : MythUIType(parent, name),
00023 m_Justification(Qt::AlignLeft | Qt::AlignTop), m_OrigDisplayRect(),
00024 m_AltDisplayRect(), m_Canvas(),
00025 m_drawRect(), m_cursorPos(-1, -1),
00026 m_Message(""), m_CutMessage(""),
00027 m_DefaultMessage(""), m_TemplateText(""),
00028 m_ShrinkNarrow(true), m_Cutdown(Qt::ElideRight),
00029 m_MultiLine(false), m_Ascent(0),
00030 m_Descent(0), m_leftBearing(0),
00031 m_rightBearing(0), m_Leading(1),
00032 m_extraLeading(0), m_lineHeight(0),
00033 m_textCursor(-1),
00034 m_Font(new MythFontProperties()), m_colorCycling(false),
00035 m_startColor(), m_endColor(),
00036 m_numSteps(0), m_curStep(0),
00037 curR(0.0), curG(0.0), curB(0.0),
00038 incR(0.0), incG(0.0), incB(0.0),
00039 m_scrollPause(ScrollBounceDelay), m_scrollOffset(0),
00040 m_scrollBounce(false), m_scrolling(false),
00041 m_scrollDirection(ScrollNone), m_textCase(CaseNormal)
00042 {
00043 #if 0 // Not currently used
00044 m_usingAltArea = false;
00045 #endif
00046 m_EnableInitiator = true;
00047
00048 m_FontStates.insert("default", MythFontProperties());
00049 *m_Font = m_FontStates["default"];
00050 }
00051
00052 MythUIText::MythUIText(const QString &text, const MythFontProperties &font,
00053 QRect displayRect, QRect altDisplayRect,
00054 MythUIType *parent, const QString &name)
00055 : MythUIType(parent, name),
00056 m_Justification(Qt::AlignLeft | Qt::AlignTop),
00057 m_OrigDisplayRect(displayRect), m_AltDisplayRect(altDisplayRect),
00058 m_Canvas(0, 0, displayRect.width(), displayRect.height()),
00059 m_drawRect(displayRect), m_cursorPos(-1, -1),
00060 m_Message(text.trimmed()),
00061 m_CutMessage(""), m_DefaultMessage(text),
00062 m_Cutdown(Qt::ElideRight), m_Font(new MythFontProperties()),
00063 m_colorCycling(false), m_startColor(),
00064 m_endColor(), m_numSteps(0),
00065 m_curStep(0),
00066 curR(0.0), curG(0.0), curB(0.0),
00067 incR(0.0), incG(0.0), incB(0.0)
00068 {
00069 #if 0 // Not currently used
00070 m_usingAltArea = false;
00071 #endif
00072 m_ShrinkNarrow = true;
00073 m_MultiLine = false;
00074 m_scrollPause = ScrollBounceDelay;
00075 m_scrollBounce = false;
00076 m_scrolling = false;
00077 m_scrollDirection = ScrollNone;
00078 m_textCase = CaseNormal;
00079 m_Ascent = m_Descent = m_leftBearing = m_rightBearing = 0;
00080 m_Leading = 1;
00081 m_extraLeading = 0;
00082 m_lineHeight = 0;
00083 m_textCursor = -1;
00084 m_EnableInitiator = true;
00085
00086 SetArea(displayRect);
00087 m_FontStates.insert("default", font);
00088 *m_Font = m_FontStates["default"];
00089 }
00090
00091 MythUIText::~MythUIText()
00092 {
00093 delete m_Font;
00094 m_Font = NULL;
00095
00096 QVector<QTextLayout *>::iterator Ilayout;
00097 for (Ilayout = m_Layouts.begin(); Ilayout != m_Layouts.end(); ++Ilayout)
00098 delete *Ilayout;
00099 }
00100
00101 void MythUIText::Reset()
00102 {
00103 if (m_Message != m_DefaultMessage)
00104 {
00105 SetText(m_DefaultMessage);
00106 SetRedraw();
00107 emit DependChanged(true);
00108 }
00109
00110 SetFontState("default");
00111
00112 MythUIType::Reset();
00113 }
00114
00115 void MythUIText::SetText(const QString &text)
00116 {
00117 QString newtext = text;
00118
00119 if (!m_Layouts.isEmpty() && newtext == m_Message)
00120 return;
00121
00122 if (newtext.isEmpty())
00123 {
00124 m_Message = m_DefaultMessage;
00125 emit DependChanged(true);
00126 }
00127 else
00128 {
00129 m_Message = newtext;
00130 emit DependChanged(false);
00131 }
00132 m_CutMessage.clear();
00133 FillCutMessage();
00134
00135 SetRedraw();
00136 }
00137
00138 void MythUIText::SetTextFromMap(QHash<QString, QString> &map)
00139 {
00140 if (!IsVisible())
00141 return;
00142
00143 if (map.contains(objectName()))
00144 {
00145 QString newText = GetTemplateText();
00146
00147 if (newText.isEmpty())
00148 newText = GetDefaultText();
00149
00150 QRegExp regexp("%(([^\\|%]+)?\\||\\|(.))?(\\w+)(\\|(.+))?%");
00151 regexp.setMinimal(true);
00152
00153 if (!newText.isEmpty() && newText.contains(regexp))
00154 {
00155 int pos = 0;
00156
00157 QString translatedTemplate = qApp->translate("ThemeUI",
00158 newText.toUtf8(),
00159 NULL,
00160 QCoreApplication::UnicodeUTF8);
00161
00162 QString tempString = translatedTemplate;
00163
00164 while ((pos = regexp.indexIn(translatedTemplate, pos)) != -1)
00165 {
00166 QString key = regexp.cap(4).toLower().trimmed();
00167 QString replacement;
00168
00169 if (!map.value(key).isEmpty())
00170 {
00171 replacement = QString("%1%2%3%4")
00172 .arg(regexp.cap(2))
00173 .arg(regexp.cap(3))
00174 .arg(map.value(key))
00175 .arg(regexp.cap(6));
00176 }
00177
00178 tempString.replace(regexp.cap(0), replacement);
00179 pos += regexp.matchedLength();
00180 }
00181
00182 newText = tempString;
00183 }
00184 else
00185 newText = map.value(objectName());
00186
00187 SetText(newText);
00188 }
00189 }
00190
00191 QString MythUIText::GetText(void) const
00192 {
00193 return m_Message;
00194 }
00195
00196 QString MythUIText::GetDefaultText(void) const
00197 {
00198 return m_DefaultMessage;
00199 }
00200
00201 void MythUIText::SetFontProperties(const MythFontProperties &fontProps)
00202 {
00203 m_FontStates.insert("default", fontProps);
00204 if (m_Font->GetHash() != m_FontStates["default"].GetHash())
00205 {
00206 *m_Font = m_FontStates["default"];
00207 if (!m_Message.isEmpty())
00208 {
00209 FillCutMessage();
00210 SetRedraw();
00211 }
00212 }
00213 }
00214
00215 void MythUIText::SetFontState(const QString &state)
00216 {
00217 if (m_FontStates.contains(state))
00218 {
00219 if (m_Font->GetHash() == m_FontStates[state].GetHash())
00220 return;
00221 *m_Font = m_FontStates[state];
00222 }
00223 else
00224 {
00225 if (m_Font->GetHash() == m_FontStates["default"].GetHash())
00226 return;
00227 *m_Font = m_FontStates["default"];
00228 }
00229 if (!m_Message.isEmpty())
00230 {
00231 FillCutMessage();
00232 SetRedraw();
00233 }
00234 }
00235
00236 #if 0 // Not currently used
00237 void MythUIText::UseAlternateArea(bool useAlt)
00238 {
00239 if (useAlt && m_AltDisplayRect.width() > 1)
00240 {
00241 MythUIType::SetArea(m_AltDisplayRect);
00242 m_usingAltArea = true;
00243 }
00244 else
00245 {
00246 MythUIType::SetArea(m_OrigDisplayRect);
00247 m_usingAltArea = false;
00248 }
00249
00250 FillCutMessage();
00251 }
00252 #endif
00253
00254 void MythUIText::SetJustification(int just)
00255 {
00256 int h = just & Qt::AlignHorizontal_Mask;
00257 int v = just & Qt::AlignVertical_Mask;
00258
00259 if ((h && (m_Justification & Qt::AlignHorizontal_Mask) ^ h) ||
00260 (v && (m_Justification & Qt::AlignVertical_Mask) ^ v))
00261 {
00262
00263 m_Justification = m_Justification & Qt::TextWordWrap;
00264 m_Justification |= just;
00265 if (!m_Message.isEmpty())
00266 {
00267 FillCutMessage();
00268 SetRedraw();
00269 }
00270 }
00271 }
00272
00273 int MythUIText::GetJustification(void)
00274 {
00275 return m_Justification;
00276 }
00277
00278 void MythUIText::SetCutDown(Qt::TextElideMode mode)
00279 {
00280 if (mode != m_Cutdown)
00281 {
00282 m_Cutdown = mode;
00283 if (m_scrolling && m_Cutdown != Qt::ElideNone)
00284 {
00285 LOG(VB_GENERAL, LOG_ERR, QString("'%1' (%2): <scroll> and "
00286 "<cutdown> are not combinable.")
00287 .arg(objectName()).arg(GetXMLLocation()));
00288 m_Cutdown = Qt::ElideNone;
00289 }
00290 if (!m_Message.isEmpty())
00291 {
00292 FillCutMessage();
00293 SetRedraw();
00294 }
00295 }
00296 }
00297
00298 void MythUIText::SetMultiLine(bool multiline)
00299 {
00300 if (multiline != m_MultiLine)
00301 {
00302 m_MultiLine = multiline;
00303
00304 if (m_MultiLine)
00305 m_Justification |= Qt::TextWordWrap;
00306 else
00307 m_Justification &= ~Qt::TextWordWrap;
00308
00309 if (!m_Message.isEmpty())
00310 {
00311 FillCutMessage();
00312 SetRedraw();
00313 }
00314 }
00315 }
00316
00317 void MythUIText::SetArea(const MythRect &rect)
00318 {
00319 MythUIType::SetArea(rect);
00320 m_CutMessage.clear();
00321
00322 m_drawRect = m_Area;
00323 FillCutMessage();
00324 }
00325
00326 void MythUIText::SetPosition(const MythPoint &pos)
00327 {
00328 MythUIType::SetPosition(pos);
00329 m_drawRect.moveTopLeft(m_Area.topLeft());
00330 }
00331
00332 void MythUIText::SetCanvasPosition(int x, int y)
00333 {
00334 QPoint newpoint(x, y);
00335
00336 if (newpoint == m_Canvas.topLeft())
00337 return;
00338
00339 m_Canvas.moveTopLeft(newpoint);
00340 SetRedraw();
00341 }
00342
00343 void MythUIText::ShiftCanvas(int x, int y)
00344 {
00345 if (x == 0 && y == 0)
00346 return;
00347
00348 m_Canvas.moveTop(m_Canvas.y() + y);
00349 m_Canvas.moveLeft(m_Canvas.x() + x);
00350 SetRedraw();
00351 }
00352
00353 void MythUIText::DrawSelf(MythPainter *p, int xoffset, int yoffset,
00354 int alphaMod, QRect clipRect)
00355 {
00356 if (m_Canvas.isNull())
00357 return;
00358
00359 FormatVector formats;
00360 QRect drawrect = m_drawRect.toQRect();
00361 drawrect.translate(xoffset, yoffset);
00362 QRect canvas = m_Canvas.toQRect();
00363
00364 int alpha = CalcAlpha(alphaMod);
00365
00366 if (m_Ascent)
00367 {
00368 drawrect.setY(drawrect.y() - m_Ascent);
00369 canvas.moveTop(canvas.y() + m_Ascent);
00370 canvas.setHeight(canvas.height() + m_Ascent);
00371 }
00372 if (m_Descent)
00373 {
00374 drawrect.setHeight(drawrect.height() + m_Descent);
00375 canvas.setHeight(canvas.height() + m_Descent);
00376 }
00377
00378 if (m_leftBearing)
00379 {
00380 drawrect.setX(drawrect.x() + m_leftBearing);
00381 canvas.moveLeft(canvas.x() - m_leftBearing);
00382 canvas.setWidth(canvas.width() - m_leftBearing);
00383 }
00384 if (m_rightBearing)
00385 {
00386 drawrect.setWidth(drawrect.width() - m_rightBearing);
00387 canvas.setWidth(canvas.width() - m_rightBearing);
00388 }
00389
00390 if (GetFontProperties()->hasOutline())
00391 {
00392 QTextLayout::FormatRange range;
00393
00394 QColor outlineColor;
00395 int outlineSize, outlineAlpha;
00396
00397 GetFontProperties()->GetOutline(outlineColor, outlineSize,
00398 outlineAlpha);
00399 outlineColor.setAlpha(outlineAlpha);
00400
00401 MythPoint outline(outlineSize, outlineSize);
00402 outline.NormPoint();
00403
00404 QPen pen;
00405 pen.setBrush(outlineColor);
00406 pen.setWidth(outline.x());
00407
00408 range.start = 0;
00409 range.length = m_CutMessage.size();
00410 range.format.setTextOutline(pen);
00411 formats.push_back(range);
00412
00413 drawrect.setX(drawrect.x() - outline.x());
00414 drawrect.setWidth(drawrect.width() + outline.x());
00415 drawrect.setY(drawrect.y() - outline.y());
00416 drawrect.setHeight(drawrect.height() + outline.y());
00417
00418
00419
00420 canvas.moveLeft(canvas.x() + outline.x());
00421 canvas.setWidth(canvas.width() + outline.x());
00422 canvas.moveTop(canvas.y() + outline.y());
00423 canvas.setHeight(canvas.height() + outline.y());
00424 }
00425
00426 if (GetFontProperties()->hasShadow())
00427 {
00428 QPoint shadowOffset;
00429 QColor shadowColor;
00430 int shadowAlpha;
00431
00432 GetFontProperties()->GetShadow(shadowOffset, shadowColor, shadowAlpha);
00433
00434 MythPoint shadow(shadowOffset);
00435 shadow.NormPoint();
00436
00437 drawrect.setWidth(drawrect.width() + shadow.x());
00438 drawrect.setHeight(drawrect.height() + shadow.y());
00439
00440 canvas.setWidth(canvas.width() + shadow.x());
00441 canvas.setHeight(canvas.height() + shadow.y());
00442 }
00443
00444 p->DrawTextLayout(canvas, m_Layouts, formats,
00445 *GetFontProperties(), alpha, drawrect);
00446 }
00447
00448 bool MythUIText::Layout(QString & paragraph, QTextLayout *layout, bool final,
00449 bool & overflow, qreal width, qreal & height,
00450 bool force, qreal & last_line_width,
00451 QRectF & min_rect, int & num_lines)
00452 {
00453 int last_line = 0;
00454
00455 layout->setText(paragraph);
00456 layout->beginLayout();
00457 num_lines = 0;
00458 for (;;)
00459 {
00460 QTextLine line = layout->createLine();
00461 if (!line.isValid())
00462 break;
00463
00464
00465 line.setLineWidth(width);
00466
00467 if (!m_MultiLine && line.textLength() < paragraph.size())
00468 {
00469 if (!force && m_Cutdown != Qt::ElideNone)
00470 {
00471 QFontMetrics fm(GetFontProperties()->face());
00472 paragraph = fm.elidedText(paragraph, m_Cutdown,
00473 width - fm.averageCharWidth());
00474 return false;
00475 }
00476
00477 line.setLineWidth(INT_MAX);
00478 }
00479
00480 height += m_Leading;
00481 line.setPosition(QPointF(0, height));
00482 height += m_lineHeight;
00483 if (!overflow)
00484 {
00485 if (height > m_Area.height())
00486 {
00487 LOG(VB_GUI, num_lines ? LOG_DEBUG : LOG_NOTICE,
00488 QString("'%1' (%2): height overflow. line height %3 "
00489 "paragraph height %4, area height %5")
00490 .arg(objectName())
00491 .arg(GetXMLLocation())
00492 .arg(line.height())
00493 .arg(height)
00494 .arg(m_Area.height()));
00495
00496 if (!m_MultiLine)
00497 m_drawRect.setHeight(height);
00498 if (m_Cutdown != Qt::ElideNone)
00499 {
00500 QFontMetrics fm(GetFontProperties()->face());
00501 QString cut_line = fm.elidedText
00502 (paragraph.mid(last_line),
00503 Qt::ElideRight,
00504 width - fm.averageCharWidth());
00505 paragraph = paragraph.left(last_line) + cut_line;
00506 if (last_line == 0)
00507 min_rect |= line.naturalTextRect();
00508 return false;
00509 }
00510 overflow = true;
00511 }
00512 else
00513 m_drawRect.setHeight(height);
00514 if (!m_MultiLine)
00515 overflow = true;
00516 }
00517
00518 last_line = line.textStart();
00519 last_line_width = line.naturalTextWidth();
00520 min_rect |= line.naturalTextRect();
00521 ++num_lines;
00522
00523 if (final && line.textLength())
00524 {
00533 QFontMetrics fm(GetFontProperties()->face());
00534 int bearing;
00535
00536 bearing = fm.leftBearing(m_CutMessage[last_line]);
00537 if (m_leftBearing > bearing)
00538 m_leftBearing = bearing;
00539 bearing = fm.rightBearing
00540 (m_CutMessage[last_line + line.textLength() - 1]);
00541 if (m_rightBearing > bearing)
00542 m_rightBearing = bearing;
00543 }
00544 }
00545
00546 layout->endLayout();
00547 return true;
00548 }
00549
00550 bool MythUIText::LayoutParagraphs(const QStringList & paragraphs,
00551 const QTextOption & textoption,
00552 qreal width, qreal & height,
00553 QRectF & min_rect, qreal & last_line_width,
00554 int & num_lines, bool final)
00555 {
00556 QStringList::const_iterator Ipara;
00557 QVector<QTextLayout *>::iterator Ilayout;
00558 QTextLayout *layout;
00559 QString para;
00560 bool overflow = false;
00561 qreal saved_height;
00562 QRectF saved_rect;
00563 int idx;
00564
00565 for (Ilayout = m_Layouts.begin(); Ilayout != m_Layouts.end(); ++Ilayout)
00566 (*Ilayout)->clearLayout();
00567
00568 for (Ipara = paragraphs.begin(), idx = 0;
00569 Ipara != paragraphs.end(); ++Ipara, ++idx)
00570 {
00571 layout = m_Layouts[idx];
00572 layout->setTextOption(textoption);
00573 layout->setFont(m_Font->face());
00574
00575 para = *Ipara;
00576 saved_height = height;
00577 saved_rect = min_rect;
00578 if (!Layout(para, layout, final, overflow, width, height, false,
00579 last_line_width, min_rect, num_lines))
00580 {
00581
00582 min_rect = saved_rect;
00583 height = saved_height;
00584 Layout(para, layout, final, overflow, width, height, true,
00585 last_line_width, min_rect, num_lines);
00586 break;
00587 }
00588 }
00589 m_drawRect.setWidth(width);
00590
00591 return (!overflow);
00592 }
00593
00594 bool MythUIText::GetNarrowWidth(const QStringList & paragraphs,
00595 const QTextOption & textoption, qreal & width)
00596 {
00597 MythRect textrect(m_Area);
00598 qreal height, last_line_width, lines;
00599 int best_width, too_narrow, last_width = -1;
00600 int num_lines, line_height = 0;
00601 int attempt = 0;
00602 Qt::TextElideMode cutdown = m_Cutdown;
00603 m_Cutdown = Qt::ElideNone;
00604
00605 line_height = m_Leading + m_lineHeight;
00606 width = m_Area.width() / 2.0;
00607 best_width = m_Area.width();
00608 too_narrow = 0;
00609
00610 for (attempt = 0; attempt < 10; ++attempt)
00611 {
00612 QRectF min_rect;
00613
00614 m_drawRect.setWidth(0);
00615 height = 0;
00616
00617 LayoutParagraphs(paragraphs, textoption, width, height,
00618 min_rect, last_line_width, num_lines, false);
00619
00620 if (num_lines <= 0)
00621 return false;
00622
00623 if (height > m_drawRect.height())
00624 {
00625 if (too_narrow < width)
00626 too_narrow = width;
00627
00628
00629 lines = static_cast<int>
00630 ((height - m_drawRect.height()) / line_height);
00631 lines -= (1.0 - last_line_width / width);
00632 width += (lines * width) /
00633 (m_drawRect.height() / line_height);
00634
00635 if (width > best_width || static_cast<int>(width) == last_width)
00636 {
00637 width = best_width;
00638 m_Cutdown = cutdown;
00639 return true;
00640 }
00641 }
00642 else
00643 {
00644 if (best_width > width)
00645 best_width = width;
00646
00647 lines = static_cast<int>
00648 (m_Area.height() - height) / line_height;
00649 if (lines >= 1)
00650 {
00651
00652 width -= width *
00653 (lines / static_cast<qreal>(num_lines - 1 + lines));
00654 if (static_cast<int>(width) == last_width)
00655 {
00656 m_Cutdown = cutdown;
00657 return true;
00658 }
00659 }
00660 else if (last_line_width < m_Area.width())
00661 {
00662
00663 width -= (1.0 - last_line_width / width) / num_lines;
00664 if (width > last_line_width)
00665 width = last_line_width;
00666 if (static_cast<int>(width) == last_width)
00667 {
00668 m_Cutdown = cutdown;
00669 return true;
00670 }
00671 }
00672 if (width < too_narrow)
00673 width = too_narrow;
00674 }
00675 last_width = width;
00676 }
00677
00678 LOG(VB_GENERAL, LOG_ERR, QString("'%1' (%2) GetNarrowWidth: Gave up "
00679 "while trying to find optimal width "
00680 "for '%3'.")
00681 .arg(objectName()).arg(GetXMLLocation()).arg(m_CutMessage));
00682
00683 width = best_width;
00684 m_Cutdown = cutdown;
00685 return false;
00686 }
00687
00688 void MythUIText::FillCutMessage(void)
00689 {
00690 if (m_Area.isNull())
00691 return;
00692
00693 qreal width, height;
00694 QRectF min_rect;
00695 QFontMetrics fm(GetFontProperties()->face());
00696
00697 m_lineHeight = fm.height();
00698 m_Leading = m_MultiLine ? fm.leading() + m_extraLeading : m_extraLeading;
00699 m_CutMessage.clear();
00700 m_textCursor = -1;
00701
00702 if (m_Message != m_DefaultMessage)
00703 {
00704 bool isNumber;
00705 int value = m_Message.toInt(&isNumber);
00706
00707 if (isNumber && m_TemplateText.contains("%n"))
00708 {
00709 m_CutMessage = qApp->translate("ThemeUI",
00710 m_TemplateText.toUtf8(), NULL,
00711 QCoreApplication::UnicodeUTF8,
00712 qAbs(value));
00713 }
00714 else if (m_TemplateText.contains("%1"))
00715 {
00716 QString tmp = qApp->translate("ThemeUI", m_TemplateText.toUtf8(),
00717 NULL, QCoreApplication::UnicodeUTF8);
00718 m_CutMessage = tmp.arg(m_Message);
00719 }
00720 }
00721
00722 if (m_CutMessage.isEmpty())
00723 m_CutMessage = m_Message;
00724
00725 if (m_CutMessage.isEmpty())
00726 {
00727 if (m_Layouts.empty())
00728 m_Layouts.push_back(new QTextLayout);
00729
00730 QTextLine line;
00731 QTextOption textoption(static_cast<Qt::Alignment>(m_Justification));
00732 QVector<QTextLayout *>::iterator Ilayout = m_Layouts.begin();
00733
00734 (*Ilayout)->setTextOption(textoption);
00735 (*Ilayout)->setText("");
00736 (*Ilayout)->beginLayout();
00737 line = (*Ilayout)->createLine();
00738 line.setLineWidth(m_Area.width());
00739 line.setPosition(QPointF(0, 0));
00740 (*Ilayout)->endLayout();
00741 m_drawRect.setWidth(m_Area.width());
00742 m_drawRect.setHeight(m_lineHeight);
00743
00744 for (++Ilayout ; Ilayout != m_Layouts.end(); ++Ilayout)
00745 (*Ilayout)->clearLayout();
00746
00747 m_Ascent = m_Descent = m_leftBearing = m_rightBearing = 0;
00748 }
00749 else
00750 {
00751 QStringList templist;
00752 QStringList::iterator it;
00753
00754 switch (m_textCase)
00755 {
00756 case CaseUpper :
00757 m_CutMessage = m_CutMessage.toUpper();
00758 break;
00759 case CaseLower :
00760 m_CutMessage = m_CutMessage.toLower();
00761 break;
00762 case CaseCapitaliseFirst :
00763
00764 templist = m_CutMessage.split(". ");
00765
00766 for (it = templist.begin(); it != templist.end(); ++it)
00767 (*it).replace(0, 1, (*it).left(1).toUpper());
00768
00769 m_CutMessage = templist.join(". ");
00770 break;
00771 case CaseCapitaliseAll :
00772
00773 templist = m_CutMessage.split(" ");
00774
00775 for (it = templist.begin(); it != templist.end(); ++it)
00776 (*it).replace(0, 1, (*it).left(1).toUpper());
00777
00778 m_CutMessage = templist.join(" ");
00779 break;
00780 }
00781
00782 QTextOption textoption(static_cast<Qt::Alignment>(m_Justification));
00783 textoption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
00784
00785 int idx, num_lines;
00786 qreal last_line_width;
00787 QStringList paragraphs = m_CutMessage.split('\n',
00788 QString::KeepEmptyParts);
00789
00790 for (idx = m_Layouts.size(); idx < paragraphs.size(); ++idx)
00791 m_Layouts.push_back(new QTextLayout);
00792
00793 if (m_MultiLine && m_ShrinkNarrow &&
00794 m_MinSize.isValid() && !m_CutMessage.isEmpty())
00795 GetNarrowWidth(paragraphs, textoption, width);
00796 else
00797 width = m_Area.width();
00798
00799 height = 0;
00800 m_leftBearing = m_rightBearing = 0;
00801 LayoutParagraphs(paragraphs, textoption, width, height,
00802 min_rect, last_line_width, num_lines, true);
00803
00804 m_Canvas.setRect(0, 0, min_rect.x() + min_rect.width(), height);
00805 m_scrollPause = ScrollBounceDelay;
00806 m_scrollBounce = false;
00807
00814 QRect actual = fm.boundingRect(m_CutMessage);
00815 m_Ascent = -(actual.y() + fm.ascent());
00816 m_Descent = actual.height() - fm.height();
00817 }
00818
00819 if (m_scrolling)
00820 {
00821 if (m_scrollDirection == ScrollLeft ||
00822 m_scrollDirection == ScrollRight ||
00823 m_scrollDirection == ScrollHorizontal)
00824 {
00825 if (m_Canvas.width() > m_drawRect.width())
00826 {
00827 m_drawRect.setX(m_Area.x());
00828 m_drawRect.setWidth(m_Area.width());
00829 m_scrollOffset = m_drawRect.x() - m_Canvas.x();
00830 }
00831 }
00832 else
00833 {
00834 if (m_Canvas.height() > m_drawRect.height())
00835 {
00836 m_drawRect.setY(m_Area.y());
00837 m_drawRect.setHeight(m_Area.height());
00838 m_scrollOffset = m_drawRect.y() - m_Canvas.y();
00839 }
00840 }
00841 }
00842
00843
00844 if (m_Justification & (Qt::AlignCenter|Qt::AlignJustify))
00845 {
00846 m_drawRect.moveCenter(m_Area.center());
00847 min_rect.moveCenter(m_Area.center());
00848 }
00849
00850
00851 if (m_Justification & Qt::AlignLeft)
00852 {
00853
00854 if (m_ShrinkNarrow && m_MinSize.isValid() && min_rect.isValid() &&
00855 min_rect.width() < m_MinSize.x())
00856 {
00857 m_drawRect.moveLeft(m_Area.x() +
00858 (((m_MinSize.x() - min_rect.width() +
00859 fm.averageCharWidth()) / 2)));
00860 min_rect.setWidth(m_MinSize.x());
00861 }
00862 else
00863 m_drawRect.moveLeft(m_Area.x());
00864
00865 min_rect.moveLeft(m_Area.x());
00866 }
00867 else if (m_Justification & Qt::AlignRight)
00868 {
00869
00870 if (m_ShrinkNarrow && m_MinSize.isValid() && min_rect.isValid() &&
00871 min_rect.width() < m_MinSize.x())
00872 {
00873 m_drawRect.moveRight(m_Area.x() + m_Area.width() -
00874 (((m_MinSize.x() - min_rect.width() +
00875 fm.averageCharWidth()) / 2)));
00876 min_rect.setWidth(m_MinSize.x());
00877 }
00878 else
00879 m_drawRect.moveRight(m_Area.x() + m_Area.width());
00880
00881 min_rect.moveRight(m_Area.x() + m_Area.width());
00882 }
00883
00884
00885 if (m_Justification & Qt::AlignTop)
00886 {
00887
00888 if (!m_ShrinkNarrow && m_MinSize.isValid() && min_rect.isValid() &&
00889 min_rect.height() < m_MinSize.y())
00890 {
00891 m_drawRect.moveTop(m_Area.y() +
00892 ((m_MinSize.y() - min_rect.height()) / 2));
00893 min_rect.setHeight(m_MinSize.y());
00894 }
00895 else
00896 m_drawRect.moveTop(m_Area.y());
00897
00898 min_rect.moveTop(m_Area.y());
00899 }
00900 else if (m_Justification & Qt::AlignBottom)
00901 {
00902
00903 if (!m_ShrinkNarrow && m_MinSize.isValid() && min_rect.isValid() &&
00904 min_rect.height() < m_MinSize.y())
00905 {
00906 m_drawRect.moveBottom(m_Area.y() + m_Area.height() -
00907 ((m_MinSize.y() - min_rect.height()) / 2));
00908 min_rect.setHeight(m_MinSize.y());
00909 }
00910 else
00911 m_drawRect.moveBottom(m_Area.y() + m_Area.height());
00912
00913 min_rect.moveBottom(m_Area.y() + m_Area.height());
00914 }
00915
00916 m_Initiator = m_EnableInitiator;
00917 if (m_MinSize.isValid())
00918 {
00919
00920 SetMinArea(min_rect.toRect());
00921 }
00922 }
00923
00924 QPoint MythUIText::CursorPosition(int text_offset)
00925 {
00926 if (m_Layouts.empty())
00927 return m_Area.topLeft().toQPoint();
00928
00929 if (text_offset == m_textCursor)
00930 return m_cursorPos;
00931 m_textCursor = text_offset;
00932
00933 QVector<QTextLayout *>::const_iterator Ipara;
00934 QPoint pos;
00935 int x, y, mid, line_height;
00936 int offset = text_offset;
00937
00938 for (Ipara = m_Layouts.constBegin(); Ipara != m_Layouts.constEnd(); ++Ipara)
00939 {
00940 QTextLine line = (*Ipara)->lineForTextPosition(offset);
00941
00942 if (line.isValid())
00943 {
00944 pos.setX(line.cursorToX(&offset));
00945 pos.setY(line.y());
00946 break;
00947 }
00948 offset -= ((*Ipara)->text().size() + 1);
00949 }
00950 if (Ipara == m_Layouts.constEnd())
00951 {
00952 LOG(VB_GENERAL, LOG_ERR,
00953 QString("'%1' (%2) CursorPosition offset %3 not found in "
00954 "ANY paragraph!")
00955 .arg(objectName()).arg(GetXMLLocation()).arg(text_offset));
00956 return m_Area.topLeft().toQPoint();
00957 }
00958
00959 mid = m_drawRect.width() / 2;
00960 if (m_Canvas.width() <= m_drawRect.width() || pos.x() <= mid)
00961 x = 0;
00962 else if (pos.x() >= m_Canvas.width() - mid)
00963 {
00964 x = m_Canvas.width() - m_drawRect.width();
00965 pos.setX(pos.x() - x);
00966 }
00967 else
00968 {
00969 x = pos.x() - mid;
00970 pos.setX(pos.x() - x);
00971 }
00972
00973 line_height = m_lineHeight + m_Leading;
00974 mid = m_Area.height() / 2;
00975 mid -= (mid % line_height);
00976 y = pos.y() - mid;
00977
00978 if (y <= 0 || m_Canvas.height() <= m_Area.height())
00979 y = 0;
00980 else if (y + m_Area.height() > m_Canvas.height())
00981 {
00982 int visible_lines = ((m_Area.height() / line_height) * line_height);
00983 y = m_Canvas.height() - visible_lines;
00984 pos.setY(visible_lines - (m_Canvas.height() - pos.y()));
00985 }
00986 else
00987 {
00988 y -= m_Leading;
00989 pos.setY(mid + m_Leading);
00990 }
00991
00992 m_Canvas.moveTopLeft(QPoint(-x, -y));
00993
00994
00995 pos.setY(pos.y() + m_drawRect.y() - m_Area.y());
00996
00997 pos += m_Area.topLeft().toQPoint();
00998 m_cursorPos = pos;
00999
01000 return pos;
01001 }
01002
01003 void MythUIText::Pulse(void)
01004 {
01005
01006
01007
01008
01009
01010 MythUIType::Pulse();
01011
01012 if (m_colorCycling)
01013 {
01014 curR += incR;
01015 curG += incG;
01016 curB += incB;
01017
01018 m_curStep++;
01019
01020 if (m_curStep >= m_numSteps)
01021 {
01022 m_curStep = 0;
01023 incR *= -1;
01024 incG *= -1;
01025 incB *= -1;
01026 }
01027
01028 QColor newColor = QColor((int)curR, (int)curG, (int)curB);
01029
01030 if (newColor != m_Font->color())
01031 {
01032 m_Font->SetColor(newColor);
01033 SetRedraw();
01034 }
01035 }
01036
01037 if (m_scrolling)
01038 {
01039 switch (m_scrollDirection)
01040 {
01041 case ScrollLeft :
01042 if (m_Canvas.width() > m_drawRect.width())
01043 {
01044 ShiftCanvas(-1, 0);
01045 if (m_Canvas.x() + m_Canvas.width() < 0)
01046 SetCanvasPosition(m_drawRect.width(), 0);
01047 }
01048 break;
01049 case ScrollRight :
01050 if (m_Canvas.width() > m_drawRect.width())
01051 {
01052 ShiftCanvas(1, 0);
01053 if (m_Canvas.x() > m_drawRect.width())
01054 SetCanvasPosition(-m_Canvas.width(), 0);
01055 }
01056 break;
01057 case ScrollHorizontal:
01058 if (m_Canvas.width() <= m_drawRect.width())
01059 break;
01060 if (m_scrollPause > 0)
01061 {
01062 --m_scrollPause;
01063 break;
01064 }
01065 if (m_scrollBounce)
01066 {
01067 if (m_Canvas.x() + m_scrollOffset > m_drawRect.x())
01068 {
01069 m_scrollBounce = false;
01070 m_scrollPause = ScrollBounceDelay;
01071 }
01072 else
01073 ShiftCanvas(1, 0);
01074 }
01075 else
01076 {
01077 if (m_Canvas.x() + m_Canvas.width() + m_scrollOffset <
01078 m_drawRect.x() + m_drawRect.width())
01079 {
01080 m_scrollBounce = true;
01081 m_scrollPause = ScrollBounceDelay;
01082 }
01083 else
01084 ShiftCanvas(-1, 0);
01085 }
01086 break;
01087 case ScrollUp :
01088 if (m_Canvas.height() > m_drawRect.height())
01089 {
01090 ShiftCanvas(0, -1);
01091 if (m_Canvas.y() + m_Canvas.height() < 0)
01092 SetCanvasPosition(0, m_drawRect.height());
01093 }
01094 break;
01095 case ScrollDown :
01096 if (m_Canvas.height() > m_drawRect.height())
01097 {
01098 ShiftCanvas(0, 1);
01099 if (m_Canvas.y() > m_drawRect.height())
01100 SetCanvasPosition(0, -m_Canvas.height());
01101 }
01102 break;
01103 case ScrollVertical:
01104 if (m_Canvas.height() <= m_drawRect.height())
01105 break;
01106 if (m_scrollPause > 0)
01107 {
01108 --m_scrollPause;
01109 break;
01110 }
01111 if (m_scrollBounce)
01112 {
01113 if (m_Canvas.y() + m_scrollOffset > m_drawRect.y())
01114 {
01115 m_scrollBounce = false;
01116 m_scrollPause = ScrollBounceDelay;
01117 }
01118 else
01119 ShiftCanvas(0, 1);
01120 }
01121 else
01122 {
01123 if (m_Canvas.y() + m_Canvas.height() + m_scrollOffset <
01124 m_drawRect.y() + m_drawRect.height())
01125 {
01126 m_scrollBounce = true;
01127 m_scrollPause = ScrollBounceDelay;
01128 }
01129 else
01130 ShiftCanvas(0, -1);
01131 }
01132 break;
01133 }
01134 }
01135 }
01136
01137 void MythUIText::CycleColor(QColor startColor, QColor endColor, int numSteps)
01138 {
01139 if (!GetPainter()->SupportsAnimation())
01140 return;
01141
01142 m_startColor = startColor;
01143 m_endColor = endColor;
01144 m_numSteps = numSteps;
01145 m_curStep = 0;
01146
01147 curR = startColor.red();
01148 curG = startColor.green();
01149 curB = startColor.blue();
01150
01151 incR = (endColor.red() * 1.0 - curR) / m_numSteps;
01152 incG = (endColor.green() * 1.0 - curG) / m_numSteps;
01153 incB = (endColor.blue() * 1.0 - curB) / m_numSteps;
01154
01155 m_colorCycling = true;
01156 }
01157
01158 void MythUIText::StopCycling(void)
01159 {
01160 if (!m_colorCycling)
01161 return;
01162
01163 m_Font->SetColor(m_startColor);
01164 m_colorCycling = false;
01165 SetRedraw();
01166 }
01167
01168 bool MythUIText::ParseElement(
01169 const QString &filename, QDomElement &element, bool showWarnings)
01170 {
01171 if (element.tagName() == "area")
01172 {
01173 SetArea(parseRect(element));
01174 m_OrigDisplayRect = m_Area;
01175 }
01176
01177
01178 else if (element.tagName() == "font")
01179 {
01180 QString fontname = getFirstText(element);
01181 MythFontProperties *fp = GetFont(fontname);
01182
01183 if (!fp)
01184 fp = GetGlobalFontMap()->GetFont(fontname);
01185
01186 if (fp)
01187 {
01188 MythFontProperties font = *fp;
01189 int screenHeight = GetMythMainWindow()->GetUIScreenRect().height();
01190 font.Rescale(screenHeight);
01191 int fontStretch = GetMythUI()->GetFontStretch();
01192 font.AdjustStretch(fontStretch);
01193 QString state = element.attribute("state", "");
01194
01195 if (!state.isEmpty())
01196 {
01197 m_FontStates.insert(state, font);
01198 }
01199 else
01200 {
01201 m_FontStates.insert("default", font);
01202 *m_Font = m_FontStates["default"];
01203 }
01204 }
01205 }
01206 else if (element.tagName() == "extraleading")
01207 {
01208 m_extraLeading = getFirstText(element).toInt();
01209 }
01210 else if (element.tagName() == "value")
01211 {
01212 if (element.attribute("lang", "").isEmpty())
01213 {
01214 m_Message = qApp->translate("ThemeUI",
01215 parseText(element).toUtf8(), NULL,
01216 QCoreApplication::UnicodeUTF8);
01217 }
01218 else if (element.attribute("lang", "").toLower() ==
01219 gCoreContext->GetLanguageAndVariant())
01220 {
01221 m_Message = parseText(element);
01222 }
01223 else if (element.attribute("lang", "").toLower() ==
01224 gCoreContext->GetLanguage())
01225 {
01226 m_Message = parseText(element);
01227 }
01228
01229 m_DefaultMessage = m_Message;
01230 SetText(m_Message);
01231 }
01232 else if (element.tagName() == "template")
01233 {
01234 m_TemplateText = parseText(element);
01235 }
01236 else if (element.tagName() == "cutdown")
01237 {
01238 QString mode = getFirstText(element).toLower();
01239
01240 if (mode == "left")
01241 SetCutDown(Qt::ElideLeft);
01242 else if (mode == "middle")
01243 SetCutDown(Qt::ElideMiddle);
01244 else if (mode == "right" || parseBool(element))
01245 SetCutDown(Qt::ElideRight);
01246 else
01247 SetCutDown(Qt::ElideNone);
01248 }
01249 else if (element.tagName() == "multiline")
01250 {
01251 SetMultiLine(parseBool(element));
01252 }
01253 else if (element.tagName() == "align")
01254 {
01255 QString align = getFirstText(element).toLower();
01256 SetJustification(parseAlignment(align));
01257 }
01258 else if (element.tagName() == "colorcycle")
01259 {
01260 if (GetPainter()->SupportsAnimation())
01261 {
01262 QString tmp = element.attribute("start");
01263
01264 if (!tmp.isEmpty())
01265 m_startColor = QColor(tmp);
01266
01267 tmp = element.attribute("end");
01268
01269 if (!tmp.isEmpty())
01270 m_endColor = QColor(tmp);
01271
01272 tmp = element.attribute("steps");
01273
01274 if (!tmp.isEmpty())
01275 m_numSteps = tmp.toInt();
01276
01277
01278 CycleColor(m_startColor, m_endColor, m_numSteps);
01279 }
01280 else
01281 m_colorCycling = false;
01282
01283 m_colorCycling = parseBool(element.attribute("disable"));
01284 }
01285 else if (element.tagName() == "scroll")
01286 {
01287 if (GetPainter()->SupportsAnimation())
01288 {
01289 QString tmp = element.attribute("direction");
01290
01291 if (!tmp.isEmpty())
01292 {
01293 tmp = tmp.toLower();
01294
01295 if (tmp == "left")
01296 m_scrollDirection = ScrollLeft;
01297 else if (tmp == "right")
01298 m_scrollDirection = ScrollRight;
01299 else if (tmp == "up")
01300 m_scrollDirection = ScrollUp;
01301 else if (tmp == "down")
01302 m_scrollDirection = ScrollDown;
01303 else if (tmp == "horizontal")
01304 m_scrollDirection = ScrollHorizontal;
01305 else if (tmp == "vertical")
01306 m_scrollDirection = ScrollVertical;
01307 else
01308 {
01309 m_scrollDirection = ScrollNone;
01310 LOG(VB_GENERAL, LOG_ERR,
01311 QString("'%1' (%2) Invalid scroll attribute")
01312 .arg(objectName()).arg(GetXMLLocation()));
01313 }
01314 }
01315
01316 m_scrolling = true;
01317 }
01318 else
01319 m_scrolling = false;
01320 }
01321 else if (element.tagName() == "case")
01322 {
01323 QString stringCase = getFirstText(element).toLower();
01324
01325 if (stringCase == "lower")
01326 m_textCase = CaseLower;
01327 else if (stringCase == "upper")
01328 m_textCase = CaseUpper;
01329 else if (stringCase == "capitalisefirst")
01330 m_textCase = CaseCapitaliseFirst;
01331 else if (stringCase == "capitaliseall")
01332 m_textCase = CaseCapitaliseAll;
01333 else
01334 m_textCase = CaseNormal;
01335 }
01336 else
01337 {
01338 if (element.tagName() == "minsize" && element.hasAttribute("shrink"))
01339 {
01340 m_ShrinkNarrow = (element.attribute("shrink")
01341 .toLower() != "short");
01342 }
01343
01344 return MythUIType::ParseElement(filename, element, showWarnings);
01345 }
01346
01347 return true;
01348 }
01349
01350 void MythUIText::CopyFrom(MythUIType *base)
01351 {
01352 MythUIText *text = dynamic_cast<MythUIText *>(base);
01353
01354 if (!text)
01355 {
01356 LOG(VB_GENERAL, LOG_ERR,
01357 QString("'%1' (%2) ERROR, bad parsing '%3' (%4)")
01358 .arg(objectName()).arg(GetXMLLocation())
01359 .arg(base->objectName()).arg(base->GetXMLLocation()));
01360 return;
01361 }
01362
01363 m_Justification = text->m_Justification;
01364 m_OrigDisplayRect = text->m_OrigDisplayRect;
01365 m_AltDisplayRect = text->m_AltDisplayRect;
01366 m_Canvas = text->m_Canvas;
01367 m_drawRect = text->m_drawRect;
01368
01369 m_DefaultMessage = text->m_DefaultMessage;
01370 SetText(text->m_Message);
01371 m_CutMessage = text->m_CutMessage;
01372 m_TemplateText = text->m_TemplateText;
01373
01374 m_ShrinkNarrow = text->m_ShrinkNarrow;
01375 m_Cutdown = text->m_Cutdown;
01376 m_MultiLine = text->m_MultiLine;
01377 m_Leading = text->m_Leading;
01378 m_extraLeading = text->m_extraLeading;
01379 m_lineHeight = text->m_lineHeight;
01380 m_textCursor = text->m_textCursor;
01381
01382 QMutableMapIterator<QString, MythFontProperties> it(text->m_FontStates);
01383
01384 while (it.hasNext())
01385 {
01386 it.next();
01387 m_FontStates.insert(it.key(), it.value());
01388 }
01389
01390 *m_Font = m_FontStates["default"];
01391
01392 m_colorCycling = text->m_colorCycling;
01393 m_startColor = text->m_startColor;
01394 m_endColor = text->m_endColor;
01395 m_numSteps = text->m_numSteps;
01396 m_curStep = text->m_curStep;
01397 curR = text->curR;
01398 curG = text->curG;
01399 curB = text->curB;
01400 incR = text->incR;
01401 incG = text->incG;
01402 incB = text->incB;
01403
01404 m_scrolling = text->m_scrolling;
01405 m_scrollDirection = text->m_scrollDirection;
01406
01407 m_textCase = text->m_textCase;
01408
01409 MythUIType::CopyFrom(base);
01410 FillCutMessage();
01411 }
01412
01413 void MythUIText::CreateCopy(MythUIType *parent)
01414 {
01415 MythUIText *text = new MythUIText(parent, objectName());
01416 text->CopyFrom(this);
01417 }
01418
01419
01420 void MythUIText::Finalize(void)
01421 {
01422 if (m_scrolling && m_Cutdown != Qt::ElideNone)
01423 {
01424 LOG(VB_GENERAL, LOG_ERR,
01425 QString("'%1' (%2): <scroll> and <cutdown> are not combinable.")
01426 .arg(objectName()).arg(GetXMLLocation()));
01427 m_Cutdown = Qt::ElideNone;
01428 }
01429 FillCutMessage();
01430 }