/*
Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
This file is part of CamCOPS.
CamCOPS 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.
CamCOPS 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 CamCOPS. If not, see <https://www.gnu.org/licenses/>.
*/
// #define DEBUG_CHANGES
// #define DEBUG_DRAW
// #define DEBUG_EVENTS
// #define DEBUG_MOVE
#include "adjustablepie.h"
#include <QDebug>
#include <QFrame>
#include <QMouseEvent>
#include <QPainter>
#include <QTimer>
#include "common/colourdefs.h"
#include "graphics/geometry.h"
#include "graphics/graphicsfunc.h"
#include "graphics/linesegment.h"
#include "graphics/paintertranslaterotatecontext.h"
#include "lib/containers.h"
#include "lib/timerfunc.h"
#include "lib/widgetfunc.h"
using containers::forceVectorSize;
using geometry::convertHeadingToTrueNorth;
using geometry::DEG_0;
using geometry::DEG_180;
using geometry::DEG_270;
using geometry::DEG_360;
using geometry::DEG_90;
using geometry::distanceBetween;
using geometry::headingInRange;
using geometry::headingNearlyEq;
using geometry::headingToPolarThetaDeg;
using geometry::lineFromPointInHeadingWithRadius;
using geometry::normalizeHeading;
using geometry::polarThetaDeg;
using geometry::polarThetaToHeading;
using geometry::polarToCartesian;
using graphicsfunc::drawSector;
using graphicsfunc::drawText;
// ============================================================================
// Constants
// ============================================================================
const PenBrush DEFAULT_SECTOR_PENBRUSH(QCOLOR_BLACK, QCOLOR_GREEN);
const PenBrush DEFAULT_CURSOR_PENBRUSH(QCOLOR_BLACK, QCOLOR_RED);
const PenBrush DEFAULT_CURSOR_ACTIVE_PENBRUSH(QCOLOR_BLUE, QCOLOR_YELLOW);
const QColor DEFAULT_LABEL_COLOUR(QCOLOR_DARKBLUE);
// ============================================================================
// #defines
// ============================================================================
#define ENSURE_SECTOR_INDEX_OK_OR_RETURN(sector_index) \
if ((sector_index) < 0 || (sector_index) >= m_n_sectors) { \
qWarning() << Q_FUNC_INFO << "Bad sector index:" << (sector_index); \
return; \
}
#define ENSURE_CURSOR_INDEX_OK_OR_RETURN(cursor_index) \
if ((cursor_index) < 0 || (cursor_index) >= m_n_sectors - 1) { \
qWarning() << Q_FUNC_INFO << "Bad cursor index:" << (cursor_index); \
return; \
}
#define ENSURE_VECTOR_SIZE_MATCHES_SECTORS(vec) \
if ((vec).size() != m_n_sectors) { \
qWarning() << Q_FUNC_INFO << "Bad vector size:" << (vec).size() \
<< "- should match #sectors of" << m_n_sectors; \
return; \
}
#define ENSURE_VECTOR_SIZE_MATCHES_CURSORS(vec) \
if ((vec).size() != m_n_sectors - 1) { \
qWarning() << Q_FUNC_INFO << "Bad vector size:" << (vec).size() \
<< "- should match #cursors of" << m_n_sectors - 1; \
return; \
}
// ============================================================================
// Construction and configuration
// ============================================================================
AdjustablePie::AdjustablePie(const int n_sectors, QWidget* parent) :
QWidget(parent),
m_background_brush(QBrush(QCOLOR_TRANSPARENT)),
m_centre_label_colour(QCOLOR_BLACK),
m_sector_radius(75),
m_cursor_inner_radius(75),
m_cursor_outer_radius(125),
m_cursor_angle_degrees(30),
m_label_start_radius(125),
m_overall_radius(200),
m_base_compass_heading_deg(180),
m_reporting_delay_ms(0),
m_rotate_labels(true),
m_user_dragging_cursor(false),
m_cursor_num_being_dragged(-1)
{
widgetfunc::setBackgroundColour(this, QCOLOR_TRANSPARENT);
setContentsMargins(0, 0, 0, 0);
setNSectors(n_sectors);
timerfunc::makeSingleShotTimer(m_timer);
connect(m_timer.data(), &QTimer::timeout, this, &AdjustablePie::report);
}
void AdjustablePie::setNSectors(int n_sectors)
{
if (n_sectors < 1) {
qWarning() << Q_FUNC_INFO << "Bad n_sectors:" << n_sectors;
return;
}
m_n_sectors = n_sectors;
normalize();
}
int AdjustablePie::nSectors() const
{
return m_n_sectors;
}
void AdjustablePie::setBackgroundBrush(const QBrush& brush)
{
m_background_brush = brush;
}
void AdjustablePie::setSectorPenBrush(
const int sector_index, const PenBrush& penbrush
)
{
ENSURE_SECTOR_INDEX_OK_OR_RETURN(sector_index);
m_sector_penbrushes[sector_index] = penbrush;
update();
}
void AdjustablePie::setSectorPenBrushes(const QVector<PenBrush>& penbrushes)
{
ENSURE_VECTOR_SIZE_MATCHES_SECTORS(penbrushes);
m_sector_penbrushes = penbrushes;
update();
}
void AdjustablePie::setLabel(const int sector_index, const QString& label)
{
ENSURE_SECTOR_INDEX_OK_OR_RETURN(sector_index);
m_labels[sector_index] = label;
update();
}
void AdjustablePie::setLabels(const QVector<QString>& labels)
{
ENSURE_VECTOR_SIZE_MATCHES_SECTORS(labels);
m_labels = labels;
update();
}
void AdjustablePie::setLabelColour(
const int sector_index, const QColor& colour
)
{
ENSURE_SECTOR_INDEX_OK_OR_RETURN(sector_index);
m_label_colours[sector_index] = colour;
update();
}
void AdjustablePie::setLabelColours(const QVector<QColor>& colours)
{
ENSURE_VECTOR_SIZE_MATCHES_SECTORS(colours);
m_label_colours = colours;
update();
}
void AdjustablePie::setLabelRotation(bool rotate)
{
m_rotate_labels = rotate;
}
int AdjustablePie::nCursors() const
{
return m_n_sectors - 1;
}
void AdjustablePie::setCursorPenBrush(
const int cursor_index, const PenBrush& penbrush
)
{
ENSURE_CURSOR_INDEX_OK_OR_RETURN(cursor_index);
m_cursor_penbrushes[cursor_index] = penbrush;
update();
}
void AdjustablePie::setCursorPenBrushes(const QVector<PenBrush>& penbrushes)
{
ENSURE_VECTOR_SIZE_MATCHES_CURSORS(penbrushes);
m_cursor_penbrushes = penbrushes;
update();
}
void AdjustablePie::setCursorActivePenBrush(
const int cursor_index, const PenBrush& penbrush
)
{
ENSURE_CURSOR_INDEX_OK_OR_RETURN(cursor_index);
m_cursor_active_penbrushes[cursor_index] = penbrush;
update();
}
void AdjustablePie::setCursorActivePenBrushes(
const QVector<PenBrush>& penbrushes
)
{
ENSURE_VECTOR_SIZE_MATCHES_CURSORS(penbrushes);
m_cursor_active_penbrushes = penbrushes;
update();
}
void AdjustablePie::setOuterLabelFont(const QFont& font)
{
m_outer_label_font = font;
}
void AdjustablePie::setSectorRadius(const qreal radius)
{
m_sector_radius = radius;
updateGeometry();
}
void AdjustablePie::setCursorRadius(qreal inner_radius, qreal outer_radius)
{
if (inner_radius > outer_radius) {
std::swap(inner_radius, outer_radius);
}
m_cursor_inner_radius = inner_radius;
m_cursor_outer_radius = outer_radius;
updateGeometry();
}
void AdjustablePie::setCursorAngle(const qreal degrees)
{
m_cursor_angle_degrees = degrees;
update();
}
void AdjustablePie::setLabelStartRadius(const qreal radius)
{
m_label_start_radius = radius;
}
void AdjustablePie::setCentreLabel(const QString& label)
{
m_centre_label = label;
update();
}
void AdjustablePie::setCentreLabelFont(const QFont& font)
{
m_centre_label_font = font;
update();
}
void AdjustablePie::setCentreLabelColour(const QColor& colour)
{
m_centre_label_colour = colour;
update();
}
void AdjustablePie::setOverallRadius(const qreal radius)
{
m_overall_radius = radius;
}
void AdjustablePie::setBaseCompassHeading(const qreal degrees)
{
m_base_compass_heading_deg = degrees;
}
void AdjustablePie::setReportingDelay(const int delay_ms)
{
m_reporting_delay_ms = delay_ms;
}
void AdjustablePie::setProportionCumulative(
const int cursor_index, const qreal proportion
)
{
ENSURE_CURSOR_INDEX_OK_OR_RETURN(cursor_index);
if (proportion < 0.0 || proportion > 1.0) {
qWarning() << Q_FUNC_INFO << "Bad proportion:" << proportion;
return;
}
m_cursor_props_cum[cursor_index] = proportion;
for (int i = 0; i < m_n_sectors - 1; ++i) {
if (i < cursor_index) {
m_cursor_props_cum[i]
= qBound(0.0, m_cursor_props_cum.at(i), proportion);
} else if (i > cursor_index) {
m_cursor_props_cum[i]
= qBound(proportion, m_cursor_props_cum.at(i), 1.0);
}
}
#ifdef DEBUG_CHANGES
qDebug() << Q_FUNC_INFO << m_cursor_props_cum;
#endif
update();
}
void AdjustablePie::setProportions(const QVector<qreal>& proportions)
{
for (const qreal p : proportions) {
if (p < 0.0 || p > 1.0) {
qWarning() << Q_FUNC_INFO << "Bad proportion:" << p;
return;
}
}
const int n = proportions.size();
QVector<qreal> props;
if (n == m_n_sectors - 1) {
// Set cursor proportions directly
props = proportions;
} else if (n == m_n_sectors) {
// Set from all but the last
props = proportions.toList().mid(0, n - 1).toVector();
} else {
qWarning() << Q_FUNC_INFO << "proportions has a bad size of" << n;
return;
}
m_cursor_props_cum.clear();
qreal sum = 0.0;
for (auto p : props) {
sum += p;
m_cursor_props_cum.append(sum);
}
normalizeProportions();
update();
}
void AdjustablePie::setProportionsCumulative(const QVector<qreal>& proportions)
{
for (qreal p : proportions) {
if (p < 0.0 || p > 1.0) {
qWarning() << Q_FUNC_INFO << "Bad proportion:" << p;
return;
}
}
const int n = proportions.size();
if (n == m_n_sectors - 1) {
// Set cursor proportions directly
m_cursor_props_cum = proportions;
} else if (n == m_n_sectors) {
// Set from all but the last
m_cursor_props_cum = proportions.toList().mid(0, n - 1).toVector();
} else {
qWarning() << Q_FUNC_INFO << "proportions has a bad size of" << n;
return;
}
normalizeProportions();
update();
}
qreal AdjustablePie::sectorProportionCumulative(const int sector_index) const
{
if (sector_index < m_n_sectors - 1) {
return m_cursor_props_cum.at(sector_index);
}
return 1.0;
}
// ============================================================================
// Widget information and events
// ============================================================================
QSize AdjustablePie::sizeHint() const
{
auto diameter = static_cast<int>(m_overall_radius * 2);
return QSize(diameter, diameter);
}
void AdjustablePie::paintEvent(QPaintEvent* event)
{
// We use virtual coordinates with the pie centred at (0,0).
// Then we translate to the actual centre (by adding centre).
Q_UNUSED(event)
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);
const QRect cr = contentsRect();
const QPoint widget_centre = cr.center();
// Paint background
p.setPen(QPen(Qt::PenStyle::NoPen));
p.setBrush(m_background_brush);
p.drawRect(cr);
#ifdef DEBUG_DRAW
qDebug() << Q_FUNC_INFO << "contentsRect()" << cr << "widget_centre"
<< widget_centre << "m_cursor_props_cum" << m_cursor_props_cum;
#endif
// ------------------------------------------------------------------------
// Sectors, cursors, labels
// ------------------------------------------------------------------------
// Draw them separately, in case they overlap (e.g. thick pens).
const QPointF sector_tip = widget_centre;
const qreal cursor_radius = m_cursor_outer_radius - m_cursor_inner_radius;
qreal sector_start_angle;
qreal sector_end_angle;
auto startLoop
= [this, §or_start_angle, §or_end_angle](int i) -> void {
const qreal prev_prop
= i == 0 ? 0.0 : sectorProportionCumulative(i - 1);
sector_start_angle = prev_prop * DEG_360;
const qreal prop = sectorProportionCumulative(i);
sector_end_angle = prop * DEG_360;
};
auto endLoop = [§or_start_angle, §or_end_angle]() -> void {
sector_start_angle = sector_end_angle; // for the next one
};
// ------------------------------------------------------------------------
// Sectors
// ------------------------------------------------------------------------
for (int i = 0; i < m_n_sectors; ++i) {
startLoop(i);
// Sector
const PenBrush& spb = m_sector_penbrushes.at(i);
if (m_n_sectors == 1) {
// Avoid the "first cut" line:
p.setPen(spb.pen);
p.setBrush(spb.brush);
p.drawEllipse(sector_tip, m_sector_radius, m_sector_radius);
} else {
drawSector(
p,
sector_tip,
m_sector_radius,
convertAngleToQt(sector_start_angle),
convertAngleToQt(sector_end_angle),
true,
spb.pen,
spb.brush
);
}
endLoop();
}
// ------------------------------------------------------------------------
// Cursors
// ------------------------------------------------------------------------
for (int i = 0; i < m_n_sectors; ++i) {
startLoop(i);
if (i < m_n_sectors - 1) {
const qreal cursor_half_angle = m_cursor_angle_degrees / 2.0;
const qreal cursor_start_angle
= sector_end_angle - cursor_half_angle;
const qreal cursor_end_angle
= sector_end_angle + cursor_half_angle;
const QPointF cursor_tip = widget_centre
+ polarToCartesian(m_cursor_inner_radius,
convertAngleToQt(sector_end_angle));
const PenBrush& cpb
= ((m_user_dragging_cursor && i == m_cursor_num_being_dragged)
? m_cursor_active_penbrushes
: m_cursor_penbrushes)
.at(i);
drawSector(
p,
cursor_tip,
cursor_radius,
convertAngleToQt(cursor_start_angle),
convertAngleToQt(cursor_end_angle),
true,
cpb.pen,
cpb.brush
);
}
endLoop();
}
// ------------------------------------------------------------------------
// Labels
// ------------------------------------------------------------------------
for (int i = 0; i < m_n_sectors; ++i) {
// Label
startLoop(i);
const qreal sector_mid_angle
= sector_end_angle - (sector_end_angle - sector_start_angle) / 2;
const QPointF label_tip = widget_centre
+ polarToCartesian(m_label_start_radius,
convertAngleToQt(sector_mid_angle));
const QString label = m_labels.at(i);
if (!label.isEmpty()) {
const qreal abs_heading = convertHeadingToTrueNorth(
sector_mid_angle, m_base_compass_heading_deg
);
// 0 up, 90 right...
const qreal rotation = abs_heading;
// Easiest way to think of it: something at 180 is at the top
// and shouldn't be rotated.
// Something at 90 is on the left and should be rotated
// anticlockwise.
// QPainter::rotate() rotates clockwise.
p.setPen(m_label_colours.at(i));
if (m_rotate_labels) {
#ifdef DEBUG_DRAW
qDebug() << "... label:" << label << "label_tip" << label_tip
<< "sector_mid_angle" << sector_mid_angle
<< "rotation" << rotation;
#endif
PainterTranslateRotateContext ptrc(p, label_tip, rotation);
// rotation is clockwise
drawText(
p,
QPointF(0, 0),
label,
m_outer_label_font,
Qt::AlignHCenter | Qt::AlignBottom
);
} else {
PainterTranslateRotateContext ptrc(p, label_tip, 0);
// ... relative to North = up
const bool hcentre = headingNearlyEq(abs_heading, DEG_0)
|| headingNearlyEq(abs_heading, DEG_180);
const bool left = !hcentre
&& headingInRange(DEG_180, abs_heading, DEG_360);
const bool vcentre = headingNearlyEq(abs_heading, DEG_90)
|| headingNearlyEq(abs_heading, DEG_270);
const bool bottom
= !vcentre && headingInRange(DEG_90, abs_heading, DEG_270);
Qt::Alignment halign = hcentre
? Qt::AlignHCenter
: (left ? Qt::AlignRight : Qt::AlignLeft);
Qt::Alignment valign = vcentre
? Qt::AlignVCenter
: (bottom ? Qt::AlignTop : Qt::AlignBottom);
#ifdef DEBUG_DRAW
qDebug() << "... label:" << label << "label_tip" << label_tip
<< "sector_mid_angle" << sector_mid_angle << "hcentre"
<< hcentre << "left" << left << "vcentre" << vcentre
<< "bottom" << bottom << "halign" << halign
<< "valign" << valign;
#endif
drawText(
p,
QPointF(0, 0),
label,
m_outer_label_font,
halign | valign
);
}
}
endLoop();
}
// ------------------------------------------------------------------------
// Centre label
// ------------------------------------------------------------------------
if (!m_centre_label.isEmpty()) {
p.setPen(m_centre_label_colour);
drawText(
p,
widget_centre,
m_centre_label,
m_centre_label_font,
Qt::AlignHCenter | Qt::AlignVCenter
);
}
}
void AdjustablePie::mousePressEvent(QMouseEvent* event)
{
// We draw the cursors from 0 upwards, so we detect their touching in the
// reverse order, in case they're stacked.
const QPoint pos = event->pos();
#ifdef DEBUG_EVENTS
qDebug() << Q_FUNC_INFO << pos;
#endif
for (int i = m_n_sectors - 2; i >= 0; --i) {
if (posInCursor(pos, i)) {
#ifdef DEBUG_MOVE
qDebug() << "mousePressEvent: in cursor" << i;
#endif
m_user_dragging_cursor = true;
m_cursor_num_being_dragged = i;
m_last_mouse_pos = event->pos();
qreal mouse_angle = angleOfPos(m_last_mouse_pos);
qreal cursor_angle = cursorAngle(i);
m_angle_offset_from_cursor_centre = mouse_angle - cursor_angle;
update();
break;
}
}
}
void AdjustablePie::mouseMoveEvent(QMouseEvent* event)
{
if (!m_user_dragging_cursor) {
// ... for example: an irrelevant mouse click/drag on an inactive
// part of our widget
return;
}
#ifdef DEBUG_EVENTS
qDebug() << Q_FUNC_INFO;
#endif
const QPoint newpos = event->pos();
const QPoint oldpos = m_last_mouse_pos;
m_last_mouse_pos = newpos;
const qreal mouse_angle = angleOfPos(newpos);
const qreal new_cursor_angle
= mouse_angle - m_angle_offset_from_cursor_centre;
const qreal oldprop = m_cursor_props_cum.at(m_cursor_num_being_dragged);
qreal target_prop = angleToProportion(new_cursor_angle);
// Post-processing magic since target_prop will never be 1.0:
if (target_prop <= 0.0 && oldprop > 0.5) {
target_prop = 1.0;
}
if ((oldprop <= 0.0 && target_prop > 0.5)
|| (oldprop >= 1.0 && target_prop < 0.5)) {
#ifdef DEBUG_MOVE
qDebug() << "DECISION: already at end stop; ignored";
#endif
return;
}
qreal prop;
const QPoint pie_centre = contentsRect().center();
const LineSegment baseline = lineFromPointInHeadingWithRadius(
pie_centre, DEG_0, m_base_compass_heading_deg
);
const LineSegment movement(oldpos, newpos);
const bool from_on = baseline.pointOn(oldpos);
const bool to_on = baseline.pointOn(newpos);
const bool crosses = movement.intersects(baseline) && !from_on && !to_on;
if (oldprop < 0.5 && target_prop > 0.75 && !(oldprop > 0.25 && !crosses)) {
#ifdef DEBUG_MOVE
qDebug() << "DECISION: hit bottom end stop";
#endif
prop = 0.0;
} else if (oldprop > 0.5 && target_prop < 0.25 && !(oldprop < 0.75 && !crosses)) {
#ifdef DEBUG_MOVE
qDebug() << "DECISION: hit top end stop";
#endif
prop = 1.0;
} else {
#ifdef DEBUG_MOVE
qDebug() << "DECISION: free:" << target_prop;
#endif
prop = target_prop;
}
#ifdef DEBUG_MOVE
qDebug() << "... setting cursor" << m_cursor_num_being_dragged
<< "cumulative proportion to" << prop;
#endif
setProportionCumulative(m_cursor_num_being_dragged, prop);
scheduleReport();
}
void AdjustablePie::mouseReleaseEvent(QMouseEvent* event)
{
Q_UNUSED(event)
if (m_user_dragging_cursor) {
m_user_dragging_cursor = false;
#ifdef DEBUG_EVENTS
qDebug() << Q_FUNC_INFO;
#endif
update();
}
}
// ============================================================================
// Readout
// ============================================================================
QVector<qreal> AdjustablePie::cursorProportionsCumulative() const
{
return m_cursor_props_cum;
}
QVector<qreal> AdjustablePie::cursorProportions() const
{
QVector<qreal> props;
qreal previous = 0.0;
for (auto p : props) {
qreal diff = p - previous;
props.append(diff);
previous = p;
}
return props;
}
QVector<qreal> AdjustablePie::allProportionsCumulative() const
{
QVector<qreal> props = m_cursor_props_cum;
qreal sum = 0.0;
for (qreal p : props) {
sum += p;
}
props.append(1.0 - sum);
return props;
}
QVector<qreal> AdjustablePie::allProportions() const
{
QVector<qreal> cum = allProportionsCumulative();
QVector<qreal> props;
qreal previous = 0.0;
for (auto p : cum) {
qreal diff = p - previous;
props.append(diff);
previous = p;
}
return props;
}
// ============================================================================
// Internals
// ============================================================================
qreal AdjustablePie::convertAngleToQt(const qreal degrees) const
{
return headingToPolarThetaDeg(degrees, m_base_compass_heading_deg, false);
}
qreal AdjustablePie::convertAngleToInternal(const qreal degrees) const
{
return polarThetaToHeading(degrees, m_base_compass_heading_deg);
}
bool AdjustablePie::posInCursor(
const QPoint& pos, const int cursor_index
) const
{
const qreal angle = angleOfPos(pos);
const qreal cursor_angle_centre = cursorAngle(cursor_index);
const qreal cursor_half_angle = m_cursor_angle_degrees / 2;
const qreal cursor_min_angle = cursor_angle_centre - cursor_half_angle;
const qreal cursor_max_angle = cursor_angle_centre + cursor_half_angle;
if (!headingInRange(cursor_min_angle, angle, cursor_max_angle)) {
return false;
}
qreal radius = radiusOfPos(pos);
if (radius < m_cursor_inner_radius || radius > m_cursor_outer_radius) {
return false;
}
// Could be refined! This allows the user to grab a cursor by the "missing"
// bit within its "zone" but not within its true pie shape.
return true;
}
qreal AdjustablePie::angleToProportion(const qreal angle_degrees) const
{
// BEWARE that this will never produce 1.0, so some post-processing
// magic is required for that; see mouseMoveEvent().
return qBound(0.0, normalizeHeading(angle_degrees) / DEG_360, 1.0);
}
qreal AdjustablePie::proportionToAngle(const qreal proportion) const
{
return DEG_360 * proportion;
}
qreal AdjustablePie::angleOfPos(const QPoint& pos) const
{
const QPoint pie_centre = contentsRect().center();
return convertAngleToInternal(polarThetaDeg(pie_centre, pos));
}
qreal AdjustablePie::radiusOfPos(const QPoint& pos) const
{
return distanceBetween(pos, contentsRect().center());
}
qreal AdjustablePie::cursorAngle(const int cursor_index) const
{
const qreal prop = m_cursor_props_cum.at(cursor_index);
return proportionToAngle(prop);
}
void AdjustablePie::scheduleReport()
{
if (m_reporting_delay_ms > 0) {
m_timer->start(m_reporting_delay_ms);
} else {
report();
}
}
void AdjustablePie::report()
{
emit proportionsChanged(allProportions());
emit cumulativeProportionsChanged(allProportionsCumulative());
}
void AdjustablePie::normalize()
{
forceVectorSize(m_sector_penbrushes, m_n_sectors, DEFAULT_SECTOR_PENBRUSH);
forceVectorSize(m_labels, m_n_sectors);
forceVectorSize(m_label_colours, m_n_sectors, DEFAULT_LABEL_COLOUR);
forceVectorSize(
m_cursor_penbrushes, m_n_sectors - 1, DEFAULT_CURSOR_PENBRUSH
);
forceVectorSize(
m_cursor_active_penbrushes,
m_n_sectors - 1,
DEFAULT_CURSOR_ACTIVE_PENBRUSH
);
forceVectorSize(m_cursor_props_cum, m_n_sectors - 1, 0.0);
normalizeProportions();
update();
}
void AdjustablePie::normalizeProportions()
{
if (m_n_sectors == 1) {
return;
}
m_cursor_props_cum[0] = qBound(0.0, m_cursor_props_cum[0], 1.0);
for (int i = 1; i < m_n_sectors - 1; ++i) {
m_cursor_props_cum[i] = qBound(
m_cursor_props_cum.at(i - 1), m_cursor_props_cum.at(i), 1.0
);
}
}