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

0001 /*
0002     File                 : ResizeItem.cpp
0003     Project              : LabPlot
0004     Description          : Item allowing to resize worksheet elements with the mouse
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2021 Alexander Semke <alexander.semke@web.de>
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "ResizeItem.h"
0011 #include "backend/worksheet/WorksheetElementContainer.h"
0012 #include <QBrush>
0013 #include <QCursor>
0014 
0015 ResizeItem::HandleItem::HandleItem(int position, ResizeItem* parent)
0016     : QGraphicsRectItem(-10, -10, 20, 20, parent)
0017     , m_position(position)
0018     , m_parent(parent) {
0019     setBrush(QBrush(Qt::red));
0020     setFlag(ItemIsMovable);
0021     setFlag(ItemSendsGeometryChanges);
0022     setAcceptHoverEvents(true);
0023 }
0024 
0025 int ResizeItem::HandleItem::position() const {
0026     return m_position;
0027 }
0028 
0029 QVariant ResizeItem::HandleItem::itemChange(GraphicsItemChange change, const QVariant& value) {
0030     QVariant newValue = value;
0031 
0032     if (change == ItemPositionChange)
0033         newValue = restrictPosition(value.toPointF());
0034     else if (change == ItemPositionHasChanged) {
0035         QPointF pos = value.toPointF();
0036 
0037         switch (m_position) {
0038         case TopLeft:
0039             m_parent->setTopLeft(pos);
0040             break;
0041         case Top:
0042             m_parent->setTop(pos.y());
0043             break;
0044         case TopRight:
0045             m_parent->setTopRight(pos);
0046             break;
0047         case Right:
0048             m_parent->setRight(pos.x());
0049             break;
0050         case BottomRight:
0051             m_parent->setBottomRight(pos);
0052             break;
0053         case Bottom:
0054             m_parent->setBottom(pos.y());
0055             break;
0056         case BottomLeft:
0057             m_parent->setBottomLeft(pos);
0058             break;
0059         case Left:
0060             m_parent->setLeft(pos.x());
0061             break;
0062         }
0063     }
0064 
0065     return newValue;
0066 }
0067 
0068 void ResizeItem::HandleItem::mousePressEvent(QGraphicsSceneMouseEvent*) {
0069     m_parent->container()->setUndoAware(false);
0070     m_oldRect = m_parent->container()->rect();
0071 }
0072 
0073 void ResizeItem::HandleItem::mouseReleaseEvent(QGraphicsSceneMouseEvent*) {
0074     m_parent->container()->setUndoAware(true);
0075 
0076     // the container has already the current rect set, we just need
0077     // to pass the previous rect so the undo-step can be done properly
0078     m_parent->container()->setPrevRect(m_oldRect);
0079 }
0080 
0081 void ResizeItem::HandleItem::hoverEnterEvent(QGraphicsSceneHoverEvent* event) {
0082     // HACK: make the parent container/plot non-movable otherwise
0083     // the move event doesn't reach HandleItem. Better solution?
0084     m_parent->container()->graphicsItem()->setFlag(ItemIsMovable, false);
0085 
0086     switch (m_position) {
0087     case TopLeft:
0088         setCursor(Qt::SizeFDiagCursor);
0089         break;
0090     case Top:
0091         setCursor(Qt::SizeVerCursor);
0092         break;
0093     case TopRight:
0094         setCursor(Qt::SizeBDiagCursor);
0095         break;
0096     case Right:
0097         setCursor(Qt::SizeHorCursor);
0098         break;
0099     case BottomRight:
0100         setCursor(Qt::SizeFDiagCursor);
0101         break;
0102     case Bottom:
0103         setCursor(Qt::SizeVerCursor);
0104         break;
0105     case BottomLeft:
0106         setCursor(Qt::SizeBDiagCursor);
0107         break;
0108     case Left:
0109         setCursor(Qt::SizeHorCursor);
0110         break;
0111     }
0112     QGraphicsItem::hoverLeaveEvent(event);
0113 }
0114 
0115 void ResizeItem::HandleItem::hoverLeaveEvent(QGraphicsSceneHoverEvent* event) {
0116     m_parent->container()->graphicsItem()->setFlag(ItemIsMovable, true);
0117     QGraphicsItem::hoverLeaveEvent(event);
0118 }
0119 
0120 QPointF ResizeItem::HandleItem::restrictPosition(const QPointF& pos) {
0121     QPointF newPos = this->pos();
0122 
0123     if (m_position & Top || m_position & Bottom)
0124         newPos.setY(pos.y());
0125 
0126     if (m_position & Left || m_position & Right)
0127         newPos.setX(pos.x());
0128 
0129     if (m_position & Top && newPos.y() > m_parent->m_rect.bottom())
0130         newPos.setY(m_parent->m_rect.bottom());
0131     else if (m_position & Bottom && newPos.y() < m_parent->m_rect.top())
0132         newPos.setY(m_parent->m_rect.top());
0133 
0134     if (m_position & Left && newPos.x() > m_parent->m_rect.right())
0135         newPos.setX(m_parent->m_rect.right());
0136     else if (m_position & Right && newPos.x() < m_parent->m_rect.left())
0137         newPos.setX(m_parent->m_rect.left());
0138 
0139     return newPos;
0140 }
0141 
0142 ResizeItem::ResizeItem(WorksheetElementContainer* container)
0143     : QGraphicsItem(container->graphicsItem())
0144     , m_container(container) {
0145     m_handleItems.append(new HandleItem(TopLeft, this));
0146     m_handleItems.append(new HandleItem(Top, this));
0147     m_handleItems.append(new HandleItem(TopRight, this));
0148     m_handleItems.append(new HandleItem(Right, this));
0149     m_handleItems.append(new HandleItem(BottomRight, this));
0150     m_handleItems.append(new HandleItem(Bottom, this));
0151     m_handleItems.append(new HandleItem(BottomLeft, this));
0152     m_handleItems.append(new HandleItem(Left, this));
0153 }
0154 
0155 ResizeItem::~ResizeItem() = default;
0156 
0157 void ResizeItem::setRect(QRectF rect) {
0158     prepareGeometryChange();
0159     m_rect = mapRectFromScene(rect);
0160     updateHandleItemPositions();
0161 }
0162 
0163 QRectF ResizeItem::boundingRect() const {
0164     return m_rect;
0165 }
0166 
0167 WorksheetElementContainer* ResizeItem::container() {
0168     return m_container;
0169 }
0170 
0171 void ResizeItem::paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*) {
0172 }
0173 
0174 #define IMPL_SET_FN(TYPE, POS)                                                                                                                                 \
0175     void ResizeItem::set##POS(TYPE v) {                                                                                                                        \
0176         m_rect.set##POS(v);                                                                                                                                    \
0177         m_container->setRect(mapRectToScene(m_rect));                                                                                                          \
0178     }
0179 
0180 IMPL_SET_FN(qreal, Top)
0181 IMPL_SET_FN(qreal, Right)
0182 IMPL_SET_FN(qreal, Bottom)
0183 IMPL_SET_FN(qreal, Left)
0184 IMPL_SET_FN(const QPointF&, TopLeft)
0185 IMPL_SET_FN(const QPointF&, TopRight)
0186 IMPL_SET_FN(const QPointF&, BottomRight)
0187 IMPL_SET_FN(const QPointF&, BottomLeft)
0188 
0189 void ResizeItem::updateHandleItemPositions() {
0190     for (auto* item : m_handleItems) {
0191         item->setFlag(ItemSendsGeometryChanges, false);
0192 
0193         switch (item->position()) {
0194         case TopLeft:
0195             item->setPos(m_rect.topLeft());
0196             break;
0197         case Top:
0198             item->setPos(m_rect.left() + m_rect.width() / 2 - 1, m_rect.top());
0199             break;
0200         case TopRight:
0201             item->setPos(m_rect.topRight());
0202             break;
0203         case Right:
0204             item->setPos(m_rect.right(), m_rect.top() + m_rect.height() / 2 - 1);
0205             break;
0206         case BottomRight:
0207             item->setPos(m_rect.bottomRight());
0208             break;
0209         case Bottom:
0210             item->setPos(m_rect.left() + m_rect.width() / 2 - 1, m_rect.bottom());
0211             break;
0212         case BottomLeft:
0213             item->setPos(m_rect.bottomLeft());
0214             break;
0215         case Left:
0216             item->setPos(m_rect.left(), m_rect.top() + m_rect.height() / 2 - 1);
0217             break;
0218         }
0219 
0220         item->setFlag(ItemSendsGeometryChanges, true);
0221     }
0222 }