From a27065673cf1cf98cbdf49ec8ade2a4fe40a585c Mon Sep 17 00:00:00 2001 From: Michel Dagenais Date: Wed, 24 Jun 2020 10:58:08 -0400 Subject: [PATCH] Allow Text meter widgets to be of same size and well aligned (#3506) The y scale is computed from the font instead of only from the current content, to avoid having different scales between different Text widgets of the same size. For example, "kph" has a high k and low p, resulting in a text widget with bigger bounding box, and thus smaller scale than a text widget with "watts". Options are added for alignment and for text width, to help align the different entries. The formatting of metrics with the integer part as Text and fraction as AltText is more systematic and it is possible to add an AltTextSuffix, to specify the units when they are not provided in VideoWindow. The visibility of the BoundingRect and Background car be controlled. --- src/Train/MeterWidget.cpp | 55 +++++++++++++------- src/Train/MeterWidget.h | 7 ++- src/Train/VideoLayoutParser.cpp | 89 ++++++++++++++++++++++++++------- src/Train/VideoLayoutParser.h | 9 +--- src/Train/VideoWindow.cpp | 47 +++++++++++------ 5 files changed, 146 insertions(+), 61 deletions(-) diff --git a/src/Train/MeterWidget.cpp b/src/Train/MeterWidget.cpp index 53d498028..9baa84511 100644 --- a/src/Train/MeterWidget.cpp +++ b/src/Train/MeterWidget.cpp @@ -54,6 +54,7 @@ MeterWidget::MeterWidget(QString Name, QWidget *parent, QString Source) : QWidge m_SubRange = 10; m_Zoom = 16; boundingRectVisibility = false; + backgroundVisibility = false; forceSquareRatio = true; } @@ -117,19 +118,29 @@ QSize MeterWidget::minimumSize() const void MeterWidget::paintEvent(QPaintEvent* paintevent) { Q_UNUSED(paintevent); + if(!boundingRectVisibility && !backgroundVisibility) return; + + QPainter painter(this); + painter.setClipRegion(videoContainerRegion); + painter.setRenderHint(QPainter::Antialiasing); + + int radius = qMin(m_Width, m_Height) * 0.1; + if(backgroundVisibility) painter.setBrush(QBrush(m_BackgroundColor)); + else painter.setBrush(Qt::NoBrush); + if (boundingRectVisibility) { - m_OutlinePen = QPen(boundingRectColor); + m_OutlinePen = QPen(m_BoundingRectColor); m_OutlinePen.setWidth(2); m_OutlinePen.setStyle(Qt::SolidLine); - //painter - QPainter painter(this); - painter.setClipRegion(videoContainerRegion); - painter.setRenderHint(QPainter::Antialiasing); - painter.setPen(m_OutlinePen); - painter.drawRect (1, 1, m_Width-2, m_Height-2); + painter.drawRoundedRect (1, 1, m_Width-2, m_Height-2, radius, radius); + } + else + { + painter.setPen(Qt::NoPen); + painter.drawRoundedRect (0, 0, m_Width, m_Height, radius, radius); } } @@ -141,7 +152,7 @@ void MeterWidget::setColor(QColor mainColor) void MeterWidget::setBoundingRectVisibility(bool show, QColor boundingRectColor) { this->boundingRectVisibility=show; - this->boundingRectColor = boundingRectColor; + this->m_BoundingRectColor = boundingRectColor; } TextMeterWidget::TextMeterWidget(QString Name, QWidget *parent, QString Source) : MeterWidget(Name, parent, Source) @@ -154,7 +165,6 @@ void TextMeterWidget::paintEvent(QPaintEvent* paintevent) MeterWidget::paintEvent(paintevent); m_MainBrush = QBrush(m_MainColor); - m_BackgroundBrush = QBrush(m_BackgroundColor); m_OutlinePen = QPen(m_OutlineColor); m_OutlinePen.setWidth(1); m_OutlinePen.setStyle(Qt::SolidLine); @@ -164,21 +174,32 @@ void TextMeterWidget::paintEvent(QPaintEvent* paintevent) painter.setClipRegion(videoContainerRegion); painter.setRenderHint(QPainter::Antialiasing); - //draw background - painter.setPen(Qt::NoPen); - painter.setBrush(m_BackgroundBrush); - if (Text!=QString("")) - painter.drawRect (0, 0, m_Width, m_Height); - QPainterPath my_painterPath; my_painterPath.addText(QPointF(0,0),m_MainFont,Text); my_painterPath.addText(QPointF(QFontMetrics(m_MainFont).width(Text), 0),m_AltFont,AltText); QRectF ValueBoundingRct = my_painterPath.boundingRect(); - // define scale + // We use leading whitespace for alignment which boundingRect() does not count + ValueBoundingRct.setLeft(qMin(0.0, ValueBoundingRct.left())); + + // The scale should not change with the string content, we use Font ascent and descent + ValueBoundingRct.setTop(qMin(ValueBoundingRct.top(), qreal(qMin(-QFontMetrics(m_MainFont).ascent(), + -QFontMetrics(m_AltFont).ascent())))); + ValueBoundingRct.setBottom(qMax(ValueBoundingRct.bottom(), qreal(qMax(QFontMetrics(m_MainFont).descent(), + QFontMetrics(m_AltFont).descent())))); + + // scale to fit the available space float fontscale = qMin(m_Width / ValueBoundingRct.width(), m_Height / ValueBoundingRct.height()); painter.scale(fontscale, fontscale); - painter.translate(-ValueBoundingRct.x()+(m_Width/fontscale - ValueBoundingRct.width())/2, -ValueBoundingRct.y()+(m_Height/fontscale - ValueBoundingRct.height())/2); + + float translationX = -ValueBoundingRct.x(); // AlignLeft + + if(alignment == Qt::AlignHCenter) + translationX += (m_Width/fontscale - ValueBoundingRct.width())/2; + else if(alignment == Qt::AlignRight) + translationX += (m_Width/fontscale - ValueBoundingRct.width()); + + painter.translate(translationX, -ValueBoundingRct.y()+(m_Height/fontscale - ValueBoundingRct.height())/2); // Write Value painter.setPen(m_OutlinePen); diff --git a/src/Train/MeterWidget.h b/src/Train/MeterWidget.h index 0b02d03b4..1ca12b660 100644 --- a/src/Train/MeterWidget.h +++ b/src/Train/MeterWidget.h @@ -51,7 +51,9 @@ class MeterWidget : public QWidget void setBoundingRectVisibility(bool show, QColor boundingRectColor = QColor(255,0,0,255)); float Value, ValueMin, ValueMax; - QString Text, AltText; + QString Text, AltText, AltTextSuffix; + Qt::Alignment alignment; + int textWidth; protected: QString m_Name; @@ -72,6 +74,7 @@ class MeterWidget : public QWidget QColor m_ScaleColor; QColor m_OutlineColor; QColor m_BackgroundColor; + QColor m_BoundingRectColor; QFont m_MainFont; QFont m_AltFont; @@ -81,8 +84,8 @@ class MeterWidget : public QWidget QPen m_OutlinePen; QPen m_ScalePen; + bool backgroundVisibility; bool boundingRectVisibility; - QColor boundingRectColor; friend class VideoLayoutParser; }; diff --git a/src/Train/VideoLayoutParser.cpp b/src/Train/VideoLayoutParser.cpp index 91574964e..0033a7a79 100644 --- a/src/Train/VideoLayoutParser.cpp +++ b/src/Train/VideoLayoutParser.cpp @@ -37,23 +37,23 @@ VideoLayoutParser::VideoLayoutParser (QList* metersWidget, QListm_RelativeWidth = 20.0; - meterWidget->m_RelativeHeight = 20.0; - meterWidget->m_RelativePosX = 50.0; - meterWidget->m_RelativePosY = 80.0; - - meterWidget->m_RangeMin = 0.0; - meterWidget->m_RangeMax = 80.0; - meterWidget->m_MainColor = QColor(255,0,0,200); - meterWidget->m_ScaleColor = QColor(255,255,255,200); - meterWidget->m_OutlineColor = QColor(100,100,100,200); - meterWidget->m_BackgroundColor = QColor(100,100,100,0); - meterWidget->m_MainFont = QFont(meterWidget->font().family(), 64); - meterWidget->m_AltFont = QFont(meterWidget->font().family(), 48); - meterWidget->m_VideoContainer = VideoContainer; - } + meterWidget->m_RelativeWidth = 20.0; + meterWidget->m_RelativeHeight = 20.0; + meterWidget->m_RelativePosX = 50.0; + meterWidget->m_RelativePosY = 80.0; + meterWidget->m_RangeMin = 0.0; + meterWidget->m_RangeMax = 80.0; + meterWidget->m_MainColor = QColor(255,0,0,200); + meterWidget->m_ScaleColor = QColor(255,255,255,200); + meterWidget->m_OutlineColor = QColor(100,100,100,200); + meterWidget->m_BackgroundColor = QColor(100,100,100,0); + meterWidget->m_BoundingRectColor = QColor(150,150,150,100); + meterWidget->m_MainFont = QFont(meterWidget->font().family(), 64); + meterWidget->m_AltFont = QFont(meterWidget->font().family(), 48); + meterWidget->m_VideoContainer = VideoContainer; + meterWidget->Text = ""; + meterWidget->AltText = ""; + meterWidget->AltTextSuffix = ""; } QColor GetColorFromFields(const QXmlAttributes& qAttributes) @@ -77,6 +77,15 @@ bool VideoLayoutParser::startElement( const QString&, const QString&, const QString& qName, const QXmlAttributes& qAttributes) { + QString source; + QString meterName; + QString meterType; + QString container; // will be "Video" when not defined otherwise another meter name (allows positioning of one meter inside another one) + int textWidth; + Qt::Alignment alignment; + bool boundingRectVisibility; + bool backgroundVisibility; + if(skipLayout) return true; buffer.clear(); @@ -114,6 +123,32 @@ bool VideoLayoutParser::startElement( const QString&, const QString&, else meterType = QString("Text"); + i = qAttributes.index("alignment"); + if(i >= 0) + if(qAttributes.value(i) == QString("AlignLeft")) alignment = Qt::AlignLeft; + else if(qAttributes.value(i) == QString("AlignRight")) alignment = Qt::AlignRight; + else alignment = Qt::AlignHCenter; + else + alignment = Qt::AlignHCenter; + + i = qAttributes.index("textWidth"); + if(i >= 0) + textWidth = qAttributes.value(i).toInt(); + else + textWidth = 0; + + i = qAttributes.index("BoundingRect"); + if(i >= 0 && qAttributes.value(i) == QString("true")) + boundingRectVisibility = true; + else + boundingRectVisibility = false; + + i = qAttributes.index("Background"); + if(i >= 0 && qAttributes.value(i) == QString("true")) + backgroundVisibility = true; + else + backgroundVisibility = false; + //TODO: allow creation of meter when container will be created later QWidget* containerWidget = NULL; if (container == QString("Video")) @@ -159,7 +194,14 @@ bool VideoLayoutParser::startElement( const QString&, const QString&, qDebug() << QObject::tr("Error creating meter"); } - SetDefaultValues(); + if (meterWidget) + { + SetDefaultValues(); + meterWidget->textWidth = textWidth; + meterWidget->alignment = alignment; + meterWidget->boundingRectVisibility = boundingRectVisibility; + meterWidget->backgroundVisibility = backgroundVisibility; + } } else if ((qName == "MainColor") && meterWidget) meterWidget->m_MainColor = GetColorFromFields(qAttributes); @@ -169,6 +211,8 @@ bool VideoLayoutParser::startElement( const QString&, const QString&, meterWidget->m_ScaleColor = GetColorFromFields(qAttributes); else if ((qName == "BackgroundColor") && meterWidget) meterWidget->m_BackgroundColor = GetColorFromFields(qAttributes); + else if ((qName == "BoundingRectColor") && meterWidget) + meterWidget->m_BoundingRectColor = GetColorFromFields(qAttributes); else if ((qName == "RelativeSize") && meterWidget) { @@ -204,6 +248,8 @@ bool VideoLayoutParser::startElement( const QString&, const QString&, if ((i = qAttributes.index("Size")) >= 0) FontSize = qAttributes.value(i).toInt(); meterWidget->m_MainFont = QFont(FontName, FontSize); + // Set fixed width, otherwise the meter digits keep moving sideways from a thin "1" to a fat "5" + meterWidget->m_MainFont.setFixedPitch(true); } else if ((qName == "AltFont") && meterWidget) { @@ -215,6 +261,11 @@ bool VideoLayoutParser::startElement( const QString&, const QString&, if ((i = qAttributes.index("Size")) >= 0) FontSize = qAttributes.value(i).toInt(); meterWidget->m_AltFont = QFont(FontName, FontSize); + meterWidget->m_AltFont.setFixedPitch(true); + } + else if(qName != "layouts" && qName != "Text" && qName != "AltText" && qName != "Angle" && qName != "SubRange") + { + qDebug() << QObject::tr("Unknown start element ") << qName; } return true; @@ -241,6 +292,8 @@ bool VideoLayoutParser::endElement( const QString&, const QString&, const QStrin meterWidget->m_Zoom = buffer.toInt(); else if (qName == "Text") meterWidget->Text = QString(buffer); + else if (qName == "AltText") + meterWidget->AltTextSuffix = QString(buffer); else if (qName == "meter") { diff --git a/src/Train/VideoLayoutParser.h b/src/Train/VideoLayoutParser.h index e0c527700..70b99378d 100644 --- a/src/Train/VideoLayoutParser.h +++ b/src/Train/VideoLayoutParser.h @@ -46,18 +46,11 @@ private: QList* layoutNames; QWidget* VideoContainer; + MeterWidget* meterWidget; QString buffer; - int nonameindex; int layoutPosition; bool skipLayout; - - QString source; - QString meterName; - QString meterType; - QString container; // will be "Video" when not defined otherwise another meter name (allows positioning of one meter inside another one) - - MeterWidget* meterWidget; }; #endif // _VideoLayoutParser_h diff --git a/src/Train/VideoWindow.cpp b/src/Train/VideoWindow.cpp index 32fc06213..3187661e2 100644 --- a/src/Train/VideoWindow.cpp +++ b/src/Train/VideoWindow.cpp @@ -326,13 +326,13 @@ void VideoWindow::telemetryUpdate(RealtimeData rtd) std::string smyStr1 = myQstr1.toStdString(); if (p_meterWidget->Source() == QString("None")) { - //Nothing + p_meterWidget->AltText = p_meterWidget->AltTextSuffix; } else if (p_meterWidget->Source() == QString("Speed")) { p_meterWidget->Value = rtd.getSpeed() * (metric ? 1.0 : MILES_PER_KM); - p_meterWidget->Text = QString::number((int)p_meterWidget->Value); - p_meterWidget->AltText = QString(".") +QString::number((int)(p_meterWidget->Value * 10.0) - (((int) p_meterWidget->Value) * 10)) + (metric ? tr(" kph") : tr(" mph")); + p_meterWidget->Text = QString::number((int)p_meterWidget->Value).rightJustified(p_meterWidget->textWidth); + p_meterWidget->AltText = QString(".") +QString::number((int)(p_meterWidget->Value * 10.0) - (((int) p_meterWidget->Value) * 10)) + (metric ? tr(" kph") : tr(" mph")) + p_meterWidget->AltTextSuffix; } else if (p_meterWidget->Source() == QString("Elevation")) { @@ -368,58 +368,73 @@ void VideoWindow::telemetryUpdate(RealtimeData rtd) else if (p_meterWidget->Source() == QString("Cadence")) { p_meterWidget->Value = rtd.getCadence(); - p_meterWidget->Text = QString::number((int)p_meterWidget->Value); + p_meterWidget->Text = QString::number((int)p_meterWidget->Value).rightJustified(p_meterWidget->textWidth); + p_meterWidget->AltText = p_meterWidget->AltTextSuffix; } else if (p_meterWidget->Source() == QString("Watt")) { p_meterWidget->Value = rtd.getWatts(); - p_meterWidget->Text = QString::number((int)p_meterWidget->Value); + p_meterWidget->Text = QString::number((int)p_meterWidget->Value).rightJustified(p_meterWidget->textWidth); + p_meterWidget->AltText = p_meterWidget->AltTextSuffix; + } + else if (p_meterWidget->Source() == QString("Altitude")) + { + p_meterWidget->Value = rtd.getAltitude() * (metric ? 1.0 : FEET_PER_METER); + p_meterWidget->Text = QString::number((int)p_meterWidget->Value).rightJustified(p_meterWidget->textWidth); + p_meterWidget->AltText = (metric ? tr(" m") : tr(" feet")) + p_meterWidget->AltTextSuffix; } else if (p_meterWidget->Source() == QString("HRM")) { p_meterWidget->Value = rtd.getHr(); - p_meterWidget->Text = QString::number((int)p_meterWidget->Value); + p_meterWidget->Text = QString::number((int)p_meterWidget->Value).rightJustified(p_meterWidget->textWidth); + p_meterWidget->AltText = p_meterWidget->AltTextSuffix; } else if (p_meterWidget->Source() == QString("Load")) { if (rtd.mode == ERG || rtd.mode == MRC) { p_meterWidget->Value = rtd.getLoad(); - p_meterWidget->Text = QString("%1").arg(round(p_meterWidget->Value)); - p_meterWidget->AltText = tr("w"); + p_meterWidget->Text = QString("%1").arg(round(p_meterWidget->Value)).rightJustified(p_meterWidget->textWidth); + p_meterWidget->AltText = tr("w") + p_meterWidget->AltTextSuffix; } else { p_meterWidget->Value = rtd.getSlope(); - p_meterWidget->Text = QString("%1").arg(p_meterWidget->Value, 0, 'f', 1); - p_meterWidget->AltText = tr("%"); + p_meterWidget->Text = QString::number((int)p_meterWidget->Value).rightJustified(p_meterWidget->textWidth); + p_meterWidget->AltText = QString(".") + QString::number(abs((int)(p_meterWidget->Value * 10.0) - (((int) p_meterWidget->Value) * 10))) + tr("%") + p_meterWidget->AltTextSuffix; } } else if (p_meterWidget->Source() == QString("Distance")) { p_meterWidget->Value = rtd.getDistance() * (metric ? 1.0 : MILES_PER_KM); - p_meterWidget->Text = QString::number((int) p_meterWidget->Value); - p_meterWidget->AltText = QString(".") +QString::number((int)(p_meterWidget->Value * 10.0) - (((int) p_meterWidget->Value) * 10)) + (metric ? tr(" km") : tr(" mi")); + p_meterWidget->Text = QString::number((int) p_meterWidget->Value).rightJustified(p_meterWidget->textWidth); + p_meterWidget->AltText = QString(".") +QString::number((int)(p_meterWidget->Value * 10.0) - (((int) p_meterWidget->Value) * 10)) + (metric ? tr(" km") : tr(" mi")) + p_meterWidget->AltTextSuffix; } else if (p_meterWidget->Source() == QString("Time")) { p_meterWidget->Value = round(rtd.value(RealtimeData::Time)/100.0)/10.0; - p_meterWidget->Text = time_to_string(p_meterWidget->Value); + p_meterWidget->Text = time_to_string(trunc(p_meterWidget->Value)).rightJustified(p_meterWidget->textWidth); + p_meterWidget->AltText = QString(".") + QString::number((int)(p_meterWidget->Value * 10.0) - (((int) p_meterWidget->Value) * 10)) + p_meterWidget->AltTextSuffix; } else if (p_meterWidget->Source() == QString("LapTime")) { p_meterWidget->Value = round(rtd.value(RealtimeData::LapTime)/100.0)/10.0; - p_meterWidget->Text = time_to_string(p_meterWidget->Value); + p_meterWidget->Text = time_to_string(trunc(p_meterWidget->Value)).rightJustified(p_meterWidget->textWidth); + p_meterWidget->AltText = QString(".") + QString::number((int)(p_meterWidget->Value * 10.0) - (((int) p_meterWidget->Value) * 10)) + p_meterWidget->AltTextSuffix; } else if (p_meterWidget->Source() == QString("LapTimeRemaining")) { p_meterWidget->Value = round(rtd.value(RealtimeData::LapTimeRemaining)/100.0)/10.0; - p_meterWidget->Text = time_to_string(p_meterWidget->Value); + p_meterWidget->Text = time_to_string(trunc(p_meterWidget->Value)).rightJustified(p_meterWidget->textWidth); + p_meterWidget->AltText = QString(".") + QString::number((int)(p_meterWidget->Value * 10.0) - (((int) p_meterWidget->Value) * 10)) + p_meterWidget->AltTextSuffix; } else if (p_meterWidget->Source() == QString("ErgTimeRemaining")) { p_meterWidget->Value = round(rtd.value(RealtimeData::ErgTimeRemaining)/100.0)/10.0; - p_meterWidget->Text = time_to_string(p_meterWidget->Value); + p_meterWidget->Text = time_to_string(trunc(p_meterWidget->Value)).rightJustified(p_meterWidget->textWidth); + p_meterWidget->AltText = QString(".") + QString::number((int)(p_meterWidget->Value * 10.0) - (((int) p_meterWidget->Value) * 10)) + p_meterWidget->AltTextSuffix; } else if (p_meterWidget->Source() == QString("TrainerStatus")) { + p_meterWidget->AltText = p_meterWidget->AltTextSuffix; + if (!rtd.getTrainerStatusAvailable()) { // we don't have status from trainer thus we cannot indicate anything on screen p_meterWidget->Text = tr("");