6 #include <QCoreApplication>
8 #include <QDomDocument>
9 #include <QFontMetrics>
12 #include <QRegularExpression>
34 QRect displayRect, QRect altDisplayRect,
37 m_origDisplayRect(displayRect), m_altDisplayRect(altDisplayRect),
38 m_canvas(0, 0, displayRect.width(), displayRect.height()),
39 m_drawRect(displayRect),
40 m_message(text.trimmed()),
41 m_defaultMessage(text),
44 #if 0 // Not currently used
45 m_usingAltArea =
false;
100 if (newText.isEmpty())
103 static const QRegularExpression re {R
"(%(([^\|%]+)?\||\|(.))?([\w#]+)(\|(.+?))?%)",
104 QRegularExpression::DotMatchesEverythingOption};
106 bool replaced = map.contains(objectName());
108 if (!replaced && !newText.isEmpty() && newText.contains(re))
110 QString translatedTemplate = QCoreApplication::translate(
"ThemeUI",
113 QRegularExpressionMatchIterator i = re.globalMatch(translatedTemplate);
114 while (i.hasNext()) {
115 QRegularExpressionMatch match = i.next();
116 QString key = match.captured(4).toLower().trimmed();
118 if (map.contains(key))
134 const QString& newtext = text;
139 if (newtext.isEmpty())
159 if (newText.isEmpty())
162 static const QRegularExpression re {R
"(%(([^\|%]+)?\||\|(.))?([\w#]+)(\|(.+?))?%)",
163 QRegularExpression::DotMatchesEverythingOption};
165 if (!newText.isEmpty() && newText.contains(re))
167 QString translatedTemplate = QCoreApplication::translate(
"ThemeUI",
170 QString tempString = translatedTemplate;
171 bool replaced = map.contains(objectName());
173 QRegularExpressionMatchIterator i = re.globalMatch(translatedTemplate);
174 while (i.hasNext()) {
175 QRegularExpressionMatch match = i.next();
176 QString key = match.captured(4).toLower().trimmed();
179 if (map.contains(key))
183 if (!map.value(key).isEmpty())
185 replacement = QString(
"%1%2%3%4")
186 .arg(match.captured(2),
192 tempString.replace(match.captured(0), replacement);
199 else if (map.contains(objectName()))
201 SetText(map.value(objectName()));
240 #if 0 // Not currently used
241 void MythUIText::UseAlternateArea(
bool useAlt)
246 m_usingAltArea =
true;
251 m_usingAltArea =
false;
260 int h = just & Qt::AlignHorizontal_Mask;
261 int v = just & Qt::AlignVertical_Mask;
289 LOG(VB_GENERAL, LOG_ERR, QString(
"'%1' (%2): <scroll> and "
290 "<cutdown> are not combinable.")
338 QPoint newpoint(x, y);
349 if (x == 0 && y == 0)
358 int alphaMod, QRect clipRect)
365 drawrect.translate(xoffset, yoffset);
372 drawrect.setY(drawrect.y() -
m_ascent);
373 canvas.moveTop(canvas.y() +
m_ascent);
374 canvas.setHeight(canvas.height() +
m_ascent);
378 drawrect.setHeight(drawrect.height() +
m_descent);
379 canvas.setHeight(canvas.height() +
m_descent);
398 int outlineAlpha = 255;
403 MythPoint outline(outlineSize, outlineSize);
405 drawrect.setX(drawrect.x() - outline.x());
406 drawrect.setWidth(drawrect.width() + outline.x());
407 drawrect.setY(drawrect.y() - outline.y());
408 drawrect.setHeight(drawrect.height() + outline.y());
412 canvas.moveLeft(canvas.x() + outline.x());
413 canvas.setWidth(canvas.width() + outline.x());
414 canvas.moveTop(canvas.y() + outline.y());
415 canvas.setHeight(canvas.height() + outline.y());
422 int shadowAlpha = 255;
429 drawrect.setWidth(drawrect.width() + shadow.x());
430 drawrect.setHeight(drawrect.height() + shadow.y());
432 canvas.setWidth(canvas.width() + shadow.x());
433 canvas.setHeight(canvas.height() + shadow.y());
436 p->SetClipRect(clipRect);
443 layout->clearFormats();
446 QTextLayout::FormatRange range;
453 int outlineAlpha = 255;
459 outlineColor.setAlpha(outlineAlpha);
461 MythPoint outline(outlineSize, outlineSize);
465 pen.setBrush(outlineColor);
466 pen.setWidth(outline.x());
469 range.length = paragraph.size();
470 range.format.setTextOutline(pen);
479 while ((pos = paragraph.indexOf(
"[font]", pos, Qt::CaseInsensitive)) != -1)
481 if ((end = paragraph.indexOf(
"[/font]", pos + 1, Qt::CaseInsensitive))
484 if (range.length == -1)
487 range.length = pos - range.start;
488 if (range.length > 0)
491 LOG(VB_GUI, LOG_DEBUG,
492 QString(
"'%1' Setting \"%2\" with FONT %3")
494 paragraph.mid(range.start, range.length),
500 int len = end - pos - 6;
501 fontname = paragraph.mid(pos + 6, len);
508 range.format.setFont(fnt->
face());
509 #if QT_VERSION < QT_VERSION_CHECK(5,15,0)
510 range.format.setFontStyleHint(QFont::SansSerif,
511 QFont::OpenGLCompatible);
513 range.format.setFontStyleHint(QFont::SansSerif);
515 range.format.setForeground(fnt->
GetBrush());
520 QString(
"'%1' Unknown Font '%2' specified in template.")
521 .arg(objectName(), fontname));
524 LOG(VB_GUI, LOG_DEBUG, QString(
"Removing %1 through %2 '%3'")
525 .arg(pos).arg(end + 7 - pos).arg(paragraph.mid(pos,
527 paragraph.remove(pos, end + 7 - pos);
533 QString(
"'%1' Non-terminated [font] found in template")
539 if (range.length == -1)
541 range.length = paragraph.length() - range.start;
543 LOG(VB_GUI, LOG_DEBUG,
544 QString(
"'%1' Setting \"%2\" with FONT %3")
546 paragraph.mid(range.start, range.length),
557 bool & overflow, qreal width, qreal & height,
558 bool force, qreal & last_line_width,
559 QRectF & min_rect,
int & num_lines)
564 layout->setText(paragraph);
565 layout->beginLayout();
569 QTextLine line = layout->createLine();
574 line.setLineWidth(width);
576 if (!
m_multiLine && line.textLength() < paragraph.size())
581 paragraph = fm.elidedText(paragraph,
m_cutdown,
582 width - fm.averageCharWidth());
586 line.setLineWidth(INT_MAX);
590 line.setPosition(QPointF(0, height));
594 if (height >
m_area.height())
596 LOG(VB_GUI, num_lines ? LOG_DEBUG : LOG_NOTICE,
597 QString(
"'%1' (%2): height overflow. line height %3 "
598 "paragraph height %4, area height %5")
610 QString cut_line = fm.elidedText
611 (paragraph.mid(last_line),
613 width - fm.averageCharWidth());
614 paragraph = paragraph.left(last_line) + cut_line;
616 min_rect |= line.naturalTextRect();
627 last_line = line.textStart();
628 last_line_width = line.naturalTextWidth();
629 min_rect |= line.naturalTextRect();
632 if (
final && line.textLength())
647 bearing = fm.rightBearing
659 const QTextOption & textoption,
660 qreal width, qreal & height,
661 QRectF & min_rect, qreal & last_line_width,
662 int & num_lines,
bool final)
664 QStringList::const_iterator Ipara;
665 bool overflow =
false;
669 layout->clearLayout();
671 for (Ipara = paragraphs.begin(), idx = 0;
672 Ipara != paragraphs.end(); ++Ipara, ++idx)
675 layout->setTextOption(textoption);
678 QString para = *Ipara;
679 qreal saved_height = height;
680 QRectF saved_rect = min_rect;
681 if (!
Layout(para, layout,
final, overflow, width, height,
false,
682 last_line_width, min_rect, num_lines))
685 min_rect = saved_rect;
686 height = saved_height;
687 Layout(para, layout,
final, overflow, width, height,
true,
688 last_line_width, min_rect, num_lines);
698 const QTextOption & textoption, qreal & width)
700 qreal last_line_width = NAN;
707 width =
m_area.width() / 2.0;
708 int best_width =
m_area.width();
711 for (
int attempt = 0; attempt < 10; ++attempt)
719 min_rect, last_line_width, num_lines,
false);
726 if (too_narrow < width)
730 qreal lines = roundf((height -
m_drawRect.height()) / line_height);
731 lines -= (1.0 - last_line_width / width);
732 width += (lines * width) /
735 if (width > best_width ||
static_cast<int>(width) == last_width)
744 if (best_width > width)
747 qreal lines = floor((
m_area.height() - height) / line_height);
751 width -= width * (lines / num_lines - 1 + lines);
752 if (
static_cast<int>(width) == last_width)
758 else if (last_line_width <
m_area.width())
761 width -= (1.0 - last_line_width / width) / num_lines;
762 if (width > last_line_width)
763 width = last_line_width;
764 if (
static_cast<int>(width) == last_width)
770 if (width < too_narrow)
776 LOG(VB_GENERAL, LOG_ERR, QString(
"'%1' (%2) GetNarrowWidth: Gave up "
777 "while trying to find optimal width "
801 bool isNumber =
false;
827 QVector<QTextLayout *>::iterator Ilayout =
m_layouts.begin();
829 (*Ilayout)->setTextOption(textoption);
830 (*Ilayout)->setText(
"");
831 (*Ilayout)->beginLayout();
832 line = (*Ilayout)->createLine();
833 line.setLineWidth(
m_area.width());
834 line.setPosition(QPointF(0, 0));
835 (*Ilayout)->endLayout();
839 for (++Ilayout ; Ilayout !=
m_layouts.end(); ++Ilayout)
840 (*Ilayout)->clearLayout();
846 QStringList templist;
847 QStringList::iterator it;
861 for (it = templist.begin(); it != templist.end(); ++it)
862 if (!(*it).isEmpty())
863 (*it).replace(0, 1, (*it).at(0).toUpper());
871 for (it = templist.begin(); it != templist.end(); ++it)
872 if (!(*it).isEmpty())
873 (*it).replace(0, 1, (*it).at(0).toUpper());
882 textoption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
884 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
886 QString::KeepEmptyParts);
888 QStringList paragraphs =
m_cutMessage.split(
'\n', Qt::KeepEmptyParts);
891 for (
int idx =
m_layouts.size(); idx < paragraphs.size(); ++idx)
904 qreal last_line_width = NAN;
906 min_rect, last_line_width, num_lines,
true);
919 m_ascent = -(actual.y() + fm.ascent());
920 m_descent = actual.height() - fm.height();
951 min_rect.moveCenter(
m_area.center());
963 fm.averageCharWidth()) / 2)));
969 min_rect.moveLeft(
m_area.x());
979 fm.averageCharWidth()) / 2)));
996 ((
m_minSize.y() - min_rect.height()) / 2));
1002 min_rect.moveTop(
m_area.y());
1011 ((
m_minSize.y() - min_rect.height()) / 2));
1033 int layoutStartPos = 0;
1036 for (
int x = 0; x <
m_layouts.count(); x++)
1040 for (
int y = 0; y < layout->lineCount(); y++)
1046 QTextLine line = layout->lineAt(y);
1049 + (line.lineNumber() == layout->lineCount() - 1 ? 1 : 0))
1051 lineNo = lineCount - 1;
1056 currPos += line.textLength();
1060 layoutStartPos = currPos;
1064 if (lineNo == 0 && lines < 0)
1068 if (lineNo == lineCount - 1 && lines > 0)
1073 LOG(VB_GENERAL, LOG_ERR,
1074 QString(
"'%1' (%2) MoveCursor offset %3 not found in ANY paragraph!")
1079 int newLine = lineNo + lines;
1084 if (newLine >= lineCount)
1085 newLine = lineCount - 1;
1090 for (
int x = 0; x <
m_layouts.count(); x++)
1094 for (
int y = 0; y < layout->lineCount(); y++)
1097 QTextLine line = layout->lineAt(y);
1099 if (lineNo == newLine)
1100 return layoutStartPos + line.xToCursor(xPos);
1103 layoutStartPos += layout->text().length() + 1;
1119 QVector<QTextLayout *>::const_iterator Ipara {};
1123 int offset = text_offset;
1127 QTextLine line = (*Ipara)->lineForTextPosition(offset);
1131 pos.setX(line.cursorToX(&offset));
1135 offset -= ((*Ipara)->text().size() + 1);
1139 LOG(VB_GENERAL, LOG_ERR,
1140 QString(
"'%1' (%2) CursorPosition offset %3 not found in "
1142 .arg(objectName(),
GetXMLLocation(), QString::number(text_offset)));
1149 else if (pos.x() >=
m_canvas.width() - mid)
1152 pos.
setX(pos.x() - x);
1157 pos.setX(pos.x() - x);
1161 mid =
m_area.height() / 2;
1162 mid -= (mid % line_height);
1169 int visible_lines = ((
m_area.height() / line_height) * line_height);
1170 y =
m_canvas.height() - visible_lines;
1171 pos.
setY(visible_lines - (
m_canvas.height() - pos.y()));
1201 int64_t currentmsecs = QDateTime::currentMSecsSinceEpoch();
1202 int64_t interval = std::min(currentmsecs -
m_lastUpdate,
static_cast<int64_t
>(50));
1225 QColor newColor = QColor(
static_cast<int>(
m_curR),
1226 static_cast<int>(
m_curG),
1227 static_cast<int>(
m_curB));
1370 m_curR = startColor.red();
1371 m_curG = startColor.green();
1372 m_curB = startColor.blue();
1392 const QString &
filename, QDomElement &element,
bool showWarnings)
1394 if (element.tagName() ==
"area")
1401 else if (element.tagName() ==
"font")
1415 QString state = element.attribute(
"state",
"");
1417 if (!state.isEmpty())
1428 else if (element.tagName() ==
"extraleading")
1432 else if (element.tagName() ==
"value")
1434 if (element.attribute(
"lang",
"").isEmpty())
1436 m_message = QCoreApplication::translate(
"ThemeUI",
1439 else if ((element.attribute(
"lang",
"").toLower() ==
1441 (element.attribute(
"lang",
"").toLower() ==
1450 else if (element.tagName() ==
"template")
1454 else if (element.tagName() ==
"cutdown")
1460 else if (mode ==
"middle")
1462 else if (mode ==
"right" ||
parseBool(element))
1467 else if (element.tagName() ==
"multiline")
1471 else if (element.tagName() ==
"align")
1476 else if (element.tagName() ==
"colorcycle")
1480 QString
tmp = element.attribute(
"start");
1485 tmp = element.attribute(
"end");
1490 tmp = element.attribute(
"steps");
1503 else if (element.tagName() ==
"scroll")
1507 QString
tmp = element.attribute(
"direction");
1515 else if (
tmp ==
"right")
1517 else if (
tmp ==
"up")
1519 else if (
tmp ==
"down")
1521 else if (
tmp ==
"horizontal")
1523 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");
1549 #if 0 // scroll rate as a percentage of 70Hz
1550 float percent =
tmp.toFloat() / 100.0;
1552 #else // scroll rate as pixels per second
1553 int pixels =
tmp.toInt();
1557 tmp = element.attribute(
"returnrate");
1560 #if 0 // scroll rate as a percentage of 70Hz
1561 float percent =
tmp.toFloat() / 100.0;
1563 #else // scroll rate as pixels per second
1564 int pixels =
tmp.toInt();
1574 else if (element.tagName() ==
"case")
1578 if (stringCase ==
"lower")
1580 else if (stringCase ==
"upper")
1582 else if (stringCase ==
"capitalisefirst")
1584 else if (stringCase ==
"capitaliseall")
1591 if (element.tagName() ==
"minsize" && element.hasAttribute(
"shrink"))
1594 .toLower() !=
"short");
1605 auto *text =
dynamic_cast<MythUIText *
>(base);
1609 LOG(VB_GENERAL, LOG_ERR,
1610 QString(
"'%1' (%2) ERROR, bad parsing '%3' (%4)")
1635 QMutableMapIterator<QString, MythFontProperties> it(text->m_fontStates);
1637 while (it.hasNext())
1672 auto *text =
new MythUIText(parent, objectName());
1673 text->CopyFrom(
this);
1681 LOG(VB_GENERAL, LOG_ERR,
1682 QString(
"'%1' (%2): <scroll> and <cutdown> are not combinable.")