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 }