1
2 /*
3 Copyright (c) 2003-2007 Clarence Dang <dang@kde.org>
4 All rights reserved.
5
6 Redistribution and use in source and binary forms, with or without
7 modification, are permitted provided that the following conditions
8 are met:
9
10 1. Redistributions of source code must retain the above copyright
11 notice, this list of conditions and the following disclaimer.
12 2. Redistributions in binary form must reproduce the above copyright
13 notice, this list of conditions and the following disclaimer in the
14 documentation and/or other materials provided with the distribution.
15
16 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28
29 #define DEBUG_KP_VIEW 0
30 #define DEBUG_KP_VIEW_RENDERER ((DEBUG_KP_VIEW && 1) || 0)
31
32
33 #include "views/kpView.h"
34 #include "kpViewPrivate.h"
35
36 #include "layers/selections/kpAbstractSelection.h"
37 #include "layers/selections/text/kpTextSelection.h"
38 #include "tools/kpTool.h"
39
40
41 // public
selectionViewRect() const42 QRect kpView::selectionViewRect () const
43 {
44 return selection () ?
45 transformDocToView (selection ()->boundingRect ()) :
46 QRect ();
47
48 }
49
50
51 // public
mouseViewPointRelativeToSelection(const QPoint & viewPoint) const52 QPoint kpView::mouseViewPointRelativeToSelection (const QPoint &viewPoint) const
53 {
54 if (!selection ()) {
55 return KP_INVALID_POINT;
56 }
57
58 return mouseViewPoint (viewPoint) - transformDocToView (selection ()->topLeft ());
59 }
60
61 // public
mouseOnSelection(const QPoint & viewPoint) const62 bool kpView::mouseOnSelection (const QPoint &viewPoint) const
63 {
64 const QRect selViewRect = selectionViewRect ();
65 if (!selViewRect.isValid ()) {
66 return false;
67 }
68
69 return selViewRect.contains (mouseViewPoint (viewPoint));
70 }
71
72
73 // public
textSelectionMoveBorderAtomicSize() const74 int kpView::textSelectionMoveBorderAtomicSize () const
75 {
76 if (!textSelection ()) {
77 return 0;
78 }
79
80 return qMax (4, zoomLevelX () / 100);
81 }
82
83 // public
mouseOnSelectionToMove(const QPoint & viewPoint) const84 bool kpView::mouseOnSelectionToMove (const QPoint &viewPoint) const
85 {
86 if (!mouseOnSelection (viewPoint)) {
87 return false;
88 }
89
90 if (!textSelection ()) {
91 return true;
92 }
93
94 if (mouseOnSelectionResizeHandle (viewPoint)) {
95 return false;
96 }
97
98
99 const QPoint viewPointRelSel = mouseViewPointRelativeToSelection (viewPoint);
100
101 // Middle point should always be selectable
102 const QPoint selCenterDocPoint = selection ()->boundingRect ().center ();
103 if (tool () &&
104 tool ()->calculateCurrentPoint () == selCenterDocPoint)
105 {
106 return false;
107 }
108
109
110 const int atomicSize = textSelectionMoveBorderAtomicSize ();
111 const QRect selViewRect = selectionViewRect ();
112
113 return (viewPointRelSel.x () < atomicSize ||
114 viewPointRelSel.x () >= selViewRect.width () - atomicSize ||
115 viewPointRelSel.y () < atomicSize ||
116 viewPointRelSel.y () >= selViewRect.height () - atomicSize);
117 }
118
119 //---------------------------------------------------------------------
120
121 // protected
selectionLargeEnoughToHaveResizeHandlesIfAtomicSize(int atomicSize) const122 bool kpView::selectionLargeEnoughToHaveResizeHandlesIfAtomicSize (int atomicSize) const
123 {
124 if (!selection ()) {
125 return false;
126 }
127
128 const QRect selViewRect = selectionViewRect ();
129
130 return (selViewRect.width () >= atomicSize * 5 ||
131 selViewRect.height () >= atomicSize * 5);
132 }
133
134 //---------------------------------------------------------------------
135
136 // public
selectionResizeHandleAtomicSize() const137 int kpView::selectionResizeHandleAtomicSize () const
138 {
139 int atomicSize = qMin (13, qMax (9, zoomLevelX () / 100));
140 while (atomicSize > 0 &&
141 !selectionLargeEnoughToHaveResizeHandlesIfAtomicSize (atomicSize))
142 {
143 atomicSize--;
144 }
145
146 return atomicSize;
147 }
148
149 //---------------------------------------------------------------------
150
151 // public
selectionLargeEnoughToHaveResizeHandles() const152 bool kpView::selectionLargeEnoughToHaveResizeHandles () const
153 {
154 return (selectionResizeHandleAtomicSize () > 0);
155 }
156
157 //---------------------------------------------------------------------
158
159 // public
selectionResizeHandlesViewRegion(bool forRenderer) const160 QRegion kpView::selectionResizeHandlesViewRegion (bool forRenderer) const
161 {
162 const int atomicLength = selectionResizeHandleAtomicSize ();
163 if (atomicLength <= 0) {
164 return {};
165 }
166
167
168 // HACK: At low zoom (e.g. 100%), resize handles will probably be too
169 // big and overlap text / cursor / too much of selection.
170 //
171 // So limit the _visual_ size of handles at low zoom. The
172 // handles' grab area remains the same for usability; so yes,
173 // there are a few pixels that don't look grabable but they are.
174 //
175 // The real solution is to be able to partially render the
176 // handles outside of the selection view rect. If not possible,
177 // at least for text boxes, render text on top of handles.
178 int normalAtomicLength = atomicLength;
179 int vertEdgeAtomicLength = atomicLength;
180 if (forRenderer && selection ())
181 {
182 if (zoomLevelX () <= 150)
183 {
184 if (normalAtomicLength > 1) {
185 normalAtomicLength--;
186 }
187
188 if (vertEdgeAtomicLength > 1) {
189 vertEdgeAtomicLength--;
190 }
191 }
192
193 // 1 line of text?
194 if (textSelection () && textSelection ()->textLines ().size () == 1)
195 {
196 if (zoomLevelX () <= 150) {
197 vertEdgeAtomicLength = qMin (vertEdgeAtomicLength, qMax (2, zoomLevelX () / 100));
198 }
199 else if (zoomLevelX () <= 250) {
200 vertEdgeAtomicLength = qMin (vertEdgeAtomicLength, qMax (3, zoomLevelX () / 100));
201 }
202 }
203 }
204
205
206 const QRect selViewRect = selectionViewRect ();
207 QRegion ret;
208
209 // top left
210 ret += QRect(0, 0, normalAtomicLength, normalAtomicLength);
211
212 // top middle
213 ret += QRect((selViewRect.width() - normalAtomicLength) / 2, 0,
214 normalAtomicLength, normalAtomicLength);
215
216 // top right
217 ret += QRect(selViewRect.width() - normalAtomicLength - 1, 0,
218 normalAtomicLength, normalAtomicLength);
219
220 // left middle
221 ret += QRect(0, (selViewRect.height() - vertEdgeAtomicLength) / 2,
222 vertEdgeAtomicLength, vertEdgeAtomicLength);
223
224 // right middle
225 ret += QRect(selViewRect.width() - vertEdgeAtomicLength - 1, (selViewRect.height() - vertEdgeAtomicLength) / 2,
226 vertEdgeAtomicLength, vertEdgeAtomicLength);
227
228 // bottom left
229 ret += QRect(0, selViewRect.height() - normalAtomicLength - 1,
230 normalAtomicLength, normalAtomicLength);
231
232 // bottom middle
233 ret += QRect((selViewRect.width() - normalAtomicLength) / 2, selViewRect.height() - normalAtomicLength - 1,
234 normalAtomicLength, normalAtomicLength);
235
236 // bottom right
237 ret += QRect(selViewRect.width() - normalAtomicLength - 1, selViewRect.height() - normalAtomicLength - 1,
238 normalAtomicLength, normalAtomicLength);
239
240 ret.translate (selViewRect.x (), selViewRect.y ());
241 ret = ret.intersected (selViewRect);
242
243 return ret;
244 }
245
246 //---------------------------------------------------------------------
247
248 // public
249 // REFACTOR: use QFlags as the return type for better type safety.
mouseOnSelectionResizeHandle(const QPoint & viewPoint) const250 int kpView::mouseOnSelectionResizeHandle (const QPoint &viewPoint) const
251 {
252 #if DEBUG_KP_VIEW
253 qCDebug(kpLogViews) << "kpView::mouseOnSelectionResizeHandle(viewPoint="
254 << viewPoint << ")" << endl;
255 #endif
256
257 if (!mouseOnSelection (viewPoint))
258 {
259 #if DEBUG_KP_VIEW
260 qCDebug(kpLogViews) << "\tmouse not on sel";
261 #endif
262 return 0;
263 }
264
265
266 const QRect selViewRect = selectionViewRect ();
267 #if DEBUG_KP_VIEW
268 qCDebug(kpLogViews) << "\tselViewRect=" << selViewRect;
269 #endif
270
271
272 const int atomicLength = selectionResizeHandleAtomicSize ();
273 #if DEBUG_KP_VIEW
274 qCDebug(kpLogViews) << "\tatomicLength=" << atomicLength;
275 #endif
276
277 if (atomicLength <= 0)
278 {
279 #if DEBUG_KP_VIEW
280 qCDebug(kpLogViews) << "\tsel not large enough to have resize handles";
281 #endif
282 // Want to make it possible to move a small selection
283 return 0;
284 }
285
286
287 const QPoint viewPointRelSel = mouseViewPointRelativeToSelection (viewPoint);
288 #if DEBUG_KP_VIEW
289 qCDebug(kpLogViews) << "\tviewPointRelSel=" << viewPointRelSel;
290 #endif
291
292
293 #define LOCAL_POINT_IN_BOX_AT(x,y) \
294 QRect ((x), (y), atomicLength, atomicLength).contains (viewPointRelSel)
295
296 // Favour the bottom & right and the corners.
297 if (LOCAL_POINT_IN_BOX_AT (selViewRect.width () - atomicLength,
298 selViewRect.height () - atomicLength))
299 {
300 return kpView::Bottom | kpView::Right;
301 }
302
303 if (LOCAL_POINT_IN_BOX_AT (selViewRect.width () - atomicLength, 0))
304 {
305 return kpView::Top | kpView::Right;
306 }
307
308 if (LOCAL_POINT_IN_BOX_AT (0, selViewRect.height () - atomicLength))
309 {
310 return kpView::Bottom | kpView::Left;
311 }
312
313 if (LOCAL_POINT_IN_BOX_AT (0, 0))
314 {
315 return kpView::Top | kpView::Left;
316 }
317
318 if (LOCAL_POINT_IN_BOX_AT (selViewRect.width () - atomicLength,
319 (selViewRect.height () - atomicLength) / 2))
320 {
321 return kpView::Right;
322 }
323
324 if (LOCAL_POINT_IN_BOX_AT ((selViewRect.width () - atomicLength) / 2,
325 selViewRect.height () - atomicLength))
326 {
327 return kpView::Bottom;
328 }
329
330 if (LOCAL_POINT_IN_BOX_AT ((selViewRect.width () - atomicLength) / 2, 0))
331 {
332 return kpView::Top;
333 }
334
335 if (LOCAL_POINT_IN_BOX_AT (0, (selViewRect.height () - atomicLength) / 2))
336 {
337 return kpView::Left;
338 }
339 else
340 {
341 #if DEBUG_KP_VIEW
342 qCDebug(kpLogViews) << "\tnot on sel resize handle";
343 #endif
344 return 0;
345 }
346 #undef LOCAL_POINT_IN_BOX_AT
347 }
348
349 // public
mouseOnSelectionToSelectText(const QPoint & viewPoint) const350 bool kpView::mouseOnSelectionToSelectText (const QPoint &viewPoint) const
351 {
352 #if DEBUG_KP_VIEW
353 qCDebug(kpLogViews) << "kpView::mouseOnSelectionToSelectText(viewPoint="
354 << viewPoint << ")" << endl;
355 #endif
356
357 if (!mouseOnSelection (viewPoint))
358 {
359 #if DEBUG_KP_VIEW
360 qCDebug(kpLogViews) << "\tmouse non on sel";
361 #endif
362 return false;
363 }
364
365 if (!textSelection ())
366 {
367 #if DEBUG_KP_VIEW
368 qCDebug(kpLogViews) << "\tsel not text";
369 #endif
370 return false;
371 }
372
373 #if DEBUG_KP_VIEW
374 qCDebug(kpLogViews) << "\tmouse on sel: to move=" << mouseOnSelectionToMove ()
375 << " to resize=" << mouseOnSelectionResizeHandle ()
376 << endl;
377 #endif
378
379 return (!mouseOnSelectionToMove (viewPoint) &&
380 !mouseOnSelectionResizeHandle (viewPoint));
381 }
382