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