File indexing completed on 2024-05-12 03:48:24

0001 /*
0002     File                 : WorksheetElementContainer.cpp
0003     Project              : LabPlot
0004     Description          : Worksheet element container - parent of multiple elements
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2009 Tilman Benkert <thzs@gmx.net>
0007     SPDX-FileCopyrightText: 2012-2021 Alexander Semke <alexander.semke@web.de>
0008 
0009     SPDX-License-Identifier: GPL-2.0-or-later
0010 */
0011 
0012 #include "backend/worksheet/WorksheetElementContainer.h"
0013 #include "backend/lib/commandtemplates.h"
0014 #include "backend/lib/macros.h"
0015 #include "backend/lib/trace.h"
0016 #include "backend/worksheet/ResizeItem.h"
0017 #include "backend/worksheet/Worksheet.h"
0018 #include "backend/worksheet/WorksheetElementContainerPrivate.h"
0019 #include "backend/worksheet/plots/cartesian/XYCurve.h"
0020 
0021 #include <QGraphicsScene>
0022 #include <QGraphicsSceneContextMenuEvent>
0023 #include <QMenu>
0024 #include <QPainter>
0025 
0026 #include <KLocalizedString>
0027 
0028 /**
0029  * \class WorksheetElementContainer
0030  * \ingroup worksheet
0031  * \brief Worksheet element container - parent of multiple elements
0032  * This class provides the functionality for a containers of multiple
0033  * worksheet elements. Such a container can be a plot or group of elements.
0034  */
0035 
0036 WorksheetElementContainer::WorksheetElementContainer(const QString& name, AspectType type)
0037     : WorksheetElement(name, new WorksheetElementContainerPrivate(this), type) {
0038     connect(this, &WorksheetElementContainer::childAspectAdded, this, &WorksheetElementContainer::handleAspectAdded);
0039 }
0040 
0041 WorksheetElementContainer::WorksheetElementContainer(const QString& name, WorksheetElementContainerPrivate* dd, AspectType type)
0042     : WorksheetElement(name, dd, type) {
0043     connect(this, &WorksheetElementContainer::childAspectAdded, this, &WorksheetElementContainer::handleAspectAdded);
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 WorksheetElementContainer::~WorksheetElementContainer() = default;
0049 
0050 QRectF WorksheetElementContainer::rect() const {
0051     Q_D(const WorksheetElementContainer);
0052     return d->rect;
0053 }
0054 
0055 STD_SWAP_METHOD_SETTER_CMD_IMPL(WorksheetElementContainer, SetVisible, bool, swapVisible)
0056 void WorksheetElementContainer::setVisible(bool on) {
0057     Q_D(WorksheetElementContainer);
0058 
0059     // take care of proper ordering on the undo-stack,
0060     // when making the container and all its children visible/invisible.
0061     // if visible is set true, change the visibility of the container first
0062     if (on) {
0063         beginMacro(i18n("%1: set visible", name()));
0064         exec(new WorksheetElementContainerSetVisibleCmd(d, on, ki18n("%1: set visible")));
0065     } else
0066         beginMacro(i18n("%1: set invisible", name()));
0067 
0068     // change the visibility of all children
0069     const auto& elements = children<WorksheetElement>(AbstractAspect::ChildIndexFlag::IncludeHidden | AbstractAspect::ChildIndexFlag::Compress);
0070     for (auto* elem : elements) {
0071         auto* curve = dynamic_cast<XYCurve*>(elem);
0072         if (curve) {
0073             // making curves invisible triggers the recalculation of plot ranges if auto-scale is active.
0074             // this should be avoided by supressing the retransformation in the curves.
0075             curve->setSuppressRetransform(true);
0076             elem->setVisible(on);
0077             curve->setSuppressRetransform(false);
0078         } else if (elem)
0079             elem->setVisible(on);
0080     }
0081 
0082     // if visible is set false, change the visibility of the container last
0083     if (!on)
0084         exec(new WorksheetElementContainerSetVisibleCmd(d, false, ki18n("%1: set invisible")));
0085 
0086     endMacro();
0087 }
0088 
0089 bool WorksheetElementContainer::isFullyVisible() const {
0090     const auto& elements = children<WorksheetElement>(AbstractAspect::ChildIndexFlag::IncludeHidden | AbstractAspect::ChildIndexFlag::Compress);
0091     for (const auto* elem : elements) {
0092         if (!elem->isVisible())
0093             return false;
0094     }
0095     return true;
0096 }
0097 
0098 void WorksheetElementContainer::setPrinting(bool on) {
0099     Q_D(WorksheetElementContainer);
0100     d->m_printing = on;
0101 }
0102 
0103 void WorksheetElementContainer::setResizeEnabled(bool enabled) {
0104     if (m_resizeItem)
0105         m_resizeItem->setVisible(enabled);
0106     else {
0107         if (enabled) {
0108             m_resizeItem = new ResizeItem(this);
0109             m_resizeItem->setRect(rect());
0110         }
0111     }
0112 }
0113 
0114 void WorksheetElementContainer::retransform() {
0115     if (isLoading())
0116         return;
0117 
0118     PERFTRACE(QStringLiteral("WorksheetElementContainer::retransform()"));
0119     Q_D(WorksheetElementContainer);
0120 
0121     const auto& elements = children<WorksheetElement>(AbstractAspect::ChildIndexFlag::IncludeHidden | AbstractAspect::ChildIndexFlag::Compress);
0122     for (auto* child : elements)
0123         child->retransform();
0124 
0125     d->recalcShapeAndBoundingRect();
0126 
0127     if (m_resizeItem)
0128         m_resizeItem->setRect(rect());
0129 }
0130 
0131 /*!
0132  * called if the size of the worksheet page was changed and the content has to be adjusted/resized (\c pageResize = true)
0133  * or if a new rectangular for the element container was set (\c pageResize = false).
0134  * In the second case, \c WorksheetElement::handleResize() is called for every worksheet child to adjust the content to the new size.
0135  * In the first case, a new rectangular for the container is calculated and set first, which on the other hand, triggers the content adjustments
0136  * in the container children.
0137  */
0138 void WorksheetElementContainer::handleResize(double horizontalRatio, double verticalRatio, bool pageResize) {
0139     DEBUG(Q_FUNC_INFO);
0140     Q_D(const WorksheetElementContainer);
0141     if (pageResize) {
0142         QRectF rect(d->rect);
0143         rect.setWidth(d->rect.width() * horizontalRatio);
0144         rect.setHeight(d->rect.height() * verticalRatio);
0145         setRect(rect);
0146     } else {
0147         //      for (auto* elem : children<WorksheetElement>(IncludeHidden))
0148         //          elem->handleResize(horizontalRatio, verticalRatio);
0149     }
0150 }
0151 
0152 void WorksheetElementContainer::handleAspectAdded(const AbstractAspect* aspect) {
0153     Q_D(WorksheetElementContainer);
0154 
0155     const auto* element = qobject_cast<const WorksheetElement*>(aspect);
0156     if (element && (aspect->parentAspect() == this)) {
0157         connect(element, &WorksheetElement::hovered, this, &WorksheetElementContainer::childHovered);
0158         connect(element, &WorksheetElement::unhovered, this, &WorksheetElementContainer::childUnhovered);
0159         connect(element, &WorksheetElement::changed, this, &WorksheetElementContainer::changed);
0160         element->graphicsItem()->setParentItem(d);
0161 
0162         qreal zVal = 0;
0163         for (auto* child : children<WorksheetElement>(ChildIndexFlag::IncludeHidden))
0164             child->setZValue(zVal++);
0165     }
0166 
0167     if (!isLoading())
0168         d->recalcShapeAndBoundingRect();
0169 }
0170 
0171 void WorksheetElementContainer::childHovered() {
0172     Q_D(WorksheetElementContainer);
0173     if (!d->isSelected()) {
0174         if (isHovered())
0175             setHover(false);
0176         else
0177             d->update();
0178     }
0179 }
0180 
0181 void WorksheetElementContainer::childUnhovered() {
0182     Q_D(WorksheetElementContainer);
0183     if (!d->isSelected()) {
0184         setHover(true);
0185     }
0186 }
0187 
0188 void WorksheetElementContainer::prepareGeometryChange() {
0189     Q_D(WorksheetElementContainer);
0190     d->prepareGeometryChangeRequested();
0191 }
0192 
0193 // ################################################################
0194 // ################### Private implementation ##########################
0195 // ################################################################
0196 WorksheetElementContainerPrivate::WorksheetElementContainerPrivate(WorksheetElementContainer* owner)
0197     : WorksheetElementPrivate(owner)
0198     , q(owner) {
0199     setAcceptHoverEvents(true);
0200 }
0201 
0202 void WorksheetElementContainerPrivate::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) {
0203     scene()->clearSelection();
0204     setSelected(true);
0205     QMenu* menu = q->createContextMenu();
0206     menu->exec(event->screenPos());
0207 }
0208 
0209 void WorksheetElementContainerPrivate::prepareGeometryChangeRequested() {
0210     prepareGeometryChange(); // this is not const!
0211     recalcShapeAndBoundingRect();
0212 }
0213 
0214 void WorksheetElementContainerPrivate::recalcShapeAndBoundingRect() {
0215     //  if (q->isLoading())
0216     //      return;
0217 
0218     // old logic calculating the bounding box as the box covering all children.
0219     // we might need this logic later once we implement something like selection of multiple plots, etc.
0220     //  boundingRectangle = QRectF();
0221     //  QVector<WorksheetElement*> childList = q->children<WorksheetElement>(AbstractAspect::IncludeHidden | AbstractAspect::Compress);
0222     //  foreach (const WorksheetElement* elem, childList)
0223     //      boundingRectangle |= elem->graphicsItem()->mapRectToParent(elem->graphicsItem()->boundingRect());
0224     //
0225     qreal penWidth = 2.;
0226     m_boundingRectangle = q->rect();
0227     // QDEBUG(Q_FUNC_INFO << ", bound rect = " << boundingRectangle)
0228     m_boundingRectangle = QRectF(-m_boundingRectangle.width() / 2. - penWidth / 2.,
0229                                  -m_boundingRectangle.height() / 2. - penWidth / 2.,
0230                                  m_boundingRectangle.width() + penWidth,
0231                                  m_boundingRectangle.height() + penWidth);
0232 
0233     QPainterPath path;
0234     path.addRect(m_boundingRectangle);
0235 
0236     // make the shape somewhat thicker than the hoveredPen to make the selection/hovering box more visible
0237     m_shape = QPainterPath();
0238     m_shape.addPath(WorksheetElement::shapeFromPath(path, QPen(QBrush(), penWidth)));
0239 }
0240 
0241 // Inherited from QGraphicsItem
0242 QRectF WorksheetElementContainerPrivate::boundingRect() const {
0243     return m_boundingRectangle;
0244 }
0245 
0246 // Inherited from QGraphicsItem
0247 void WorksheetElementContainerPrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget*) {
0248     if (!isVisible())
0249         return;
0250 
0251     if (m_hovered && !isSelected() && !m_printing) {
0252         painter->setPen(QPen(QApplication::palette().color(QPalette::Shadow), 2, Qt::SolidLine));
0253         painter->drawPath(m_shape);
0254     }
0255 
0256     if (isSelected() && !m_printing) {
0257         painter->setPen(QPen(QApplication::palette().color(QPalette::Highlight), 2, Qt::SolidLine));
0258         painter->drawPath(m_shape);
0259     }
0260 }
0261 
0262 void WorksheetElementContainerPrivate::retransform() {
0263 }