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;
78 for (
auto *layout : std::as_const(
m_layouts))
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 range.format.setFontStyleHint(QFont::SansSerif);
510 range.format.setForeground(fnt->
GetBrush());
515 QString(
"'%1' Unknown Font '%2' specified in template.")
516 .arg(objectName(), fontname));
519 LOG(VB_GUI, LOG_DEBUG, QString(
"Removing %1 through %2 '%3'")
520 .arg(pos).arg(end + 7 - pos).arg(paragraph.mid(pos,
522 paragraph.remove(pos, end + 7 - pos);
528 QString(
"'%1' Non-terminated [font] found in template")
534 if (range.length == -1)
536 range.length = paragraph.length() - range.start;
538 LOG(VB_GUI, LOG_DEBUG,
539 QString(
"'%1' Setting \"%2\" with FONT %3")
541 paragraph.mid(range.start, range.length),
552 bool & overflow, qreal width, qreal & height,
553 bool force, qreal & last_line_width,
554 QRectF & min_rect,
int & num_lines)
559 layout->setText(paragraph);
560 layout->beginLayout();
564 QTextLine line = layout->createLine();
569 line.setLineWidth(width);
571 if (!
m_multiLine && line.textLength() < paragraph.size())
576 paragraph = fm.elidedText(paragraph,
m_cutdown,
577 width - fm.averageCharWidth());
581 line.setLineWidth(INT_MAX);
585 line.setPosition(QPointF(0, height));
589 if (height >
m_area.height())
591 LOG(VB_GUI, num_lines ? LOG_DEBUG : LOG_NOTICE,
592 QString(
"'%1' (%2): height overflow. line height %3 "
593 "paragraph height %4, area height %5")
605 QString cut_line = fm.elidedText
606 (paragraph.mid(last_line),
608 width - fm.averageCharWidth());
609 paragraph = paragraph.left(last_line) + cut_line;
611 min_rect |= line.naturalTextRect();
622 last_line = line.textStart();
623 last_line_width = line.naturalTextWidth();
624 min_rect |= line.naturalTextRect();
627 if (
final && line.textLength())
642 bearing = fm.rightBearing
654 const QTextOption & textoption,
655 qreal width, qreal & height,
656 QRectF & min_rect, qreal & last_line_width,
657 int & num_lines,
bool final)
659 QStringList::const_iterator Ipara;
660 bool overflow =
false;
663 for (
auto *layout : std::as_const(
m_layouts))
664 layout->clearLayout();
666 for (Ipara = paragraphs.begin(), idx = 0;
667 Ipara != paragraphs.end(); ++Ipara, ++idx)
670 layout->setTextOption(textoption);
673 QString para = *Ipara;
674 qreal saved_height = height;
675 QRectF saved_rect = min_rect;
676 if (!
Layout(para, layout,
final, overflow, width, height,
false,
677 last_line_width, min_rect, num_lines))
680 min_rect = saved_rect;
681 height = saved_height;
682 Layout(para, layout,
final, overflow, width, height,
true,
683 last_line_width, min_rect, num_lines);
693 const QTextOption & textoption, qreal & width)
695 qreal last_line_width = NAN;
702 width =
m_area.width() / 2.0;
703 int best_width =
m_area.width();
706 for (
int attempt = 0; attempt < 10; ++attempt)
714 min_rect, last_line_width, num_lines,
false);
721 if (too_narrow < width)
725 qreal lines = roundf((height -
m_drawRect.height()) / line_height);
726 lines -= (1.0 - last_line_width / width);
727 width += (lines * width) /
730 if (width > best_width ||
static_cast<int>(width) == last_width)
739 if (best_width > width)
742 qreal lines = floor((
m_area.height() - height) / line_height);
746 width -= width * (lines / num_lines - 1 + lines);
747 if (
static_cast<int>(width) == last_width)
753 else if (last_line_width <
m_area.width())
756 width -= (1.0 - last_line_width / width) / num_lines;
757 if (width > last_line_width)
758 width = last_line_width;
759 if (
static_cast<int>(width) == last_width)
765 if (width < too_narrow)
771 LOG(VB_GENERAL, LOG_ERR, QString(
"'%1' (%2) GetNarrowWidth: Gave up "
772 "while trying to find optimal width "
796 bool isNumber =
false;
822 QVector<QTextLayout *>::iterator Ilayout =
m_layouts.begin();
824 (*Ilayout)->setTextOption(textoption);
825 (*Ilayout)->setText(
"");
826 (*Ilayout)->beginLayout();
827 line = (*Ilayout)->createLine();
828 line.setLineWidth(
m_area.width());
829 line.setPosition(QPointF(0, 0));
830 (*Ilayout)->endLayout();
834 for (++Ilayout ; Ilayout !=
m_layouts.end(); ++Ilayout)
835 (*Ilayout)->clearLayout();
841 QStringList templist;
842 QStringList::iterator it;
856 for (it = templist.begin(); it != templist.end(); ++it)
857 if (!(*it).isEmpty())
858 (*it).replace(0, 1, (*it).at(0).toUpper());
866 for (it = templist.begin(); it != templist.end(); ++it)
867 if (!(*it).isEmpty())
868 (*it).replace(0, 1, (*it).at(0).toUpper());
877 textoption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
879 QStringList paragraphs =
m_cutMessage.split(
'\n', Qt::KeepEmptyParts);
880 for (
int idx =
m_layouts.size(); idx < paragraphs.size(); ++idx)
893 qreal last_line_width = NAN;
895 min_rect, last_line_width, num_lines,
true);
908 m_ascent = -(actual.y() + fm.ascent());
909 m_descent = actual.height() - fm.height();
940 min_rect.moveCenter(
m_area.center());
952 fm.averageCharWidth()) / 2)));
958 min_rect.moveLeft(
m_area.x());
968 fm.averageCharWidth()) / 2)));
985 ((
m_minSize.y() - min_rect.height()) / 2));
991 min_rect.moveTop(
m_area.y());
1000 ((
m_minSize.y() - min_rect.height()) / 2));
1022 int layoutStartPos = 0;
1025 for (
int x = 0; x <
m_layouts.count(); x++)
1029 for (
int y = 0; y < layout->lineCount(); y++)
1035 QTextLine line = layout->lineAt(y);
1038 + (line.lineNumber() == layout->lineCount() - 1 ? 1 : 0))
1040 lineNo = lineCount - 1;
1045 currPos += line.textLength();
1049 layoutStartPos = currPos;
1053 if (lineNo == 0 && lines < 0)
1057 if (lineNo == lineCount - 1 && lines > 0)
1062 LOG(VB_GENERAL, LOG_ERR,
1063 QString(
"'%1' (%2) MoveCursor offset %3 not found in ANY paragraph!")
1068 int newLine = lineNo + lines;
1073 if (newLine >= lineCount)
1074 newLine = lineCount - 1;
1079 for (
int x = 0; x <
m_layouts.count(); x++)
1083 for (
int y = 0; y < layout->lineCount(); y++)
1086 QTextLine line = layout->lineAt(y);
1088 if (lineNo == newLine)
1089 return layoutStartPos + line.xToCursor(xPos);
1092 layoutStartPos += layout->text().length() + 1;
1108 QVector<QTextLayout *>::const_iterator Ipara {};
1112 int offset = text_offset;
1116 QTextLine line = (*Ipara)->lineForTextPosition(offset);
1120 pos.setX(line.cursorToX(&offset));
1124 offset -= ((*Ipara)->text().size() + 1);
1128 LOG(VB_GENERAL, LOG_ERR,
1129 QString(
"'%1' (%2) CursorPosition offset %3 not found in "
1131 .arg(objectName(),
GetXMLLocation(), QString::number(text_offset)));
1138 else if (pos.x() >=
m_canvas.width() - mid)
1141 pos.
setX(pos.x() - x);
1146 pos.setX(pos.x() - x);
1150 mid =
m_area.height() / 2;
1151 mid -= (mid % line_height);
1158 int visible_lines = ((
m_area.height() / line_height) * line_height);
1159 y =
m_canvas.height() - visible_lines;
1160 pos.
setY(visible_lines - (
m_canvas.height() - pos.y()));
1190 int64_t currentmsecs = QDateTime::currentMSecsSinceEpoch();
1191 int64_t interval = std::min(currentmsecs -
m_lastUpdate,
static_cast<int64_t
>(50));
1214 QColor newColor = QColor(
static_cast<int>(
m_curR),
1215 static_cast<int>(
m_curG),
1216 static_cast<int>(
m_curB));
1359 m_curR = startColor.red();
1360 m_curG = startColor.green();
1361 m_curB = startColor.blue();
1381 const QString &
filename, QDomElement &element,
bool showWarnings)
1383 if (element.tagName() ==
"area")
1390 else if (element.tagName() ==
"font")
1404 QString state = element.attribute(
"state",
"");
1406 if (!state.isEmpty())
1417 else if (element.tagName() ==
"extraleading")
1421 else if (element.tagName() ==
"value")
1423 if (element.attribute(
"lang",
"").isEmpty())
1425 m_message = QCoreApplication::translate(
"ThemeUI",
1428 else if ((element.attribute(
"lang",
"").toLower() ==
1430 (element.attribute(
"lang",
"").toLower() ==
1439 else if (element.tagName() ==
"template")
1443 else if (element.tagName() ==
"cutdown")
1449 else if (mode ==
"middle")
1451 else if (mode ==
"right" ||
parseBool(element))
1456 else if (element.tagName() ==
"multiline")
1460 else if (element.tagName() ==
"align")
1465 else if (element.tagName() ==
"colorcycle")
1469 QString
tmp = element.attribute(
"start");
1474 tmp = element.attribute(
"end");
1479 tmp = element.attribute(
"steps");
1492 else if (element.tagName() ==
"scroll")
1496 QString
tmp = element.attribute(
"direction");
1504 else if (
tmp ==
"right")
1506 else if (
tmp ==
"up")
1508 else if (
tmp ==
"down")
1510 else if (
tmp ==
"horizontal")
1512 else if (
tmp ==
"vertical")
1517 LOG(VB_GENERAL, LOG_ERR,
1518 QString(
"'%1' (%2) Invalid scroll attribute")
1523 tmp = element.attribute(
"startdelay");
1526 float seconds =
tmp.toFloat();
1529 tmp = element.attribute(
"returndelay");
1532 float seconds =
tmp.toFloat();
1535 tmp = element.attribute(
"rate");
1538 #if 0 // scroll rate as a percentage of 70Hz
1539 float percent =
tmp.toFloat() / 100.0;
1541 #else // scroll rate as pixels per second
1542 int pixels =
tmp.toInt();
1546 tmp = element.attribute(
"returnrate");
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();
1563 else if (element.tagName() ==
"case")
1567 if (stringCase ==
"lower")
1569 else if (stringCase ==
"upper")
1571 else if (stringCase ==
"capitalisefirst")
1573 else if (stringCase ==
"capitaliseall")
1580 if (element.tagName() ==
"minsize" && element.hasAttribute(
"shrink"))
1583 .toLower() !=
"short");
1594 auto *text =
dynamic_cast<MythUIText *
>(base);
1598 LOG(VB_GENERAL, LOG_ERR,
1599 QString(
"'%1' (%2) ERROR, bad parsing '%3' (%4)")
1624 QMutableMapIterator<QString, MythFontProperties> it(text->m_fontStates);
1626 while (it.hasNext())
1661 auto *text =
new MythUIText(parent, objectName());
1662 text->CopyFrom(
this);
1670 LOG(VB_GENERAL, LOG_ERR,
1671 QString(
"'%1' (%2): <scroll> and <cutdown> are not combinable.")