1 /*
2  *  Copyright (c) 2013 Dmitry Kazakov <dimula73@gmail.com>
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  */
18 
19 #include "kis_infinity_manager.h"
20 
21 #include <QPainter>
22 
23 #include <klocalizedstring.h>
24 
25 #include <KoCanvasController.h>
26 
27 #include <kis_debug.h>
28 #include <KisViewManager.h>
29 #include <kis_canvas2.h>
30 #include <input/kis_input_manager.h>
31 #include <kis_config.h>
32 #include <KisDocument.h>
33 #include <kis_image.h>
34 #include <kis_canvas_controller.h>
35 #include <KisView.h>
36 #include <kis_algebra_2d.h>
37 
KisInfinityManager(QPointer<KisView> view,KisCanvas2 * canvas)38 KisInfinityManager::KisInfinityManager(QPointer<KisView>view, KisCanvas2 *canvas)
39   : KisCanvasDecoration(INFINITY_DECORATION_ID, view),
40     m_filteringEnabled(false),
41     m_cursorSwitched(false),
42     m_sideRects(NSides),
43     m_canvas(canvas)
44 {
45     connect(canvas, SIGNAL(documentOffsetUpdateFinished()), SLOT(imagePositionChanged()));
46 }
47 
addDecoration(const QRect & areaRect,const QPointF & handlePoint,qreal angle,Side side)48 inline void KisInfinityManager::addDecoration(const QRect &areaRect, const QPointF &handlePoint, qreal angle, Side side)
49 {
50     QTransform t;
51     t.rotate(angle);
52     t = t * QTransform::fromTranslate(handlePoint.x(), handlePoint.y());
53     m_handleTransform << t;
54 
55     m_decorationPath.addRect(areaRect);
56     m_sideRects[side] = areaRect;
57 }
58 
imagePositionChanged()59 void KisInfinityManager::imagePositionChanged()
60 {
61     const QRect imageRect = m_canvas->coordinatesConverter()->imageRectInWidgetPixels().toAlignedRect();
62     const QRect widgetRect = m_canvas->canvasWidget()->rect();
63 
64     KisConfig cfg(true);
65     qreal vastScrolling = cfg.vastScrolling();
66 
67     int xReserve = vastScrolling * widgetRect.width();
68     int yReserve = vastScrolling * widgetRect.height();
69 
70     int xThreshold = imageRect.width() - 0.4 * xReserve;
71     int yThreshold = imageRect.height() - 0.4 * yReserve;
72 
73     const int stripeWidth = 48;
74 
75     int xCut = widgetRect.width() - stripeWidth;
76     int yCut = widgetRect.height() - stripeWidth;
77 
78     m_decorationPath = QPainterPath();
79     m_decorationPath.setFillRule(Qt::WindingFill);
80 
81     m_handleTransform.clear();
82 
83     m_sideRects.clear();
84     m_sideRects.resize(NSides);
85 
86     bool visible = false;
87 
88     if (imageRect.x() <= -xThreshold) {
89         QRect areaRect(widgetRect.adjusted(xCut, 0, 0, 0));
90         QPointF pt = areaRect.center() + QPointF(-0.1 * stripeWidth, 0);
91         addDecoration(areaRect, pt, 0, Right);
92         visible = true;
93     }
94 
95     if (imageRect.y() <= -yThreshold) {
96         QRect areaRect(widgetRect.adjusted(0, yCut, 0, 0));
97         QPointF pt = areaRect.center() + QPointF(0, -0.1 * stripeWidth);
98         addDecoration(areaRect, pt, 90, Bottom);
99         visible = true;
100     }
101 
102     if (imageRect.right() > widgetRect.width() + xThreshold) {
103         QRect areaRect(widgetRect.adjusted(0, 0, -xCut, 0));
104         QPointF pt = areaRect.center() + QPointF(0.1 * stripeWidth, 0);
105         addDecoration(areaRect, pt, 180, Left);
106         visible = true;
107     }
108 
109     if (imageRect.bottom() > widgetRect.height() + yThreshold) {
110         QRect areaRect(widgetRect.adjusted(0, 0, 0, -yCut));
111         QPointF pt = areaRect.center() + QPointF(0, 0.1 * stripeWidth);
112         addDecoration(areaRect, pt, 270, Top);
113         visible = true;
114     }
115 
116     if (!m_filteringEnabled && visible && this->visible()) {
117         KisInputManager *inputManager = m_canvas->globalInputManager();
118         if (inputManager) {
119             inputManager->attachPriorityEventFilter(this);
120         }
121 
122         m_filteringEnabled = true;
123     }
124 
125     if (m_filteringEnabled && (!visible || !this->visible())) {
126         KisInputManager *inputManager = m_canvas->globalInputManager();
127         if (inputManager) {
128             inputManager->detachPriorityEventFilter(this);
129         }
130 
131         m_filteringEnabled = false;
132     }
133 }
134 
drawDecoration(QPainter & gc,const QRectF & updateArea,const KisCoordinatesConverter * converter,KisCanvas2 * canvas)135 void KisInfinityManager::drawDecoration(QPainter& gc, const QRectF& updateArea, const KisCoordinatesConverter *converter, KisCanvas2 *canvas)
136 {
137     Q_UNUSED(updateArea);
138     Q_UNUSED(converter);
139     Q_UNUSED(canvas);
140 
141     if (!m_filteringEnabled) return;
142 
143     gc.save();
144     gc.setTransform(QTransform(), false);
145 
146     KisConfig cfg(true);
147     QColor color = cfg.canvasBorderColor();
148     gc.fillPath(m_decorationPath, color.darker(115));
149 
150     QPainterPath p = KisAlgebra2D::smallArrow();
151 
152     Q_FOREACH (const QTransform &t, m_handleTransform) {
153         gc.fillPath(t.map(p), color);
154     }
155 
156     gc.restore();
157 }
158 
expandLeft(int x0,int x1,int maxExpand)159 inline int expandLeft(int x0, int x1, int maxExpand)
160 {
161     return qMax(x0 - maxExpand, qMin(x0, x1));
162 }
163 
expandRight(int x0,int x1,int maxExpand)164 inline int expandRight(int x0, int x1, int maxExpand)
165 {
166     return qMin(x0 + maxExpand, qMax(x0, x1));
167 }
168 
getPointFromEvent(QEvent * event)169 inline QPoint getPointFromEvent(QEvent *event)
170 {
171     QPoint result;
172 
173     if (event->type() == QEvent::MouseMove ||
174         event->type() == QEvent::MouseButtonPress ||
175         event->type() == QEvent::MouseButtonRelease ||
176         event->type() == QEvent::Enter) {
177 
178         QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
179         result = mouseEvent->pos();
180 
181     } else if (event->type() == QEvent::TabletMove ||
182                event->type() == QEvent::TabletPress ||
183                event->type() == QEvent::TabletRelease) {
184 
185         QTabletEvent *tabletEvent = static_cast<QTabletEvent*>(event);
186         result = tabletEvent->pos();
187     }
188 
189     return result;
190 }
191 
getButtonFromEvent(QEvent * event)192 inline Qt::MouseButton getButtonFromEvent(QEvent *event)
193 {
194     Qt::MouseButton button = Qt::NoButton;
195 
196     if (event->type() == QEvent::MouseMove ||
197         event->type() == QEvent::MouseButtonPress ||
198         event->type() == QEvent::MouseButtonRelease) {
199 
200         QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
201         button = mouseEvent->button();
202 
203     } else if (event->type() == QEvent::TabletMove ||
204                event->type() == QEvent::TabletPress ||
205                event->type() == QEvent::TabletRelease) {
206 
207         QTabletEvent *tabletEvent = static_cast<QTabletEvent*>(event);
208         button = tabletEvent->button();
209     }
210 
211     return button;
212 }
213 
eventFilter(QObject * obj,QEvent * event)214 bool KisInfinityManager::eventFilter(QObject *obj, QEvent *event)
215 {
216     /**
217      * We connect our event filter to the global InputManager which is
218      * shared among all the canvases. Ideally we should disconnect our
219      * event filter whin this canvas is not active, but for now we can
220      * just check the destination of the event, if it is correct.
221      */
222     if (m_canvas == NULL || obj != m_canvas->canvasWidget()) return false;
223 
224     KIS_ASSERT_RECOVER_NOOP(m_filteringEnabled);
225 
226     bool retval = false;
227 
228     switch (event->type()) {
229     case QEvent::Enter:
230     case QEvent::MouseMove:
231     case QEvent::TabletMove: {
232         QPoint pos = getPointFromEvent(event);
233 
234         if (m_decorationPath.contains(pos)) {
235             if (!m_cursorSwitched) {
236                 m_oldCursor = m_canvas->canvasWidget()->cursor();
237                 m_cursorSwitched = true;
238             }
239             m_canvas->canvasWidget()->setCursor(Qt::PointingHandCursor);
240             retval = true;
241         } else if (m_cursorSwitched) {
242             m_canvas->canvasWidget()->setCursor(m_oldCursor);
243             m_cursorSwitched = false;
244         }
245         break;
246     }
247     case QEvent::Leave: {
248         if (m_cursorSwitched) {
249             m_canvas->canvasWidget()->setCursor(m_oldCursor);
250             m_cursorSwitched = false;
251         }
252         break;
253     }
254     case QEvent::MouseButtonPress:
255     case QEvent::TabletPress: {
256         Qt::MouseButton button = getButtonFromEvent(event);
257         retval = button == Qt::LeftButton && m_cursorSwitched;
258 
259         if (button == Qt::RightButton) {
260             imagePositionChanged();
261         }
262 
263         break;
264     }
265     case QEvent::MouseButtonRelease:
266     case QEvent::TabletRelease: {
267         Qt::MouseButton button = getButtonFromEvent(event);
268         retval = button == Qt::LeftButton && m_cursorSwitched;
269 
270         if (retval) {
271             QPoint pos = getPointFromEvent(event);
272 
273             const KisCoordinatesConverter *converter = m_canvas->coordinatesConverter();
274             QRect widgetRect = converter->widgetToImage(m_canvas->canvasWidget()->rect()).toAlignedRect();
275             KisImageWSP image = view()->image();
276 
277             QRect cropRect = image->bounds();
278 
279             const int hLimit = cropRect.width();
280             const int vLimit = cropRect.height();
281 
282             if (m_sideRects[Right].contains(pos)) {
283                 cropRect.setRight(expandRight(cropRect.right(), widgetRect.right(), hLimit));
284             }
285             if (m_sideRects[Bottom].contains(pos)) {
286                 cropRect.setBottom(expandRight(cropRect.bottom(), widgetRect.bottom(), vLimit));
287             }
288             if (m_sideRects[Left].contains(pos)) {
289                 cropRect.setLeft(expandLeft(cropRect.left(), widgetRect.left(), hLimit));
290             }
291             if (m_sideRects[Top].contains(pos)) {
292                 cropRect.setTop(expandLeft(cropRect.top(), widgetRect.top(), vLimit));
293             }
294 
295             image->resizeImage(cropRect);
296 
297             // since resizing the image can cause the cursor to end up on the canvas without a move event,
298             // it can get stuck in an overridden state until it is changed by another event,
299             // and we don't want that.
300             if (m_cursorSwitched) {
301                 m_canvas->canvasWidget()->setCursor(m_oldCursor);
302                 m_cursorSwitched = false;
303             }
304         }
305         break;
306     }
307     default:
308         break;
309     }
310 
311     return !retval ? KisCanvasDecoration::eventFilter(obj, event) : true;
312 }
313