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)));
1125 else if (pos.x() >=
m_canvas.width() - mid)
1128 pos.
setX(pos.x() - x);
1133 pos.setX(pos.x() - x);
1137 mid =
m_area.height() / 2;
1138 mid -= (mid % line_height);
1145 int visible_lines = ((
m_area.height() / line_height) * line_height);
1146 y =
m_canvas.height() - visible_lines;
1147 pos.
setY(visible_lines - (
m_canvas.height() - pos.y()));
1177 int64_t currentmsecs = QDateTime::currentMSecsSinceEpoch();
1178 int64_t interval = std::min(currentmsecs -
m_lastUpdate,
static_cast<int64_t
>(50));
1201 QColor newColor = QColor(
static_cast<int>(
m_curR),
1202 static_cast<int>(
m_curG),
1203 static_cast<int>(
m_curB));
1354 m_curR = startColor.red();
1355 m_curG = startColor.green();
1356 m_curB = startColor.blue();
1376 const QString &
filename, QDomElement &element,
bool showWarnings)
1378 if (element.tagName() ==
"area")
1385 else if (element.tagName() ==
"font")
1399 QString state = element.attribute(
"state",
"");
1401 if (!state.isEmpty())
1412 else if (element.tagName() ==
"extraleading")
1416 else if (element.tagName() ==
"value")
1418 if (element.attribute(
"lang",
"").isEmpty())
1420 m_message = QCoreApplication::translate(
"ThemeUI",
1421 parseText(element).toUtf8().constData());
1423 else if ((element.attribute(
"lang",
"").toLower() ==
1425 (element.attribute(
"lang",
"").toLower() ==
1434 else if (element.tagName() ==
"template")
1438 else if (element.tagName() ==
"cutdown")
1444 else if (mode ==
"middle")
1446 else if (mode ==
"right" ||
parseBool(element))
1451 else if (element.tagName() ==
"multiline")
1455 else if (element.tagName() ==
"align")
1460 else if (element.tagName() ==
"colorcycle")
1464 QString
tmp = element.attribute(
"start");
1469 tmp = element.attribute(
"end");
1474 tmp = element.attribute(
"steps");
1489 else if (element.tagName() ==
"scroll")
1493 QString
tmp = element.attribute(
"direction");
1501 else if (
tmp ==
"right")
1503 else if (
tmp ==
"up")
1505 else if (
tmp ==
"down")
1507 else if (
tmp ==
"horizontal")
1509 else if (
tmp ==
"vertical")
1514 LOG(VB_GENERAL, LOG_ERR,
1515 QString(
"'%1' (%2) Invalid scroll attribute")
1520 tmp = element.attribute(
"startdelay");
1523 float seconds =
tmp.toFloat();
1526 tmp = element.attribute(
"returndelay");
1529 float seconds =
tmp.toFloat();
1532 tmp = element.attribute(
"rate");
1536 float percent =
tmp.toFloat() / 100.0;
1539 int pixels =
tmp.toInt();
1543 tmp = element.attribute(
"returnrate");
1547 float percent =
tmp.toFloat() / 100.0;
1550 int pixels =
tmp.toInt();
1562 else if (element.tagName() ==
"case")
1566 if (stringCase ==
"lower")
1568 else if (stringCase ==
"upper")
1570 else if (stringCase ==
"capitalisefirst")
1572 else if (stringCase ==
"capitaliseall")
1579 if (element.tagName() ==
"minsize" && element.hasAttribute(
"shrink"))
1582 .toLower() !=
"short");
1593 auto *text =
dynamic_cast<MythUIText *
>(base);
1597 LOG(VB_GENERAL, LOG_ERR,
1598 QString(
"'%1' (%2) ERROR, bad parsing '%3' (%4)")
1623 for (
auto it = text->m_fontStates.cbegin(); it != text->m_fontStates.cend(); ++it)
1655 auto *text =
new MythUIText(parent, objectName());
1656 text->CopyFrom(
this);
1664 LOG(VB_GENERAL, LOG_ERR,
1665 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