File indexing completed on 2024-05-12 04:21:26

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_TOOL_POLYGON 0
0030 
0031 
0032 #include "kpToolPolygonalBase.h"
0033 
0034 #include <cfloat>
0035 #include <QtMath>
0036 
0037 #include <QCursor>
0038 #include <QPolygon>
0039 
0040 #include <KLocalizedString>
0041 
0042 #include "kpLogCategories.h"
0043 #include "commands/kpCommandHistory.h"
0044 #include "document/kpDocument.h"
0045 #include "kpDefs.h"
0046 #include "imagelib/kpPainter.h"
0047 #include "pixmapfx/kpPixmapFX.h"
0048 #include "layers/tempImage/kpTempImage.h"
0049 #include "environments/tools/kpToolEnvironment.h"
0050 #include "commands/tools/polygonal/kpToolPolygonalCommand.h"
0051 #include "widgets/toolbars/kpToolToolBar.h"
0052 #include "widgets/toolbars/options/kpToolWidgetLineWidth.h"
0053 #include "views/manager/kpViewManager.h"
0054 
0055 
0056 struct kpToolPolygonalBasePrivate
0057 {
0058     kpToolPolygonalBasePrivate ()
0059         : drawShapeFunc(nullptr), toolWidgetLineWidth(nullptr), originatingMouseButton(-1)
0060     {
0061     }
0062 
0063     kpToolPolygonalBase::DrawShapeFunc drawShapeFunc;
0064 
0065     kpToolWidgetLineWidth *toolWidgetLineWidth;
0066 
0067     int originatingMouseButton;
0068 
0069     QPolygon points;
0070 };
0071 
0072 //---------------------------------------------------------------------
0073 
0074 kpToolPolygonalBase::kpToolPolygonalBase (
0075         const QString &text,
0076         const QString &description,
0077         DrawShapeFunc drawShapeFunc,
0078         int key,
0079         kpToolEnvironment *environ, QObject *parent,
0080         const QString &name)
0081 
0082     : kpTool (text, description, key, environ, parent, name),
0083       d (new kpToolPolygonalBasePrivate ())
0084 {
0085     d->drawShapeFunc = drawShapeFunc;
0086 
0087     d->toolWidgetLineWidth = nullptr;
0088 
0089     // (hopefully cause crash if we use it before initialising it)
0090     d->originatingMouseButton = -1;
0091 }
0092 
0093 //---------------------------------------------------------------------
0094 
0095 kpToolPolygonalBase::~kpToolPolygonalBase ()
0096 {
0097     delete d;
0098 }
0099 
0100 //---------------------------------------------------------------------
0101 
0102 // virtual
0103 void kpToolPolygonalBase::begin ()
0104 {
0105     kpToolToolBar *tb = toolToolBar ();
0106     Q_ASSERT (tb);
0107 
0108 #if DEBUG_KP_TOOL_POLYGON
0109     qCDebug(kpLogTools) << "kpToolPolygonalBase::begin() tb=" << tb;
0110 #endif
0111 
0112     d->toolWidgetLineWidth = tb->toolWidgetLineWidth ();
0113     connect (d->toolWidgetLineWidth, &kpToolWidgetLineWidth::lineWidthChanged,
0114              this, &kpToolPolygonalBase::updateShape);
0115     d->toolWidgetLineWidth->show ();
0116 
0117     viewManager ()->setCursor (QCursor (Qt::ArrowCursor));
0118 
0119     d->originatingMouseButton = -1;
0120 
0121     setUserMessage (/*virtual*/haventBegunShapeUserMessage ());
0122 }
0123 
0124 //---------------------------------------------------------------------
0125 
0126 // virtual
0127 void kpToolPolygonalBase::end ()
0128 {
0129     // TODO: needed?
0130     endShape ();
0131 
0132     disconnect (d->toolWidgetLineWidth, &kpToolWidgetLineWidth::lineWidthChanged,
0133              this, &kpToolPolygonalBase::updateShape);
0134     d->toolWidgetLineWidth = nullptr;
0135 
0136     viewManager ()->unsetCursor ();
0137 }
0138 
0139 
0140 void kpToolPolygonalBase::beginDraw ()
0141 {
0142 #if DEBUG_KP_TOOL_POLYGON
0143     qCDebug(kpLogTools) << "kpToolPolygonalBase::beginDraw()  d->points=" << d->points.toList ()
0144                << ", startPoint=" << startPoint ();
0145 #endif
0146 
0147     bool endedShape = false;
0148 
0149     // We now need to start with dragging out the initial line?
0150     if (d->points.count () == 0)
0151     {
0152         d->originatingMouseButton = mouseButton ();
0153 
0154         // The line starts and ends at the start point of the drag.
0155         // draw() will modify the last point in d->points to reflect the
0156         // mouse drag, as the drag proceeds.
0157         d->points.append (startPoint ());
0158         d->points.append (startPoint ());
0159     }
0160     // Already have control points - not dragging out initial line.
0161     else
0162     {
0163         // Clicking the other mouse button?
0164         if (mouseButton () != d->originatingMouseButton)
0165         {
0166             // Finish shape.  TODO: I suspect we need to call endShapeInternal instead.
0167             endShape ();
0168             endedShape = true;
0169         }
0170         // Are we dragging out an extra control point?
0171         else
0172         {
0173             // Add another control point.
0174             d->points.append (startPoint ());
0175         }
0176     }
0177 
0178 #if DEBUG_KP_TOOL_POLYGON
0179     qCDebug(kpLogTools) << "\tafterwards, d->points=" << d->points.toList ();
0180 #endif
0181 
0182     if (!endedShape)
0183     {
0184         // We've started dragging.  Print instructions on how to cancel shape.
0185         setUserMessage (cancelUserMessage ());
0186     }
0187 }
0188 
0189 
0190 // protected
0191 void kpToolPolygonalBase::applyModifiers ()
0192 {
0193     const int count = d->points.count ();
0194 
0195     QPoint &lineStartPoint = d->points [count - 2];
0196     QPoint &lineEndPoint = d->points [count - 1];
0197 
0198 #if DEBUG_KP_TOOL_POLYGON && 1
0199     qCDebug(kpLogTools) << "kpToolPolygonalBase::applyModifiers() #pts=" << count
0200                << "   line: startPt=" << lineStartPoint
0201                << " endPt=" << lineEndPoint
0202                << "   modifiers: shift=" << shiftPressed ()
0203                << "   alt=" << altPressed ()
0204                << "   ctrl=" << controlPressed ();
0205 #endif
0206 
0207     // angles
0208     if (shiftPressed () || controlPressed ())
0209     {
0210         int diffx = lineEndPoint.x () - lineStartPoint.x ();
0211         int diffy = lineEndPoint.y () - lineStartPoint.y ();
0212 
0213         double ratio;
0214         if (diffx == 0) {
0215             ratio = DBL_MAX;
0216         }
0217         else {
0218             ratio = fabs (double (diffy) / double (diffx));
0219         }
0220     #if DEBUG_KP_TOOL_POLYGON && 1
0221         qCDebug(kpLogTools) << "\tdiffx=" << diffx << " diffy=" << diffy
0222                    << " ratio=" << ratio;
0223     #endif
0224 
0225         // Shift        = 0, 45, 90
0226         // Ctrl         = 0, 30, 60, 90
0227         // Shift + Ctrl = 0, 30, 45, 60, 90
0228         double angles [10];  // "ought to be enough for anybody"
0229         int numAngles = 0;
0230         angles [numAngles++] = 0;
0231         if (controlPressed ()) {
0232             angles [numAngles++] = M_PI / 6;
0233         }
0234         if (shiftPressed ()) {
0235             angles [numAngles++] = M_PI / 4;
0236         }
0237         if (controlPressed ()) {
0238             angles [numAngles++] = M_PI / 3;
0239         }
0240         angles [numAngles++] = M_PI / 2;
0241         Q_ASSERT (numAngles <= int (sizeof (angles) / sizeof (angles [0])));
0242 
0243         double angle = angles [numAngles - 1];
0244         for (int i = 0; i < numAngles - 1; i++)
0245         {
0246             double acceptingRatio = std::tan ((angles [i] + angles [i + 1]) / 2.0);
0247             if (ratio < acceptingRatio)
0248             {
0249                 angle = angles [i];
0250                 break;
0251             }
0252         }
0253 
0254         // horizontal (dist from start not maintained)
0255         if (std::fabs (qRadiansToDegrees (angle) - 0)
0256             < kpPixmapFX::AngleInDegreesEpsilon)
0257         {
0258             lineEndPoint =
0259                 QPoint (lineEndPoint.x (), lineStartPoint.y ());
0260         }
0261         // vertical (dist from start not maintained)
0262         else if (std::fabs (qRadiansToDegrees (angle) - 90)
0263                  < kpPixmapFX::AngleInDegreesEpsilon)
0264         {
0265             lineEndPoint =
0266                 QPoint (lineStartPoint.x (), lineEndPoint.y ());
0267         }
0268         // diagonal (dist from start maintained)
0269         else
0270         {
0271             const double dist = std::sqrt (static_cast<double> (diffx * diffx + diffy * diffy));
0272 
0273             #define sgn(a) ((a)<0?-1:1)
0274             // Round distances _before_ adding to any coordinate
0275             // (ensures consistent rounding behaviour in x & y directions)
0276             const int newdx = qRound (dist * cos (angle) * sgn (diffx));
0277             const int newdy = qRound (dist * sin (angle) * sgn (diffy));
0278             #undef sgn
0279 
0280             lineEndPoint = QPoint (lineStartPoint.x () + newdx,
0281                                          lineStartPoint.y () + newdy);
0282 
0283         #if DEBUG_KP_TOOL_POLYGON && 1
0284             qCDebug(kpLogTools) << "\t\tdiagonal line: dist=" << dist
0285                        << " angle=" << (angle * 180 / M_PI)
0286                        << " endPoint=" << lineEndPoint;
0287         #endif
0288         }
0289     }    // if (shiftPressed () || controlPressed ()) {
0290 
0291     // centring
0292     if (altPressed () && 0/*ALT is unreliable*/)
0293     {
0294         // start = start - diff
0295         //       = start - (end - start)
0296         //       = start - end + start
0297         //       = 2 * start - end
0298         if (count == 2) {
0299             lineStartPoint += (lineStartPoint - lineEndPoint);
0300         }
0301         else {
0302             lineEndPoint += (lineEndPoint - lineStartPoint);
0303         }
0304     }    // if (altPressed ()) {
0305 }
0306 
0307 
0308 // protected
0309 QPolygon *kpToolPolygonalBase::points () const
0310 {
0311     return &d->points;
0312 }
0313 
0314 
0315 // protected
0316 int kpToolPolygonalBase::originatingMouseButton () const
0317 {
0318     Q_ASSERT (hasBegunShape ());
0319     return d->originatingMouseButton;
0320 }
0321 
0322 
0323 // virtual
0324 void kpToolPolygonalBase::draw (const QPoint &, const QPoint &, const QRect &)
0325 {
0326     // A click of the other mouse button (to finish shape, instead of adding
0327     // another control point) would have caused endShape() to have been
0328     // called in kpToolPolygonalBase::beginDraw().  The points list would now
0329     // be empty.  We are being called by kpTool::mouseReleaseEvent().
0330     if (d->points.count () == 0) {
0331         return;
0332     }
0333 
0334 #if DEBUG_KP_TOOL_POLYGON
0335     qCDebug(kpLogTools) << "kpToolPolygonalBase::draw()  d->points=" << d->points.toList ()
0336                << ", endPoint=" << currentPoint ();
0337 #endif
0338 
0339     // Update points() so that last point reflects current mouse position.
0340     const int count = d->points.count ();
0341     d->points [count - 1] = currentPoint ();
0342 
0343 #if DEBUG_KP_TOOL_POLYGON
0344     qCDebug(kpLogTools) << "\tafterwards, d->points=" << d->points.toList ();
0345 #endif
0346 
0347     // Are we drawing a line?
0348     if (/*virtual*/drawingALine ())
0349     {
0350         // Adjust the line (end points given by the last 2 points of points())
0351         // in response to keyboard modifiers.
0352         applyModifiers ();
0353 
0354         // Update the preview of the shape.
0355         updateShape ();
0356 
0357         // Inform the user that we're dragging out a line with 2 control points.
0358         setUserShapePoints (d->points [count - 2], d->points [count - 1]);
0359     }
0360     // We're modifying a point.
0361     else
0362     {
0363         // Update the preview of the shape.
0364         updateShape ();
0365 
0366         // Informs the user that we're just modifying a point (perhaps, a control
0367         // point of a Bezier).
0368         setUserShapePoints (d->points [count - 1]);
0369     }
0370 }
0371 
0372 
0373 // TODO: code dup with kpToolRectangle
0374 // private
0375 kpColor kpToolPolygonalBase::drawingForegroundColor () const
0376 {
0377     return color (originatingMouseButton ());
0378 }
0379 
0380 // protected virtual
0381 kpColor kpToolPolygonalBase::drawingBackgroundColor () const
0382 {
0383     return kpColor::Invalid;
0384 }
0385 
0386 // TODO: code dup with kpToolRectangle
0387 // protected slot
0388 void kpToolPolygonalBase::updateShape ()
0389 {
0390     if (d->points.count () == 0) {
0391         return;
0392     }
0393 
0394     const QRect boundingRect = kpTool::neededRect (
0395             d->points.boundingRect (),
0396             d->toolWidgetLineWidth->lineWidth ());
0397 
0398 #if DEBUG_KP_TOOL_POLYGON
0399     qCDebug(kpLogTools) << "kpToolPolygonalBase::updateShape() boundingRect="
0400                << boundingRect
0401                << " lineWidth="
0402                << d->toolWidgetLineWidth->lineWidth ()
0403                << endl;
0404 #endif
0405 
0406     kpImage image = document ()->getImageAt (boundingRect);
0407 
0408     QPolygon pointsTranslated = d->points;
0409     pointsTranslated.translate (-boundingRect.x (), -boundingRect.y ());
0410 
0411     (*d->drawShapeFunc) (&image,
0412         pointsTranslated,
0413         drawingForegroundColor (), d->toolWidgetLineWidth->lineWidth (),
0414         /*virtual*/drawingBackgroundColor (),
0415         false/*not final*/);
0416 
0417     kpTempImage newTempImage (false/*always display*/,
0418                                 kpTempImage::SetImage/*render mode*/,
0419                                 boundingRect.topLeft (),
0420                                 image);
0421 
0422     viewManager ()->setFastUpdates ();
0423     {
0424         viewManager ()->setTempImage (newTempImage);
0425     }
0426     viewManager ()->restoreFastUpdates ();
0427 }
0428 
0429 // virtual
0430 void kpToolPolygonalBase::cancelShape ()
0431 {
0432     viewManager ()->invalidateTempImage ();
0433     d->points.resize (0);
0434 
0435     setUserMessage (i18n ("Let go of all the mouse buttons."));
0436 }
0437 
0438 void kpToolPolygonalBase::releasedAllButtons ()
0439 {
0440     if (!hasBegunShape ()) {
0441         setUserMessage (/*virtual*/haventBegunShapeUserMessage ());
0442     }
0443 
0444     // --- else case already handled by endDraw() ---
0445 }
0446 
0447 // public virtual [base kpTool]
0448 void kpToolPolygonalBase::endShape (const QPoint &, const QRect &)
0449 {
0450 #if DEBUG_KP_TOOL_POLYGON
0451     qCDebug(kpLogTools) << "kpToolPolygonalBase::endShape()  d->points="
0452         << d->points.toList () << endl;
0453 #endif
0454 
0455     if (!hasBegunShape ()) {
0456         return;
0457     }
0458 
0459     viewManager ()->invalidateTempImage ();
0460 
0461     QRect boundingRect = kpTool::neededRect (
0462         d->points.boundingRect (),
0463         d->toolWidgetLineWidth->lineWidth ());
0464 
0465     commandHistory ()->addCommand (
0466         new kpToolPolygonalCommand (
0467             text (),
0468             d->drawShapeFunc,
0469             d->points, boundingRect,
0470             drawingForegroundColor (), d->toolWidgetLineWidth->lineWidth (),
0471             /*virtual*/drawingBackgroundColor (),
0472             environ ()->commandEnvironment ()));
0473 
0474     d->points.resize (0);
0475     setUserMessage (/*virtual*/haventBegunShapeUserMessage ());
0476 
0477 }
0478 
0479 // public virtual [base kpTool]
0480 bool kpToolPolygonalBase::hasBegunShape () const
0481 {
0482     return (d->points.count () > 0);
0483 }
0484 
0485 
0486 // virtual protected slot [base kpTool]
0487 void kpToolPolygonalBase::slotForegroundColorChanged (const kpColor &)
0488 {
0489     updateShape ();
0490 }
0491 
0492 // virtual protected slot [base kpTool]
0493 void kpToolPolygonalBase::slotBackgroundColorChanged (const kpColor &)
0494 {
0495     updateShape ();
0496 }
0497 
0498 #include "moc_kpToolPolygonalBase.cpp"