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