6 #include <QCoreApplication>
8 #include <QDomDocument>
9 #include <QFontMetrics>
12 #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;
101 if (newText.isEmpty())
104 QRegularExpression re {R
"(%(([^\|%]+)?\||\|(.))?([\w#]+)(\|(.+?))?%)",
105 QRegularExpression::DotMatchesEverythingOption};
107 bool replaced = map.contains(objectName());
109 if (!replaced && !newText.isEmpty() && newText.contains(re))
111 QString translatedTemplate = QCoreApplication::translate(
"ThemeUI",
114 QRegularExpressionMatchIterator i = re.globalMatch(translatedTemplate);
115 while (i.hasNext()) {
116 QRegularExpressionMatch match = i.next();
117 QString key = match.captured(4).toLower().trimmed();
119 if (map.contains(key))
135 const QString& newtext = text;
140 if (newtext.isEmpty())
160 if (newText.isEmpty())
163 QRegularExpression re {R
"(%(([^\|%]+)?\||\|(.))?([\w#]+)(\|(.+?))?%)",
164 QRegularExpression::DotMatchesEverythingOption};
166 if (!newText.isEmpty() && newText.contains(re))
168 QString translatedTemplate = QCoreApplication::translate(
"ThemeUI",
171 QString tempString = translatedTemplate;
172 bool replaced = map.contains(objectName());
174 QRegularExpressionMatchIterator i = re.globalMatch(translatedTemplate);
175 while (i.hasNext()) {
176 QRegularExpressionMatch match = i.next();
177 QString key = match.captured(4).toLower().trimmed();
180 if (map.contains(key))
184 if (!map.value(key).isEmpty())
186 replacement = QString(
"%1%2%3%4")
187 .arg(match.captured(2))
188 .arg(match.captured(3))
190 .arg(match.captured(6));
193 tempString.replace(match.captured(0), replacement);
200 else if (map.contains(objectName()))
202 SetText(map.value(objectName()));
241 #if 0 // Not currently used
242 void MythUIText::UseAlternateArea(
bool useAlt)
247 m_usingAltArea =
true;
252 m_usingAltArea =
false;
261 int h = just & Qt::AlignHorizontal_Mask;
262 int v = just & Qt::AlignVertical_Mask;
290 LOG(VB_GENERAL, LOG_ERR, QString(
"'%1' (%2): <scroll> and "
291 "<cutdown> are not combinable.")
339 QPoint newpoint(x, y);
350 if (x == 0 && y == 0)
359 int alphaMod, QRect clipRect)
366 drawrect.translate(xoffset, yoffset);
373 drawrect.setY(drawrect.y() -
m_ascent);
374 canvas.moveTop(canvas.y() +
m_ascent);
375 canvas.setHeight(canvas.height() +
m_ascent);
379 drawrect.setHeight(drawrect.height() +
m_descent);
380 canvas.setHeight(canvas.height() +
m_descent);
399 int outlineAlpha = 255;
404 MythPoint outline(outlineSize, outlineSize);
406 drawrect.setX(drawrect.x() - outline.x());
407 drawrect.setWidth(drawrect.width() + outline.x());
408 drawrect.setY(drawrect.y() - outline.y());
409 drawrect.setHeight(drawrect.height() + outline.y());
413 canvas.moveLeft(canvas.x() + outline.x());
414 canvas.setWidth(canvas.width() + outline.x());
415 canvas.moveTop(canvas.y() + outline.y());
416 canvas.setHeight(canvas.height() + outline.y());
423 int shadowAlpha = 255;
430 drawrect.setWidth(drawrect.width() + shadow.x());
431 drawrect.setHeight(drawrect.height() + shadow.y());
433 canvas.setWidth(canvas.width() + shadow.x());
434 canvas.setHeight(canvas.height() + shadow.y());
437 p->SetClipRect(clipRect);
444 layout->clearFormats();
447 QTextLayout::FormatRange range;
454 int outlineAlpha = 255;
460 outlineColor.setAlpha(outlineAlpha);
462 MythPoint outline(outlineSize, outlineSize);
466 pen.setBrush(outlineColor);
467 pen.setWidth(outline.x());
470 range.length = paragraph.size();
471 range.format.setTextOutline(pen);
480 while ((pos = paragraph.indexOf(
"[font]", pos, Qt::CaseInsensitive)) != -1)
482 if ((end = paragraph.indexOf(
"[/font]", pos + 1, Qt::CaseInsensitive))
485 if (range.length == -1)
488 range.length = pos - range.start;
489 if (range.length > 0)
492 LOG(VB_GUI, LOG_DEBUG,
493 QString(
"'%1' Setting \"%2\" with FONT %3")
495 .
arg(paragraph.mid(range.start, range.length))
501 int len = end - pos - 6;
502 fontname = paragraph.mid(pos + 6, len);
509 range.format.setFont(fnt->
face());
510 #if QT_VERSION < QT_VERSION_CHECK(5,15,0)
511 range.format.setFontStyleHint(QFont::SansSerif,
512 QFont::OpenGLCompatible);
514 range.format.setFontStyleHint(QFont::SansSerif);
516 range.format.setForeground(fnt->
GetBrush());
521 QString(
"'%1' Unknown Font '%2' specified in template.")
526 LOG(VB_GUI, LOG_DEBUG, QString(
"Removing %1 through %2 '%3'")
527 .
arg(pos).
arg(end + 7 - pos).
arg(paragraph.mid(pos,
529 paragraph.remove(pos, end + 7 - pos);
535 QString(
"'%1' Non-terminated [font] found in template")
541 if (range.length == -1)
543 range.length = paragraph.length() - range.start;
545 LOG(VB_GUI, LOG_DEBUG,
546 QString(
"'%1' Setting \"%2\" with FONT %3")
548 .
arg(paragraph.mid(range.start, range.length))
559 bool & overflow, qreal width, qreal & height,
560 bool force, qreal & last_line_width,
561 QRectF & min_rect,
int & num_lines)
566 layout->setText(paragraph);
567 layout->beginLayout();
571 QTextLine line = layout->createLine();
576 line.setLineWidth(width);
578 if (!
m_multiLine && line.textLength() < paragraph.size())
583 paragraph = fm.elidedText(paragraph,
m_cutdown,
584 width - fm.averageCharWidth());
588 line.setLineWidth(INT_MAX);
592 line.setPosition(QPointF(0, height));
596 if (height >
m_area.height())
598 LOG(VB_GUI, num_lines ? LOG_DEBUG : LOG_NOTICE,
599 QString(
"'%1' (%2): height overflow. line height %3 "
600 "paragraph height %4, area height %5")
612 QString cut_line = fm.elidedText
613 (paragraph.mid(last_line),
615 width - fm.averageCharWidth());
616 paragraph = paragraph.left(last_line) + cut_line;
618 min_rect |= line.naturalTextRect();
629 last_line = line.textStart();
630 last_line_width = line.naturalTextWidth();
631 min_rect |= line.naturalTextRect();
634 if (
final && line.textLength())
649 bearing = fm.rightBearing
661 const QTextOption & textoption,
662 qreal width, qreal & height,
663 QRectF & min_rect, qreal & last_line_width,
664 int & num_lines,
bool final)
666 QStringList::const_iterator Ipara;
667 bool overflow =
false;
671 layout->clearLayout();
673 for (Ipara = paragraphs.begin(), idx = 0;
674 Ipara != paragraphs.end(); ++Ipara, ++idx)
677 layout->setTextOption(textoption);
680 QString para = *Ipara;
681 qreal saved_height = height;
682 QRectF saved_rect = min_rect;
683 if (!
Layout(para, layout,
final, overflow, width, height,
false,
684 last_line_width, min_rect, num_lines))
687 min_rect = saved_rect;
688 height = saved_height;
689 Layout(para, layout,
final, overflow, width, height,
true,
690 last_line_width, min_rect, num_lines);
700 const QTextOption & textoption, qreal & width)
702 qreal last_line_width = NAN;
709 width =
m_area.width() / 2.0;
710 int best_width =
m_area.width();
713 for (
int attempt = 0; attempt < 10; ++attempt)
721 min_rect, last_line_width, num_lines,
false);
728 if (too_narrow < width)
732 qreal lines = roundf((height -
m_drawRect.height()) / line_height);
733 lines -= (1.0 - last_line_width / width);
734 width += (lines * width) /
737 if (width > best_width ||
static_cast<int>(width) == last_width)
746 if (best_width > width)
749 qreal lines = floor((
m_area.height() - height) / line_height);
753 width -= width * (lines / num_lines - 1 + lines);
754 if (
static_cast<int>(width) == last_width)
760 else if (last_line_width <
m_area.width())
763 width -= (1.0 - last_line_width / width) / num_lines;
764 if (width > last_line_width)
765 width = last_line_width;
766 if (
static_cast<int>(width) == last_width)
772 if (width < too_narrow)
778 LOG(VB_GENERAL, LOG_ERR, QString(
"'%1' (%2) GetNarrowWidth: Gave up "
779 "while trying to find optimal width "
803 bool isNumber =
false;
829 QVector<QTextLayout *>::iterator Ilayout =
m_layouts.begin();
831 (*Ilayout)->setTextOption(textoption);
832 (*Ilayout)->setText(
"");
833 (*Ilayout)->beginLayout();
834 line = (*Ilayout)->createLine();
835 line.setLineWidth(
m_area.width());
836 line.setPosition(QPointF(0, 0));
837 (*Ilayout)->endLayout();
841 for (++Ilayout ; Ilayout !=
m_layouts.end(); ++Ilayout)
842 (*Ilayout)->clearLayout();
848 QStringList templist;
849 QStringList::iterator it;
863 for (it = templist.begin(); it != templist.end(); ++it)
864 if (!(*it).isEmpty())
865 (*it).replace(0, 1, (*it).at(0).toUpper());
873 for (it = templist.begin(); it != templist.end(); ++it)
874 if (!(*it).isEmpty())
875 (*it).replace(0, 1, (*it).at(0).toUpper());
884 textoption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
886 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
888 QString::KeepEmptyParts);
890 QStringList paragraphs =
m_cutMessage.split(
'\n', Qt::KeepEmptyParts);
893 for (
int idx =
m_layouts.size(); idx < paragraphs.size(); ++idx)
906 qreal last_line_width = NAN;
908 min_rect, last_line_width, num_lines,
true);
921 m_ascent = -(actual.y() + fm.ascent());
922 m_descent = actual.height() - fm.height();
953 min_rect.moveCenter(
m_area.center());
965 fm.averageCharWidth()) / 2)));
971 min_rect.moveLeft(
m_area.x());
981 fm.averageCharWidth()) / 2)));
998 ((
m_minSize.y() - min_rect.height()) / 2));
1004 min_rect.moveTop(
m_area.y());
1013 ((
m_minSize.y() - min_rect.height()) / 2));
1035 int layoutStartPos = 0;
1038 for (
int x = 0; x <
m_layouts.count(); x++)
1042 for (
int y = 0; y < layout->lineCount(); y++)
1048 QTextLine line = layout->lineAt(y);
1051 + (line.lineNumber() == layout->lineCount() - 1 ? 1 : 0))
1053 lineNo = lineCount - 1;
1058 currPos += line.textLength();
1062 layoutStartPos = currPos;
1066 if (lineNo == 0 && lines < 0)
1070 if (lineNo == lineCount - 1 && lines > 0)
1075 LOG(VB_GENERAL, LOG_ERR,
1076 QString(
"'%1' (%2) MoveCursor offset %3 not found in ANY paragraph!")
1081 int newLine = lineNo + lines;
1086 if (newLine >= lineCount)
1087 newLine = lineCount - 1;
1092 for (
int x = 0; x <
m_layouts.count(); x++)
1096 for (
int y = 0; y < layout->lineCount(); y++)
1099 QTextLine line = layout->lineAt(y);
1101 if (lineNo == newLine)
1102 return layoutStartPos + line.xToCursor(xPos);
1105 layoutStartPos += layout->text().length() + 1;
1121 QVector<QTextLayout *>::const_iterator Ipara =
nullptr;
1125 int offset = text_offset;
1129 QTextLine line = (*Ipara)->lineForTextPosition(offset);
1133 pos.setX(line.cursorToX(&offset));
1137 offset -= ((*Ipara)->text().size() + 1);
1141 LOG(VB_GENERAL, LOG_ERR,
1142 QString(
"'%1' (%2) CursorPosition offset %3 not found in "
1151 else if (pos.x() >=
m_canvas.width() - mid)
1154 pos.
setX(pos.x() - x);
1159 pos.setX(pos.x() - x);
1163 mid =
m_area.height() / 2;
1164 mid -= (mid % line_height);
1171 int visible_lines = ((
m_area.height() / line_height) * line_height);
1172 y =
m_canvas.height() - visible_lines;
1173 pos.
setY(visible_lines - (
m_canvas.height() - pos.y()));
1203 int64_t currentmsecs = QDateTime::currentMSecsSinceEpoch();
1204 int64_t interval = std::min(currentmsecs -
m_lastUpdate,
static_cast<int64_t
>(50));
1227 QColor newColor = QColor(
static_cast<int>(
m_curR),
1228 static_cast<int>(
m_curG),
1229 static_cast<int>(
m_curB));
1372 m_curR = startColor.red();
1373 m_curG = startColor.green();
1374 m_curB = startColor.blue();
1394 const QString &
filename, QDomElement &element,
bool showWarnings)
1396 if (element.tagName() ==
"area")
1403 else if (element.tagName() ==
"font")
1417 QString state = element.attribute(
"state",
"");
1419 if (!state.isEmpty())
1430 else if (element.tagName() ==
"extraleading")
1434 else if (element.tagName() ==
"value")
1436 if (element.attribute(
"lang",
"").isEmpty())
1438 m_message = QCoreApplication::translate(
"ThemeUI",
1441 else if ((element.attribute(
"lang",
"").toLower() ==
1443 (element.attribute(
"lang",
"").toLower() ==
1452 else if (element.tagName() ==
"template")
1456 else if (element.tagName() ==
"cutdown")
1462 else if (mode ==
"middle")
1464 else if (mode ==
"right" ||
parseBool(element))
1469 else if (element.tagName() ==
"multiline")
1473 else if (element.tagName() ==
"align")
1478 else if (element.tagName() ==
"colorcycle")
1482 QString
tmp = element.attribute(
"start");
1487 tmp = element.attribute(
"end");
1492 tmp = element.attribute(
"steps");
1505 else if (element.tagName() ==
"scroll")
1509 QString
tmp = element.attribute(
"direction");
1517 else if (
tmp ==
"right")
1519 else if (
tmp ==
"up")
1521 else if (
tmp ==
"down")
1523 else if (
tmp ==
"horizontal")
1525 else if (
tmp ==
"vertical")
1530 LOG(VB_GENERAL, LOG_ERR,
1531 QString(
"'%1' (%2) Invalid scroll attribute")
1536 tmp = element.attribute(
"startdelay");
1539 float seconds =
tmp.toFloat();
1542 tmp = element.attribute(
"returndelay");
1545 float seconds =
tmp.toFloat();
1548 tmp = element.attribute(
"rate");
1551 #if 0 // scroll rate as a percentage of 70Hz
1552 float percent =
tmp.toFloat() / 100.0;
1554 #else // scroll rate as pixels per second
1555 int pixels =
tmp.toInt();
1559 tmp = element.attribute(
"returnrate");
1562 #if 0 // scroll rate as a percentage of 70Hz
1563 float percent =
tmp.toFloat() / 100.0;
1565 #else // scroll rate as pixels per second
1566 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 QMutableMapIterator<QString, MythFontProperties> it(text->m_fontStates);
1639 while (it.hasNext())
1674 auto *text =
new MythUIText(parent, objectName());
1675 text->CopyFrom(
this);
1683 LOG(VB_GENERAL, LOG_ERR,
1684 QString(
"'%1' (%2): <scroll> and <cutdown> are not combinable.")