Dialwindow font scaling (v2) (#4511)

* DialWindow: Scaling font in both dimensions

Currently the font in DialWindow is only scaled to make the text fit
into the label vertically. This commit scales the font in both
dimensions and prevents cutting off the content.

The font is rescaled
* If the DialWindow itself is resized
* If the text set and is longer than the previous one
* Every 10 text-changes

* Trainmode: Added a global setting to finetune telemetry font scaling

* Added option to Options -> Train -> Preferences for configuring the
  font scaling strategy of Telemetry / DialWindows
  * "Height only" (default, as before)
  * "Height and width"
* Changed the layout of this Page to QFormLayout to match the other
  Pages
This commit is contained in:
Joachim Kohlhammer
2024-06-13 18:11:30 +02:00
committed by GitHub
parent fb768969ee
commit ad6a0c30e2
8 changed files with 328 additions and 44 deletions

View File

@@ -238,6 +238,7 @@
#define TRAIN_USESIMULATEDSPEED "<global-trainmode>train/usesimulatedspeed"
#define TRAIN_USESIMULATEDHYPOXIA "<global-trainmode>train/usesimulatedhypoxia"
#define TRAIN_COALESCE_SECTIONS "<global-trainmode>train/coalesceSections"
#define TRAIN_TELEMETRY_FONT_SCALING "<global-trainmode>train/telemetryFontScaling"
#define GC_REMOTE_START "<global-trainmode>remote/start"
#define GC_REMOTE_STOP "<global-trainmode>remote/stop"
#define GC_REMOTE_LAP "<global-trainmode>remote/lap"

View File

@@ -757,6 +757,12 @@ TrainOptionsPage::TrainOptionsPage(QWidget *parent, Context *context) : QWidget(
coalesce = new QCheckBox(tr("Coalesce contiguous sections of same wattage"), this);
coalesce->setChecked(appsettings->value(this, TRAIN_COALESCE_SECTIONS, false).toBool());
telemetryScalingLabel = new QLabel(tr("Telemetry font scaling"));
telemetryScaling = new QComboBox();
telemetryScaling->addItem(tr("Fit to height only"), 0);
telemetryScaling->addItem(tr("Fit to height and width"), 1);
telemetryScaling->setCurrentIndex(appsettings->value(this, TRAIN_TELEMETRY_FONT_SCALING, 0).toInt());
delayLabel = new QLabel(tr("Start Countdown"));
startDelay = new QSpinBox(this);
startDelay->setMaximum(600);
@@ -765,29 +771,22 @@ TrainOptionsPage::TrainOptionsPage(QWidget *parent, Context *context) : QWidget(
startDelay->setValue(appsettings->value(this, TRAIN_STARTDELAY, 0).toUInt());
startDelay->setToolTip(tr("Countdown for workout start"));
QVBoxLayout *all = new QVBoxLayout(this);
QFormLayout *all = new QFormLayout(this);
QGridLayout *wdLayout = new QGridLayout;
wdLayout->addWidget(workoutLabel, 0,0, Qt::AlignRight);
wdLayout->addWidget(workoutDirectory, 0,1);
wdLayout->addWidget(workoutBrowseButton, 0,2);
all->addLayout(wdLayout);
all->addWidget(useSimulatedSpeed);
all->addWidget(useSimulatedHypoxia);
all->addWidget(multiCheck);
all->addWidget(autoConnect);
all->addWidget(autoHide);
all->addWidget(lapAlert);
all->addWidget(coalesce);
QHBoxLayout *delayLayout = new QHBoxLayout;
delayLayout->addWidget(delayLabel);
delayLayout->addWidget(startDelay);
delayLayout->addStretch();
all->addLayout(delayLayout);
all->addStretch();
all->addRow(workoutLabel, wdLayout);
all->addRow("", useSimulatedSpeed);
all->addRow("", useSimulatedHypoxia);
all->addRow("", multiCheck);
all->addRow("", autoConnect);
all->addRow("", autoHide);
all->addRow("", lapAlert);
all->addRow("", coalesce);
all->addRow(delayLabel, startDelay);
all->addRow(telemetryScalingLabel, telemetryScaling);
}
@@ -804,6 +803,7 @@ TrainOptionsPage::saveClicked()
appsettings->setValue(TRAIN_AUTOHIDE, autoHide->isChecked());
appsettings->setValue(TRAIN_LAPALERT, lapAlert->isChecked());
appsettings->setValue(TRAIN_COALESCE_SECTIONS, coalesce->isChecked());
appsettings->setValue(TRAIN_TELEMETRY_FONT_SCALING, telemetryScaling->currentIndex());
return 0;
}

View File

@@ -240,6 +240,8 @@ class TrainOptionsPage : public QWidget
QCheckBox *autoHide;
QCheckBox *lapAlert;
QCheckBox *coalesce;
QLabel *telemetryScalingLabel;
QComboBox *telemetryScaling;
};
class RemotePage : public QWidget

View File

@@ -80,7 +80,11 @@ DialWindow::DialWindow(Context *context) :
QVBoxLayout *layout = new QVBoxLayout;
layout->setSpacing(0);
layout->setContentsMargins(3,3,3,3);
valueLabel = new QLabel(this);
valueLabel = new ScalingLabel(this);
valueLabel->setStrategy(appsettings->value(this, TRAIN_TELEMETRY_FONT_SCALING, 0).toInt() == 0 ? ScalingLabelStrategy::HeightOnly : ScalingLabelStrategy::Linear);
QFont vlFont = valueLabel->font();
vlFont.setWeight(QFont::Bold);
valueLabel->setFont(vlFont);
valueLabel->setAlignment(Qt::AlignCenter | Qt::AlignVCenter);
layout->addWidget(valueLabel);
setChartLayout(layout);
@@ -99,9 +103,6 @@ DialWindow::DialWindow(Context *context) :
// setup colors
seriesChanged();
// setup fontsize etc
resizeEvent(NULL);
// set to zero
resetValues();
}
@@ -546,21 +547,6 @@ DialWindow::telemetryUpdate(const RealtimeData &rtData)
}
}
void DialWindow::resizeEvent(QResizeEvent * )
{
QFont font;
// set point size within reasonable limits for low dpi screens
int size = (geometry().height() - 24) * 72 / logicalDpiY();
if (size <= 0) size = 4;
if (size >= 64) size = 64;
font.setPointSize(size);
font.setWeight(QFont::Bold);
valueLabel->setFont(font);
}
void DialWindow::seriesChanged()
{
// we got some!
@@ -719,6 +705,7 @@ void DialWindow::seriesChanged()
break;
}
valueLabel->setStrategy(appsettings->value(this, TRAIN_TELEMETRY_FONT_SCALING, 0).toInt() == 0 ? ScalingLabelStrategy::HeightOnly : ScalingLabelStrategy::Linear);
// ugh. we use style sheets because palettes don't work on labels
background = GColor(CTRAINPLOTBACKGROUND);
setProperty("color", background);

View File

@@ -26,6 +26,8 @@
#include <QLineEdit>
#include <QFormLayout>
#include "ScalingLabel.h"
#include "Context.h"
#include "Zones.h" // for data series types
#include "RideFile.h" // for data series types
@@ -74,9 +76,6 @@ class DialWindow : public GcChartWindow
int style() const { return _style; }
int avgSecs() const { return average; }
// change font as window resizes
void resizeEvent(QResizeEvent *);
public slots:
// trap signals
@@ -148,7 +147,7 @@ class DialWindow : public GcChartWindow
QLineEdit *averageEdit;
// display
QLabel *valueLabel;
ScalingLabel *valueLabel;
QColor foreground, background;

221
src/Train/ScalingLabel.cpp Normal file
View File

@@ -0,0 +1,221 @@
/*
* Copyright (c) 2022 Joachim Kohlhammer (joachim.kohlhammer@gmx.de)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "ScalingLabel.h"
#include <QDebug>
ScalingLabel::ScalingLabel
(QWidget *parent, Qt::WindowFlags f)
: ScalingLabel(4, 64, parent, f)
{
}
ScalingLabel::ScalingLabel
(int minFontPointSize, int maxFontPointSize, QWidget *parent, Qt::WindowFlags f)
: QLabel(parent, f), minFontPointSize(minFontPointSize), maxFontPointSize(maxFontPointSize)
{
QFont fnt = font();
fnt.setPointSize(minFontPointSize);
QLabel::setFont(fnt);
}
ScalingLabel::~ScalingLabel
()
{
}
void
ScalingLabel::resizeEvent
(QResizeEvent *evt)
{
Q_UNUSED(evt)
scaleFont(text(), ScalingLabelReason::ResizeEvent);
}
void
ScalingLabel::setText
(const QString &text)
{
++counter;
if (text.length() > QLabel::text().length()) {
scaleFont(text, ScalingLabelReason::TextLengthChanged);
} else if (counter > 10) {
scaleFont(text, ScalingLabelReason::CounterExceeded);
}
QLabel::setText(text);
}
void
ScalingLabel::setFont
(const QFont &font)
{
if (! scaleFont(text(), font, ScalingLabelReason::FontChanged)) {
QLabel::setFont(font);
}
}
int
ScalingLabel::getMinFontPointSize
() const
{
return minFontPointSize;
}
void
ScalingLabel::setMinFontPointSize
(int size)
{
minFontPointSize = size;
}
int
ScalingLabel::getMaxFontPointSize
() const
{
return maxFontPointSize;
}
void
ScalingLabel::setMaxFontPointSize
(int size)
{
maxFontPointSize = size;
}
ScalingLabelStrategy
ScalingLabel::getStrategy
() const
{
return strategy;
}
void
ScalingLabel::setStrategy
(ScalingLabelStrategy strategy)
{
if (this->strategy != strategy) {
this->strategy = strategy;
scaleFont(text(), ScalingLabelReason::StrategyChanged);
}
}
bool
ScalingLabel::scaleFont
(const QString &text, ScalingLabelReason reason)
{
return scaleFont(text, font(), reason);
}
bool
ScalingLabel::scaleFont
(const QString &text, const QFont &font, ScalingLabelReason reason)
{
counter = 0;
switch (strategy) {
case ScalingLabelStrategy::Linear:
return scaleFontLinear(text, font, reason);
case ScalingLabelStrategy::Exact:
return scaleFontExact(text, font, reason);
case ScalingLabelStrategy::HeightOnly:
default:
return scaleFontHeightOnly(text, font, reason);
}
}
bool
ScalingLabel::scaleFontHeightOnly
(const QString &text, const QFont &font, ScalingLabelReason reason)
{
Q_UNUSED(text)
if ( reason != ScalingLabelReason::ResizeEvent
&& reason != ScalingLabelReason::StrategyChanged) {
return false;
}
QFont f(font);
// set point size within reasonable limits for low dpi screens
int size = (geometry().height() - 24) * 72 / logicalDpiY();
size = std::min(maxFontPointSize, std::max(minFontPointSize, size));
f.setPointSize(size);
setFont(f);
return true;
}
bool
ScalingLabel::scaleFontExact
(const QString &text, const QFont &font, ScalingLabelReason reason)
{
int size = maxFontPointSize + 1;
if (reason == ScalingLabelReason::CounterExceeded) {
size = font.pointSize() + 1;
}
QFont f(font);
QRect br;
do {
f.setPointSize(--size);
QFontMetrics fm = QFontMetrics(f, this);
br = fm.boundingRect(text);
} while (size >= minFontPointSize && (br.width() > width() || br.height() > height()));
QLabel::setFont(f);
return true;
}
bool
ScalingLabel::scaleFontLinear
(const QString &text, const QFont &font, ScalingLabelReason reason)
{
if (text.length() == 0 || width() <= 0 || height() <= 0) {
return false;
}
int maxSize = maxFontPointSize;
if (reason == ScalingLabelReason::CounterExceeded) {
maxSize = font.pointSize();
}
QFont f(font);
f.setPointSize(minFontPointSize);
QFontMetrics fmS = QFontMetrics(f, this);
f.setPointSize(maxSize);
QFontMetrics fmL = QFontMetrics(f, this);
QRect brS = fmS.boundingRect(text);
QRect brL = fmL.boundingRect(text);
int sizeWidth = (maxSize - minFontPointSize) / float(brL.width() - brS.width()) * width();
int sizeHeight = (maxSize - minFontPointSize) / float(brL.height() - brS.height()) * height();
int calcSize = std::min<int>(sizeWidth, sizeHeight);
f.setPointSize(std::max<int>(std::min<int>(calcSize, maxSize), minFontPointSize));
QLabel::setFont(f);
return true;
}

74
src/Train/ScalingLabel.h Normal file
View File

@@ -0,0 +1,74 @@
/*
* Copyright (c) 2022 Joachim Kohlhammer (joachim.kohlhammer@gmx.de)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef _GC_ScalingLabel_h
#define _GC_ScalingLabel_h 1
#include <QLabel>
enum class ScalingLabelReason : uint8_t {
TextLengthChanged,
FontChanged,
ResizeEvent,
CounterExceeded,
StrategyChanged
};
enum class ScalingLabelStrategy : uint8_t {
Exact,
Linear,
HeightOnly
};
class ScalingLabel : public QLabel
{
Q_OBJECT
public:
ScalingLabel(QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
ScalingLabel(int minFontPointSize, int maxFontPointSize, QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
virtual ~ScalingLabel();
virtual void resizeEvent(QResizeEvent *evt);
virtual void setFont(const QFont &font);
int getMinFontPointSize() const;
int getMaxFontPointSize() const;
ScalingLabelStrategy getStrategy() const;
public slots:
void setText(const QString &text);
void setMinFontPointSize(int size);
void setMaxFontPointSize(int size);
void setStrategy(ScalingLabelStrategy strategy);
private:
bool scaleFont(const QString &text, const QFont &font, ScalingLabelReason reason);
bool scaleFont(const QString &text, ScalingLabelReason reason);
bool scaleFontExact(const QString &text, const QFont &font, ScalingLabelReason reason);
bool scaleFontLinear(const QString &text, const QFont &font, ScalingLabelReason reason);
bool scaleFontHeightOnly(const QString &text, const QFont &font, ScalingLabelReason reason);
int minFontPointSize;
int maxFontPointSize;
ScalingLabelStrategy strategy = ScalingLabelStrategy::Linear;
int counter = 0;
};
#endif

View File

@@ -706,7 +706,7 @@ HEADERS += Train/AddDeviceWizard.h Train/CalibrationData.h Train/ComputrainerCon
HEADERS += Train/TrainBottom.h Train/TrainDB.h Train/TrainSidebar.h \
Train/VideoLayoutParser.h Train/VideoSyncFile.h Train/WorkoutPlotWindow.h Train/WebPageWindow.h \
Train/WorkoutWidget.h Train/WorkoutWidgetItems.h Train/WorkoutWindow.h Train/WorkoutWizard.h Train/ZwoParser.h \
Train/LiveMapWebPageWindow.h \
Train/LiveMapWebPageWindow.h Train/ScalingLabel.h \
Train/InfoWidget.h Train/PowerInfoWidget.h Train/PowerZonesWidget.h Train/RatingWidget.h \
Train/ErgOverview.h Train/Shy.h \
Train/WorkoutTagWrapper.h
@@ -716,7 +716,7 @@ HEADERS += Train/TrainBottom.h Train/TrainDB.h Train/TrainSidebar.h \
### SOURCE FILES
###=============
## ANT+
## ANT+
SOURCES += ANT/ANTChannel.cpp ANT/ANT.cpp ANT/ANTlocalController.cpp ANT/ANTLogger.cpp ANT/ANTMessage.cpp
## Charts and related
@@ -817,7 +817,7 @@ SOURCES += Train/AddDeviceWizard.cpp Train/CalibrationData.cpp Train/Computraine
SOURCES += Train/TrainBottom.cpp Train/TrainDB.cpp Train/TrainSidebar.cpp \
Train/VideoLayoutParser.cpp Train/VideoSyncFile.cpp Train/WorkoutPlotWindow.cpp Train/WebPageWindow.cpp \
Train/WorkoutWidget.cpp Train/WorkoutWidgetItems.cpp Train/WorkoutWindow.cpp Train/WorkoutWizard.cpp Train/ZwoParser.cpp \
Train/LiveMapWebPageWindow.cpp \
Train/LiveMapWebPageWindow.cpp Train/ScalingLabel.cpp \
Train/InfoWidget.cpp Train/PowerInfoWidget.cpp Train/PowerZonesWidget.cpp Train/RatingWidget.cpp \
Train/ErgOverview.cpp Train/Shy.cpp \
Train/WorkoutTagWrapper.cpp