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 }