7 #include <QCoreApplication>
9 #include <QDomDocument>
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),
45 #if 0 // Not currently used
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()));
223 #if 0 // Not currently used
224 void 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");
1534 #if 0 // scroll rate as a percentage of 70Hz
1535 float percent =
tmp.toFloat() / 100.0;
1537 #else // scroll rate as pixels per second
1538 int pixels =
tmp.toInt();
1542 tmp = element.attribute(
"returnrate");
1545 #if 0 // scroll rate as a percentage of 70Hz
1546 float percent =
tmp.toFloat() / 100.0;
1548 #else // scroll rate as pixels per second
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.")