7#include <QCoreApplication>
10#include <QFontMetrics>
13#include <QRegularExpression>
35 QRect displayRect, QRect altDisplayRect,
38 m_origDisplayRect(displayRect), m_altDisplayRect(altDisplayRect),
39 m_canvas(0, 0, displayRect.width(), displayRect.height()),
40 m_drawRect(displayRect),
41 m_message(text.trimmed()),
42 m_defaultMessage(text),
46 m_usingAltArea =
false;
61 for (
auto *layout : std::as_const(
m_layouts))
83 if (newText.isEmpty())
86 static const QRegularExpression re {R
"(%(([^\|%]+)?\||\|(.))?([\w#]+)(\|(.+?))?%)",
87 QRegularExpression::DotMatchesEverythingOption};
89 bool replaced = map.contains(objectName());
91 if (!replaced && !newText.isEmpty() && newText.contains(re))
93 QString translatedTemplate = QCoreApplication::translate(
"ThemeUI",
96 QRegularExpressionMatchIterator i = re.globalMatch(translatedTemplate);
98 QRegularExpressionMatch match = i.next();
99 QString key = match.captured(4).toLower().trimmed();
101 if (map.contains(key))
117 const QString& newtext = text;
122 if (newtext.isEmpty())
142 if (newText.isEmpty())
145 static const QRegularExpression re {R
"(%(([^\|%]+)?\||\|(.))?([\w#]+)(\|(.+?))?%)",
146 QRegularExpression::DotMatchesEverythingOption};
148 if (!newText.isEmpty() && newText.contains(re))
150 QString translatedTemplate = QCoreApplication::translate(
"ThemeUI",
153 QString tempString = translatedTemplate;
154 bool replaced = map.contains(objectName());
156 QRegularExpressionMatchIterator i = re.globalMatch(translatedTemplate);
157 while (i.hasNext()) {
158 QRegularExpressionMatch match = i.next();
159 QString key = match.captured(4).toLower().trimmed();
162 if (map.contains(key))
166 if (!map.value(key).isEmpty())
168 replacement = QString(
"%1%2%3%4")
169 .arg(match.captured(2),
175 tempString.replace(match.captured(0), replacement);
182 else if (map.contains(objectName()))
184 SetText(map.value(objectName()));
224void MythUIText::UseAlternateArea(
bool useAlt)
229 m_usingAltArea =
true;
234 m_usingAltArea =
false;
243 int h = just & Qt::AlignHorizontal_Mask;
244 int v = just & Qt::AlignVertical_Mask;
272 LOG(VB_GENERAL, LOG_ERR, QString(
"'%1' (%2): <scroll> and "
273 "<cutdown> are not combinable.")
321 QPoint newpoint(x, y);
332 if (x == 0 && y == 0)
341 int alphaMod, QRect clipRect)
348 drawrect.translate(xoffset, yoffset);
355 drawrect.setY(drawrect.y() -
m_ascent);
356 canvas.moveTop(canvas.y() +
m_ascent);
357 canvas.setHeight(canvas.height() +
m_ascent);
361 drawrect.setHeight(drawrect.height() +
m_descent);
362 canvas.setHeight(canvas.height() +
m_descent);
381 int outlineAlpha = 255;
386 MythPoint outline(outlineSize, outlineSize);
388 drawrect.setX(drawrect.x() - outline.x());
389 drawrect.setWidth(drawrect.width() + outline.x());
390 drawrect.setY(drawrect.y() - outline.y());
391 drawrect.setHeight(drawrect.height() + outline.y());
395 canvas.moveLeft(canvas.x() + outline.x());
396 canvas.setWidth(canvas.width() + outline.x());
397 canvas.moveTop(canvas.y() + outline.y());
398 canvas.setHeight(canvas.height() + outline.y());
405 int shadowAlpha = 255;
412 drawrect.setWidth(drawrect.width() + shadow.x());
413 drawrect.setHeight(drawrect.height() + shadow.y());
415 canvas.setWidth(canvas.width() + shadow.x());
416 canvas.setHeight(canvas.height() + shadow.y());
419 p->SetClipRect(clipRect);
426 layout->clearFormats();
429 QTextLayout::FormatRange range;
436 int outlineAlpha = 255;
442 outlineColor.setAlpha(outlineAlpha);
444 MythPoint outline(outlineSize, outlineSize);
448 pen.setBrush(outlineColor);
449 pen.setWidth(outline.x());
452 range.length = paragraph.size();
453 range.format.setTextOutline(pen);
462 while ((pos = paragraph.indexOf(
"[font]", pos, Qt::CaseInsensitive)) != -1)
464 end = paragraph.indexOf(
"[/font]", pos + 1, Qt::CaseInsensitive);
467 if (range.length == -1)
470 range.length = pos - range.start;
471 if (range.length > 0)
474 LOG(VB_GUI, LOG_DEBUG,
475 QString(
"'%1' Setting \"%2\" with FONT %3")
477 paragraph.mid(range.start, range.length),
483 int len = end - pos - 6;
484 fontname = paragraph.mid(pos + 6, len);
491 range.format.setFont(fnt->
face());
492 range.format.setFontStyleHint(QFont::SansSerif);
493 range.format.setForeground(fnt->
GetBrush());
498 QString(
"'%1' Unknown Font '%2' specified in template.")
499 .arg(objectName(), fontname));
502 LOG(VB_GUI, LOG_DEBUG, QString(
"Removing %1 through %2 '%3'")
503 .arg(pos).arg(end + 7 - pos).arg(paragraph.mid(pos,
505 paragraph.remove(pos, end + 7 - pos);
511 QString(
"'%1' Non-terminated [font] found in template")
517 if (range.length == -1)
519 range.length = paragraph.length() - range.start;
521 LOG(VB_GUI, LOG_DEBUG,
522 QString(
"'%1' Setting \"%2\" with FONT %3")
524 paragraph.mid(range.start, range.length),
535 bool & overflow, qreal width, qreal & height,
536 bool force, qreal & last_line_width,
537 QRectF & min_rect,
int & num_lines)
542 layout->setText(paragraph);
543 layout->beginLayout();
547 QTextLine line = layout->createLine();
552 line.setLineWidth(width);
554 if (!
m_multiLine && line.textLength() < paragraph.size())
559 paragraph = fm.elidedText(paragraph,
m_cutdown,
560 width - fm.averageCharWidth());
564 line.setLineWidth(INT_MAX);
568 line.setPosition(QPointF(0, height));
572 if (height >
m_area.height())
574 LOG(VB_GUI, num_lines ? LOG_DEBUG : LOG_NOTICE,
575 QString(
"'%1' (%2): height overflow. line height %3 "
576 "paragraph height %4, area height %5")
588 QString cut_line = fm.elidedText
589 (paragraph.mid(last_line),
591 width - fm.averageCharWidth());
592 paragraph = paragraph.left(last_line) + cut_line;
594 min_rect |= line.naturalTextRect();
607 last_line = line.textStart();
608 last_line_width = line.naturalTextWidth();
609 min_rect |= line.naturalTextRect();
612 if (
final && line.textLength())
626 bearing = fm.rightBearing
637 const QTextOption & textoption,
638 qreal width, qreal & height,
639 QRectF & min_rect, qreal & last_line_width,
640 int & num_lines,
bool final)
642 QStringList::const_iterator Ipara;
643 bool overflow =
false;
646 for (
auto *layout : std::as_const(
m_layouts))
647 layout->clearLayout();
649 for (Ipara = paragraphs.begin(), idx = 0;
650 Ipara != paragraphs.end(); ++Ipara, ++idx)
653 layout->setTextOption(textoption);
656 QString para = *Ipara;
657 qreal saved_height = height;
658 QRectF saved_rect = min_rect;
659 if (!
Layout(para, layout,
final, overflow, width, height,
false,
660 last_line_width, min_rect, num_lines))
663 min_rect = saved_rect;
664 height = saved_height;
665 Layout(para, layout,
final, overflow, width, height,
true,
666 last_line_width, min_rect, num_lines);
676 const QTextOption & textoption, qreal & width)
678 qreal last_line_width = __builtin_nan(
"");
685 width =
m_area.width() / 2.0;
686 int best_width =
m_area.width();
689 for (
int attempt = 0; attempt < 10; ++attempt)
697 min_rect, last_line_width, num_lines,
false);
704 too_narrow = std::max<qreal>(too_narrow, width);
707 qreal lines = round((height -
m_drawRect.height()) / line_height);
708 lines -= (1.0 - (last_line_width / width));
709 width += (lines * width) /
712 if (width > best_width ||
static_cast<int>(width) == last_width)
721 best_width = std::min<qreal>(best_width, width);
723 qreal lines = floor((
m_area.height() - height) / line_height);
727 width -= width * (lines / num_lines - 1 + lines);
728 if (
static_cast<int>(width) == last_width)
734 else if (last_line_width <
m_area.width())
737 width -= (1.0 - last_line_width / width) / num_lines;
738 width = std::min(width, last_line_width);
739 if (
static_cast<int>(width) == last_width)
745 width = std::max<qreal>(width, too_narrow);
750 LOG(VB_GENERAL, LOG_ERR, QString(
"'%1' (%2) GetNarrowWidth: Gave up "
751 "while trying to find optimal width "
775 bool isNumber =
false;
801 QVector<QTextLayout *>::iterator Ilayout =
m_layouts.begin();
803 (*Ilayout)->setTextOption(textoption);
804 (*Ilayout)->setText(
"");
805 (*Ilayout)->beginLayout();
806 line = (*Ilayout)->createLine();
807 line.setLineWidth(
m_area.width());
808 line.setPosition(QPointF(0, 0));
809 (*Ilayout)->endLayout();
813 for (++Ilayout ; Ilayout !=
m_layouts.end(); ++Ilayout)
814 (*Ilayout)->clearLayout();
820 QStringList templist;
821 QStringList::iterator it;
835 for (it = templist.begin(); it != templist.end(); ++it)
836 if (!(*it).isEmpty())
837 (*it).replace(0, 1, (*it).at(0).toUpper());
845 for (it = templist.begin(); it != templist.end(); ++it)
846 if (!(*it).isEmpty())
847 (*it).replace(0, 1, (*it).at(0).toUpper());
856 textoption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
858 QStringList paragraphs =
m_cutMessage.split(
'\n', Qt::KeepEmptyParts);
859 for (
int idx =
m_layouts.size(); idx < paragraphs.size(); ++idx)
862 qreal width = __builtin_nan(
"");
872 qreal last_line_width = __builtin_nan(
"");
874 min_rect, last_line_width, num_lines,
true);
887 m_ascent = -(actual.y() + fm.ascent());
888 m_descent = actual.height() - fm.height();
919 min_rect.moveCenter(
m_area.center());
931 fm.averageCharWidth()) / 2)));
939 min_rect.moveLeft(
m_area.x());
949 fm.averageCharWidth()) / 2)));
968 ((
m_minSize.y() - min_rect.height()) / 2));
976 min_rect.moveTop(
m_area.y());
985 ((
m_minSize.y() - min_rect.height()) / 2));
1009 int layoutStartPos = 0;
1012 for (
int x = 0; x <
m_layouts.count(); x++)
1016 for (
int y = 0; y < layout->lineCount(); y++)
1022 QTextLine line = layout->lineAt(y);
1025 + (line.lineNumber() == layout->lineCount() - 1 ? 1 : 0))
1027 lineNo = lineCount - 1;
1032 currPos += line.textLength();
1036 layoutStartPos = currPos;
1040 if (lineNo == 0 && lines < 0)
1044 if (lineNo == lineCount - 1 && lines > 0)
1049 LOG(VB_GENERAL, LOG_ERR,
1050 QString(
"'%1' (%2) MoveCursor offset %3 not found in ANY paragraph!")
1055 int newLine = lineNo + lines;
1057 newLine = std::max(newLine, 0);
1059 if (newLine >= lineCount)
1060 newLine = lineCount - 1;
1065 for (
int x = 0; x <
m_layouts.count(); x++)
1069 for (
int y = 0; y < layout->lineCount(); y++)
1072 QTextLine line = layout->lineAt(y);
1074 if (lineNo == newLine)
1075 return layoutStartPos + line.xToCursor(xPos);
1078 layoutStartPos += layout->text().length() + 1;
1094 QVector<QTextLayout *>::const_iterator Ipara {};
1098 int offset = text_offset;
1102 QTextLine line = (*Ipara)->lineForTextPosition(offset);
1106 pos.setX(line.cursorToX(&offset));
1110 offset -= ((*Ipara)->text().size() + 1);
1114 LOG(VB_GENERAL, LOG_ERR,
1115 QString(
"'%1' (%2) CursorPosition offset %3 not found in "
1117 .arg(objectName(),
GetXMLLocation(), QString::number(text_offset)));
1124 else if (pos.x() >=
m_canvas.width() - mid)
1127 pos.
setX(pos.x() - x);
1132 pos.setX(pos.x() - x);
1136 mid =
m_area.height() / 2;
1137 mid -= (mid % line_height);
1144 int visible_lines = ((
m_area.height() / line_height) * line_height);
1145 y =
m_canvas.height() - visible_lines;
1146 pos.
setY(visible_lines - (
m_canvas.height() - pos.y()));
1176 int64_t currentmsecs = QDateTime::currentMSecsSinceEpoch();
1177 int64_t interval = std::min(currentmsecs -
m_lastUpdate,
static_cast<int64_t
>(50));
1200 QColor newColor = QColor(
static_cast<int>(
m_curR),
1201 static_cast<int>(
m_curG),
1202 static_cast<int>(
m_curB));
1353 m_curR = startColor.red();
1354 m_curG = startColor.green();
1355 m_curB = startColor.blue();
1375 const QString &
filename, QDomElement &element,
bool showWarnings)
1377 if (element.tagName() ==
"area")
1384 else if (element.tagName() ==
"font")
1398 QString state = element.attribute(
"state",
"");
1400 if (!state.isEmpty())
1411 else if (element.tagName() ==
"extraleading")
1415 else if (element.tagName() ==
"value")
1417 if (element.attribute(
"lang",
"").isEmpty())
1419 m_message = QCoreApplication::translate(
"ThemeUI",
1422 else if ((element.attribute(
"lang",
"").toLower() ==
1424 (element.attribute(
"lang",
"").toLower() ==
1433 else if (element.tagName() ==
"template")
1437 else if (element.tagName() ==
"cutdown")
1443 else if (mode ==
"middle")
1445 else if (mode ==
"right" ||
parseBool(element))
1450 else if (element.tagName() ==
"multiline")
1454 else if (element.tagName() ==
"align")
1459 else if (element.tagName() ==
"colorcycle")
1463 QString
tmp = element.attribute(
"start");
1468 tmp = element.attribute(
"end");
1473 tmp = element.attribute(
"steps");
1488 else if (element.tagName() ==
"scroll")
1492 QString
tmp = element.attribute(
"direction");
1500 else if (
tmp ==
"right")
1502 else if (
tmp ==
"up")
1504 else if (
tmp ==
"down")
1506 else if (
tmp ==
"horizontal")
1508 else if (
tmp ==
"vertical")
1513 LOG(VB_GENERAL, LOG_ERR,
1514 QString(
"'%1' (%2) Invalid scroll attribute")
1519 tmp = element.attribute(
"startdelay");
1522 float seconds =
tmp.toFloat();
1525 tmp = element.attribute(
"returndelay");
1528 float seconds =
tmp.toFloat();
1531 tmp = element.attribute(
"rate");
1535 float percent =
tmp.toFloat() / 100.0;
1538 int pixels =
tmp.toInt();
1542 tmp = element.attribute(
"returnrate");
1546 float percent =
tmp.toFloat() / 100.0;
1549 int pixels =
tmp.toInt();
1561 else if (element.tagName() ==
"case")
1565 if (stringCase ==
"lower")
1567 else if (stringCase ==
"upper")
1569 else if (stringCase ==
"capitalisefirst")
1571 else if (stringCase ==
"capitaliseall")
1578 if (element.tagName() ==
"minsize" && element.hasAttribute(
"shrink"))
1581 .toLower() !=
"short");
1592 auto *text =
dynamic_cast<MythUIText *
>(base);
1596 LOG(VB_GENERAL, LOG_ERR,
1597 QString(
"'%1' (%2) ERROR, bad parsing '%3' (%4)")
1622 QMutableMapIterator<QString, MythFontProperties> it(text->m_fontStates);
1624 while (it.hasNext())
1659 auto *text =
new MythUIText(parent, objectName());
1660 text->CopyFrom(
this);
1668 LOG(VB_GENERAL, LOG_ERR,
1669 QString(
"'%1' (%2): <scroll> and <cutdown> are not combinable.")
MythFontProperties * GetFont(const QString &text)
QString GetLanguage(void)
Returns two character ISO-639 language descriptor for UI language.
QString GetLanguageAndVariant(void)
Returns the user-set language and variant.
void AdjustStretch(int stretch)
QBrush GetBrush(void) const
void GetShadow(QPoint &offset, QColor &color, int &alpha) const
void GetOutline(QColor &color, int &size, int &alpha) const
void SetColor(const QColor &color)
QString GetHash(void) const
Wrapper around QPoint allowing us to handle percentage and other relative values for positioning in m...
QPoint toQPoint(void) const
Wrapper around QRect allowing us to handle percentage and other relative values for areas in mythui.
void setRect(const QString &sX, const QString &sY, const QString &sWidth, const QString &sHeight, const QString &baseRes=QString())
MythPoint topLeft(void) const
void setY(const QString &sY)
void moveTop(const QString &sY)
void moveLeft(const QString &sX)
void setX(const QString &sX)
void setWidth(const QString &sWidth)
void setHeight(const QString &sHeight)
void moveTopLeft(QPoint point)
QRect toQRect(void) const
int GetFontStretch() const
All purpose text widget, displays a text string.
bool ParseElement(const QString &filename, QDomElement &element, bool showWarnings) override
Parse the xml definition of this widget setting the state of the object accordingly.
MythUIText(MythUIType *parent, const QString &name)
void DrawSelf(MythPainter *p, int xoffset, int yoffset, int alphaMod, QRect clipRect) override
float m_scrollForwardRate
bool LayoutParagraphs(const QStringList ¶graphs, const QTextOption &textoption, qreal width, qreal &height, QRectF &min_rect, qreal &last_line_width, int &num_lines, bool final)
void SetArea(const MythRect &rect) override
void SetPosition(const MythPoint &pos) override
Qt::TextElideMode m_cutdown
int MoveCursor(int lines)
MythFontProperties * m_font
void ShiftCanvas(int x, int y)
void CopyFrom(MythUIType *base) override
Copy this widgets state from another.
bool FormatTemplate(QString ¶graph, QTextLayout *layout)
QPoint CursorPosition(int text_offset)
void Reset(void) override
Reset the widget to it's original state, should not reset changes made by the theme.
void CycleColor(const QColor &startColor, const QColor &endColor, int numSteps)
void SetJustification(int just)
void SetMultiLine(bool multiline)
MythRect m_altDisplayRect
QString GetTemplateText(void) const
void SetFontProperties(const MythFontProperties &fontProps)
void CreateCopy(MythUIType *parent) override
Copy the state of this widget to the one given, it must be of the same type.
void SetTextFromMap(const InfoMap &map)
int GetJustification(void) const
void Finalize(void) override
Perform any post-xml parsing initialisation tasks.
void ResetMap(const InfoMap &map)
bool Layout(QString ¶graph, QTextLayout *layout, bool final, bool &overflow, qreal width, qreal &height, bool force, qreal &last_line_width, QRectF &min_rect, int &num_lines)
MythRect m_origDisplayRect
void FillCutMessage(void)
void Pulse(void) override
Pulse is called 70 times a second to trigger a single frame of an animation.
const MythFontProperties * GetFontProperties()
bool GetNarrowWidth(const QStringList ¶graphs, const QTextOption &textoption, qreal &width)
void SetFontState(const QString &state)
QString GetDefaultText(void) const
void SetCanvasPosition(int x, int y)
ScrollDir m_scrollDirection
void SetCutDown(Qt::TextElideMode mode)
QVector< QTextLayout * > m_layouts
virtual void SetText(const QString &text)
The base class on which all widgets and screens are based.
virtual MythPainter * GetPainter(void)
QString GetXMLLocation(void) const
virtual void SetArea(const MythRect &rect)
virtual void CopyFrom(MythUIType *base)
Copy this widgets state from another.
virtual void SetMinArea(const MythRect &rect)
Set the minimum area based on the given size.
virtual void Pulse(void)
Pulse is called 70 times a second to trigger a single frame of an animation.
MythFontProperties * GetFont(const QString &text) const
int CalcAlpha(int alphamod) const
void SetPosition(int x, int y)
Convenience method, calls SetPosition(const MythPoint&) Override that instead to change functionality...
void DependChanged(bool isDefault)
virtual void Reset(void)
Reset the widget to it's original state, should not reset changes made by the theme.
virtual bool ParseElement(const QString &filename, QDomElement &element, bool showWarnings)
Parse the xml definition of this widget setting the state of the object accordingly.
static MythRect parseRect(const QString &text, bool normalize=true)
static int parseAlignment(const QString &text)
static QString getFirstText(QDomElement &element)
static bool parseBool(const QString &text)
static QString parseText(QDomElement &element)
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
FontMap * GetGlobalFontMap(void)
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
MythMainWindow * GetMythMainWindow(void)
QVector< QTextLayout::FormatRange > FormatVector
QHash< QString, QString > InfoMap
static constexpr uint8_t DEFAULT_REFRESH_RATE
const std::array< const std::string, 8 > formats