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"