diff options
Diffstat (limited to 'stats/legend.cpp')
| -rw-r--r-- | stats/legend.cpp | 124 |
1 files changed, 67 insertions, 57 deletions
diff --git a/stats/legend.cpp b/stats/legend.cpp index 27607fb51..fc8656828 100644 --- a/stats/legend.cpp +++ b/stats/legend.cpp @@ -3,9 +3,8 @@ #include "statscolors.h" #include "zvalues.h" +#include <cmath> #include <QFontMetrics> -#include <QGraphicsScene> -#include <QGraphicsSceneMouseEvent> #include <QPen> static const double legendBorderSize = 2.0; @@ -13,54 +12,33 @@ static const double legendBoxBorderSize = 1.0; static const double legendBoxBorderRadius = 4.0; // radius of rounded corners static const double legendBoxScale = 0.8; // 1.0: text-height of the used font static const double legendInternalBorderSize = 2.0; -static const QColor legendColor(0x00, 0x8e, 0xcc, 192); // Note: fourth argument is opacity -static const QColor legendBorderColor(Qt::black); -Legend::Legend(const std::vector<QString> &names) : - RoundRectItem(legendBoxBorderRadius), - displayedItems(0), width(0.0), height(0.0) +Legend::Legend(StatsView &view, const std::vector<QString> &names) : + ChartRectItem(view, ChartZValue::Legend, + QPen(legendBorderColor, legendBorderSize), QBrush(legendColor), legendBoxBorderRadius), + displayedItems(0), width(0.0), height(0.0), + font(QFont()), // Make configurable + posInitialized(false) { - setZValue(ZValues::legend); entries.reserve(names.size()); + QFontMetrics fm(font); + fontHeight = fm.height(); int idx = 0; for (const QString &name: names) - entries.emplace_back(name, idx++, (int)names.size(), this); - - // Calculate the height and width of the elements - if (!entries.empty()) { - QFontMetrics fm(entries[0].text->font()); - fontHeight = fm.height(); - for (Entry &e: entries) - e.width = fontHeight + 2.0 * legendBoxBorderSize + - fm.size(Qt::TextSingleLine, e.text->text()).width(); - } else { - // Set to an arbitrary non-zero value, because Coverity doesn't understand - // that we don't use the value as divisor below if entries is empty. - fontHeight = 10.0; - } - setPen(QPen(legendBorderColor, legendBorderSize)); - setBrush(QBrush(legendColor)); + entries.emplace_back(name, idx++, (int)names.size(), fm); } -Legend::Entry::Entry(const QString &name, int idx, int numBins, QGraphicsItem *parent) : - rect(new QGraphicsRectItem(parent)), - text(new QGraphicsSimpleTextItem(name, parent)), - width(0) +Legend::Entry::Entry(const QString &name, int idx, int numBins, const QFontMetrics &fm) : + name(name), + rectBrush(QBrush(binColor(idx, numBins))) { - rect->setZValue(ZValues::legend); - rect->setPen(QPen(legendBorderColor, legendBoxBorderSize)); - rect->setBrush(QBrush(binColor(idx, numBins))); - text->setZValue(ZValues::legend); - text->setBrush(QBrush(darkLabelColor)); + width = fm.height() + 2.0 * legendBoxBorderSize + fm.size(Qt::TextSingleLine, name).width(); } void Legend::hide() { - for (Entry &e: entries) { - e.rect->hide(); - e.text->hide(); - } - QGraphicsRectItem::hide(); + ChartRectItem::resize(QSizeF(1,1)); + img->fill(Qt::transparent); } void Legend::resize() @@ -68,7 +46,7 @@ void Legend::resize() if (entries.empty()) return hide(); - QSizeF size = scene()->sceneRect().size(); + QSizeF size = sceneSize(); // Silly heuristics: make the legend at most half as high and half as wide as the chart. // Not sure if that makes sense - this might need some optimization. @@ -100,31 +78,63 @@ void Legend::resize() } width += legendInternalBorderSize; height = 2 * legendInternalBorderSize + numRows * fontHeight; - updatePosition(); -} -void Legend::updatePosition() -{ - if (displayedItems <= 0) - return hide(); - // For now, place the legend in the top right corner. - QPointF pos(scene()->sceneRect().width() - width - 10.0, 10.0); - setRect(QRectF(pos, QSizeF(width, height))); + ChartRectItem::resize(QSizeF(width, height)); + + // Paint rectangles + painter->setPen(QPen(legendBorderColor, legendBoxBorderSize)); for (int i = 0; i < displayedItems; ++i) { - QPointF itemPos = pos + entries[i].pos; + QPointF itemPos = entries[i].pos; + painter->setBrush(entries[i].rectBrush); QRectF rect(itemPos, QSizeF(fontHeight, fontHeight)); // Decrease box size by legendBoxScale factor double delta = fontHeight * (1.0 - legendBoxScale) / 2.0; rect = rect.adjusted(delta, delta, -delta, -delta); - entries[i].rect->setRect(rect); + painter->drawRect(rect); + } + + // Paint labels + painter->setPen(darkLabelColor); // QPainter uses pen not brush for text! + painter->setFont(font); + for (int i = 0; i < displayedItems; ++i) { + QPointF itemPos = entries[i].pos; itemPos.rx() += fontHeight + 2.0 * legendBoxBorderSize; - entries[i].text->setPos(itemPos); - entries[i].rect->show(); - entries[i].text->show(); + QRectF rect(itemPos, QSizeF(entries[i].width, fontHeight)); + painter->drawText(rect, entries[i].name); } - for (int i = displayedItems; i < (int)entries.size(); ++i) { - entries[i].rect->hide(); - entries[i].text->hide(); + + if (!posInitialized) { + // At first, place in top right corner + setPos(QPointF(size.width() - width - 10.0, 10.0)); + posInitialized = true; + } else { + // Try to keep relative position with what it was before + setPos(QPointF(size.width() * centerPos.x() - width / 2.0, + size.height() * centerPos.y() - height / 2.0)); } - show(); +} + +void Legend::setPos(QPointF newPos) +{ + // Round the position to integers or horrible artifacts appear (at least on desktop) + QPointF posInt(round(newPos.x()), round(newPos.y())); + + // Make sure that the center is inside the drawing area, + // so that the user can't lose the legend. + QSizeF size = sceneSize(); + if (size.width() < 1.0 || size.height() < 1.0) + return; + double widthHalf = floor(width / 2.0); + double heightHalf = floor(height / 2.0); + QPointF sanitizedPos(std::clamp(posInt.x(), -widthHalf, size.width() - widthHalf - 1.0), + std::clamp(posInt.y(), -heightHalf, size.height() - heightHalf - 1.0)); + + // Set position + ChartRectItem::setPos(sanitizedPos); + + // Remember relative position of center for next time + QPointF centerPosAbsolute(sanitizedPos.x() + width / 2.0, + sanitizedPos.y() + height / 2.0); + centerPos = QPointF(centerPosAbsolute.x() / size.width(), + centerPosAbsolute.y() / size.height()); } |