File indexing completed on 2024-05-05 04:21:20
0001 0002 /* 0003 Copyright (c) 2003-2007 Clarence Dang <dang@kde.org> 0004 All rights reserved. 0005 0006 Redistribution and use in source and binary forms, with or without 0007 modification, are permitted provided that the following conditions 0008 are met: 0009 0010 1. Redistributions of source code must retain the above copyright 0011 notice, this list of conditions and the following disclaimer. 0012 2. Redistributions in binary form must reproduce the above copyright 0013 notice, this list of conditions and the following disclaimer in the 0014 documentation and/or other materials provided with the distribution. 0015 0016 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 0017 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 0018 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 0019 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 0020 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 0021 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 0022 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 0023 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 0024 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 0025 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 0026 */ 0027 0028 0029 #define DEBUG_KP_VIEW 0 0030 #define DEBUG_KP_VIEW_RENDERER ((DEBUG_KP_VIEW && 1) || 0) 0031 0032 0033 #include "views/kpView.h" 0034 #include "kpViewPrivate.h" 0035 0036 #include "layers/selections/kpAbstractSelection.h" 0037 #include "layers/selections/text/kpTextSelection.h" 0038 #include "tools/kpTool.h" 0039 0040 0041 // public 0042 QRect kpView::selectionViewRect () const 0043 { 0044 return selection () ? 0045 transformDocToView (selection ()->boundingRect ()) : 0046 QRect (); 0047 0048 } 0049 0050 0051 // public 0052 QPoint kpView::mouseViewPointRelativeToSelection (const QPoint &viewPoint) const 0053 { 0054 if (!selection ()) { 0055 return KP_INVALID_POINT; 0056 } 0057 0058 return mouseViewPoint (viewPoint) - transformDocToView (selection ()->topLeft ()); 0059 } 0060 0061 // public 0062 bool kpView::mouseOnSelection (const QPoint &viewPoint) const 0063 { 0064 const QRect selViewRect = selectionViewRect (); 0065 if (!selViewRect.isValid ()) { 0066 return false; 0067 } 0068 0069 return selViewRect.contains (mouseViewPoint (viewPoint)); 0070 } 0071 0072 0073 // public 0074 int kpView::textSelectionMoveBorderAtomicSize () const 0075 { 0076 if (!textSelection ()) { 0077 return 0; 0078 } 0079 0080 return qMax (4, zoomLevelX () / 100); 0081 } 0082 0083 // public 0084 bool kpView::mouseOnSelectionToMove (const QPoint &viewPoint) const 0085 { 0086 if (!mouseOnSelection (viewPoint)) { 0087 return false; 0088 } 0089 0090 if (!textSelection ()) { 0091 return true; 0092 } 0093 0094 if (mouseOnSelectionResizeHandle (viewPoint)) { 0095 return false; 0096 } 0097 0098 0099 const QPoint viewPointRelSel = mouseViewPointRelativeToSelection (viewPoint); 0100 0101 // Middle point should always be selectable 0102 const QPoint selCenterDocPoint = selection ()->boundingRect ().center (); 0103 if (tool () && 0104 tool ()->calculateCurrentPoint () == selCenterDocPoint) 0105 { 0106 return false; 0107 } 0108 0109 0110 const int atomicSize = textSelectionMoveBorderAtomicSize (); 0111 const QRect selViewRect = selectionViewRect (); 0112 0113 return (viewPointRelSel.x () < atomicSize || 0114 viewPointRelSel.x () >= selViewRect.width () - atomicSize || 0115 viewPointRelSel.y () < atomicSize || 0116 viewPointRelSel.y () >= selViewRect.height () - atomicSize); 0117 } 0118 0119 //--------------------------------------------------------------------- 0120 0121 // protected 0122 bool kpView::selectionLargeEnoughToHaveResizeHandlesIfAtomicSize (int atomicSize) const 0123 { 0124 if (!selection ()) { 0125 return false; 0126 } 0127 0128 const QRect selViewRect = selectionViewRect (); 0129 0130 return (selViewRect.width () >= atomicSize * 5 || 0131 selViewRect.height () >= atomicSize * 5); 0132 } 0133 0134 //--------------------------------------------------------------------- 0135 0136 // public 0137 int kpView::selectionResizeHandleAtomicSize () const 0138 { 0139 int atomicSize = qMin (13, qMax (9, zoomLevelX () / 100)); 0140 while (atomicSize > 0 && 0141 !selectionLargeEnoughToHaveResizeHandlesIfAtomicSize (atomicSize)) 0142 { 0143 atomicSize--; 0144 } 0145 0146 return atomicSize; 0147 } 0148 0149 //--------------------------------------------------------------------- 0150 0151 // public 0152 bool kpView::selectionLargeEnoughToHaveResizeHandles () const 0153 { 0154 return (selectionResizeHandleAtomicSize () > 0); 0155 } 0156 0157 //--------------------------------------------------------------------- 0158 0159 // public 0160 QRegion kpView::selectionResizeHandlesViewRegion (bool forRenderer) const 0161 { 0162 const int atomicLength = selectionResizeHandleAtomicSize (); 0163 if (atomicLength <= 0) { 0164 return {}; 0165 } 0166 0167 0168 // HACK: At low zoom (e.g. 100%), resize handles will probably be too 0169 // big and overlap text / cursor / too much of selection. 0170 // 0171 // So limit the _visual_ size of handles at low zoom. The 0172 // handles' grab area remains the same for usability; so yes, 0173 // there are a few pixels that don't look grabable but they are. 0174 // 0175 // The real solution is to be able to partially render the 0176 // handles outside of the selection view rect. If not possible, 0177 // at least for text boxes, render text on top of handles. 0178 int normalAtomicLength = atomicLength; 0179 int vertEdgeAtomicLength = atomicLength; 0180 if (forRenderer && selection ()) 0181 { 0182 if (zoomLevelX () <= 150) 0183 { 0184 if (normalAtomicLength > 1) { 0185 normalAtomicLength--; 0186 } 0187 0188 if (vertEdgeAtomicLength > 1) { 0189 vertEdgeAtomicLength--; 0190 } 0191 } 0192 0193 // 1 line of text? 0194 if (textSelection () && textSelection ()->textLines ().size () == 1) 0195 { 0196 if (zoomLevelX () <= 150) { 0197 vertEdgeAtomicLength = qMin (vertEdgeAtomicLength, qMax (2, zoomLevelX () / 100)); 0198 } 0199 else if (zoomLevelX () <= 250) { 0200 vertEdgeAtomicLength = qMin (vertEdgeAtomicLength, qMax (3, zoomLevelX () / 100)); 0201 } 0202 } 0203 } 0204 0205 0206 const QRect selViewRect = selectionViewRect (); 0207 QRegion ret; 0208 0209 // top left 0210 ret += QRect(0, 0, normalAtomicLength, normalAtomicLength); 0211 0212 // top middle 0213 ret += QRect((selViewRect.width() - normalAtomicLength) / 2, 0, 0214 normalAtomicLength, normalAtomicLength); 0215 0216 // top right 0217 ret += QRect(selViewRect.width() - normalAtomicLength - 1, 0, 0218 normalAtomicLength, normalAtomicLength); 0219 0220 // left middle 0221 ret += QRect(0, (selViewRect.height() - vertEdgeAtomicLength) / 2, 0222 vertEdgeAtomicLength, vertEdgeAtomicLength); 0223 0224 // right middle 0225 ret += QRect(selViewRect.width() - vertEdgeAtomicLength - 1, (selViewRect.height() - vertEdgeAtomicLength) / 2, 0226 vertEdgeAtomicLength, vertEdgeAtomicLength); 0227 0228 // bottom left 0229 ret += QRect(0, selViewRect.height() - normalAtomicLength - 1, 0230 normalAtomicLength, normalAtomicLength); 0231 0232 // bottom middle 0233 ret += QRect((selViewRect.width() - normalAtomicLength) / 2, selViewRect.height() - normalAtomicLength - 1, 0234 normalAtomicLength, normalAtomicLength); 0235 0236 // bottom right 0237 ret += QRect(selViewRect.width() - normalAtomicLength - 1, selViewRect.height() - normalAtomicLength - 1, 0238 normalAtomicLength, normalAtomicLength); 0239 0240 ret.translate (selViewRect.x (), selViewRect.y ()); 0241 ret = ret.intersected (selViewRect); 0242 0243 return ret; 0244 } 0245 0246 //--------------------------------------------------------------------- 0247 0248 // public 0249 // REFACTOR: use QFlags as the return type for better type safety. 0250 int kpView::mouseOnSelectionResizeHandle (const QPoint &viewPoint) const 0251 { 0252 #if DEBUG_KP_VIEW 0253 qCDebug(kpLogViews) << "kpView::mouseOnSelectionResizeHandle(viewPoint=" 0254 << viewPoint << ")" << endl; 0255 #endif 0256 0257 if (!mouseOnSelection (viewPoint)) 0258 { 0259 #if DEBUG_KP_VIEW 0260 qCDebug(kpLogViews) << "\tmouse not on sel"; 0261 #endif 0262 return 0; 0263 } 0264 0265 0266 const QRect selViewRect = selectionViewRect (); 0267 #if DEBUG_KP_VIEW 0268 qCDebug(kpLogViews) << "\tselViewRect=" << selViewRect; 0269 #endif 0270 0271 0272 const int atomicLength = selectionResizeHandleAtomicSize (); 0273 #if DEBUG_KP_VIEW 0274 qCDebug(kpLogViews) << "\tatomicLength=" << atomicLength; 0275 #endif 0276 0277 if (atomicLength <= 0) 0278 { 0279 #if DEBUG_KP_VIEW 0280 qCDebug(kpLogViews) << "\tsel not large enough to have resize handles"; 0281 #endif 0282 // Want to make it possible to move a small selection 0283 return 0; 0284 } 0285 0286 0287 const QPoint viewPointRelSel = mouseViewPointRelativeToSelection (viewPoint); 0288 #if DEBUG_KP_VIEW 0289 qCDebug(kpLogViews) << "\tviewPointRelSel=" << viewPointRelSel; 0290 #endif 0291 0292 0293 #define LOCAL_POINT_IN_BOX_AT(x,y) \ 0294 QRect ((x), (y), atomicLength, atomicLength).contains (viewPointRelSel) 0295 0296 // Favour the bottom & right and the corners. 0297 if (LOCAL_POINT_IN_BOX_AT (selViewRect.width () - atomicLength, 0298 selViewRect.height () - atomicLength)) 0299 { 0300 return kpView::Bottom | kpView::Right; 0301 } 0302 0303 if (LOCAL_POINT_IN_BOX_AT (selViewRect.width () - atomicLength, 0)) 0304 { 0305 return kpView::Top | kpView::Right; 0306 } 0307 0308 if (LOCAL_POINT_IN_BOX_AT (0, selViewRect.height () - atomicLength)) 0309 { 0310 return kpView::Bottom | kpView::Left; 0311 } 0312 0313 if (LOCAL_POINT_IN_BOX_AT (0, 0)) 0314 { 0315 return kpView::Top | kpView::Left; 0316 } 0317 0318 if (LOCAL_POINT_IN_BOX_AT (selViewRect.width () - atomicLength, 0319 (selViewRect.height () - atomicLength) / 2)) 0320 { 0321 return kpView::Right; 0322 } 0323 0324 if (LOCAL_POINT_IN_BOX_AT ((selViewRect.width () - atomicLength) / 2, 0325 selViewRect.height () - atomicLength)) 0326 { 0327 return kpView::Bottom; 0328 } 0329 0330 if (LOCAL_POINT_IN_BOX_AT ((selViewRect.width () - atomicLength) / 2, 0)) 0331 { 0332 return kpView::Top; 0333 } 0334 0335 if (LOCAL_POINT_IN_BOX_AT (0, (selViewRect.height () - atomicLength) / 2)) 0336 { 0337 return kpView::Left; 0338 } 0339 else 0340 { 0341 #if DEBUG_KP_VIEW 0342 qCDebug(kpLogViews) << "\tnot on sel resize handle"; 0343 #endif 0344 return 0; 0345 } 0346 #undef LOCAL_POINT_IN_BOX_AT 0347 } 0348 0349 // public 0350 bool kpView::mouseOnSelectionToSelectText (const QPoint &viewPoint) const 0351 { 0352 #if DEBUG_KP_VIEW 0353 qCDebug(kpLogViews) << "kpView::mouseOnSelectionToSelectText(viewPoint=" 0354 << viewPoint << ")" << endl; 0355 #endif 0356 0357 if (!mouseOnSelection (viewPoint)) 0358 { 0359 #if DEBUG_KP_VIEW 0360 qCDebug(kpLogViews) << "\tmouse non on sel"; 0361 #endif 0362 return false; 0363 } 0364 0365 if (!textSelection ()) 0366 { 0367 #if DEBUG_KP_VIEW 0368 qCDebug(kpLogViews) << "\tsel not text"; 0369 #endif 0370 return false; 0371 } 0372 0373 #if DEBUG_KP_VIEW 0374 qCDebug(kpLogViews) << "\tmouse on sel: to move=" << mouseOnSelectionToMove () 0375 << " to resize=" << mouseOnSelectionResizeHandle () 0376 << endl; 0377 #endif 0378 0379 return (!mouseOnSelectionToMove (viewPoint) && 0380 !mouseOnSelectionResizeHandle (viewPoint)); 0381 }