src/RangedSlider.cpp

Sat, 11 Feb 2023 15:48:02 +0100

author
Michiel Broek <mbroek@mbse.eu>
date
Sat, 11 Feb 2023 15:48:02 +0100
changeset 493
520306773450
parent 101
1d14d3bf2465
permissions
-rw-r--r--

Monitor iSpindels: use global variables instead of repeated expensive MySQL calls. Use the yeast temperature ranges for the colors on the thermometer scale. Don't show SVG and ABV if the OG is not yet known. Turn statusfield red if offline. Extra mon_ispindles fields yeast_lo and yeast_hi. Need at least bmsd version 0.3.42. If a websocket message is received that cannot be parsed, show the whole received message.

/*
 * RangedSlider.cpp is part bmsapp.
 *
 * Original written for Brewtarget, and is Copyright the following
 * authors 2009-2020
 * - Matt Young <mfsy@yahoo.com>
 * - Mik Firestone <mikfire@gmail.com>
 * - Philip G. Lee <rocketman768@gmail.com>
 *
 * bmsapp 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 3 of the License, or
 * (at your option) any later version.
 *
 * bmsapp 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, see <http://www.gnu.org/licenses/>.
 */

#include "RangedSlider.h"
#include <QPaintEvent>
#include <QPainter>
#include <QColor>
#include <QPalette>
#include <QApplication>
#include <QRectF>
#include <QFont>
#include <QFontMetrics>
#include <QMouseEvent>
#include <QLabel>
#include <QToolTip>
#include <QLinearGradient>
#include <QPainterPath>

#include <QDebug>

RangedSlider::RangedSlider(QWidget* parent) : QWidget(parent),
    _min(0.0),
    _max(1.0),
    _prefMin(0.25),
    _prefMax(0.75),
    _val(0.5),
    _prec(3),
    _tooltipText(""),
    _bgBrush(QColor(255,0,0)),
    _prefRangeBrush(QColor(0,127,0)),
    _prefRangePen(Qt::NoPen),
    _markerBrush(QColor(255,255,255)),
    _markerTextIsValue(false),
    indicatorTextFont("Arial", 10, QFont::Normal) // Previously we just did the indicator text in 'default' font
{
    // Fixed minimum size.
    this->setMinimumSize(60, 20);
    this->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );

    // There no particular reason to limit our horizontal size, so, in principle, this call asks that there be no such
    // (practical) limit.
    this->setMaximumWidth(QWIDGETSIZE_MAX);

    // Generate mouse move events whenever mouse movers over widget.
    this->setMouseTracking(true);

    this->repaint();
}


/**
 * @brief Set the normal limits in the _prexXxx values and calculate
 *        the real drawing limits so that we can see if a value is slightly
 *        out of range.
 */
void RangedSlider::setRange( double min, double max )
{
    _prefMin = min;
    _prefMax = max;
    // Calculate the outer limits
    _min = _prefMin - ((_prefMax - _prefMin) * 0.2);
    _max = _prefMax + ((_prefMax - _prefMin) * 0.2);

    // Set the tooltip with the ranges.
    _tooltipText = QString("%1 - %2").arg(min, 0, 'f', _prec).arg(max, 0, 'f', _prec);

    update();
}


void RangedSlider::setRange(QPair<double,double> minmax)
{
    setRange( minmax.first, minmax.second );
}


void RangedSlider::setValue(double value)
{
    _val = value;

    if (_val < _min)
	_val = _min;
    if (_val > _max)
	_val = _max;

    if (_markerTextIsValue) {
	_markerText = QString("%1").arg(value, 0, 'f', _prec);
    }

    update();
    return;
}


void RangedSlider::setPrecision(int precision)
{
    _prec = precision;
    update();
}


void RangedSlider::setBackgroundBrush( QBrush const& brush )
{
    _bgBrush = brush;
    update();
}


void RangedSlider::setMarkerBrush( QBrush const& brush )
{
    _markerBrush = brush;
    update();
}

void RangedSlider::setMarkerText( QString const& text )
{
    _markerText = text;
    update();
}

void RangedSlider::setMarkerTextIsValue(bool val)
{
    _markerTextIsValue = val;
    update();
}


void RangedSlider::mouseMoveEvent(QMouseEvent* event)
{
    event->accept();

    QPoint tipPoint( mapToGlobal(QPoint(0,0)) );
    QToolTip::showText( tipPoint, _tooltipText, this );
}


void RangedSlider::paintEvent(QPaintEvent* event)
{
   //
   // Simplistically, the high-level layout of the slider is:
   //
   //    |------------------------------------------------|
   //    | <--------------- Graphical Area -------------> |
   //    |------------------------------------------------|
   //
   // The graphical area has:
   //  - a background rectangle of the full width of the area, representing the range from this->_min to this->_max
   //  - a foreground rectangle showing the sub-range of this background from this->_prefMin to this->_prefMax
   //  - a vertical line ("the indicator") showing where this->_val lies in the (this->_min to this->_max) range
   //
   // The indicator text sits on the center of the area and shows either its value (this->_valText) or some textual
   // description (eg "Slightly Malty" on the IBU/GU scale) which comes from this->_markerText.
   //

   int graphicalAreaHeight = this->height();

   // Although the Qt calls take an x- and a y- radius, we want the radius on the rectangle corners to be the same
   // vertically and horizontally, so only define one measure here.
   int rectangleCornerRadius = graphicalAreaHeight / 4;

   static const QPalette palette(QApplication::palette());
   static const int indicatorLineWidth   = 4;
   static const QColor fgRectColor(0,127,0);
   static const QColor indicatorTextColor(0,0,0);
   static const QColor valueTextColor(0,127,0);

   QLinearGradient glassGrad( QPointF(0,0), QPointF(0,graphicalAreaHeight) );
   glassGrad.setColorAt( 0, QColor(255,255,255,127) );
   glassGrad.setColorAt( 1, QColor(255,255,255,0) );
   QBrush glassBrush(glassGrad);

   // Per https://doc.qt.io/qt-5/highdpi.html, for best High DPI display support, we need to:
   //  • Always use the qreal versions of the QPainter drawing API
   //  • Size windows and dialogs in relation to the corresponding screen size
   //  • Replace hard-coded sizes in layouts and drawing code with values calculated from font metrics or screen size
   QPainter painter(this);

   // Work out the left-to-right (ie x-coordinate) positions of things in the graphical area
   double graphicalAreaWidth  = this->width();
   double range               = this->_max - this->_min;
   double fgRectLeft          = graphicalAreaWidth * ((this->_prefMin - this->_min    )/range);
   double fgRectWidth         = graphicalAreaWidth * ((this->_prefMax - this->_prefMin)/range);
   double indicatorLineMiddle = graphicalAreaWidth * ((this->_val     - this->_min    )/range);
   double indicatorLineLeft   = indicatorLineMiddle - (indicatorLineWidth / 2);

   // Make sure all coordinates are valid.
   fgRectLeft          = qBound(0.0, fgRectLeft,          graphicalAreaWidth);
   fgRectWidth         = qBound(0.0, fgRectWidth,         graphicalAreaWidth - fgRectLeft);
   indicatorLineMiddle = qBound(0.0, indicatorLineMiddle, graphicalAreaWidth - (indicatorLineWidth / 2));
   indicatorLineLeft   = qBound(0.0, indicatorLineLeft,   graphicalAreaWidth - indicatorLineWidth);

   // All the rest of what we need to do is inside the graphical area, so move the origin to the top-left corner of it
   painter.translate(0, 0);
   painter.setPen(Qt::NoPen);

   // Make sure anything we draw "inside" the "glass rectangle" stays inside.
   QPainterPath clipRect;
   clipRect.addRoundedRect( QRectF(0, 0, graphicalAreaWidth, graphicalAreaHeight), rectangleCornerRadius, rectangleCornerRadius );
   painter.setClipPath(clipRect);

   // Draw the background rectangle.
   painter.setBrush(_bgBrush);
   painter.setRenderHint(QPainter::Antialiasing);
   painter.drawRoundedRect( QRectF(0, 0, graphicalAreaWidth, graphicalAreaHeight), rectangleCornerRadius, rectangleCornerRadius );
   painter.setRenderHint(QPainter::Antialiasing, false);

   // Draw the style "foreground" rectangle.
   painter.save();
   painter.setBrush(_prefRangeBrush);
   painter.setPen(_prefRangePen);
   painter.setRenderHint(QPainter::Antialiasing);
   painter.drawRoundedRect( QRectF(fgRectLeft, 0, fgRectWidth, graphicalAreaHeight), rectangleCornerRadius, rectangleCornerRadius );
   painter.restore();

   // Draw the indicator.
   painter.setBrush(_markerBrush);
   painter.drawRect( QRectF(indicatorLineLeft, 0, indicatorLineWidth, graphicalAreaHeight) );

    // Draw a white-to-clear gradient to suggest "glassy."
    painter.setBrush(glassBrush);
    painter.setRenderHint(QPainter::Antialiasing);
    painter.drawRoundedRect( QRectF(0, 0, graphicalAreaWidth, graphicalAreaHeight), rectangleCornerRadius, rectangleCornerRadius );
    painter.setRenderHint(QPainter::Antialiasing, false);

    // Draw the _markerText in the center of the widget.
    // First we ask the painter what size rectangle it will need to display this text
    painter.setPen(indicatorTextColor);
    painter.setFont(this->indicatorTextFont);
    QRectF indicatorTextRect = painter.boundingRect(QRectF(), Qt::AlignCenter | Qt::AlignBottom, this->_markerText);

    // Then we use the size of this rectangle to try to calculate the X and Y position for the markerText.
    double indicatorTextX = (graphicalAreaWidth - indicatorTextRect.width()) / 2;
    double indicatorTextY = (graphicalAreaHeight - indicatorTextRect.height()) / 2;

    // Now we can draw the indicator text
    painter.drawText( indicatorTextX, indicatorTextY, indicatorTextRect.width(), indicatorTextRect.height(), Qt::AlignCenter | Qt::AlignBottom, this->_markerText);

    return;
}


void RangedSlider::moveEvent(QMoveEvent *event)
{
    QWidget::moveEvent(event);
    return;
}

mercurial