Files
PDF4QT/Pdf4QtEditorPlugins/ObjectInspectorPlugin/statisticsgraphwidget.cpp
2025-04-27 12:39:06 +02:00

314 lines
9.9 KiB
C++

// MIT License
//
// Copyright (c) 2018-2025 Jakub Melka and Contributors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include "statisticsgraphwidget.h"
#include "ui_statisticsgraphwidget.h"
#include "pdfwidgetutils.h"
#include "pdfpainterutils.h"
#include <QPainter>
namespace pdfplugin
{
StatisticsGraphWidget::StatisticsGraphWidget(QWidget* parent) :
QWidget(parent),
ui(new Ui::StatisticsGraphWidget)
{
ui->setupUi(this);
}
StatisticsGraphWidget::~StatisticsGraphWidget()
{
delete ui;
}
void StatisticsGraphWidget::setStatistics(Statistics statistics)
{
if (m_statistics != statistics)
{
m_statistics = qMove(statistics);
updateGeometry();
update();
}
}
StatisticsGraphWidget::GeometryHint StatisticsGraphWidget::getGeometryHint() const
{
GeometryHint hint;
int fivePixelsX = pdf::PDFWidgetUtils::scaleDPI_x(this, 5);
int fivePixelsY = pdf::PDFWidgetUtils::scaleDPI_y(this, 5);
int textMargin = fivePixelsX;
hint.margins = QMargins(fivePixelsX, fivePixelsY, fivePixelsX, fivePixelsY);
hint.colorRectangleWidth = pdf::PDFWidgetUtils::scaleDPI_x(this, 80);
hint.linesWidthLeft = pdf::PDFWidgetUtils::scaleDPI_x(this, 15);
hint.linesWidthRight = pdf::PDFWidgetUtils::scaleDPI_x(this, 5);
hint.markerSize = fivePixelsX;
hint.normalLineWidth = pdf::PDFWidgetUtils::scaleDPI_y(this, 2);
hint.selectedLineWidth = pdf::PDFWidgetUtils::scaleDPI_y(this, 3);
hint.colorRectangeLeftMargin = pdf::PDFWidgetUtils::scaleDPI_x(this, 40);
QFontMetrics fontMetricsTitle(getTitleFont());
hint.titleWidth = fontMetricsTitle.horizontalAdvance(m_statistics.title);
hint.titleHeight = fontMetricsTitle.lineSpacing();
QFontMetrics fontMetricsHeader(getHeaderFont());
hint.textHeight = fontMetricsHeader.lineSpacing();
QFontMetrics fontMetricsText(getTextFont());
hint.textHeight += fontMetricsText.lineSpacing() * int(m_statistics.items.size());
hint.textMargin = textMargin;
hint.textLineHeight = fontMetricsText.lineSpacing();
// Determine text header width
hint.textWidths.resize(m_statistics.headers.size(), 0);
for (int i = 0; i < m_statistics.headers.size(); ++i)
{
hint.textWidths[i] = fontMetricsHeader.horizontalAdvance(m_statistics.headers[i]);
}
for (const auto& item : m_statistics.items)
{
Q_ASSERT(static_cast<size_t>(item.texts.size()) == hint.textWidths.size());
for (int i = 0; i < item.texts.size(); ++i)
{
hint.textWidths[i] = qMax(hint.textWidths[i], fontMetricsHeader.horizontalAdvance(item.texts[i]));
}
}
int totalTextWidth = textMargin;
for (int& textWidth : hint.textWidths)
{
textWidth += 2 * textMargin;
totalTextWidth += textWidth;
}
const int widthHint = hint.margins.left() + hint.colorRectangeLeftMargin + hint.colorRectangleWidth + hint.linesWidthLeft + hint.linesWidthRight + qMax(totalTextWidth, hint.titleWidth) + hint.margins.right();
const int heightHint = hint.margins.top() + hint.titleHeight + qMax(pdf::PDFWidgetUtils::scaleDPI_y(this, 100), hint.textHeight) + hint.margins.bottom();
hint.minimalWidgetSize = QSize(widthHint, heightHint).expandedTo(pdf::PDFWidgetUtils::scaleDPI(this, QSize(400, 300)));
return hint;
}
void StatisticsGraphWidget::paintEvent(QPaintEvent* event)
{
Q_UNUSED(event);
QPainter painter(this);
QRect rect = this->rect();
painter.fillRect(rect, Qt::white);
GeometryHint geometryHint = getGeometryHint();
rect = rect.marginsRemoved(geometryHint.margins);
if (!m_statistics.title.isEmpty())
{
pdf::PDFPainterStateGuard guard(&painter);
QRect titleRect = rect;
titleRect.setHeight(geometryHint.titleHeight);
painter.setFont(getHeaderFont());
painter.drawText(titleRect, Qt::AlignCenter, m_statistics.title);
rect.setTop(titleRect.bottom());
}
rect.setLeft(rect.left() + geometryHint.colorRectangeLeftMargin);
// Color boxes
m_colorBoxes.clear();
const int height = rect.height();
QColor selectedColor = Qt::darkYellow;
int top = rect.top();
for (size_t i = 0; i < m_statistics.items.size(); ++i)
{
const StatisticsItem& item = m_statistics.items[i];
QRect currentRect(rect.left(), top, geometryHint.colorRectangleWidth, height * item.portion);
top = currentRect.bottom();
QColor color = (m_selectedColorBox == i) ? selectedColor: item.color;
color.setAlphaF(0.8f);
painter.fillRect(currentRect, color);
m_colorBoxes.push_back(currentRect);
}
QPoint minLinePoint;
QPoint maxLinePoint;
// Marked lines
{
pdf::PDFPainterStateGuard guard(&painter);
for (size_t i = 0; i < m_statistics.items.size(); ++i)
{
QPen pen = painter.pen();
pen.setColor((i == m_selectedColorBox) ? selectedColor : Qt::black);
pen.setWidth(geometryHint.markerSize);
pen.setCapStyle(Qt::RoundCap);
painter.setPen(pen);
QRect colorBox = m_colorBoxes[i];
QPoint marker = colorBox.center();
marker.setX(colorBox.right());
painter.drawPoint(marker);
pen.setWidth((i == m_selectedColorBox) ? geometryHint.selectedLineWidth : geometryHint.normalLineWidth);
painter.setPen(pen);
QPoint lineEnd = marker + QPoint(geometryHint.linesWidthLeft, 0);
painter.drawLine(marker, lineEnd);
if (i == 0)
{
minLinePoint = lineEnd;
}
maxLinePoint = lineEnd;
}
}
// Texts
{
pdf::PDFPainterStateGuard guard(&painter);
QFont headerFont = getHeaderFont();
QFont textFont = getTextFont();
QRect textRect = rect;
textRect.setLeft(maxLinePoint.x() + geometryHint.textMargin);
textRect.setHeight(geometryHint.textLineHeight);
std::vector<QLine> horizontalLines;
horizontalLines.reserve(m_statistics.items.size());
auto drawTexts = [&](const QStringList& texts, bool isHeader)
{
pdf::PDFPainterStateGuard guard(&painter);
painter.setFont(isHeader ? headerFont : textFont);
const int y = textRect.center().y();
minLinePoint.ry() = qMin(minLinePoint.ry(), y);
maxLinePoint.ry() = qMax(maxLinePoint.ry(), y);
QRect cellRect = textRect;
for (int i = 0; i < texts.size(); ++i)
{
cellRect.setWidth(geometryHint.textWidths[i]);
QRect finalCellRect = cellRect.marginsRemoved(QMargins(geometryHint.textMargin, 0, geometryHint.textMargin, 0));
QString text = texts[i];
Qt::Alignment alignment = (i == 0) ? Qt::Alignment(Qt::AlignLeft | Qt::AlignVCenter) : Qt::Alignment(Qt::AlignRight | Qt::AlignVCenter);
painter.drawText(finalCellRect, alignment, text);
cellRect.translate(cellRect.width(), 0);
}
if (!isHeader)
{
QPoint startPoint = textRect.center();
startPoint.setX(textRect.left());
QPoint endPoint(maxLinePoint.x(), startPoint.y());
horizontalLines.emplace_back(startPoint, endPoint);
}
textRect.translate(0, textRect.height());
};
if (!m_statistics.headers.isEmpty())
{
drawTexts(m_statistics.headers, true);
}
for (size_t i = 0; i < m_statistics.items.size(); ++i)
{
const StatisticsItem& item = m_statistics.items[i];
drawTexts(item.texts, i == m_selectedColorBox);
}
QPen pen = painter.pen();
pen.setColor(Qt::black);
pen.setWidth(geometryHint.normalLineWidth);
pen.setCapStyle(Qt::RoundCap);
painter.setPen(pen);
painter.drawLine(minLinePoint, maxLinePoint);
for (const QLine& line : horizontalLines)
{
painter.drawLine(line);
}
pen = painter.pen();
pen.setColor(Qt::black);
pen.setWidth(geometryHint.markerSize);
pen.setCapStyle(Qt::RoundCap);
painter.setPen(pen);
for (const QLine& line : horizontalLines)
{
painter.drawPoint(line.p1());
}
}
}
QFont StatisticsGraphWidget::getTitleFont() const
{
QFont font = this->font();
font.setPointSize(font.pointSize() * 2);
font.setBold(true);
return font;
}
QFont StatisticsGraphWidget::getHeaderFont() const
{
QFont font = this->font();
font.setBold(true);
return font;
}
QFont StatisticsGraphWidget::getTextFont() const
{
return this->font();
}
QSize StatisticsGraphWidget::sizeHint() const
{
return getGeometryHint().minimalWidgetSize + pdf::PDFWidgetUtils::scaleDPI(this, QSize(100, 100));
}
QSize StatisticsGraphWidget::minimumSizeHint() const
{
return getGeometryHint().minimalWidgetSize;
}
} // namespace pdfplugin