File indexing completed on 2025-02-23 04:09:01

0001 /*
0002  *  SPDX-FileCopyrightText: 2013 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "kis_infinity_manager.h"
0008 
0009 #include <QPainter>
0010 
0011 #include <klocalizedstring.h>
0012 
0013 #include <KoCanvasController.h>
0014 
0015 #include <kis_debug.h>
0016 #include <KisViewManager.h>
0017 #include <kis_canvas2.h>
0018 #include <input/kis_input_manager.h>
0019 #include <kis_config.h>
0020 #include <KisDocument.h>
0021 #include <kis_image.h>
0022 #include <kis_canvas_controller.h>
0023 #include <KisView.h>
0024 #include <kis_algebra_2d.h>
0025 
0026 KisInfinityManager::KisInfinityManager(QPointer<KisView>view, KisCanvas2 *canvas)
0027   : KisCanvasDecoration(INFINITY_DECORATION_ID, view),
0028     m_filteringEnabled(false),
0029     m_cursorSwitched(false),
0030     m_sideRects(NSides),
0031     m_canvas(canvas)
0032 {
0033     connect(canvas, SIGNAL(documentOffsetUpdateFinished()), SLOT(imagePositionChanged()));
0034 }
0035 
0036 inline void KisInfinityManager::addDecoration(const QRect &areaRect, const QPointF &handlePoint, qreal angle, Side side)
0037 {
0038     QTransform t;
0039     t.rotate(angle);
0040     t = t * QTransform::fromTranslate(handlePoint.x(), handlePoint.y());
0041     m_handleTransform << t;
0042 
0043     m_decorationPath.addRect(areaRect);
0044     m_sideRects[side] = areaRect;
0045 }
0046 
0047 void KisInfinityManager::imagePositionChanged()
0048 {
0049     const QRect imageRect = m_canvas->coordinatesConverter()->imageRectInWidgetPixels().toAlignedRect();
0050     const QRect widgetRect = m_canvas->canvasWidget()->rect();
0051 
0052     KisConfig cfg(true);
0053     qreal vastScrolling = cfg.vastScrolling();
0054 
0055     int xReserve = vastScrolling * widgetRect.width();
0056     int yReserve = vastScrolling * widgetRect.height();
0057 
0058     int xThreshold = imageRect.width() - 0.4 * xReserve;
0059     int yThreshold = imageRect.height() - 0.4 * yReserve;
0060 
0061     const int stripeWidth = 48;
0062 
0063     int xCut = widgetRect.width() - stripeWidth;
0064     int yCut = widgetRect.height() - stripeWidth;
0065 
0066     m_decorationPath = QPainterPath();
0067     m_decorationPath.setFillRule(Qt::WindingFill);
0068 
0069     m_handleTransform.clear();
0070 
0071     m_sideRects.clear();
0072     m_sideRects.resize(NSides);
0073 
0074     bool visible = false;
0075 
0076     if (imageRect.x() <= -xThreshold) {
0077         QRect areaRect(widgetRect.adjusted(xCut, 0, 0, 0));
0078         QPointF pt = areaRect.center() + QPointF(-0.1 * stripeWidth, 0);
0079         addDecoration(areaRect, pt, 0, Right);
0080         visible = true;
0081     }
0082 
0083     if (imageRect.y() <= -yThreshold) {
0084         QRect areaRect(widgetRect.adjusted(0, yCut, 0, 0));
0085         QPointF pt = areaRect.center() + QPointF(0, -0.1 * stripeWidth);
0086         addDecoration(areaRect, pt, 90, Bottom);
0087         visible = true;
0088     }
0089 
0090     if (imageRect.right() > widgetRect.width() + xThreshold) {
0091         QRect areaRect(widgetRect.adjusted(0, 0, -xCut, 0));
0092         QPointF pt = areaRect.center() + QPointF(0.1 * stripeWidth, 0);
0093         addDecoration(areaRect, pt, 180, Left);
0094         visible = true;
0095     }
0096 
0097     if (imageRect.bottom() > widgetRect.height() + yThreshold) {
0098         QRect areaRect(widgetRect.adjusted(0, 0, 0, -yCut));
0099         QPointF pt = areaRect.center() + QPointF(0, 0.1 * stripeWidth);
0100         addDecoration(areaRect, pt, 270, Top);
0101         visible = true;
0102     }
0103 
0104     if (!m_filteringEnabled && visible && this->visible()) {
0105         KisInputManager *inputManager = m_canvas->globalInputManager();
0106         if (inputManager) {
0107             inputManager->attachPriorityEventFilter(this);
0108         }
0109 
0110         m_filteringEnabled = true;
0111     }
0112 
0113     if (m_filteringEnabled && (!visible || !this->visible())) {
0114         KisInputManager *inputManager = m_canvas->globalInputManager();
0115         if (inputManager) {
0116             inputManager->detachPriorityEventFilter(this);
0117         }
0118 
0119         m_filteringEnabled = false;
0120     }
0121 }
0122 
0123 void KisInfinityManager::drawDecoration(QPainter& gc, const QRectF& updateArea, const KisCoordinatesConverter *converter, KisCanvas2 *canvas)
0124 {
0125     Q_UNUSED(updateArea);
0126     Q_UNUSED(converter);
0127     Q_UNUSED(canvas);
0128 
0129     if (!m_filteringEnabled) return;
0130 
0131     gc.save();
0132     gc.setTransform(QTransform(), false);
0133 
0134     KisConfig cfg(true);
0135     QColor color = cfg.canvasBorderColor();
0136     gc.fillPath(m_decorationPath, color.darker(115));
0137 
0138     QPainterPath p = KisAlgebra2D::smallArrow();
0139 
0140     Q_FOREACH (const QTransform &t, m_handleTransform) {
0141         gc.fillPath(t.map(p), color);
0142     }
0143 
0144     gc.restore();
0145 }
0146 
0147 inline int expandLeft(int x0, int x1, int maxExpand)
0148 {
0149     return qMax(x0 - maxExpand, qMin(x0, x1));
0150 }
0151 
0152 inline int expandRight(int x0, int x1, int maxExpand)
0153 {
0154     return qMin(x0 + maxExpand, qMax(x0, x1));
0155 }
0156 
0157 inline QPoint getPointFromEvent(QEvent *event)
0158 {
0159     QPoint result;
0160 
0161     if (event->type() == QEvent::MouseMove ||
0162         event->type() == QEvent::MouseButtonPress ||
0163         event->type() == QEvent::MouseButtonRelease ||
0164         event->type() == QEvent::Enter) {
0165 
0166         QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
0167         result = mouseEvent->pos();
0168 
0169     } else if (event->type() == QEvent::TabletMove ||
0170                event->type() == QEvent::TabletPress ||
0171                event->type() == QEvent::TabletRelease) {
0172 
0173         QTabletEvent *tabletEvent = static_cast<QTabletEvent*>(event);
0174         result = tabletEvent->pos();
0175     }
0176 
0177     return result;
0178 }
0179 
0180 inline Qt::MouseButton getButtonFromEvent(QEvent *event)
0181 {
0182     Qt::MouseButton button = Qt::NoButton;
0183 
0184     if (event->type() == QEvent::MouseMove ||
0185         event->type() == QEvent::MouseButtonPress ||
0186         event->type() == QEvent::MouseButtonRelease) {
0187 
0188         QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
0189         button = mouseEvent->button();
0190 
0191     } else if (event->type() == QEvent::TabletMove ||
0192                event->type() == QEvent::TabletPress ||
0193                event->type() == QEvent::TabletRelease) {
0194 
0195         QTabletEvent *tabletEvent = static_cast<QTabletEvent*>(event);
0196         button = tabletEvent->button();
0197     }
0198 
0199     return button;
0200 }
0201 
0202 bool KisInfinityManager::eventFilter(QObject *obj, QEvent *event)
0203 {
0204     /**
0205      * We connect our event filter to the global InputManager which is
0206      * shared among all the canvases. Ideally we should disconnect our
0207      * event filter whin this canvas is not active, but for now we can
0208      * just check the destination of the event, if it is correct.
0209      */
0210     if (m_canvas == NULL || obj != m_canvas->canvasWidget()) return false;
0211 
0212     KIS_ASSERT_RECOVER_NOOP(m_filteringEnabled);
0213 
0214     bool retval = false;
0215 
0216     switch (event->type()) {
0217     case QEvent::Enter:
0218     case QEvent::MouseMove:
0219     case QEvent::TabletMove: {
0220         QPoint pos = getPointFromEvent(event);
0221 
0222         if (m_decorationPath.contains(pos)) {
0223             if (!m_cursorSwitched) {
0224                 m_oldCursor = m_canvas->canvasWidget()->cursor();
0225                 m_cursorSwitched = true;
0226             }
0227             m_canvas->canvasWidget()->setCursor(Qt::PointingHandCursor);
0228             retval = true;
0229         } else if (m_cursorSwitched) {
0230             m_canvas->canvasWidget()->setCursor(m_oldCursor);
0231             m_cursorSwitched = false;
0232         }
0233         break;
0234     }
0235     case QEvent::Leave: {
0236         if (m_cursorSwitched) {
0237             m_canvas->canvasWidget()->setCursor(m_oldCursor);
0238             m_cursorSwitched = false;
0239         }
0240         break;
0241     }
0242     case QEvent::MouseButtonPress:
0243     case QEvent::TabletPress: {
0244         Qt::MouseButton button = getButtonFromEvent(event);
0245         retval = button == Qt::LeftButton && m_cursorSwitched;
0246 
0247         if (button == Qt::RightButton) {
0248             imagePositionChanged();
0249         }
0250 
0251         break;
0252     }
0253     case QEvent::MouseButtonRelease:
0254     case QEvent::TabletRelease: {
0255         Qt::MouseButton button = getButtonFromEvent(event);
0256         retval = button == Qt::LeftButton && m_cursorSwitched;
0257 
0258         if (retval) {
0259             QPoint pos = getPointFromEvent(event);
0260 
0261             const KisCoordinatesConverter *converter = m_canvas->coordinatesConverter();
0262             QRect widgetRect = converter->widgetToImage(m_canvas->canvasWidget()->rect()).toAlignedRect();
0263             KisImageWSP image = view()->image();
0264 
0265             QRect cropRect = image->bounds();
0266 
0267             const int hLimit = cropRect.width();
0268             const int vLimit = cropRect.height();
0269 
0270             if (m_sideRects[Right].contains(pos)) {
0271                 cropRect.setRight(expandRight(cropRect.right(), widgetRect.right(), hLimit));
0272             }
0273             if (m_sideRects[Bottom].contains(pos)) {
0274                 cropRect.setBottom(expandRight(cropRect.bottom(), widgetRect.bottom(), vLimit));
0275             }
0276             if (m_sideRects[Left].contains(pos)) {
0277                 cropRect.setLeft(expandLeft(cropRect.left(), widgetRect.left(), hLimit));
0278             }
0279             if (m_sideRects[Top].contains(pos)) {
0280                 cropRect.setTop(expandLeft(cropRect.top(), widgetRect.top(), vLimit));
0281             }
0282 
0283             image->resizeImage(cropRect);
0284 
0285             // since resizing the image can cause the cursor to end up on the canvas without a move event,
0286             // it can get stuck in an overridden state until it is changed by another event,
0287             // and we don't want that.
0288             if (m_cursorSwitched) {
0289                 m_canvas->canvasWidget()->setCursor(m_oldCursor);
0290                 m_cursorSwitched = false;
0291             }
0292         }
0293         break;
0294     }
0295     default:
0296         break;
0297     }
0298 
0299     return !retval ? KisCanvasDecoration::eventFilter(obj, event) : true;
0300 }