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",
94 newText.toUtf8().constData());
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",
151 newText.toUtf8().constData());
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;
782 nullptr, qAbs(value));
786 QString tmp = QCoreApplication::translate(
"ThemeUI",
802 QVector<QTextLayout *>::iterator Ilayout =
m_layouts.begin();
804 (*Ilayout)->setTextOption(textoption);
805 (*Ilayout)->setText(
"");
806 (*Ilayout)->beginLayout();
807 line = (*Ilayout)->createLine();
808 line.setLineWidth(
m_area.width());
809 line.setPosition(QPointF(0, 0));
810 (*Ilayout)->endLayout();
814 for (++Ilayout ; Ilayout !=
m_layouts.end(); ++Ilayout)
815 (*Ilayout)->clearLayout();
821 QStringList templist;
822 QStringList::iterator it;
836 for (it = templist.begin(); it != templist.end(); ++it)
837 if (!(*it).isEmpty())
838 (*it).replace(0, 1, (*it).at(0).toUpper());
846 for (it = templist.begin(); it != templist.end(); ++it)
847 if (!(*it).isEmpty())
848 (*it).replace(0, 1, (*it).at(0).toUpper());
857 textoption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
859 QStringList paragraphs =
m_cutMessage.split(
'\n', Qt::KeepEmptyParts);
860 for (
int idx =
m_layouts.size(); idx < paragraphs.size(); ++idx)
863 qreal width = __builtin_nan(
"");
873 qreal last_line_width = __builtin_nan(
"");
875 min_rect, last_line_width, num_lines,
true);
888 m_ascent = -(actual.y() + fm.ascent());
889 m_descent = actual.height() - fm.height();
920 min_rect.moveCenter(
m_area.center());
932 fm.averageCharWidth()) / 2));
940 min_rect.moveLeft(
m_area.x());
950 fm.averageCharWidth()) / 2));
969 ((
m_minSize.y() - min_rect.height()) / 2));
977 min_rect.moveTop(
m_area.y());
986 ((
m_minSize.y() - min_rect.height()) / 2));
1010 int layoutStartPos = 0;
1013 for (
int x = 0; x <
m_layouts.count(); x++)
1017 for (
int y = 0; y < layout->lineCount(); y++)
1023 QTextLine line = layout->lineAt(y);
1026 + (line.lineNumber() == layout->lineCount() - 1 ? 1 : 0))
1028 lineNo = lineCount - 1;
1033 currPos += line.textLength();
1037 layoutStartPos = currPos;
1041 if (lineNo == 0 && lines < 0)
1045 if (lineNo == lineCount - 1 && lines > 0)
1050 LOG(VB_GENERAL, LOG_ERR,
1051 QString(
"'%1' (%2) MoveCursor offset %3 not found in ANY paragraph!")
1056 int newLine = lineNo + lines;
1058 newLine = std::max(newLine, 0);
1060 if (newLine >= lineCount)
1061 newLine = lineCount - 1;
1066 for (
int x = 0; x <
m_layouts.count(); x++)
1070 for (
int y = 0; y < layout->lineCount(); y++)
1073 QTextLine line = layout->lineAt(y);
1075 if (lineNo == newLine)
1076 return layoutStartPos + line.xToCursor(xPos);
1079 layoutStartPos += layout->text().length() + 1;
1095 QVector<QTextLayout *>::const_iterator Ipara {};
1099 int offset = text_offset;
1103 QTextLine line = (*Ipara)->lineForTextPosition(offset);
1107 pos.setX(line.cursorToX(&offset));
1111 offset -= ((*Ipara)->text().size() + 1);
1115 LOG(VB_GENERAL, LOG_ERR,
1116 QString(
"'%1' (%2) CursorPosition offset %3 not found in "
1118 .arg(objectName(),
GetXMLLocation(), QString::number(text_offset)));
1127 else if (pos.x() >=
m_canvas.width() - mid)
1130 pos.
setX(pos.x() - x);
1135 pos.setX(pos.x() - x);
1139 mid =
m_area.height() / 2;
1140 mid -= (mid % line_height);
1149 int visible_lines = ((
m_area.height() / line_height) * line_height);
1150 y =
m_canvas.height() - visible_lines;
1151 pos.
setY(visible_lines - (
m_canvas.height() - pos.y()));
1181 int64_t currentmsecs = QDateTime::currentMSecsSinceEpoch();
1182 int64_t interval = std::min(currentmsecs -
m_lastUpdate,
static_cast<int64_t
>(50));
1205 QColor newColor = QColor(
static_cast<int>(
m_curR),
1206 static_cast<int>(
m_curG),
1207 static_cast<int>(
m_curB));
1360#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
1361 m_curR = startColor.red();
1362 m_curG = startColor.green();
1363 m_curB = startColor.blue();
1369 m_curR = startColor.redF();
1370 m_curG = startColor.greenF();
1371 m_curB = startColor.blueF();
1391 const QString &
filename, QDomElement &element,
bool showWarnings)
1393 if (element.tagName() ==
"area")
1400 else if (element.tagName() ==
"font")
1414 QString state = element.attribute(
"state",
"");
1416 if (!state.isEmpty())
1427 else if (element.tagName() ==
"extraleading")
1431 else if (element.tagName() ==
"value")
1433 if (element.attribute(
"lang",
"").isEmpty())
1435 m_message = QCoreApplication::translate(
"ThemeUI",
1436 parseText(element).toUtf8().constData());
1438 else if ((element.attribute(
"lang",
"").toLower() ==
1440 (element.attribute(
"lang",
"").toLower() ==
1449 else if (element.tagName() ==
"template")
1453 else if (element.tagName() ==
"cutdown")
1459 else if (mode ==
"middle")
1461 else if (mode ==
"right" ||
parseBool(element))
1466 else if (element.tagName() ==
"multiline")
1470 else if (element.tagName() ==
"align")
1475 else if (element.tagName() ==
"colorcycle")
1479 QString tmp = element.attribute(
"start");
1484 tmp = element.attribute(
"end");
1489 tmp = element.attribute(
"steps");
1504 else if (element.tagName() ==
"scroll")
1508 QString tmp = element.attribute(
"direction");
1512 tmp = tmp.toLower();
1514 if (tmp ==
"left") {
1516 }
else if (tmp ==
"right") {
1518 }
else if (tmp ==
"up") {
1520 }
else if (tmp ==
"down") {
1522 }
else if (tmp ==
"horizontal") {
1524 }
else if (tmp ==
"vertical") {
1528 LOG(VB_GENERAL, LOG_ERR,
1529 QString(
"'%1' (%2) Invalid scroll attribute")
1534 tmp = element.attribute(
"startdelay");
1537 float seconds = tmp.toFloat();
1540 tmp = element.attribute(
"returndelay");
1543 float seconds = tmp.toFloat();
1546 tmp = element.attribute(
"rate");
1550 float percent = tmp.toFloat() / 100.0;
1553 int pixels = tmp.toInt();
1557 tmp = element.attribute(
"returnrate");
1561 float percent = tmp.toFloat() / 100.0;
1564 int pixels = tmp.toInt();
1576 else if (element.tagName() ==
"case")
1580 if (stringCase ==
"lower")
1582 else if (stringCase ==
"upper")
1584 else if (stringCase ==
"capitalisefirst")
1586 else if (stringCase ==
"capitaliseall")
1593 if (element.tagName() ==
"minsize" && element.hasAttribute(
"shrink"))
1596 .toLower() !=
"short");
1607 auto *text =
dynamic_cast<MythUIText *
>(base);
1611 LOG(VB_GENERAL, LOG_ERR,
1612 QString(
"'%1' (%2) ERROR, bad parsing '%3' (%4)")
1637 for (
auto it = text->m_fontStates.cbegin(); it != text->m_fontStates.cend(); ++it)
1669 auto *text =
new MythUIText(parent, objectName());
1670 text->CopyFrom(
this);
1678 LOG(VB_GENERAL, LOG_ERR,
1679 QString(
"'%1' (%2): <scroll> and <cutdown> are not combinable.")
static const std::array< const std::string, 8 > formats
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