File indexing completed on 2025-02-02 03:40:53

0001 /*
0002     File                 : PlotArea.cpp
0003     Project              : LabPlot
0004     Description          : Plot area (for background filling and clipping).
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2011-2022 Alexander Semke <alexander.semke@web.de>
0007 
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 #include "backend/worksheet/plots/PlotArea.h"
0012 #include "backend/lib/XmlStreamReader.h"
0013 #include "backend/lib/commandtemplates.h"
0014 #include "backend/lib/macros.h"
0015 #include "backend/worksheet/Background.h"
0016 #include "backend/worksheet/Line.h"
0017 #include "backend/worksheet/Worksheet.h"
0018 #include "backend/worksheet/plots/PlotAreaPrivate.h"
0019 #include "backend/worksheet/plots/cartesian/CartesianPlot.h"
0020 
0021 #include <KConfig>
0022 #include <KConfigGroup>
0023 #include <KLocalizedString>
0024 #include <QPainter>
0025 #include <QPalette>
0026 
0027 /**
0028  * \class PlotArea
0029  * \brief Plot area (for background filling and clipping).
0030  *
0031  * \ingroup worksheet
0032  */
0033 
0034 PlotArea::PlotArea(const QString& name, CartesianPlot* parent)
0035     : WorksheetElement(name, new PlotAreaPrivate(this), AspectType::PlotArea)
0036     , m_parent(parent) {
0037     init();
0038 }
0039 
0040 PlotArea::PlotArea(const QString& name, CartesianPlot* parent, PlotAreaPrivate* dd)
0041     : WorksheetElement(name, dd, AspectType::PlotArea)
0042     , m_parent(parent) {
0043     init();
0044 }
0045 
0046 // no need to delete the d-pointer here - it inherits from QGraphicsItem
0047 // and is deleted during the cleanup in QGraphicsScene
0048 PlotArea::~PlotArea() = default;
0049 
0050 void PlotArea::init() {
0051     Q_D(PlotArea);
0052 
0053     setHidden(true); // we don't show PlotArea aspect in the model view.
0054     d->rect = QRectF(0, 0, 1, 1);
0055     d->setFlag(QGraphicsItem::ItemClipsChildrenToShape, true);
0056 
0057     KConfig config;
0058     KConfigGroup group = config.group(QStringLiteral("PlotArea"));
0059 
0060     // Background
0061     d->background = new Background(QString());
0062     addChild(d->background);
0063     d->background->setHidden(true);
0064     d->background->init(group);
0065     connect(d->background, &Background::updateRequested, [=] {
0066         d->update();
0067     });
0068 
0069     // Border
0070     PlotArea::BorderType type; // default value
0071     type.setFlag(PlotArea::BorderTypeFlags::BorderLeft);
0072     type.setFlag(PlotArea::BorderTypeFlags::BorderTop);
0073     type.setFlag(PlotArea::BorderTypeFlags::BorderRight);
0074     type.setFlag(PlotArea::BorderTypeFlags::BorderBottom);
0075     d->borderType = static_cast<PlotArea::BorderType>(group.readEntry(QStringLiteral("BorderType"), static_cast<int>(type)));
0076 
0077     d->borderLine = new Line(QString());
0078     d->borderLine->setPrefix(QStringLiteral("Border"));
0079     d->borderLine->setCreateXmlElement(false);
0080     d->borderLine->setHidden(true);
0081     addChild(d->borderLine);
0082     d->borderLine->init(group);
0083     connect(d->borderLine, &Line::updatePixmapRequested, [=] {
0084         d->update();
0085     });
0086     connect(d->borderLine, &Line::updateRequested, [=] {
0087         d->recalcShapeAndBoundingRect();
0088         Q_EMIT changed();
0089     });
0090 
0091     d->borderCornerRadius = group.readEntry(QStringLiteral("BorderCornerRadius"), 0.0);
0092 }
0093 
0094 QGraphicsItem* PlotArea::graphicsItem() const {
0095     return d_ptr;
0096 }
0097 
0098 bool PlotArea::isHovered() const {
0099     return m_parent->isHovered();
0100 }
0101 
0102 bool PlotArea::isSelected() const {
0103     return m_parent->isSelected();
0104 }
0105 
0106 void PlotArea::handleResize(double horizontalRatio, double verticalRatio, bool /*pageResize*/) {
0107     DEBUG(Q_FUNC_INFO);
0108     Q_D(PlotArea);
0109 
0110     d->rect.setWidth(d->rect.width() * horizontalRatio);
0111     d->rect.setHeight(d->rect.height() * verticalRatio);
0112 
0113     // TODO: scale line width
0114 }
0115 
0116 void PlotArea::retransform() {
0117     Q_D(PlotArea);
0118     d->retransform();
0119 }
0120 
0121 /* ============================ getter methods ================= */
0122 BASIC_SHARED_D_READER_IMPL(PlotArea, bool, clippingEnabled, clippingEnabled())
0123 BASIC_SHARED_D_READER_IMPL(PlotArea, QRectF, rect, rect)
0124 
0125 // background
0126 Background* PlotArea::background() const {
0127     Q_D(const PlotArea);
0128     return d->background;
0129 }
0130 
0131 BASIC_SHARED_D_READER_IMPL(PlotArea, PlotArea::BorderType, borderType, borderType)
0132 BASIC_SHARED_D_READER_IMPL(PlotArea, qreal, borderCornerRadius, borderCornerRadius)
0133 
0134 Line* PlotArea::borderLine() const {
0135     Q_D(const PlotArea);
0136     return d->borderLine;
0137 }
0138 
0139 /* ============================ setter methods and undo commands ================= */
0140 
0141 STD_SWAP_METHOD_SETTER_CMD_IMPL(PlotArea, SetClippingEnabled, bool, toggleClipping)
0142 void PlotArea::setClippingEnabled(bool on) {
0143     Q_D(PlotArea);
0144     if (d->clippingEnabled() != on)
0145         exec(new PlotAreaSetClippingEnabledCmd(d, on, ki18n("%1: toggle clipping")));
0146 }
0147 
0148 /*!
0149  * sets plot area rect in scene coordinates.
0150  */
0151 void PlotArea::setRect(const QRectF& newRect) {
0152     Q_D(PlotArea);
0153     d->setRect(newRect);
0154 }
0155 
0156 // Border
0157 STD_SETTER_CMD_IMPL_F_S(PlotArea, SetBorderType, PlotArea::BorderType, borderType, update)
0158 void PlotArea::setBorderType(BorderType type) {
0159     Q_D(PlotArea);
0160     if (type != d->borderType)
0161         exec(new PlotAreaSetBorderTypeCmd(d, type, ki18n("%1: border type changed")));
0162 }
0163 
0164 STD_SETTER_CMD_IMPL_F_S(PlotArea, SetBorderCornerRadius, qreal, borderCornerRadius, update)
0165 void PlotArea::setBorderCornerRadius(qreal radius) {
0166     Q_D(PlotArea);
0167     if (radius != d->borderCornerRadius)
0168         exec(new PlotAreaSetBorderCornerRadiusCmd(d, radius, ki18n("%1: set plot area corner radius")));
0169 }
0170 
0171 // #####################################################################
0172 // ################### Private implementation ##########################
0173 // #####################################################################
0174 PlotAreaPrivate::PlotAreaPrivate(PlotArea* owner)
0175     : WorksheetElementPrivate(owner)
0176     , q(owner) {
0177 }
0178 
0179 bool PlotAreaPrivate::clippingEnabled() const {
0180     return (flags() & QGraphicsItem::ItemClipsChildrenToShape);
0181 }
0182 
0183 bool PlotAreaPrivate::toggleClipping(bool on) {
0184     bool oldValue = clippingEnabled();
0185     setFlag(QGraphicsItem::ItemClipsChildrenToShape, on);
0186     return oldValue;
0187 }
0188 
0189 void PlotAreaPrivate::setRect(const QRectF& r) {
0190     prepareGeometryChange();
0191     rect = mapRectFromScene(r);
0192 }
0193 
0194 QRectF PlotAreaPrivate::boundingRect() const {
0195     if (borderLine->pen().style() != Qt::NoPen) {
0196         const qreal width = rect.width();
0197         const qreal height = rect.height();
0198         const double penWidth = borderLine->pen().width();
0199         return QRectF{-width / 2 - penWidth / 2, -height / 2 - penWidth / 2, width + penWidth, height + penWidth};
0200     } else
0201         return rect;
0202 }
0203 
0204 QPainterPath PlotAreaPrivate::shape() const {
0205     QPainterPath path;
0206     if (qFuzzyIsNull(borderCornerRadius))
0207         path.addRect(rect);
0208     else
0209         path.addRoundedRect(rect, borderCornerRadius, borderCornerRadius);
0210 
0211     return path;
0212 }
0213 
0214 void PlotAreaPrivate::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) {
0215     // Ignore context menu event and forward to next
0216     QGraphicsItem::contextMenuEvent(event);
0217 }
0218 
0219 void PlotAreaPrivate::update() {
0220     QGraphicsItem::update();
0221     Q_EMIT q->changed();
0222 }
0223 
0224 void PlotAreaPrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget*) {
0225     if (!isVisible())
0226         return;
0227 
0228     // draw the area
0229     painter->setOpacity(background->opacity());
0230     painter->setPen(Qt::NoPen);
0231     if (background->type() == Background::Type::Color) {
0232         switch (background->colorStyle()) {
0233         case Background::ColorStyle::SingleColor: {
0234             painter->setBrush(QBrush(background->firstColor()));
0235             break;
0236         }
0237         case Background::ColorStyle::HorizontalLinearGradient: {
0238             QLinearGradient linearGrad(rect.topLeft(), rect.topRight());
0239             linearGrad.setColorAt(0, background->firstColor());
0240             linearGrad.setColorAt(1, background->secondColor());
0241             painter->setBrush(QBrush(linearGrad));
0242             break;
0243         }
0244         case Background::ColorStyle::VerticalLinearGradient: {
0245             QLinearGradient linearGrad(rect.topLeft(), rect.bottomLeft());
0246             linearGrad.setColorAt(0, background->firstColor());
0247             linearGrad.setColorAt(1, background->secondColor());
0248             painter->setBrush(QBrush(linearGrad));
0249             break;
0250         }
0251         case Background::ColorStyle::TopLeftDiagonalLinearGradient: {
0252             QLinearGradient linearGrad(rect.topLeft(), rect.bottomRight());
0253             linearGrad.setColorAt(0, background->firstColor());
0254             linearGrad.setColorAt(1, background->secondColor());
0255             painter->setBrush(QBrush(linearGrad));
0256             break;
0257         }
0258         case Background::ColorStyle::BottomLeftDiagonalLinearGradient: {
0259             QLinearGradient linearGrad(rect.bottomLeft(), rect.topRight());
0260             linearGrad.setColorAt(0, background->firstColor());
0261             linearGrad.setColorAt(1, background->secondColor());
0262             painter->setBrush(QBrush(linearGrad));
0263             break;
0264         }
0265         case Background::ColorStyle::RadialGradient: {
0266             QRadialGradient radialGrad(rect.center(), rect.width() / 2);
0267             radialGrad.setColorAt(0, background->firstColor());
0268             radialGrad.setColorAt(1, background->secondColor());
0269             painter->setBrush(QBrush(radialGrad));
0270             break;
0271         }
0272         }
0273     } else if (background->type() == Background::Type::Image) {
0274         if (!background->fileName().trimmed().isEmpty()) {
0275             QPixmap pix(background->fileName());
0276             switch (background->imageStyle()) {
0277             case Background::ImageStyle::ScaledCropped:
0278                 pix = pix.scaled(rect.size().toSize(), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
0279                 painter->setBrush(QBrush(pix));
0280                 painter->setBrushOrigin(pix.size().width() / 2, pix.size().height() / 2);
0281                 painter->drawRoundedRect(rect, borderCornerRadius, borderCornerRadius);
0282                 break;
0283             case Background::ImageStyle::Scaled:
0284                 pix = pix.scaled(rect.size().toSize(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
0285                 painter->setBrush(QBrush(pix));
0286                 painter->setBrushOrigin(pix.size().width() / 2, pix.size().height() / 2);
0287                 painter->drawRoundedRect(rect, borderCornerRadius, borderCornerRadius);
0288                 break;
0289             case Background::ImageStyle::ScaledAspectRatio:
0290                 pix = pix.scaled(rect.size().toSize(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
0291                 painter->setBrush(QBrush(pix));
0292                 painter->setBrushOrigin(pix.size().width() / 2, pix.size().height() / 2);
0293                 painter->drawRoundedRect(rect, borderCornerRadius, borderCornerRadius);
0294                 break;
0295             case Background::ImageStyle::Centered:
0296                 painter->drawPixmap(QPointF(rect.center().x() - pix.size().width() / 2, rect.center().y() - pix.size().height() / 2), pix);
0297                 break;
0298             case Background::ImageStyle::Tiled:
0299                 painter->setBrush(QBrush(pix));
0300                 painter->drawRoundedRect(rect, borderCornerRadius, borderCornerRadius);
0301                 break;
0302             case Background::ImageStyle::CenterTiled:
0303                 painter->setBrush(QBrush(pix));
0304                 painter->setBrushOrigin(pix.size().width() / 2, pix.size().height() / 2);
0305                 painter->drawRoundedRect(rect, borderCornerRadius, borderCornerRadius);
0306             }
0307         }
0308     } else if (background->type() == Background::Type::Pattern) {
0309         painter->setBrush(QBrush(background->firstColor(), background->brushStyle()));
0310     }
0311 
0312     // draw the background
0313     if (qFuzzyIsNull(borderCornerRadius))
0314         painter->drawRect(rect);
0315     else
0316         painter->drawRoundedRect(rect, borderCornerRadius, borderCornerRadius);
0317 
0318     // draw the border
0319     if (borderLine->pen().style() != Qt::NoPen) {
0320         painter->setPen(borderLine->pen());
0321         painter->setBrush(Qt::NoBrush);
0322         painter->setOpacity(borderLine->opacity());
0323         if (qFuzzyIsNull(borderCornerRadius)) {
0324             const double w = rect.width();
0325             const double h = rect.height();
0326             if (borderType.testFlag(PlotArea::BorderTypeFlags::BorderLeft))
0327                 painter->drawLine(-w / 2, -h / 2, -w / 2, h / 2);
0328             if (borderType.testFlag(PlotArea::BorderTypeFlags::BorderTop))
0329                 painter->drawLine(-w / 2, -h / 2, w / 2, -h / 2);
0330             if (borderType.testFlag(PlotArea::BorderTypeFlags::BorderRight))
0331                 painter->drawLine(-w / 2 + w, -h / 2, w / 2, h / 2);
0332             if (borderType.testFlag(PlotArea::BorderTypeFlags::BorderBottom))
0333                 painter->drawLine(w / 2, h / 2, -w / 2, h / 2);
0334         } else
0335             painter->drawRoundedRect(rect, borderCornerRadius, borderCornerRadius);
0336     }
0337 
0338     if (q->isHovered() || q->isSelected()) {
0339         const double penWidth = 6.;
0340         QRectF rect = boundingRect();
0341         rect = QRectF(-rect.width() / 2 + penWidth / 2, -rect.height() / 2 + penWidth / 2, rect.width() - penWidth, rect.height() - penWidth);
0342 
0343         if (q->isHovered() && !q->isSelected() && !q->isPrinting()) {
0344             painter->setPen(QPen(QApplication::palette().color(QPalette::Shadow), penWidth, Qt::SolidLine));
0345             painter->drawRect(rect);
0346         }
0347 
0348         if (q->isSelected() && !q->isPrinting()) {
0349             painter->setPen(QPen(QApplication::palette().color(QPalette::Highlight), penWidth, Qt::SolidLine));
0350             painter->drawRect(rect);
0351         }
0352     }
0353 }
0354 
0355 // ##############################################################################
0356 // ##################  Serialization/Deserialization  ###########################
0357 // ##############################################################################
0358 
0359 //! Save as XML
0360 void PlotArea::save(QXmlStreamWriter* writer) const {
0361     Q_D(const PlotArea);
0362 
0363     writer->writeStartElement(QStringLiteral("plotArea"));
0364     writeBasicAttributes(writer);
0365     writeCommentElement(writer);
0366 
0367     // background
0368     d->background->save(writer);
0369 
0370     // border
0371     writer->writeStartElement(QStringLiteral("border"));
0372     writer->writeAttribute(QStringLiteral("borderType"), QString::number(d->borderType));
0373     d->borderLine->save(writer);
0374     writer->writeAttribute(QStringLiteral("borderCornerRadius"), QString::number(d->borderCornerRadius));
0375     writer->writeEndElement();
0376 
0377     writer->writeEndElement();
0378 }
0379 
0380 //! Load from XML
0381 bool PlotArea::load(XmlStreamReader* reader, bool preview) {
0382     Q_D(PlotArea);
0383 
0384     if (!readBasicAttributes(reader))
0385         return false;
0386 
0387     QXmlStreamAttributes attribs;
0388     QString str;
0389 
0390     while (!reader->atEnd()) {
0391         reader->readNext();
0392         if (reader->isEndElement() && reader->name() == QLatin1String("plotArea"))
0393             break;
0394 
0395         if (!reader->isStartElement())
0396             continue;
0397 
0398         if (!preview && reader->name() == QLatin1String("comment")) {
0399             if (!readCommentElement(reader))
0400                 return false;
0401         } else if (!preview && reader->name() == QLatin1String("background"))
0402             d->background->load(reader, preview);
0403         else if (!preview && reader->name() == QLatin1String("border")) {
0404             attribs = reader->attributes();
0405 
0406             READ_INT_VALUE("borderType", borderType, PlotArea::BorderType);
0407             d->borderLine->load(reader, preview);
0408 
0409             str = attribs.value(QStringLiteral("borderCornerRadius")).toString();
0410             if (str.isEmpty())
0411                 reader->raiseMissingAttributeWarning(QStringLiteral("borderCornerRadius"));
0412             else
0413                 d->borderCornerRadius = str.toDouble();
0414         } else { // unknown element
0415             reader->raiseUnknownElementWarning();
0416             if (!reader->skipToEndElement())
0417                 return false;
0418         }
0419     }
0420 
0421     return true;
0422 }
0423 
0424 void PlotArea::loadThemeConfig(const KConfig& config) {
0425     KConfigGroup group;
0426     if (config.hasGroup(QStringLiteral("Theme")))
0427         group = config.group(QStringLiteral("CartesianPlot"));
0428     else
0429         group = config.group(QStringLiteral("PlotArea"));
0430 
0431     // background
0432     background()->loadThemeConfig(group);
0433 
0434     // border
0435     Q_D(PlotArea);
0436     d->borderLine->loadThemeConfig(group);
0437     this->setBorderCornerRadius(group.readEntry(QStringLiteral("BorderCornerRadius"), 0.0));
0438 }
0439 
0440 void PlotArea::saveThemeConfig(const KConfig& config) {
0441     KConfigGroup group = config.group(QStringLiteral("CartesianPlot"));
0442 
0443     // background
0444     background()->saveThemeConfig(group);
0445 
0446     // border
0447     Q_D(PlotArea);
0448     d->borderLine->saveThemeConfig(group);
0449     group.writeEntry(QStringLiteral("BorderCornerRadius"), this->borderCornerRadius());
0450 }