/* Copyright (c) 2003,2004,2005 Clarence Dang All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #define DEBUG_KP_TOOL_POLYGON 0 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if DEBUG_KP_TOOL_POLYGON static const char *pointArrayToString (const TQPointArray &pointArray) { static char string [1000]; string [0] = '\0'; for (TQPointArray::ConstIterator it = pointArray.begin (); it != pointArray.end (); it++) { TQString ps = TQString (" (%1, %2)").arg ((*it).x ()).arg ((*it).y ()); const char *pss = ps.latin1 (); if (strlen (string) + strlen (pss) + 1 > sizeof (string) / sizeof (string [0])) break; strcat (string, pss); } return string; } #endif static TQPen makeMaskPen (const kpColor &color, int lineWidth, Qt::PenStyle lineStyle) { return TQPen (color.maskColor (), lineWidth == 1 ? 0/*closer to looking width 1*/ : lineWidth, lineStyle, Qt::RoundCap, Qt::RoundJoin); } static TQPen makePen (const kpColor &color, int lineWidth, Qt::PenStyle lineStyle) { if (color.isOpaque ()) { return TQPen (color.toTQColor (), lineWidth == 1 ? 0/*closer to looking width 1*/ : lineWidth, lineStyle, Qt::RoundCap, Qt::RoundJoin); } else return Qt::NoPen; } static TQBrush makeMaskBrush (const kpColor &foregroundColor, const kpColor &backgroundColor, kpToolWidgetFillStyle *toolWidgetFillStyle) { if (toolWidgetFillStyle) return toolWidgetFillStyle->maskBrush (foregroundColor, backgroundColor); else return Qt::NoBrush; } static TQBrush makeBrush (const kpColor &foregroundColor, const kpColor &backgroundColor, kpToolWidgetFillStyle *toolWidgetFillStyle) { if (toolWidgetFillStyle) return toolWidgetFillStyle->brush (foregroundColor, backgroundColor); else return Qt::NoBrush; } static bool only1PixelInPointArray (const TQPointArray &points) { if (points.count () == 0) return false; for (int i = 1; i < (int) points.count (); i++) { if (points [i] != points [0]) return false; } return true; } static TQPixmap pixmap (const TQPixmap &oldPixmap, const TQPointArray &points, const TQRect &rect, const kpColor &foregroundColor, kpColor backgroundColor, int lineWidth, Qt::PenStyle lineStyle, kpToolWidgetFillStyle *toolWidgetFillStyle, enum kpToolPolygon::Mode mode, bool final = true) { // // figure out points to draw relative to topLeft of oldPixmap TQPointArray pointsInRect = points; pointsInRect.detach (); pointsInRect.translate (-rect.x (), -rect.y ()); #if DEBUG_KP_TOOL_POLYGON && 0 kdDebug () << "kptoolpolygon.cpp: pixmap(): points=" << pointArrayToString (points) << endl; #endif // // draw TQPen pen = makePen (foregroundColor, lineWidth, lineStyle), maskPen = makeMaskPen (foregroundColor, lineWidth, lineStyle); TQBrush brush = makeBrush (foregroundColor, backgroundColor, toolWidgetFillStyle), maskBrush = makeMaskBrush (foregroundColor, backgroundColor, toolWidgetFillStyle); TQPixmap pixmap = oldPixmap; TQBitmap maskBitmap; TQPainter painter, maskPainter; if (pixmap.mask () || (maskPen.style () != TQt::NoPen && maskPen.color () == TQt::color0/*transparent*/) || (maskBrush.style () != TQt::NoBrush && maskBrush.color () == TQt::color0/*transparent*/)) { maskBitmap = kpPixmapFX::getNonNullMask (pixmap); maskPainter.begin (&maskBitmap); maskPainter.setPen (maskPen); maskPainter.setBrush (maskBrush); #if DEBUG_KP_TOOL_POLYGON && 0 kdDebug () << "\tmaskPainter begin because:" << endl << "\t\tpixmap.mask=" << pixmap.mask () << endl << "\t\t(maskPenStyle!=NoPen)=" << (maskPen.style () != TQt::NoPen) << endl << "\t\t(maskPenColor==trans)=" << (maskPen.color () == TQt::color0) << endl << "\t\t(maskBrushStyle!=NoBrush)=" << (maskBrush.style () != TQt::NoBrush) << endl << "\t\t(maskBrushColor==trans)=" << (maskBrush.color () == TQt::color0) << endl; #endif } if (pen.style () != TQt::NoPen || brush.style () != TQt::NoBrush) { painter.begin (&pixmap); painter.setPen (pen); painter.setBrush (brush); #if DEBUG_KP_TOOL_POLYGON && 0 kdDebug () << "\tpainter begin pen.rgb=" << (int *) painter.pen ().color ().rgb () << endl; #endif } #define PAINTER_CALL(cmd) \ { \ if (painter.isActive ()) \ painter . cmd ; \ \ if (maskPainter.isActive ()) \ maskPainter . cmd ; \ } // SYNC: TQt bug if (only1PixelInPointArray (pointsInRect)) { PAINTER_CALL (drawPoint (pointsInRect [0])); } else { switch (mode) { case kpToolPolygon::Line: case kpToolPolygon::Polyline: PAINTER_CALL (drawPolyline (pointsInRect)); break; case kpToolPolygon::Polygon: // TODO: why aren't the ends rounded? PAINTER_CALL (drawPolygon (pointsInRect)); if (!final && 0/*HACK for TODO*/) { int count = pointsInRect.count (); if (count > 2) { if (painter.isActive ()) { TQPen XORpen = painter.pen (); XORpen.setColor (TQt::white); painter.setPen (XORpen); painter.setRasterOp (TQt::XorROP); } if (maskPainter.isActive ()) { TQPen XORpen = maskPainter.pen (); // TODO??? #if 0 if (kpTool::isColorTransparent (foregroundColor)) XORpen.setColor (TQt::color1/*opaque*/); else XORpen.setColor (TQt::color0/*transparent*/); #endif maskPainter.setPen (XORpen); } PAINTER_CALL (drawLine (pointsInRect [0], pointsInRect [count - 1])); } } break; case kpToolPolygon::Curve: int numPoints = pointsInRect.count (); TQPointArray pa (4); pa [0] = pointsInRect [0]; pa [3] = pointsInRect [1]; switch (numPoints) { case 2: pa [1] = pointsInRect [0]; pa [2] = pointsInRect [1]; break; case 3: pa [1] = pa [2] = pointsInRect [2]; break; case 4: pa [1] = pointsInRect [2]; pa [2] = pointsInRect [3]; } PAINTER_CALL (drawCubicBezier (pa)); } } #undef PAINTER_CALL if (painter.isActive ()) painter.end (); if (maskPainter.isActive ()) maskPainter.end (); if (!maskBitmap.isNull ()) pixmap.setMask (maskBitmap); return pixmap; } /* * kpToolPolygon */ kpToolPolygon::kpToolPolygon (Mode mode, const TQString &text, const TQString &description, int key, kpMainWindow *mainWindow, const char *name) : kpTool (text, description, key, mainWindow, name), m_mode (mode), m_toolWidgetFillStyle (0), m_toolWidgetLineWidth (0) { } kpToolPolygon::kpToolPolygon (kpMainWindow *mainWindow) : kpTool (i18n ("Polygon"), i18n ("Draws polygons"), TQt::Key_G, mainWindow, "tool_polygon"), m_mode (Polygon), m_toolWidgetFillStyle (0), m_toolWidgetLineWidth (0) { } kpToolPolygon::~kpToolPolygon () { } void kpToolPolygon::setMode (Mode m) { m_mode = m; } // private TQString kpToolPolygon::haventBegunShapeUserMessage () const { switch (m_mode) { case Line: return i18n ("Drag to draw."); case Polygon: case Polyline: return i18n ("Drag to draw the first line."); case Curve: return i18n ("Drag out the start and end points."); default: return TQString(); } } // virtual void kpToolPolygon::begin () { kpToolToolBar *tb = toolToolBar (); #if DEBUG_KP_TOOL_POLYGON kdDebug () << "kpToolPolygon::begin() tb=" << tb << endl; #endif if (tb) { if (m_mode == Polygon) m_toolWidgetFillStyle = tb->toolWidgetFillStyle (); else m_toolWidgetFillStyle = 0; m_toolWidgetLineWidth = tb->toolWidgetLineWidth (); if (m_toolWidgetFillStyle) { connect (m_toolWidgetFillStyle, TQT_SIGNAL (fillStyleChanged (kpToolWidgetFillStyle::FillStyle)), this, TQT_SLOT (slotFillStyleChanged (kpToolWidgetFillStyle::FillStyle))); } connect (m_toolWidgetLineWidth, TQT_SIGNAL (lineWidthChanged (int)), this, TQT_SLOT (slotLineWidthChanged (int))); if (m_toolWidgetFillStyle) m_toolWidgetFillStyle->show (); m_toolWidgetLineWidth->show (); m_lineWidth = m_toolWidgetLineWidth->lineWidth (); } else { m_toolWidgetFillStyle = 0; m_toolWidgetLineWidth = 0; m_lineWidth = 1; } viewManager ()->setCursor (TQCursor (CrossCursor)); m_originatingMouseButton = -1; setUserMessage (haventBegunShapeUserMessage ()); } // virtual void kpToolPolygon::end () { endShape (); if (m_toolWidgetFillStyle) { disconnect (m_toolWidgetFillStyle, TQT_SIGNAL (fillStyleChanged (kpToolWidgetFillStyle::FillStyle)), this, TQT_SLOT (slotFillStyleChanged (kpToolWidgetFillStyle::FillStyle))); m_toolWidgetFillStyle = 0; } if (m_toolWidgetLineWidth) { disconnect (m_toolWidgetLineWidth, TQT_SIGNAL (lineWidthChanged (int)), this, TQT_SLOT (slotLineWidthChanged (int))); m_toolWidgetLineWidth = 0; } viewManager ()->unsetCursor (); } void kpToolPolygon::beginDraw () { #if DEBUG_KP_TOOL_POLYGON kdDebug () << "kpToolPolygon::beginDraw() m_points=" << pointArrayToString (m_points) << ", startPoint=" << m_startPoint << endl; #endif bool endedShape = false; // starting with a line... if (m_points.count () == 0) { m_originatingMouseButton = m_mouseButton; m_points.putPoints (m_points.count (), 2, m_startPoint.x (), m_startPoint.y (), m_startPoint.x (), m_startPoint.y ()); } // continuing poly* else { if (m_mouseButton != m_originatingMouseButton) { m_mouseButton = m_originatingMouseButton; endShape (); endedShape = true; } else { int count = m_points.count (); m_points.putPoints (count, 1, m_startPoint.x (), m_startPoint.y ()); // start point = last end point; // _not_ the new/current start point // (which is disregarded in a poly* as only the end points count // after the initial line) // // Curve Tool ignores m_startPoint (doesn't call applyModifiers()) // after the initial has been defined. m_startPoint = m_points [count - 1]; } } #if DEBUG_KP_TOOL_POLYGON kdDebug () << "\tafterwards, m_points=" << pointArrayToString (m_points) << endl; #endif if (!endedShape) { switch (m_mode) { case Line: case Curve: case Polygon: case Polyline: setUserMessage (cancelUserMessage ()); break; default: kdError () << "kpToolPolygon::beginDraw() tqshape" << endl; break; } } } // private void kpToolPolygon::applyModifiers () { int count = m_points.count (); m_toolLineStartPoint = m_startPoint; /* also correct for poly* tool (see beginDraw()) */ m_toolLineEndPoint = m_currentPoint; #if DEBUG_KP_TOOL_POLYGON && 1 kdDebug () << "kpToolPolygon::applyModifiers() #pts=" << count << " line: startPt=" << m_toolLineStartPoint << " endPt=" << m_toolLineEndPoint << " modifiers: shift=" << m_shiftPressed << " alt=" << m_altPressed << " ctrl=" << m_controlPressed << endl; #endif // angles if (m_shiftPressed || m_controlPressed) { int diffx = m_toolLineEndPoint.x () - m_toolLineStartPoint.x (); int diffy = m_toolLineEndPoint.y () - m_toolLineStartPoint.y (); double ratio; if (diffx == 0) ratio = DBL_MAX; else ratio = fabs (double (diffy) / double (diffx)); #if DEBUG_KP_TOOL_POLYGON && 1 kdDebug () << "\tdiffx=" << diffx << " diffy=" << diffy << " ratio=" << ratio << endl; #endif // Shift = 0, 45, 90 // Alt = 0, 30, 60, 90 // Shift + Alt = 0, 30, 45, 60, 90 double angles [10]; // "ought to be enough for anybody" int numAngles = 0; angles [numAngles++] = 0; if (m_controlPressed) angles [numAngles++] = KP_PI / 6; if (m_shiftPressed) angles [numAngles++] = KP_PI / 4; if (m_controlPressed) angles [numAngles++] = KP_PI / 3; angles [numAngles++] = KP_PI / 2; double angle = angles [numAngles - 1]; for (int i = 0; i < numAngles - 1; i++) { double acceptingRatio = tan ((angles [i] + angles [i + 1]) / 2.0); if (ratio < acceptingRatio) { angle = angles [i]; break; } } // horizontal (dist from start !maintained) if (fabs (KP_RADIANS_TO_DEGREES (angle) - 0) < kpPixmapFX::AngleInDegreesEpsilon) { m_toolLineEndPoint = TQPoint (m_toolLineEndPoint.x (), m_toolLineStartPoint.y ()); } // vertical (dist from start !maintained) else if (fabs (KP_RADIANS_TO_DEGREES (angle) - 90) < kpPixmapFX::AngleInDegreesEpsilon) { m_toolLineEndPoint = TQPoint (m_toolLineStartPoint.x (), m_toolLineEndPoint.y ()); } // diagonal (dist from start maintained) else { const double dist = sqrt (diffx * diffx + diffy * diffy); #define sgn(a) ((a)<0?-1:1) // Round distances _before_ adding to any coordinate // (ensures consistent rounding behaviour in x & y directions) const int newdx = tqRound (dist * cos (angle) * sgn (diffx)); const int newdy = tqRound (dist * sin (angle) * sgn (diffy)); #undef sgn m_toolLineEndPoint = TQPoint (m_toolLineStartPoint.x () + newdx, m_toolLineStartPoint.y () + newdy); #if DEBUG_KP_TOOL_POLYGON && 1 kdDebug () << "\t\tdiagonal line: dist=" << dist << " angle=" << (angle * 180 / KP_PI) << " endPoint=" << m_toolLineEndPoint << endl; #endif } } // if (m_shiftPressed || m_controlPressed) { // centring if (m_altPressed && 0/*ALT is unreliable*/) { // start = start - diff // = start - (end - start) // = start - end + start // = 2 * start - end if (count == 2) m_toolLineStartPoint += (m_toolLineStartPoint - m_toolLineEndPoint); else m_toolLineEndPoint += (m_toolLineEndPoint - m_toolLineStartPoint); } // if (m_altPressed) { m_points [count - 2] = m_toolLineStartPoint; m_points [count - 1] = m_toolLineEndPoint; m_toolLineRect = kpTool::neededRect (TQRect (m_toolLineStartPoint, m_toolLineEndPoint).normalize (), m_lineWidth); } // virtual void kpToolPolygon::draw (const TQPoint &, const TQPoint &, const TQRect &) { if (m_points.count () == 0) return; #if DEBUG_KP_TOOL_POLYGON kdDebug () << "kpToolPolygon::draw() m_points=" << pointArrayToString (m_points) << ", endPoint=" << m_currentPoint << endl; #endif bool drawingALine = (m_mode != Curve) || (m_mode == Curve && m_points.count () == 2); if (drawingALine) applyModifiers (); else m_points [m_points.count () - 1] = m_currentPoint; #if DEBUG_KP_TOOL_POLYGON kdDebug () << "\tafterwards, m_points=" << pointArrayToString (m_points) << endl; #endif updateShape (); if (drawingALine) setUserShapePoints (m_toolLineStartPoint, m_toolLineEndPoint); else setUserShapePoints (m_currentPoint); } // private slot void kpToolPolygon::updateShape () { if (m_points.count () == 0) return; TQRect boundingRect = kpTool::neededRect (m_points.boundingRect (), m_lineWidth); #if DEBUG_KP_TOOL_POLYGON kdDebug () << "kpToolPolygon::updateShape() boundingRect=" << boundingRect << " lineWidth=" << m_lineWidth << endl; #endif TQPixmap oldPixmap = document ()->getPixmapAt (boundingRect); TQPixmap newPixmap = pixmap (oldPixmap, m_points, boundingRect, color (m_mouseButton), color (1 - m_mouseButton), m_lineWidth, Qt::SolidLine, m_toolWidgetFillStyle, m_mode, false/*not final*/); viewManager ()->setFastUpdates (); viewManager ()->setTempPixmap (kpTempPixmap (false/*always display*/, kpTempPixmap::SetPixmap/*render mode*/, boundingRect.topLeft (), newPixmap)); viewManager ()->restoreFastUpdates (); } // virtual void kpToolPolygon::cancelShape () { #if 0 endDraw (TQPoint (), TQRect ()); commandHistory ()->undo (); #else viewManager ()->invalidateTempPixmap (); #endif m_points.resize (0); setUserMessage (i18n ("Let go of all the mouse buttons.")); } void kpToolPolygon::releasedAllButtons () { if (!hasBegunShape ()) setUserMessage (haventBegunShapeUserMessage ()); // --- else case already handled by endDraw() --- } // virtual void kpToolPolygon::endDraw (const TQPoint &, const TQRect &) { #if DEBUG_KP_TOOL_POLYGON kdDebug () << "kpToolPolygon::endDraw() m_points=" << pointArrayToString (m_points) << endl; #endif if (m_points.count () == 0) return; if (m_mode == Line || (m_mode == Curve && m_points.count () >= 4) || m_points.count () >= 50) { endShape (); } else { switch (m_mode) { case Line: kdError () << "kpToolPolygon::endDraw() - line not ended" << endl; setUserMessage (); break; case Polygon: case Polyline: if (m_points.isEmpty ()) { kdError () << "kpToolPolygon::endDraw() exception - poly without points" << endl; setUserMessage (); } else { if (m_mouseButton == 0) { setUserMessage (i18n ("Left drag another line or right click to finish.")); } else { setUserMessage (i18n ("Right drag another line or left click to finish.")); } } break; case Curve: if (m_points.size () == 2) { if (m_mouseButton == 0) { setUserMessage (i18n ("Left drag to set the first control point or right click to finish.")); } else { setUserMessage (i18n ("Right drag to set the first control point or left click to finish.")); } } else if (m_points.size () == 3) { if (m_mouseButton == 0) { setUserMessage (i18n ("Left drag to set the last control point or right click to finish.")); } else { setUserMessage (i18n ("Right drag to set the last control point or left click to finish.")); } } else { kdError () << "kpToolPolygon::endDraw() exception - points" << endl; setUserMessage (); } break; default: kdError () << "kpToolPolygon::endDraw() - clueless" << endl; setUserMessage (); break; } } } // public virtual void kpToolPolygon::endShape (const TQPoint &, const TQRect &) { #if DEBUG_KP_TOOL_POLYGON kdDebug () << "kpToolPolygon::endShape() m_points=" << pointArrayToString (m_points) << endl; #endif if (!hasBegunShape ()) return; viewManager ()->invalidateTempPixmap (); TQRect boundingRect = kpTool::neededRect (m_points.boundingRect (), m_lineWidth); kpToolPolygonCommand *lineCommand = new kpToolPolygonCommand (text (), m_points, boundingRect, color (m_mouseButton), color (1 - m_mouseButton), m_lineWidth, Qt::SolidLine, m_toolWidgetFillStyle, document ()->getPixmapAt (boundingRect), m_mode, mainWindow ()); commandHistory ()->addCommand (lineCommand); m_points.resize (0); setUserMessage (haventBegunShapeUserMessage ()); } // public virtual bool kpToolPolygon::hasBegunShape () const { return (m_points.count () > 0); } // public slot void kpToolPolygon::slotLineWidthChanged (int width) { m_lineWidth = width; updateShape (); } // public slot void kpToolPolygon::slotFillStyleChanged (kpToolWidgetFillStyle::FillStyle /*fillStyle*/) { updateShape (); } // virtual protected slot void kpToolPolygon::slotForegroundColorChanged (const kpColor &) { updateShape (); } // virtual protected slot void kpToolPolygon::slotBackgroundColorChanged (const kpColor &) { updateShape (); } /* * kpToolPolygonCommand */ kpToolPolygonCommand::kpToolPolygonCommand (const TQString &name, const TQPointArray &points, const TQRect &normalizedRect, const kpColor &foregroundColor, const kpColor &backgroundColor, int lineWidth, Qt::PenStyle lineStyle, kpToolWidgetFillStyle *toolWidgetFillStyle, const TQPixmap &originalArea, enum kpToolPolygon::Mode mode, kpMainWindow *mainWindow) : kpNamedCommand (name, mainWindow), m_points (points), m_normalizedRect (normalizedRect), m_foregroundColor (foregroundColor), m_backgroundColor (backgroundColor), m_lineWidth (lineWidth), m_lineStyle (lineStyle), m_toolWidgetFillStyle (toolWidgetFillStyle), m_originalArea (originalArea), m_mode (mode) { m_points.detach (); } kpToolPolygonCommand::~kpToolPolygonCommand () { } // public virtual [base kpCommand] int kpToolPolygonCommand::size () const { return kpPixmapFX::pointArraySize (m_points) + kpPixmapFX::pixmapSize (m_originalArea); } // public virtual [base kpCommand] void kpToolPolygonCommand::execute () { TQPixmap p = pixmap (m_originalArea, m_points, m_normalizedRect, m_foregroundColor, m_backgroundColor, m_lineWidth, m_lineStyle, m_toolWidgetFillStyle, m_mode); document ()->setPixmapAt (p, m_normalizedRect.topLeft ()); } // public virtual [base kpCommand] void kpToolPolygonCommand::unexecute () { document ()->setPixmapAt (m_originalArea, m_normalizedRect.topLeft ()); } #include