src/RangedSlider.cpp

Thu, 18 Aug 2022 20:34:15 +0200

author
Michiel Broek <mbroek@mbse.eu>
date
Thu, 18 Aug 2022 20:34:15 +0200
changeset 401
583148eb6e01
parent 101
1d14d3bf2465
permissions
-rw-r--r--

Init est_carb field for new products.

/*
 * 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