// $Id: opfree.cpp 1282 2006-06-09 09:46:49Z alex $ /* @@tag:xara-cn@@ DO NOT MODIFY THIS LINE ================================XARAHEADERSTART=========================== Xara LX, a vector drawing and manipulation program. Copyright (C) 1993-2006 Xara Group Ltd. Copyright on certain contributions may be held in joint with their respective authors. See AUTHORS file for details. LICENSE TO USE AND MODIFY SOFTWARE ---------------------------------- This file is part of Xara LX. Xara LX is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. Xara LX and its component source files are distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Xara LX (see the file GPL in the root directory of the distribution); if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ADDITIONAL RIGHTS ----------------- Conditional upon your continuing compliance with the GNU General Public License described above, Xara Group Ltd grants to you certain additional rights. The additional rights are to use, modify, and distribute the software together with the wxWidgets library, the wxXtra library, and the "CDraw" library and any other such library that any version of Xara LX relased by Xara Group Ltd requires in order to compile and execute, including the static linking of that library to XaraLX. In the case of the "CDraw" library, you may satisfy obligation under the GNU General Public License to provide source code by providing a binary copy of the library concerned and a copy of the license accompanying it. Nothing in this section restricts any of the rights you have under the GNU General Public License. SCOPE OF LICENSE ---------------- This license applies to this program (XaraLX) and its constituent source files only, and does not necessarily apply to other Xara products which may in part share the same code base, and are subject to their own licensing terms. This license does not apply to files in the wxXtra directory, which are built into a separate library, and are subject to the wxWindows license contained within that directory in the file "WXXTRA-LICENSE". This license does not apply to the binary libraries (if any) within the "libs" directory, which are subject to a separate license contained within that directory in the file "LIBS-LICENSE". ARRANGEMENTS FOR CONTRIBUTION OF MODIFICATIONS ---------------------------------------------- Subject to the terms of the GNU Public License (see above), you are free to do whatever you like with your modifications. However, you may (at your option) wish contribute them to Xara's source tree. You can find details of how to do this at: http://www.xaraxtreme.org/developers/ Prior to contributing your modifications, you will need to complete our contributor agreement. This can be found at: http://www.xaraxtreme.org/developers/contribute/ Please note that Xara will not accept modifications which modify any of the text between the start and end of this header (marked XARAHEADERSTART and XARAHEADEREND). MARKS ----- Xara, Xara LX, Xara X, Xara X/Xtreme, Xara Xtreme, the Xtreme and Xara designs are registered or unregistered trademarks, design-marks, and/or service marks of Xara Group Ltd. All rights in these marks are reserved. Xara Group Ltd, Gaddesden Place, Hemel Hempstead, HP2 6EX, UK. http://www.xara.com/ =================================XARAHEADEREND============================ */ // The Freehand Tool operation /* */ #include "camtypes.h" #include "opfree.h" //#include "resource.h" //#include "mario.h" //#include "rik.h" //#include "viewrc.h" //#include "docview.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "paper.h" //#include "document.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "mainfrm.h" //#include "ensure.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "csrstack.h" #include "nodepath.h" //#include "paths.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "fitcurve.h" //#include "app.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "spread.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "freehand.h" #include "pen.h" #include "progress.h" //#include "attrmgr.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "blobs.h" #include "pathedit.h" //#include "fixmem.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "keypress.h" //#include "freehres.h" #include "objchge.h" #include "bubbleid.h" #include "lineattr.h" // Variable-width Path stroking headers #include "pathtrap.h" #include "ppstroke.h" #include "pressure.h" #include "strkattr.h" #include "valfunc.h" #include "nodeblnd.h" #include "blndtool.h" #include "ndbldpth.h" #include "nodebldr.h" #include "opdrbrsh.h" #include "ophist.h" #include "brshattr.h" // Set things up so that the tool will be listed in the Dialog box DECLARE_SOURCE("$Revision: 1282 $"); // Declare the class CC_IMPLEMENT_DYNCREATE( OpFreeHand, SelOperation ) // Improved memory tracking #define new CAM_DEBUG_NEW /******************************************************************************************** > OpFreeHand::OpFreeHand() Author: Rik_Heywood (Xara Group Ltd) Created: 20/7/93 Purpose: Constructor. This simply sets a few of the operation flags. ********************************************************************************************/ OpFreeHand::OpFreeHand() { // Set all our cursors to NULL to start with pFreeHandCursor = NULL; pJoinCursor = NULL; pStraightCursor = NULL; pRubOutCursor = NULL; pModifyCursor = NULL; // Set other default values TrackData = NULL; StartSpread = NULL; PreviousSpread = NULL; Smoothness = 512; LineSegmentCount = 0; CanLineJoin = FALSE; IsStraightLineMode = FALSE; AddPressureToPath = FALSE; FreeHandPressure = 0; // The paths that we are joined to, or NULL if we are joined to none pJoinInfo = NULL; StartPath = NULL; EndPath = NULL; CloseTo = 0; Mu = 0.0; IsEndNearEndpoint = FALSE; CurrentCursorID = 0; m_pNewNodePath = NULL; } /******************************************************************************************** > void OpFreeHand::DoDrag( DocCoord Anchor, Spread *pSpread, INT32 Smooth, NodePath* path, Path* ToolPath ) Author: Rik_Heywood (Xara Group Ltd) Created: 5/7/93 Inputs: Anchor - The position of the mouse at the start of the Drag pSpread - The spread that the drag started on Smooth - Closeness of fit value pFreeHandInfo - Pointer to the info about the Joining set up by the freehand tool ToolPath - The path to store the mouse tracking info in Purpose: Starts up the drag operation by storing all start positions and setting up a Path to store the mouse movement in ********************************************************************************************/ void OpFreeHand::DoDrag(DocCoord Anchor, Spread *pSpread, INT32 Smooth, FreeHandJoinInfo* pFreeHandInfo, Path* ToolPath) { TRACEUSER( "Diccon", _T("Do Drag Free, StartPos = %d, %d\n"), Anchor.x, Anchor.y); // WEBSTER - markn 25/4/97 // No pen stuff required in Webster // Taken out by vector stroking code Neville 2/10/97 #ifdef VECTOR_STROKING // Tell the pressure pen that we're starting a new stroke CCPen *pPen = GetApplication()->GetPressurePen(); if (pPen != NULL) pPen->StartStroke(); #endif // VECTOR_STROKING // Snap the starting coord if we are not joining to something else if ((pFreeHandInfo==NULL) || (pFreeHandInfo->pJoinPath==NULL)) DocView::SnapCurrent(pSpread, &Anchor, FALSE, TRUE); // Make a note of various starting conditions TrackData = ToolPath; Smoothness = Smooth; pJoinInfo = pFreeHandInfo; StartPath = pJoinInfo->pJoinPath; EndPath = NULL; // Make a mental note of the start point StartPoint = Anchor; StartSpread = pSpread; PreviousSpread= pSpread; PreviousPoint = Anchor; LineSegmentCount = 0; IsStraightLineMode = FALSE; CanLineJoin = FALSE; // Prepare the Track data path and add in the initial click point to the path if (!PrepareTrackDataPath()) { // We failed to get the memory to initialise the track data InformError(_R(IDS_OUT_OF_MEMORY), _R(IDS_OK)); FailAndExecute(); End(); return; } // Create some cursors that I might need if (!LoadCursors()) { // The cursors did not load, so fail? FailAndExecute(); End(); return; } // Push my new cursor onto the stack CurrentCursorID = CursorStack::GPush(pFreeHandCursor, TRUE); MyCurrentCursor = pFreeHandCursor; // And tell the Dragging system that we need drags to happen StartDrag( DRAGTYPE_NOSCROLL ); } /******************************************************************************************** > BOOL OpFreeHand::PrepareTrackDataPath() Author: Rik_Heywood (Xara Group Ltd) Created: 25/10/94 Returns: TRUE if it worked, FALSE if it failed Purpose: Tries to initialise the path that we will store the track data in. It also sets up the pressure info stuff it there is pressure info coming in. If the path initialise ok, then it inserts the initial position (including its pressure) into the path. This function will only fail if there is no memory to init the path with. ********************************************************************************************/ BOOL OpFreeHand::PrepareTrackDataPath() { TrackData->ClearPath(); TrackData->FindStartOfPath(); // Check to see if we want to store pressure, and if so initialise the path. AddPressureToPath = FALSE; // WEBSTER - markn 25/4/97 // No pen stuff required in Webster // Taken out by vector stroking code Neville 2/10/97 #ifdef VECTOR_STROKING CCPen *pPen = GetApplication()->GetPressurePen(); if (IsBrushOp() && pPen != NULL && pPen->GetPressureMode() != PressureMode_None) { // Yep, we want the pressure info AddPressureToPath = TRUE; // Lets gets the pressure for the first point in the path. FreeHandPressure = pPen->GetPenPressure(); // try and initialise the extra info in the path and fail if not if (!TrackData->InitExtraInfo(CI_PRESSURE)) return FALSE; } #endif // VECTOR_STROKING // insert the initial coord into the path and fail if it fails if (!TrackData->InsertMoveTo(StartPoint)) return FALSE; // and add pressure info if needed if (AddPressureToPath) TrackData->AddExtraInfo(CI_PRESSURE, FreeHandPressure); // and tell someone that we did it return TRUE; } /******************************************************************************************** > BOOL OpFreeHand::LoadCursors() Author: Rik_Heywood (Xara Group Ltd) Created: 5/5/94 Returns: TRUE if it worked, FALSE if not Purpose: Tries to Load all the cursors used by the Free Hand Operation. ********************************************************************************************/ BOOL OpFreeHand::LoadCursors() { // Try to load all the cursors if (FreeHandTool::FreehandPtrCrosshair) { pFreeHandCursor = new Cursor(TOOLID_FREEHAND, _R(IDC_FREEHANDTOOLCURSOR_X)); pJoinCursor = new Cursor(TOOLID_FREEHAND, _R(IDC_FREEHANDJOINCURSOR_X)); pRubOutCursor = new Cursor(TOOLID_FREEHAND, _R(IDC_FREEHANDRUBOUTCUR_X)); pStraightCursor = new Cursor(TOOLID_FREEHAND, _R(IDC_FREEHANDLINECURSOR_X)); pModifyCursor = new Cursor(TOOLID_FREEHAND, _R(IDC_FREEHANDMODIFY_X)); } else { pFreeHandCursor = new Cursor(TOOLID_FREEHAND, _R(IDC_FREEHANDTOOLCURSOR)); pJoinCursor = new Cursor(TOOLID_FREEHAND, _R(IDC_FREEHANDJOINCURSOR)); pRubOutCursor = new Cursor(TOOLID_FREEHAND, _R(IDC_FREEHANDRUBOUTCUR)); pStraightCursor = new Cursor(TOOLID_FREEHAND, _R(IDC_FREEHANDLINECURSOR)); pModifyCursor = new Cursor(TOOLID_FREEHAND, _R(IDC_FREEHANDMODIFY)); } // See if any of them failed if ((pRubOutCursor==NULL) || (!pRubOutCursor->IsValid()) || (pJoinCursor==NULL) || (!pJoinCursor->IsValid()) || (pFreeHandCursor==NULL) || (!pFreeHandCursor->IsValid()) || (pStraightCursor==NULL) || (!pStraightCursor->IsValid()) || (pModifyCursor==NULL) || (!pModifyCursor->IsValid())) { // They did, so Tidy up and exit delete pFreeHandCursor; delete pJoinCursor; delete pStraightCursor; delete pRubOutCursor; delete pModifyCursor; // Set them all to NULL pFreeHandCursor = NULL; pJoinCursor = NULL; pStraightCursor = NULL; pRubOutCursor = NULL; pModifyCursor = NULL; // fail return FALSE; } // It worked return TRUE; } /******************************************************************************************** > void OpFreeHand::RemoveCursors() Author: Rik_Heywood (Xara Group Ltd) Created: 25/10/94 Purpose: Gets rid of all this operations cursors. It takes the cursor I was using off the top of the stack and deletes all the cursors I had allocated. ********************************************************************************************/ void OpFreeHand::RemoveCursors() { // Get rid out my cursor from the top of the cursor stack CursorStack::GPop(CurrentCursorID); MyCurrentCursor = NULL; CurrentCursorID = 0; // destroy all the cursors that I had allocated delete pFreeHandCursor; delete pJoinCursor; delete pRubOutCursor; delete pStraightCursor; delete pModifyCursor; // and make sure that they all point to something sensible pFreeHandCursor = NULL; pJoinCursor = NULL; pStraightCursor = NULL; pRubOutCursor = NULL; pModifyCursor = NULL; } /******************************************************************************************** > void OpFreeHand::SetCursorOnMove(ClickModifiers ClickMods, Spread* pSpread, DocCoord* PointerPos) Author: Rik_Heywood (Xara Group Ltd) Created: 25/10/94 Inputs: ClickMods - Which modifiers were pressed pSpread - The spread that the mouse is over Outputs: PointerPos - The position of the mouse Purpose: Decides which cursor to display depending on which modifiers are pressed and where the mouse is. PointerPos can be changed in this function ********************************************************************************************/ void OpFreeHand::SetCursorOnMove(ClickModifiers ClickMods, Spread* pSpread, DocCoord* PointerPos) { // If the mouse is over an EndPoint, use the Join Cursor if (CheckMouseOverSelectedPath(PointerPos, pSpread, &IsEndNearEndpoint)) { // Ok the cursor is near the path if (IsEndNearEndpoint) { // it was near one of the paths open ends SetCursorAndStatus(JOIN_CURSOR); return; } // Must be near the middle of the path // We can only do a modify join if we started the path on the same path if ((StartPath==EndPath) && (pJoinInfo->IsNearEndPoint==FALSE)) { SetCursorAndStatus(MODIFY_CURSOR); return; } } // See Which of the modifiers are down... if (ClickMods.Alternative1) { // if Alt is down we are in Straight Line mode SetCursorAndStatus(STRAIGHTLINE_CURSOR); } else { if (ClickMods.Adjust) { // If Shift is down we are in RubOut mode SetCursorAndStatus(RUBOUT_CURSOR); } else { // No Modifiers so use the normal cursor SetCursorAndStatus(NORMAL_CURSOR); } } } /******************************************************************************************** > void OpFreeHand::DragPointerMove( DocCoord PointerPos, ClickModifiers ClickMods, Spread *pSpread, BOOL bSolidDrag) Author: Rik_Heywood (Xara Group Ltd) Created: 5/7/93 Inputs: PointerPos - The current position of the mouse in Doc Coords ClickMods - Which key modifiers are being pressed pSpread - The spread that the mouse is moving over Purpose: Handles the event of the mouse moving during a drag SeeAlso: ClickModifiers ********************************************************************************************/ void OpFreeHand::DragPointerMove( DocCoord PointerPos, ClickModifiers ClickMods, Spread *pSpread, BOOL bSolidDrag) { // Find out the view DocView* pView = DocView::GetSelected(); // Lets whip out the current pressure value if (AddPressureToPath) FreeHandPressure = ClickMods.Pressure; // If drag has moved onto a different spread, convert the coord to be relative to the // original spread. if (pSpread != StartSpread) PointerPos = MakeRelativeToSpread(StartSpread, pSpread, PointerPos); // Change the Cursor to display the appropriate thing. SetCursorOnMove(ClickMods, StartSpread, &PointerPos); // Are we in Straight Line Mode? We won't allow straight line mode when recording pressure if (ClickMods.Alternative1) { // Snap and Constrain if needed DocView::SnapCurrent(pSpread, &PointerPos); if (ClickMods.Constrain) DocView::ConstrainToAngle(PreviousPoint, &PointerPos); // If we are in the Straight Line mode rub out the line if (IsStraightLineMode) RenderEorStraightLine(NULL, pSpread); else { // We are just entering straight line mode, so switch the dragging mode // so that the document auto scrolls at the edges of the screen if (pView!=NULL) pView->ChangeDragType(DRAGTYPE_AUTOSCROLL); } // Update the mouse position and render the EORed straight line StraightLinePos = PointerPos; RenderEorStraightLine(NULL, pSpread); // Finally note that we are in this mode IsStraightLineMode = TRUE; } else { // See if we were in Straight Line mode before if (IsStraightLineMode) { // We do not want to rub the straight line off, so do no drawing // Add the straight line into the path and set the flags as needed AddStraightLine(); PreviousPoint = StraightLinePos; // We are just leaving straight line mode, so go back to not scrolling if (pView!=NULL) pView->ChangeDragType(DRAGTYPE_NOSCROLL); // No longer in Straight Line mode, so set the flag IsStraightLineMode = FALSE; } else { // Either rub out sections of the path, or add new points to it if (ClickMods.Adjust) RubOutPath(PointerPos, StartSpread); else AddPointsToPath(PointerPos, StartSpread); } } // Set the last spread to something appropriate PreviousSpread = pSpread; } /******************************************************************************************** > void OpFreeHand::DragPointerIdle(DocCoord PointerPos, ClickModifiers ClickMods, Spread* pSpread, BOOL bSolidDrag) Author: Rik_Heywood (Xara Group Ltd) Created: 12/5/94 Inputs: PointerPos - The position of the mouse ClickMods - The modifiers that are held down at the time of the idle event pSpread - The spread that the mouse was over at the event Purpose: This function checks the Adjust Click modifier while the mouse is not moving to see if it is released during a straight line mode edit. This means that the shift key can be toggled in mid drag to create a straight line shape ********************************************************************************************/ void OpFreeHand::DragPointerIdle(DocCoord PointerPos, ClickModifiers ClickMods, Spread* pSpread, BOOL bSolidDrag) { // If we are in straight line mode, but the alt button has just come up, then // end the straight line we are drawing if (IsStraightLineMode && !ClickMods.Alternative1) { // Add the straight line into the path and set the flags as needed AddStraightLine(); PreviousPoint = StraightLinePos; // No longer in Straight Line mode, so set the flag IsStraightLineMode = FALSE; } } /******************************************************************************************** > void OpFreeHand::DragFinished( DocCoord PointerPos, ClickModifiers ClickMods, Spread *pSpread, BOOL Success, BOOL bSolidDrag) Author: Rik_Heywood (Xara Group Ltd) Created: 5/7/93 Inputs: PointerPos - The position of the mouse at the end of the drag ClickMods - the key modifiers being pressed pSpread - The spread that the drag finished on Success - TRUE if the drag was terminated properly, FALSE if it was ended with the escape key being pressed Purpose: Handles the drag finishing by rubbing out an EOR on the screen and adding the path to the tree and building all the undo that will be needed SeeAlso: ClickModifiers ********************************************************************************************/ void OpFreeHand::DragFinished( DocCoord PointerPos, ClickModifiers ClickMods, Spread *pSpread, BOOL Success, BOOL bSolidDrag) { // Erase the whole EORed line DocRect ClipRect(0,0,0,0); RenderDragBlobs(ClipRect, StartSpread, bSolidDrag); // Get rid of all the cursors RemoveCursors(); // Put the hour glass up as we have to and end the drag BeginSlowJob(); EndDrag(); // Add the new path to the tree if it was valid BOOL Worked = FALSE; if (Success) { // were we in the middle of drawing a straight line if (IsStraightLineMode) { // we were so add it in to the track data AddStraightLine(); PreviousPoint = StraightLinePos; IsStraightLineMode = FALSE; } // try and smooth the path and insert it into the tree Worked = CompleteOperation(); // DY 9/99 Check to see if we are editing a blend on a curve, if so we may wish to // change the number of steps in the blend (making use of the path distance). NodeGroup* pParent = GetParentOfNodePath(); if (pParent!= NULL) { if (pParent->IS_KIND_OF(NodeBlend)) { InsertChangeBlendStepsAction((NodeBlend*)pParent); ObjChangeFlags cFlags(FALSE,TRUE); ObjChangeParam ObjChange(OBJCHANGE_FINISHED,cFlags,NULL,this); Worked = UpdateChangedNodes(&ObjChange); } } } // WEBSTER - markn 25/4/97 // No pen stuff required in Webster // Taken out by vector stroking code Neville 2/10/97 #ifdef VECTOR_STROKING // Inform the pressure pen that we've finished the stroke CCPen *pPen = GetApplication()->GetPressurePen(); if (pPen != NULL) pPen->EndStroke(); #endif // VECTOR_STROKING // If the operation failed, then tidy up if (Worked==FALSE) FailAndExecute(); else { // Update all the parents of the effected paths. ObjChangeFlags cFlags; ObjChangeParam ObjChange(OBJCHANGE_STARTING,cFlags,NULL,this); ObjChange.Define(OBJCHANGE_FINISHED,cFlags,NULL,this); UpdateChangedNodes(&ObjChange); } // End the operation properly End(); } /******************************************************************************************** > BOOL OpFreeHand::DragKeyPress(KeyPress* pKeyPress, BOOL bSolidDrag) Author: Rik_Heywood (Xara Group Ltd) Created: 16/11/94 Inputs: pKeyPress - pointer to a keypress object Returns: TRUE if it handled the keypress, FALSE otherwise Purpose: Find out if any of the key modifiers are being pressed when the mouse is not moving. ********************************************************************************************/ BOOL OpFreeHand::DragKeyPress(KeyPress* pKeyPress, BOOL bSolidDrag) { // Need something to see if there was a usful keypress // BOOL IsKeyPress = FALSE; // and something to keep them in ClickModifiers ClickMods; ClickMods.Adjust = FALSE; ClickMods.Menu = FALSE; ClickMods.Constrain = FALSE; ClickMods.Alternative1 = FALSE; ClickMods.Alternative2 = FALSE; ClickMods.Pressure = 0; // See if was one of the key we are interested in if (pKeyPress->IsConstrain() || pKeyPress->IsAdjust() || pKeyPress->IsAlternative() || pKeyPress->IsRelease()) { // Set up the click modifier ClickMods.Constrain = pKeyPress->IsConstrainPressed(); ClickMods.Adjust = pKeyPress->IsAdjustPressed(); ClickMods.Alternative1 = pKeyPress->IsAlternativePressed(); // fake a mouse move message DragPointerMove(PreviousPoint, ClickMods, PreviousSpread, FALSE); // Tell the keypress people that we used the keypress return TRUE; } // Did not use the click return FALSE; } /******************************************************************************************** > BOOL OpFreeHand::CompleteOperation() Author: Rik_Heywood (Xara Group Ltd) Created: 25/10/94 Returns: TRUE if it worked, FALSE if not Purpose: Does all the steps required to build a new path, smooth it, try to join it with others and finally insert it into the tree. ********************************************************************************************/ BOOL OpFreeHand::CompleteOperation() { // Create a new path to be inserted into the tree NodePath* pNewNodePath = NULL; if (!CreateNewPath(&pNewNodePath)) return FALSE; if (!SmoothNewCurve(pNewNodePath)) { // Get rid of the path we created and fail delete pNewNodePath; return FALSE; } if (!InsertSmoothCurveWithUndo(pNewNodePath)) { // Get rid of the path we created, and fail pNewNodePath->CascadeDelete(); delete pNewNodePath; return FALSE; } // Mark the selection as no longer valid (the Bounding Box will have changed) SelRange* Selection = GetApplication()->Selection; if (Selection) Selection->Update(); // All worked return TRUE; } /******************************************************************************************** > BOOL OpFreeHand::CreateNewPath(NodePath** pNewPath) Author: Rik_Heywood (Xara Group Ltd) Created: 25/10/94 Outputs: pNewPath - This will be a pointer to an initialised NodePath or NULL Returns: TRUE if it worked, FALSE if something went wrong Purpose: Builds a new Node Path ready to have a Curve fitted into it. If anything goes wrong (not enough memory, the path was not worth fitting etc) then this function fails. ********************************************************************************************/ BOOL OpFreeHand::CreateNewPath(NodePath** pNewPath) { // Get a pointer to the path and set it to NULL NodePath*& pFreeHandPath = *pNewPath; pFreeHandPath = NULL; // Only bother if they had actually drawn something if (LineSegmentCount==0) return FALSE; // See if the path ever got away from the initial snap coord DocCoord* Coords = TrackData->GetCoordArray(); INT32 NumCoords = TrackData->GetNumCoords(); BOOL IsAllSame = TRUE; // loop through all the coords comparing them with the first one for (INT32 i=0; (iSetUpPath(24, 12)) { // The path failed to init, so get rid of it and fail delete pFreeHandPath; pFreeHandPath = NULL; return FALSE; } // Must have worked return TRUE; } /******************************************************************************************** > BOOL OpFreeHand::SmoothNewCurve(NodePath* pNewNodePath) Author: Rik_Heywood (Xara Group Ltd) Created: 25/10/94 Inputs: pNewNodePath - The NodePath that we want to smooth the track data into Returns: TRUE if it worked, FALSE if something went wrong Purpose: Sets up the curve fitter and finally smooths the data in the Track data buffer into the new node path. ********************************************************************************************/ BOOL OpFreeHand::SmoothNewCurve(NodePath* pNewNodePath) { // To work out how much we need to smooth the curve by, we must know what // zoom factor we are currently at DocView* pDocView = DocView::GetSelected(); if (pDocView==NULL) return FALSE; // When this path is built it will actually replace a section from the middle of the // original path, so we must make sure that is starts and ends on the original // The start was done before the drag started, the end can be done now. if ((StartPath!=NULL) && (StartPath==EndPath) && (pJoinInfo->IsNearEndPoint==FALSE)) { // Find out the coord on the original path where the endpoint got closest to PreviousPoint = EndPath->InkPath.ClosestPointTo(Mu, CloseTo); // Add it to the path data if (TrackData->InsertLineTo(PreviousPoint)) { // and add pressure info if needed if (AddPressureToPath) TrackData->AddExtraInfo(CI_PRESSURE, FreeHandPressure); } } // Find out how flat they need the curve. This number represents // how far off the original track data the curve can get before it // is split and turned into more curves. The smaller the number // the tighter the fit. double ScaleFactor = (pDocView->GetViewScale()).MakeDouble(); double ErrorLevel = (64 + (160*Smoothness)) / ScaleFactor; ErrorLevel = ErrorLevel * ErrorLevel; // FitCurve needs the number of points in the path, which is the // number of Line Segments + 1 CurveFitObject CurveFitter(&(pNewNodePath->InkPath), ErrorLevel); // Try to initialise the curve fitter if (!CurveFitter.Initialise(TrackData, LineSegmentCount+1)) return FALSE; // All went ok, so fit the curve to the track data and return success CurveFitter.FitCurve(); return TRUE; } /******************************************************************************************** > BOOL OpFreeHand::InsertSmoothCurveWithUndo(NodePath* pNewNodePath) Author: Rik_Heywood (Xara Group Ltd) Created: 25/10/94 Inputs: pNewNodePath - The NodePath that we are trying to insert into the tree Returns: TRUE if it worked, FALSE if it failed Purpose: Starts the Selection operation (recording the selection state etc) and trys to join our new path with any others touching it, before inserting it into the tree. ********************************************************************************************/ BOOL OpFreeHand::InsertSmoothCurveWithUndo(NodePath* pNewNodePath) { // Start the selection operation. // We don't want to save the end selection status because it will be // restored automatically when the operation is redone if (!DoStartSelOp(FALSE)) return FALSE; // Now we have to go round testing if we are joined to any other paths // and building the appropriate undo info if ((StartPath!=NULL) && (StartPath==EndPath) && (pJoinInfo->IsNearEndPoint==FALSE) && (IsEndNearEndpoint==FALSE)) { // This is a case for replacing a section of an existing path // with the new smoothed path if (!TryToReplaceSection(pNewNodePath)) return FALSE; } else { // If the new curve did not start and end on the same path we made need to make a few adjustments if (pJoinInfo->IsNearEndPoint==FALSE) StartPath = NULL; // this is a case for inserting the path in the normal way // Joining it to other paths that it touches etc if (!TryToJoinNewPathWithOthers(pNewNodePath)) return FALSE; } // Clear the Node path that we made, as it will have been copied into the tree by now pNewNodePath->CascadeDelete(); delete pNewNodePath; // It all worked ok return TRUE; } /******************************************************************************************** > void OpFreeHand::RubOutPath(DocCoord Pos, Spread* pSpread) Author: Rik_Heywood (Xara Group Ltd) Created: 13/4/94 Inputs: Pos - The position of the mouse pSpread - Pointer to the spread that we are drawing on Purpose: Tries to rub out sections of the path that have already been drawn. ********************************************************************************************/ void OpFreeHand::RubOutPath(DocCoord Pos, Spread* pSpread) { // If the mouse has not moved then do not do anything if (Pos==PreviousPoint) return; // Get hold of the blob manager for finding out the size of a blob BlobManager* pBlobMgr = GetApplication()->GetBlobManager(); if (pBlobMgr==NULL) return; // Find out about all the coords in the path already INT32 NumCoords = TrackData->GetNumCoords(); DocCoord* Coords = TrackData->GetCoordArray(); // Get line width information // INT32 LineWidth = GetCurrentLineWidth(); // Find out about the area round the mouse position to consider for rubbing out DocRect BlobRect; pBlobMgr->GetBlobRect(Pos, &BlobRect); // We want to look at the last few coords in the path. So work out how far back to look INT32 StopLookingAt = 1; // Loop through the last few coords to see if the mouse is near them for (INT32 i=NumCoords-2; i>StopLookingAt; i--) { if (BlobRect.ContainsCoord(Coords[i])) { // Rub out the line from this coord to the end of the line RenderRegion* pRegion = DocView::RenderOnTop( NULL, pSpread, UnclippedEOR ); while (pRegion) { for (INT32 n = i+1; n < NumCoords; n++) RenderLine(pRegion, TrackData, n); // Get the Next render region pRegion = DocView::GetNextOnTop( NULL ); } // Rub out the actual path Data if (!TrackData->DeleteFromElement(i+1)) { InformError(_R(IDS_PATH_DELETE_ERROR)); return; } // Change the operations parameters PreviousPoint = Coords[i]; LineSegmentCount = i; // this mouse message is finished with return; } } } /******************************************************************************************** > void OpFreeHand::AddPointsToPath(DocCoord Pos, Spread* pSpread) Author: Rik_Heywood (Xara Group Ltd) Created: 13/4/94 Inputs: Pos - The position of the mouse pSpread - Pointer to the spread that we are drawing on Purpose: Adds a new point to the path of mouse moves. When the drag finishes the mouse moves will be smoothed ********************************************************************************************/ void OpFreeHand::AddPointsToPath(DocCoord PointerPos, Spread* pSpread) { // If this coord is the same as the last one, then do not bother adding to the track data if (PreviousPoint == PointerPos) return; // Insert the move if (TrackData->InsertLineTo(PointerPos)) { // and add pressure info if needed if (AddPressureToPath) TrackData->AddExtraInfo(CI_PRESSURE, FreeHandPressure); } else { // Oh no, we ran out of mem in the middle of making a new path // Tidy up and report back to HQ EndDrag(); // Tell the World that it all went horribly wrong InformError(); // Remove it from the tree and delete it TrackData->ClearPath(); // End this operation and return FailAndExecute(); End(); return; } // Now we have to render a new bit of EORed data to the screen RenderLine(NULL, pSpread, TrackData, TrackData->GetNumCoords()-1); // Make a note of the coord, inc the line count and mark joining to the start as active. PreviousPoint = PointerPos; LineSegmentCount++; CanLineJoin = TRUE; } /******************************************************************************************** > void OpFreeHand::AddStraightLine() Author: Rik_Heywood (Xara Group Ltd) Created: 22/4/94 Purpose: Adds a straight line to the Path, that has been made by the Straight Line Mode. This means it has to set some flags in the path to stop the curve fitter from smoothing this straight line segment out ********************************************************************************************/ void OpFreeHand::AddStraightLine() { PathFlags Flags; Flags.Spare1 = TRUE; // If this coord is the same as the last one, then do not bother adding to the track data if (PreviousPoint == StraightLinePos) return; // add the line segment into the path if (TrackData->InsertLineTo(StraightLinePos, &Flags)) { // and add pressure info if needed if (AddPressureToPath) TrackData->AddExtraInfo(CI_PRESSURE, FreeHandPressure); } else { // Oh no, we ran out of mem in the middle of making a new path // Tidy up and report back to HQ EndDrag(); // Tell the World that it all went horribly wrong InformError(); // Remove it from the tree and delete it TrackData->ClearPath(); // End this operation and return FailAndExecute(); End(); return; } // Count this bit of line LineSegmentCount++; CanLineJoin = TRUE; } /******************************************************************************************** > BOOL OpFreeHand::CheckMouseOverSelectedPath(DocCoord* PointerPos, Spread* pSpread, BOOL* IsNearEndpoint) Author: Rik_Heywood (Xara Group Ltd) Created: 12/4/94 Inputs: PointerPos - The position of the mouse pSpread - the spread that the cursor is over Outputs: IsNearEndPoint - if this function returns TRUE this param is valid, otherwise it is not. It will be TRUE if the cursor was found to be close to an open end of the path, or FALSE if it was near the middle of the path Returns: TRUE if the pointer was over the end of a selected line, FALSE if it was not Purpose: Checks to see if the mouse is over the endpoint of any of the selected paths in the document and if it is, it makes a note of the path and which end it was attached to ********************************************************************************************/ BOOL OpFreeHand::CheckMouseOverSelectedPath(DocCoord* PointerPos, Spread* pSpread, BOOL* IsNearEndpoint) { // Assume we do not find a path EndPath = NULL; // Find out about the blob manager so we can find the size of a blob BlobManager* pBlobMgr = GetApplication()->GetBlobManager(); if (pBlobMgr==NULL) return FALSE; // Find out about the size of a blob DocRect BlobRect; pBlobMgr->GetBlobRect(*PointerPos, &BlobRect); // Find the selected range of objects SelRange* Selected = GetApplication()->FindSelection(); Node* pNode = Selected->FindFirst(); // see if the selection is on the same spread as the drag if (pNode!=NULL) { // Get the spread and return if it is different from the one with the cursor Spread* pNodeSpread = pNode->FindParentSpread(); if (pNodeSpread!=pSpread) return FALSE; } // Walk through the selection while (pNode!=NULL) { if (pNode->IsNodePath()) { // Now we know it's a NodePath, get a pointer to the Path object within it, so // we can find any endpoints Path* ThisPath = &(((NodePath*)pNode)->InkPath); // See if it touches any point in this path INT32 CloseSlot = 0; if (ThisPath->IsNearOpenEnd(BlobRect, &CloseSlot)) { // Yep, better work out where and set the path where is all happens ThisPath->SetPathPosition(CloseSlot); *PointerPos = ThisPath->GetCoord(); *IsNearEndpoint = TRUE; EndPath = (NodePath*)pNode; return TRUE; } // See if it is near the middle of the path // This is only relavent if this node is the same as the one we started on // and we started in the middle of it if ((StartPath!=NULL) && (StartPath==pNode) && (pJoinInfo->IsNearEndPoint==FALSE)) { INT32 Range = BlobRect.Width() / 2; if (ThisPath->IsPointCloseTo(*PointerPos, Range*Range, &CloseTo, &Mu)) { // OK, we are near the path we started on // see if we are in the same sub-path as we started on if (AreSlotsInSameSubPath(CloseTo, pJoinInfo->CloseSlot, ThisPath)) { // Yep, better work out where and set the path where is all happens *IsNearEndpoint = FALSE; EndPath = (NodePath*)pNode; return TRUE; } } } } // Now find the next selected node pNode = Selected->FindNext(pNode); } // we will see if this point is anywhere near the start point if ((BlobRect.ContainsCoord(StartPoint)) && (StartPath==NULL)) { // Note the path that we are starting at and modify the coord // This will make the start and end points the same, causing the path to be closed *PointerPos = StartPoint; // Wait till the cursor has moved out of the original snap range before we start joining if (CanLineJoin==TRUE) { *IsNearEndpoint = TRUE; return TRUE; } } // Did not find a join return FALSE; } /******************************************************************************************** > BOOL OpFreeHand::AreSlotsInSameSubPath(INT32 Slot1, INT32 Slot2, Path* pPath) Author: Rik_Heywood (Xara Group Ltd) Created: 2/6/95 Inputs: Slot1, Slot2 - The slot numbers of the 2 points in question. pPath - the path that the slots live in Returns: TRUE if both these slots live in the same sub-path of pPath, FALSE if they are in different sub-paths, or there was an error. Purpose: This function is used when editing lines with the freehand tool. If the drag was started in the middle of a path and the cursor is over that path again, we need to know if we can complete the edit. This is only possible if the start and end points are both on the same sub-path in the path. This function is used to determine if the 2 points are indeed in the same sub-path to allow the tool to display correct cursor etc. ********************************************************************************************/ BOOL OpFreeHand::AreSlotsInSameSubPath(INT32 Slot1, INT32 Slot2, Path* pPath) { // saftey check ERROR2IF(pPath==NULL, FALSE, "NULL pointer passed to OpFreeHand::AreSlotsInSameSubPath"); // We need to look at the verb array in the path. PathVerb* pVerbs = pPath->GetVerbArray(); INT32 NumSlots = pPath->GetNumCoords(); // find the largest and smallest slot numbers INT32 First = Slot1NumSlots, FALSE, "Bad Slot numbers in OpFreeHand::AreSlotsInSameSubPath"); // loop from the first to the last slot looking for MoveTos while (Last>First) { // See if the current last slot is a MoveTo if (pVerbs[Last]==PT_MOVETO) return FALSE; // move the last point back Last--; } // We did not find any MoveTos between the points return TRUE; } /******************************************************************************************** > void OpFreeHand::SetCursorAndStatus(CursorType CurType) Author: Rik_Heywood (Xara Group Ltd) Created: 9/5/94 Inputs: CurType - The type of cursor to change to Purpose: Chanages the cursor to the type requested and sets the status bar message to the appropriate message for the mode we are in. ********************************************************************************************/ void OpFreeHand::SetCursorAndStatus(CursorType CurType) { // Now, if FlipCursor = TRUE, we flip it! Cursor* WhichCursor = NULL; String_256 StatusMsg(""); switch (CurType) { case NORMAL_CURSOR: WhichCursor = pFreeHandCursor; StatusMsg.Load(_R(IDS_FREEHANDDRAG)); break; case JOIN_CURSOR: WhichCursor = pJoinCursor; StatusMsg.Load(_R(IDS_FREEHANDDRAGJOIN)); break; case STRAIGHTLINE_CURSOR: WhichCursor = pStraightCursor; StatusMsg.Load(_R(IDS_FREEHANDSTRAIGHT)); break; case RUBOUT_CURSOR: WhichCursor = pRubOutCursor; StatusMsg.Load(_R(IDS_FREEHANDRUBOUT)); break; case MODIFY_CURSOR: WhichCursor = pModifyCursor; StatusMsg.Load(_R(IDS_FREEHANDMODIFY)); break; default: return; } // Change Status bar message and the cursor GetApplication()->UpdateStatusBarText(&StatusMsg); if (WhichCursor != MyCurrentCursor) { // set this cursor as the current cursor and immediately display it CursorStack::GSetTop(WhichCursor, CurrentCursorID); // remember this is our current cursor MyCurrentCursor = WhichCursor; } } /******************************************************************************************** > BOOL OpFreeHand::TryToJoinNewPathWithOthers(NodePath* FreePath) Author: Rik_Heywood (Xara Group Ltd) Created: 4/5/94 Inputs: FreePath - The new path that we will try to join with other paths Returns: TRUE if it worked, FALSE if it did not Purpose: Tries to Join the new path with any of the existing paths ********************************************************************************************/ BOOL OpFreeHand::TryToJoinNewPathWithOthers(NodePath* FreePath) { ObjChangeFlags cFlagsDelete(TRUE); ObjChangeFlags cFlagsReplace(FALSE,TRUE); ObjChangeFlags cFlagsChanging; ObjChangeParam ObjChange; if (StartPath != NULL && EndPath != NULL && StartPath != EndPath) { // Cope with the case when the two separate paths are being joined together with this freehand line. // In this case, the EndPath is hidden (effectively deleted), and the StartPath is replaced in the tree // by hiding it and inserting the resultant NodePath in its place // Is it ok to delete the end path? ObjChange.Define(OBJCHANGE_STARTING,cFlagsDelete,EndPath,this); if (!EndPath->AllowOp(&ObjChange)) return TRUE; // Pretend that we've done it. // Is it ok to replace the start path? ObjChange.Define(OBJCHANGE_STARTING,cFlagsReplace,StartPath,this); if (!StartPath->AllowOp(&ObjChange)) return TRUE; // Pretend that we've done it. } else if (StartPath != NULL || EndPath != NULL) { // Some joining will take place with either in StartPath or EndPath // Either the StartPath or the EndPath will be hidden and replaced by another NodePath ObjChange.Define(OBJCHANGE_STARTING,cFlagsChanging,StartPath,this); // If we have a start path, the op will happen to the start path if (StartPath != NULL && !StartPath->AllowOp(&ObjChange)) return TRUE; // Pretend that we've done it. // If we DONT have a start path, the op will happen to the end path if (StartPath == NULL && !EndPath->AllowOp(&ObjChange)) return TRUE; // Pretend that we've done it. } if (StartPath == NULL) { if (EndPath == NULL) { // Trivial Case - No Joining needed NodePath* JoinPath = MakeCopy(FreePath); if (JoinPath == NULL) return FALSE; // Try to close it JoinPath->InkPath.TryToClose(); // Apply some attributes if (!ApplyAttributes(JoinPath, GetWorkingDoc())) return FALSE; if (!AddPressureAttribute(JoinPath)) return(FALSE); // Insert the node and invalidate the region if (!DoInsertNewNode(JoinPath, StartSpread, FALSE)) return FALSE; if (!DoInvalidateNodeRegion(JoinPath, TRUE)) return FALSE; m_pNewNodePath = JoinPath; // Tell the freehand tool that it has a previous path SetRetroPath(JoinPath, 0, JoinPath->InkPath.GetNumCoords()); } else { if (EndPath->InkPath.IsComplexPath()) { // Complex Join (End of new path Joined) if (!ComplexJoin(FreePath, EndPath)) return FALSE; } else { // Simple Join (End of new path Joined) if (!SimpleJoin(FreePath, EndPath)) return FALSE; } } } else { if (EndPath == NULL) { if (StartPath->InkPath.IsComplexPath()) { // Complex Join (Start of new path Joined) if (!ComplexJoin(FreePath, StartPath)) return FALSE; } else { // Simple Join (Start of New Path Joined) if (!SimpleJoin(FreePath, StartPath)) return FALSE; } } else { if (StartPath == EndPath) { if (StartPath->InkPath.IsComplexPath()) { // Complex to Same Complex Join if (!ComplexToComplexJoin(FreePath, StartPath)) return FALSE; } else { // Simple to Same Simple Join (Start = End) if (!SimpleJoin(FreePath, StartPath)) return FALSE; } } else { // Compex To Complex Join (Start and End different and complex) // Complex To Simple Join (Start and End different and start complex) // Complex To Simple Join (Start and End different and end complex) // Simple To Simple Join (Start and End Different) if (!VeryComplexToComplexJoin(FreePath, StartPath, EndPath)) return FALSE; } } } // Worked return TRUE; } /******************************************************************************************** > BOOL OpFreeHand::SimpleJoin(NodePath* FreePath, NodePath* OldPath) Author: Rik_Heywood (Xara Group Ltd) Created: 5/5/94 Inputs: FreePath - The New freehand Path that is to be joined OldPath - The path that we are trying to Join to Returns: TRUE if it worked, FALSE if it failed Purpose: Joins a Simple path with the new freehand path and builds all the undo that is needed in the process. ********************************************************************************************/ BOOL OpFreeHand::SimpleJoin(NodePath* FreePath, NodePath* OldPath) { // First we should make a note of the region that we will need redrawing DocRect InvalidRect = FreePath->GetBlobBoundingRect(); DocRect OriginalRect = OldPath->GetBoundingRect(); // Spread* pSpread = Document::GetSelectedSpread(); InvalidateBrushRegion(OldPath); // Need to know where in the joined path that the new section will be INT32 NumSlots = FreePath->InkPath.GetNumCoords(); INT32 StartSlot = 0; // INT32 OldNumSlots = OldPath->InkPath.GetNumCoords(); // DocCoord* pCoords = FreePath->InkPath.GetCoordArray(); // DocCoord* pOldCoords = OldPath->InkPath.GetCoordArray(); // Make a copy of the node we are joining with NodePath* JoinPath = MakeCopy(OldPath); if (JoinPath == NULL) return FALSE; // first find out if we are editing a brush (ugh, yes I know) AttrBrushType* pAttrBrush; NodeAttribute* pAttr = NULL; BrushHandle Handle = BrushHandle_NoBrush; JoinPath->FindAppliedAttribute(CC_RUNTIME_CLASS(AttrBrushType), &pAttr); if (pAttr != NULL) { pAttrBrush = (AttrBrushType*)pAttr; Handle = pAttrBrush->GetBrushHandle(); } // find out if the two paths join at the start BOOL PathReversed = FALSE; // before Joining them BOOL NewPathReversed = FALSE; if (Handle != BrushHandle_NoBrush) SimpleJoinBrush(JoinPath, &(FreePath->InkPath)); if (!JoinPath->InkPath.SimpleJoin(&FreePath->InkPath, &StartSlot, &NewPathReversed, &PathReversed)) return FALSE; // Insert the new object if (!InsertNewNode(JoinPath, InvalidRect, OldPath)) return FALSE; // Reverse the original mouse move data if the fitted path was reversed if (NewPathReversed) TrackData->Reverse(); // if we are a brush then we don't ever want the path reversed, if (Handle != BrushHandle_NoBrush && PathReversed) { TrackData->Reverse(); JoinPath->InkPath.Reverse(); StartSlot = 0; } // Tell the freehand tool all about it SetRetroPath(JoinPath, StartSlot, NumSlots); m_pNewNodePath = JoinPath; InvalidateBrushRegion(JoinPath); // It Worked! return TRUE; } /******************************************************************************************** > BOOL OpFreeHand::ComplexJoin(NodePath* FreePath, Nodepath* OldPath) Author: Rik_Heywood (Xara Group Ltd) Created: 5/5/94 Inputs: FreePath - The New freehand Path that is to be joined OldPath - The path that we are trying to Join to Returns: TRUE if it worked, FALSE if it failed Purpose: Joins a Complex path with the new freehand path and builds all the undo that is needed in the process. ********************************************************************************************/ BOOL OpFreeHand::ComplexJoin(NodePath* FreePath, NodePath* OldPath) { // First we should make a note of the region that we will need redrawing DocRect InvalidRect = FreePath->GetBlobBoundingRect(); // Need to know where in the joined path that the new section will be INT32 NumSlots = FreePath->InkPath.GetNumCoords(); INT32 StartSlot; // Make a copy of the node we are joining with NodePath* JoinPath = MakeCopy(OldPath); if (JoinPath == NULL) return FALSE; // before Joining them BOOL NewPathReversed = FALSE; if (!JoinPath->InkPath.ComplexJoin(&FreePath->InkPath, &StartSlot, &NewPathReversed)) return FALSE; // Insert the new object if (!InsertNewNode(JoinPath, InvalidRect, OldPath)) return FALSE; // Reverse the original mouse move data if the fitted path was reversed if (NewPathReversed) TrackData->Reverse(); // Tell the freehand tool all about it SetRetroPath(JoinPath, StartSlot, NumSlots); m_pNewNodePath = JoinPath; // It Worked! return TRUE; } /******************************************************************************************** > BOOL OpFreeHand::ComplexToComplexJoin(NodePath* FreePath, NodePath* OldPath) Author: Rik_Heywood (Xara Group Ltd) Created: 5/5/94 Inputs: FreePath - The New freehand Path that is to be joined OldPath - The path that we are trying to Join to Returns: TRUE if it worked, FALSE if it failed Purpose: Joins a Complex path with the new freehand path and builds all the undo that is needed in the process. The new freehand path joins two of the complex paths sub-paths together. ********************************************************************************************/ BOOL OpFreeHand::ComplexToComplexJoin(NodePath* FreePath, NodePath* OldPath) { // First we should make a note of the region that we will need redrawing DocRect InvalidRect = FreePath->GetBlobBoundingRect(); // Need to know where in the joined path that the new section will be INT32 NumSlots = FreePath->InkPath.GetNumCoords(); INT32 StartSlot; // Make a copy of the of the path we are Joining to NodePath* JoinPath = MakeCopy(OldPath); if (JoinPath==NULL) return FALSE; // Join the new path with the one already there BOOL NewPathReversed = FALSE; if (!JoinPath->InkPath.ComplexToSameComplexJoin(&FreePath->InkPath, &StartSlot, &NewPathReversed)) return FALSE; // Insert the new object if (!InsertNewNode(JoinPath, InvalidRect, OldPath)) return FALSE; // Reverse the original mouse move data if the fitted path was reversed if (NewPathReversed) TrackData->Reverse(); // Tell the freehand tool all about it SetRetroPath(JoinPath, StartSlot, NumSlots); // It Worked! return TRUE; } /******************************************************************************************** > BOOL OpFreeHand::VeryComplexToComplexJoin(NodePath* FreePath, NodePath* TopPath, NodePath* BotPath) Author: Rik_Heywood (Xara Group Ltd) Created: 5/5/94 Inputs: FreePath - The New freehand Path that is to be joined TopPath - The path to join with the new freehand path. This is the path that is highest in the Z order of the two paths BotPath - The path to join with the new freehand path. This is the path that is lowest in the Z order of the two paths Returns: TRUE if it worked, FALSE if it failed Purpose: Joins the new freehand path with two other paths (Which may or may not be complex) and builds all the undo info that is needed. ********************************************************************************************/ BOOL OpFreeHand::VeryComplexToComplexJoin(NodePath* FreePath, NodePath* TopPath, NodePath* BotPath) { // First we should make a note of the region that we will need redrawing DocRect InvalidRect = FreePath->GetBlobBoundingRect(); // Need to know where in the joined path that the new section will be INT32 NumSlots = FreePath->InkPath.GetNumCoords(); INT32 StartSlot; // Make a copy of the of the path we are Joining to NodePath* JoinPath = MakeCopy(TopPath); if (JoinPath==NULL) return FALSE; // Merge in the other complex path if (!JoinPath->InkPath.MergeTwoPaths(BotPath->InkPath)) return FALSE; // Join the new path with the one already there BOOL NewPathReversed = FALSE; if (!JoinPath->InkPath.ComplexToSameComplexJoin(&FreePath->InkPath, &StartSlot, &NewPathReversed)) return FALSE; // Insert the new object if (!InsertNewNode(JoinPath, InvalidRect, TopPath, BotPath)) return FALSE; // Reverse the original mouse move data if the fitted path was reversed if (NewPathReversed) TrackData->Reverse(); // Tell the freehand tool all about it SetRetroPath(JoinPath, StartSlot, NumSlots); // It Worked return TRUE; } /******************************************************************************************** > BOOL OpFreeHand::ApplyAttributes(NodePath* NewPath, Document *pDocument) Author: Rik_Heywood (Xara Group Ltd) Created: 4/5/94 Inputs: NewPath - The NodePath to apply the current attributes to pDocument - the document this path will be placed into. Returns: TRUE if it worked, FALSE if it did not Purpose: Applies the current attributes to the Node passed in. It has to find the current document to do this and will ENSURE if it fails to find one. ********************************************************************************************/ BOOL OpFreeHand::ApplyAttributes(NodePath* NewPath, Document *pDocument) { // Find the current document to get the current attributes from ENSURE(pDocument!=NULL, "Null Document while setting attributes for new NodePath"); if (pDocument!=NULL) { // Apply the current attributes to the path if (pDocument->GetAttributeMgr().ApplyCurrentAttribsToNode((NodeRenderableInk*)NewPath)) { // ok special case exception here, if we have a brush attribute then delete it Node* pNode = NewPath->FindFirstChild(CC_RUNTIME_CLASS(AttrBrushType)); if (pNode) { pNode->CascadeDelete(); delete pNode; } return TRUE; } } // We failed to apply the attributes, so fail return FALSE; } /******************************************************************************************** > BOOL OpFreeHand::AddPressureAttribute(NodePath *NewPath) Author: Jason_Williams (Xara Group Ltd) Created: 21/1/97 Inputs: NewPath - The NodePath to apply the attributes to Returns: TRUE if it worked, FALSE if it did not Purpose: If pressure recording is enabled, this method builds attributes to apply to the path in order to attach the pressure as a variable-width stroking description. This attaches an AttrStrokeType to control stroking, and an AttrVariableWidth to record the pressure values ********************************************************************************************/ BOOL OpFreeHand::AddPressureAttribute(NodePath *pNewPath) { if (IsBrushOp()) return TRUE; // Taken out by vector stroking code Neville 2/10/97 #ifdef VECTOR_STROKING ERROR3IF(GetWorkingDoc() == NULL, "No working doc"); ERROR3IF(pNewPath == NULL, "Illegal NULL param"); ERROR3IF(TrackData == NULL, "Trackdata is NULL"); // If pressure recording is disabled, then we don't need to do anything if (TrackData == NULL || !AddPressureToPath) return(TRUE); // --- Generate a smoothed pressure function from the recorded pressure samples // If this fails, then we still return TRUE, and the path just loses pressure information PressureSmoother Bob; ValueFunction *pValFunc = Bob.Smooth(TrackData, GetCurrentLineWidth()); if (pValFunc == NULL) return(TRUE); // We only want to override the stroke type if it is a simple constant-width line // - if there is any new-style stroke type applied, we won't try to override it BOOL ApplyType = FALSE; // BLOCK { Node *pNode = pNewPath->FindFirstChild(); if (pNode != NULL) pNode = pNode->FindNext(CC_RUNTIME_CLASS(AttrStrokeType)); ApplyType = (pNode == NULL); // If there are no StrokeType attrs applied already, we must apply one if (!ApplyType) { // If there is another StrokeType, but it will not stroke using the variable width info, // (i.e. has a NULL PathProcessor attached to it) we will need to replace it. StrokeTypeAttrValue *pSAV = (StrokeTypeAttrValue *) ((AttrStrokeType *)pNode)->GetAttributeValue(); if (pSAV->GetPathProcessor() == NULL) ApplyType = TRUE; } } // --- Create a simple variable-width path stroke attribute AttrStrokeType *pStrokeType = NULL; if (ApplyType) { pStrokeType = new AttrStrokeType; if (pStrokeType == NULL) return(TRUE); // Return TRUE - we'll only lose the pressure info PathProcessorStroke *pProcessor = new PathProcessorStroke; if (pProcessor == NULL) { delete pStrokeType; return(TRUE); // Return TRUE - we'll only lose the pressure info } // And attach the stroker to the attribute ((StrokeTypeAttrValue *)pStrokeType->GetAttributeValue())->SetPathProcessor(pProcessor); } // --- Create a pressure table variable-width attribute AttrVariableWidth *pVarWidth = new AttrVariableWidth; if (pVarWidth != NULL) ((VariableWidthAttrValue *)pVarWidth->GetAttributeValue())->SetWidthFunction(pValFunc); if (pVarWidth == NULL) { if (pStrokeType != NULL) delete pStrokeType; return(TRUE); // Return TRUE - we'll only lose the pressure info } // --- Now apply the attributes to the path, removing any of a similar type which // are already applied to it. if (ApplyType) { DoRemoveAttrTypeFromSubtree(pNewPath, pStrokeType->GetRuntimeClass()); pStrokeType->AttachNode(pNewPath, FIRSTCHILD, FALSE, FALSE); } // And apply the variable width attribute DoRemoveAttrTypeFromSubtree(pNewPath, pVarWidth->GetRuntimeClass()); pVarWidth->AttachNode(pNewPath, FIRSTCHILD, FALSE, FALSE); #endif // VECTOR_STROKING return(TRUE); } /******************************************************************************************** > NodePath* OpFreeHand::MakeCopy(Node* pOriginalNode) Author: Rik_Heywood (Xara Group Ltd) Created: 3/5/94 Inputs: pOriginalNode - The node to do a 'deep' copy of Returns: The Copied node (NULL if it failed) Purpose: Copies the node pOriginalNode and all its children an returns a pointer to them. It makes sure that the Node is a NodePath (Which it should be). ********************************************************************************************/ NodePath* OpFreeHand::MakeCopy(Node* pOriginalNode) { // Make a copy of the node we are joining with Node* pNode; if (!pOriginalNode->NodeCopy(&pNode)) return NULL; // Check that the new node is OK ENSURE(pNode!=NULL, "Copied node seems to be NULL in TryToJoinNewPathWithOthers()"); ENSURE(pNode->IsKindOf(CC_RUNTIME_CLASS(NodePath)), "Copied node was not a Path"); // return it return (NodePath*) pNode; } /******************************************************************************************** > BOOL OpFreeHand::InsertNewNode(NodePath* pNewNode, DocRect& Invalid, Node* pOldNode, Node* pOtherOld = NULL) Author: Rik_Heywood (Xara Group Ltd) Created: 3/5/94 Inputs: pNewNode - The Node to add to the tree Invalid - The region that needs to be invalidated pOldNode - The node that we joined to and will be hidden pOtherOld - We may need to hide 2 other paths. This should be NULL(default) or a pointer to a Node Returns: TRUE if it worked, FALSE if not Purpose: Inserts a new path into the tree and hides the original node that it was Joined to. It will also invalidate the region of the new path that is different. ********************************************************************************************/ BOOL OpFreeHand::InsertNewNode(NodePath* pNewNode, DocRect& Invalid, Node* pOldNode, Node* pOtherOld) { // Insert the new object if (!DoInsertNewNode(pNewNode, pOldNode, NEXT, FALSE)) return FALSE; // Invalidate the appropriate region. // if the filled state of the path has changed, then we need to invalidate the whole thing ENSURE(pOldNode->IsKindOf(CC_RUNTIME_CLASS(NodePath)), "Joining to something that was not a path"); // if (pNewNode->InkPath.IsFilled == ((NodePath*)pOldNode)->InkPath.IsFilled) // { // Invalidate only the new bit // Spread* pSpread = pOldNode->FindParentSpread(); // if (!DoInvalidateRegion(pSpread, Invalid)) // return FALSE; // } // else // invalidate the whole thing if (!DoInvalidateNodeRegion(pNewNode, TRUE)) return FALSE; // Hide the original if (!DoHideNode(pOldNode, TRUE)) return FALSE; // and hide the other original node if it existed if (pOtherOld != NULL) if (!DoHideNode(pOtherOld, TRUE)) return FALSE; // Make sure that the Selection Cache is refreshed SelRange* Selection = GetApplication()->Selection; if (Selection) Selection->Update(); // It worked return TRUE; } /******************************************************************************************** > void OpFreeHand::SetRetroPath(NodePath* pNodePath, INT32 Start, INT32 Len) Author: Rik_Heywood (Xara Group Ltd) Created: 23/5/94 Inputs: pNodePath - The new path that we want to set up for retro fitting Start - The Start slot of the new section Len - The number of slots the new section takes up Purpose: Calls the freehand tool with details about the new path, so that future changes in the smoothness will re-build the new path ********************************************************************************************/ void OpFreeHand::SetRetroPath(NodePath* pNodePath, INT32 Start, INT32 Len) { // Go find the current tool Tool* pTool = Tool::GetCurrent(); if (pTool) { // Make sure that it is the freehand tool, and if so set the previous path if (pTool->GetID()==TOOLID_FREEHAND) ((FreeHandTool*)pTool)->SetPreviousPath(pNodePath, Start, Len); } } /******************************************************************************************** > void OpFreeHand::RenderLine(RenderRegion *pRender, Path *pPath, INT32 CoordIndex, BOOL StartIndex = FALSE) Author: Jason_Williams (Xara Group Ltd) Created: 28/1/97 Inputs: pRender - the RenderReigon to draw into pPath - The path to draw a line segment from CoodIndex - The index of the end coordinate of the line to render. This will render an EORed line from Coords[CoordIndex-1] to Coords[CoordIndex] StartIndex - is Index the start index? If not then we simply render between Index-1 and Index, otherwise we render between Index and NumCoords -1 Purpose: Renders an EORed line between 2 coordinates in the path. May render pressure information feedback using the pressure stored with the path corrdinates. ********************************************************************************************/ void OpFreeHand::RenderLine(RenderRegion *pRender, Path *pPath, INT32 Index, BOOL StartIndex) { ERROR3IF(pRender == NULL || pPath == NULL, "illegal NUL params"); ERROR3IF(Index < 1 || Index >= pPath->GetNumCoords(), "Out of range coordinate"); INT32 EndIndex = Index; if (!StartIndex) { EndIndex = Index; } else EndIndex = pPath->GetNumCoords() - 1; Index--; DocCoord *Coord = pPath->GetCoordArray(); // Always draw the blue centreline pRender->SetLineColour(COLOUR_XORNEW); pRender->DrawLine(Coord[Index], Coord[EndIndex]); // Taken out by vector stroking code Neville 2/10/97 #ifdef VECTOR_STROKING // If we're showing pressure info, and the points are not coincident, and the average // line width is large enough to avoid hideous EOR mess, render parallel width lines if (!AddPressureToPath) return; PathWidth *Width = pPath->GetWidthArray(); if (Width == NULL) return; const INT32 CurrentLineWidth = GetCurrentLineWidth(); const double WidthMul = (double)CurrentLineWidth / (double)EXTRAVALUEMAX; INT32 StartWidth = (INT32) ((double)Width[Index-1] * WidthMul); INT32 EndWidth = (INT32) ((double)Width[Index] * WidthMul); if (StartWidth > CurrentLineWidth) // Damage control in case of rampant pens giving StartWidth = CurrentLineWidth; // out of range values. You may laugh, but that's if (EndWidth > CurrentLineWidth) // exactly what the pen code was doing until I EndWidth = CurrentLineWidth; // just fixed it. This just stops wild rampancy if (Coord[Index-1] != Coord[Index] && (StartWidth + EndWidth) > pRender->GetPixelWidth() * 8) { pRender->SetLineColour(COLOUR_MIDGREY); // Get the normal to the centreline and normalise it to unit length NormCoord Normal( Coord[Index-1].y - Coord[Index].y, -(Coord[Index-1].x - Coord[Index].x)); Normal.Normalise(); // Now plot the two parallel edges DocCoord P1(Coord[Index-1].x + (INT32)(Normal.x * StartWidth), Coord[Index-1].y + (INT32)(Normal.y * StartWidth)); DocCoord P2(Coord[Index].x + (INT32)(Normal.x * EndWidth), Coord[Index].y + (INT32)(Normal.y * EndWidth)); pRender->DrawLine(P1, P2); DocCoord P3(Coord[Index-1].x - (INT32)(Normal.x * StartWidth), Coord[Index-1].y - (INT32)(Normal.y * StartWidth)); DocCoord P4(Coord[Index].x - (INT32)(Normal.x * EndWidth), Coord[Index].y - (INT32)(Normal.y * EndWidth)); pRender->DrawLine(P3, P4); } #endif // VECTOR_STROKING } /******************************************************************************************** > void OpFreeHand::RenderLine(DocRect* Rect, Spread* pSpread, Path *pPath, INT32 CoordIndex, BOOL StartIndex = FALSE) Author: Jason_Williams (Xara Group Ltd) Created: 28/1/97 Inputs: pRender - the RenderReigon to draw into pPath - The path to draw a line segment from CoodIndex - The index of the end coordinate of the line to render. This will render an EORed line from Coords[CoordIndex-1] to Coords[CoordIndex] StartIndex - is Index the start index? If not then we simply render between Index-1 and Index, otherwise we render between Index and NumCoords -1 Purpose: Renders an EORed line between 2 coordinates in the path. May render pressure information feedback using the pressure stored with the path corrdinates. ********************************************************************************************/ void OpFreeHand::RenderLine(DocRect* Rect, Spread* pSpread, Path *pPath, INT32 Index, BOOL StartIndex) { RenderRegion* pRegion = DocView::RenderOnTop(Rect, pSpread, UnclippedEOR ); while ( pRegion ) { RenderLine(pRegion, pPath, Index, StartIndex); // get the next region to draw in pRegion = DocView::GetNextOnTop(Rect); } } /******************************************************************************************** > void OpFreeHand::RenderEorStraightLine(RenderRegion *pRender, DocCoord *pStart, INT32 StartWidth, DocCoord *pEnd, INT32 EndWidth) Author: Jason_Williams (Xara Group Ltd) Created: 22/1/97 Inputs: pRender - the render region to render intp pStart - The start of the line (non-NULL) StartWidth - The line width (in millipoints) at pStart pEnd - The end of the line (non-NULL) EndWidth- The line width (in millipoints) at pEnd Purpose: Simply renders a straight-line segment to the given render region However, it may also elect to show width information by rendering parallel straight line segments on either side of the line ********************************************************************************************/ void OpFreeHand::RenderEorStraightLine(RenderRegion *pRender, DocCoord *pStart, INT32 StartWidth, DocCoord *pEnd, INT32 EndWidth) { ERROR3IF(pRender == NULL || pStart == NULL || pEnd == NULL, "illegal NUL params"); // Always draw the blue centreline pRender->SetLineColour(COLOUR_XORNEW); pRender->DrawLine(*pStart, *pEnd); // Taken out by vector stroking code Neville 2/10/97 #ifdef VECTOR_STROKING // If we're showing pressure info, and the points are not coincident, and // the average line width is large enough to avoid hideous EOR mess, render parallel width lines if (AddPressureToPath && *pStart != *pEnd && (StartWidth + EndWidth) > pRender->GetPixelWidth() * 8) { pRender->SetLineColour(COLOUR_MIDGREY); // Get the normal to the centreline and normalise it to unit length NormCoord Normal( pStart->y - pEnd->y, -(pStart->x - pEnd->x)); Normal.Normalise(); // Now plot the two parallel edges DocCoord P1(pStart->x + (INT32)(Normal.x * StartWidth), pStart->y + (INT32)(Normal.y * StartWidth)); DocCoord P2(pEnd->x + (INT32)(Normal.x * EndWidth), pEnd->y + (INT32)(Normal.y * EndWidth)); pRender->DrawLine(P1, P2); DocCoord P3(pStart->x - (INT32)(Normal.x * StartWidth), pStart->y - (INT32)(Normal.y * StartWidth)); DocCoord P4(pEnd->x - (INT32)(Normal.x * EndWidth), pEnd->y - (INT32)(Normal.y * EndWidth)); pRender->DrawLine(P3, P4); } #endif // VECTOR_STROKING } /******************************************************************************************** > void OpFreeHand::RenderEorStraightLine(DocRect* Rect, Spread* pSpread, DocCoord *pStart, DocCoord *pEnd) Author: Rik_Heywood (Xara Group Ltd) Created: 22/4/94 Inputs: Rect - The cliprect pSpread - The spread to draw to pStart - The start of the line (NULL means "PreviousPoint") pEnd - The end of the line (NULL means "StraightLinePos") Purpose: Renders the EORed rubber band straight line that is used in Straight Line Mode. It simply draws a single Straight Line segment ********************************************************************************************/ void OpFreeHand::RenderEorStraightLine(DocRect* Rect, Spread* pSpread, DocCoord *pStart, DocCoord *pEnd) { if (pStart == NULL) pStart = &PreviousPoint; if (pEnd == NULL) pEnd = &StraightLinePos; INT32 LineWidth = GetCurrentLineWidth(); RenderRegion* pRegion = DocView::RenderOnTop(Rect, pSpread, UnclippedEOR ); while ( pRegion ) { RenderEorStraightLine(pRegion, pStart, LineWidth, pEnd, LineWidth); // get the next region to draw in pRegion = DocView::GetNextOnTop(Rect); } } /******************************************************************************************** > INT32 OpFreeHand::GetCurrentLineWidth(void) Author: Jason_Williams (Xara Group Ltd) Created: 22/1/97 Returns: A line width in millipoints. NOTE that this is half the normal linewidth value (i.e. it's the line radius) Purpose: Determines the current line width (the width of any future lines the user will draw) by asking the attribute manager. ********************************************************************************************/ INT32 OpFreeHand::GetCurrentLineWidth(void) { ERROR3IF(GetWorkingDoc() == NULL, "No working doc!"); AttrLineWidth *pWidthAttr = (AttrLineWidth *) GetWorkingDoc()->GetAttributeMgr(). GetCurrentAttribute(CC_RUNTIME_CLASS(NodeRenderableInk), CC_RUNTIME_CLASS(AttrLineWidth)); INT32 LineWidth = 1000; if (pWidthAttr != NULL) LineWidth = pWidthAttr->Value.LineWidth / 2; return(LineWidth); } /******************************************************************************************** > virtual void OpFreeHand::RenderDragBlobs( DocRect Rect, Spread* pSpread, BOOL bSolidDrag ) Author: Rik_Heywood (Xara Group Ltd) Created: 6/9/93 Inputs: Rect - The Rectangle that needs to be re-rendered pSpread - The spread on which the rendering is to take place Purpose: EORs the whole path onto the screen a segment at a time. It needs to do it a segment at a time as that is how it is drawn as the path is created. This has to redraw it identically or there will be bits of EORed stuff left all over the place. SeeAlso: OpFreeHand::RenderLineSegment() ********************************************************************************************/ void OpFreeHand::RenderDragBlobs( DocRect Rect, Spread* pSpread, BOOL bSolidDrag ) { // Work out which bits we need to draw DocRect* pRect; if (Rect.IsEmpty()) pRect = NULL; else pRect = &Rect; // Get information on the path we've got to render INT32 NumCoords = TrackData->GetNumCoords(); // DocCoord* Coords = TrackData->GetCoordArray(); // Find out the current line width // INT32 LineWidth = GetCurrentLineWidth(); // Render into all the available rectangles RenderRegion* pRegion = DocView::RenderOnTop(pRect, pSpread, UnclippedEOR ); while ( pRegion ) { // Rather than just calling DrawPath, we render each line ourselves - this allows the variable // width indicator lines to be rendered as well, and ensures all the EORing remains consistent. for (INT32 n = 1; n < NumCoords; n++) RenderLine(pRegion, TrackData, n); // get the next region to draw in pRegion = DocView::GetNextOnTop(pRect); } if (IsStraightLineMode) RenderEorStraightLine(pRect, pSpread); } /******************************************************************************************** > BOOL OpFreeHand::TryToReplaceSection(NodePath* pNewNodePath) Author: Rik_Heywood (Xara Group Ltd) Created: 27/10/94 Inputs: pNewNodePath - The New Path that we want to use instead of a section of an existing path Purpose: Replaces a section from the path that the drag was started and finished on with the newly drawn curve section. ********************************************************************************************/ BOOL OpFreeHand::TryToReplaceSection(NodePath* pNewNodePath) { // The elements that the split occured on INT32 FirstElement, SecondElement, NewElements; ObjChangeFlags cFlags; ObjChangeParam ObjChange(OBJCHANGE_STARTING,cFlags,EndPath,this); if (!EndPath->AllowOp(&ObjChange, TRUE)) return TRUE; // Pretend that we've done it. // Invalidate it as it was if (!DoInvalidateNodeRegion(EndPath, TRUE)) return FALSE; // Split old path at start and end points if (!SplitAtPoint(StartPoint, &FirstElement, &NewElements)) return FALSE; if (!SplitAtPoint(PreviousPoint, &SecondElement, &NewElements)) return FALSE; // We may need to adjust the first element if the second element was // before it in the path if (SecondElement<=FirstElement) { // Swap the elements around and move the first one back a bit INT32 Temp = FirstElement + (NewElements/2); FirstElement = SecondElement; SecondElement = Temp; // We will also need to reverse the original curve here // (as well as the original track data) pNewNodePath->InkPath.Reverse(); TrackData->Reverse(); ReverseBrushPressure(); // if we are a brush we will want to reverse our pressure data } // Go and see if which part of the path we should be replacing if (!EndPath->InkPath.IsComplexPath() && EndPath->InkPath.IsFilled) { // Here we need to see if we will replace the wrong bit of the path and try and do something // about it if we are. The wrong bit is defined as follows. If you were to draw a filled shape // such as an ellipse and modified it by drawing a small loop around one of the control points // you should get an ellipse with a small notch cut out of it. However, if you do this around // the Start/End control point you end up with no ellipse and just a small notch. // To get round this, we always try to replace the smallest number of control points possible. INT32 HalfElements = EndPath->InkPath.GetNumCoords() / 2; INT32 ReplaceElements = SecondElement - FirstElement; if (HalfElements < ReplaceElements) { // We were trying to replace more than half of the elements in the path, so actually // replace the other, large half of the path // Here we have to change the the start element of the path, so we have // to hide the original and make a copy of it BOOL IsOk = FALSE; Node* pCopyNode = NULL; CALL_WITH_FAIL(EndPath->NodeCopy(&pCopyNode), this, IsOk); // See if we managed to make a copy of the node if (!IsOk) return FALSE; // Insert the copy if (!DoInsertNewNode((NodeRenderableBounded*)pCopyNode, EndPath, NEXT, FALSE)) return FALSE; // Hide the original if (!DoHideNode(EndPath, TRUE)) return FALSE; // Copy the pointer back to where it used to be EndPath = (NodePath*) pCopyNode; ERROR3IF(!EndPath->IS_KIND_OF(NodePath), "Copy of a Node path was not a node path"); // Make the Last replace element the start/end of the path if (EndPath->InkPath.ChangeStartElement(SecondElement-1)) { // It worked, so modify the Start and End Elements to match FirstElement = 1; SecondElement = EndPath->InkPath.GetNumCoords() - ReplaceElements; // We will also need to reverse the original curve here // (as well as the original track data) pNewNodePath->InkPath.Reverse(); TrackData->Reverse(); } } } // replace section with new path INT32 NumElements = SecondElement - FirstElement; if (!ReplaceMiddleOfPath(pNewNodePath, FirstElement, NumElements)) return FALSE; // Mark the nodes bounding rect as invalid EndPath->InvalidateBoundingRect(); TRACEUSER( "Diccon", _T("Replacing middle of Path\n")); InvalidateBrushRegion(EndPath); // and invalidate how it is now if (!DoInvalidateNodeRegion(EndPath, TRUE)) return FALSE; // Failed to do it return TRUE; } /******************************************************************************************** > BOOL OpFreeHand::SplitAtPoint(const DocCoord& SplitPoint) Author: Rik_Heywood (Xara Group Ltd) Created: 31/10/94 Inputs: SplitPoint - The coord that we want the split to happen close to Outputs: SplitAt - The Slot number of the new end point NewElements - The number of new elements inserted into the path Returns: TRUE if it was able to split the path ok, FALSE if it failed Purpose: Splits the path, building undo info, at the coord supplied. ********************************************************************************************/ BOOL OpFreeHand::SplitAtPoint(const DocCoord& SplitPoint, INT32* SplitAt, INT32* NewElements) { // First we need to get some space to put any new coords etc that will be generated INT32 SplitElement; UINT32 NumElements; PathVerb NewVerbs[6]; DocCoord NewCoords[6]; PathFlags NewFlags[6]; // First we had better go and see if this point is close to any of the existing coords BlobManager* pBlobMgr = GetApplication()->GetBlobManager(); if (pBlobMgr!=NULL) { // There was a blob manager, so we could test the coords // Find out about the area round the mouse position to consider for rubbing out DocRect BlobRect; DocCoord BlobPoint = SplitPoint; pBlobMgr->GetBlobRect(BlobPoint, &BlobRect); // Find out about all the coords in the path already PathFlags* Flags = EndPath->InkPath.GetFlagArray(); DocCoord* Coords = EndPath->InkPath.GetCoordArray(); INT32 NumCoords = EndPath->InkPath.GetNumCoords(); // Loop through all the coords in the path for (INT32 i=0; i0) Flags[i-1].IsRotate = FALSE; if (iInkPath.SplitAtPoint(SplitPoint, &SplitElement, &NumElements, NewVerbs, NewCoords)) { // Make a note of where the new endpoint will be *SplitAt = SplitElement+(NumElements/2); *NewElements = NumElements; // The path was split, so we know where, and how, so let's party on the path PathVerb* Verbs = EndPath->InkPath.GetVerbArray(); PathFlags* Flags = EndPath->InkPath.GetFlagArray(); DocCoord* Coords = EndPath->InkPath.GetCoordArray(); // UINT32 NumCoords = EndPath->InkPath.GetNumCoords(); INT32 NumToChange; // We're adding something, either a line or curve - check which if ((Verbs[SplitElement] & ~PT_CLOSEFIGURE) == PT_BEZIERTO) { // Adding a curve - number to change = 6 NumToChange = 6; // Initialise the flags appropriately for (INT32 i=0; i<6; i++) { // copy the flags from the old curve (repeat the flag in the first 3 elements) NewFlags[i] = Flags[SplitElement+(i%3)]; NewFlags[i].IsSelected = FALSE; // Want it to be a cusp in the middle if ((i>0) && (i<4)) { NewFlags[i].IsSmooth = FALSE; NewFlags[i].IsRotate = FALSE; } } // If this was happening at the end of a path, // then make sure the close figure flag is set correctly if (Verbs[SplitElement+2] & PT_CLOSEFIGURE) NewVerbs[5] |= PT_CLOSEFIGURE; } else { // The slit happened in a straight line section NumToChange = 2; NewFlags[0] = NewFlags[1] = Flags[SplitElement]; NewFlags[0].IsSmooth = NewFlags[0].IsRotate = FALSE; NewFlags[0].IsSelected = FALSE; // Make sure that the close figure flag is maintained if (Verbs[SplitElement] & PT_CLOSEFIGURE) NewVerbs[1] |= PT_CLOSEFIGURE; } // Start performing actions to insert the split point with full undo Action* UnAction; ActionCode Act = RemovePathElementAction::Init(this, &UndoActions, NumToChange/2, SplitElement, (Action**)(&UnAction)); // if we failed to init the action, then fail this operation if (Act==AC_FAIL) return FALSE; // record details about the path in the action before we start inserting stuff ((RemovePathElementAction*)UnAction)->RecordPath(EndPath); // Move to the place in the path where the split took place EndPath->InkPath.SetPathPosition(SplitElement); PathFlags InsertFlags; BOOL InsertWorked; // insert a curve or a line, depending on where the split happened if (NumToChange==6) InsertWorked = EndPath->InkPath.InsertCurveTo(NewCoords[0], NewCoords[1], NewCoords[2], &InsertFlags); else InsertWorked = EndPath->InkPath.InsertLineTo(NewCoords[0], &InsertFlags); // see if the insertion worked if (!InsertWorked) return FALSE; // Build an action to record the changes we're about to make to the path ModifyPathAction* ModAction; Act = ModifyPathAction::Init(this, &UndoActions, NumToChange, (Action**)(&ModAction)); if (Act==AC_FAIL) return FALSE; // If the action is happy... if (Act!=AC_NORECORD) { // Some pointers to what has changed PathVerb* ChangedVerbs; PathFlags* ChangedFlags; DocCoord* ChangedCoords; INT32* ChangedIndices; // memory for the details about what has changed ALLOC_WITH_FAIL(ChangedVerbs, (PathVerb*) CCMalloc(NumToChange * sizeof(PathVerb)), this); ALLOC_WITH_FAIL(ChangedFlags, (PathFlags*) CCMalloc(NumToChange* sizeof(PathFlags)), this); ALLOC_WITH_FAIL(ChangedCoords, (DocCoord*) CCMalloc(NumToChange* sizeof(DocCoord)), this); ALLOC_WITH_FAIL(ChangedIndices, (INT32*) CCMalloc(NumToChange* sizeof(INT32)), this); // See of all the allocations worked if (!ChangedVerbs || !ChangedFlags || !ChangedCoords || !ChangedIndices) { // They did not, so free them up and fail if (ChangedVerbs) CCFree(ChangedVerbs); if (ChangedFlags) CCFree(ChangedFlags); if (ChangedCoords) CCFree(ChangedCoords); if (ChangedIndices) CCFree(ChangedIndices); return FALSE; } // Reread the pointers, in case they've changed Verbs = EndPath->InkPath.GetVerbArray(); Flags = EndPath->InkPath.GetFlagArray(); Coords = EndPath->InkPath.GetCoordArray(); // Now record the arrays... for (INT32 i=0; iStoreArrays(ChangedVerbs, ChangedFlags, ChangedCoords, ChangedIndices, EndPath); } // Reread the pointers, in case they've changed Verbs = EndPath->InkPath.GetVerbArray(); Flags = EndPath->InkPath.GetFlagArray(); Coords = EndPath->InkPath.GetCoordArray(); // Now that the undo rigmarole has been done, let's change the path for (INT32 i=0; iInkPath.GetCoordArray(); INT32 NumCoords = EndPath->InkPath.GetNumCoords(); // see if the split point is over the last point on the curve if (Coords[NumCoords-1]==SplitPoint) *SplitAt = NumCoords; } // all worked return TRUE; } /******************************************************************************************** > BOOL OpFreeHand::ReplaceMiddleOfPath(NodePath* pNewPath, INT32 FirstChangedIndex, INT32 NumElements) Author: Rik_Heywood (Xara Group Ltd) Created: 1/11/94 Inputs: pNewPath - The New curve that has just been draw FirstChangedIndex - The first coord in the path that is to be replaced NumElements - The number of elements in the path that need replacing Returns: TRUE if it worked, FALSE if not Purpose: This will replace a section of the path with a new path, building undo as it goes. This function will also set up the Retro Curve fitting data so that it is able to retro fit the new section of the path. ********************************************************************************************/ BOOL OpFreeHand::ReplaceMiddleOfPath(NodePath* pNewPath, INT32 FirstChangedIndex, INT32 NumElements) { // if we are a brush then do our special smoothing //etroSmoothBrush(pNewPath); // if we are editing a brush with timestamp or pressure lists EditBrushLists(pNewPath, FirstChangedIndex, NumElements); // If there is something to remove from this path, the do it if (NumElements>0) { // Make ourselves an insert action as we are about to delete the // old bits of the path we no longer want ActionCode Act; InsertPathElementAction* ModAction; Act = InsertPathElementAction::Init(this, &UndoActions, NumElements, FirstChangedIndex, (Action**)(&ModAction)); // Did we init the action ok if (Act == AC_FAIL) return FALSE; // If the action will let us, record the details of what we want to delete if (Act!=AC_NORECORD) { // We need to store info about all the elements we are going to delete PathVerb* ChangedVerbs; DocCoord* ChangedCoords; PathFlags* ChangedFlags; // Get some memory to put them in ALLOC_WITH_FAIL(ChangedVerbs, (PathVerb*)CCMalloc(NumElements*sizeof(PathVerb)), this); ALLOC_WITH_FAIL(ChangedCoords, (DocCoord*)CCMalloc(NumElements*sizeof(DocCoord)), this); ALLOC_WITH_FAIL(ChangedFlags, (PathFlags*)CCMalloc(NumElements*sizeof(PathFlags)), this); // Make sure that we got the memory if (!ChangedVerbs || !ChangedCoords || !ChangedFlags) { if (ChangedVerbs) CCFree(ChangedVerbs); if (ChangedCoords) CCFree(ChangedCoords); if (ChangedFlags) CCFree(ChangedFlags); return FALSE; } // Get pointers to all the arrays of the path PathVerb* Verbs = EndPath->InkPath.GetVerbArray(); DocCoord* Coords = EndPath->InkPath.GetCoordArray(); PathFlags* Flags = EndPath->InkPath.GetFlagArray(); // Now copy the data from the path into the arrays for (INT32 i=0; iRecordPath(ChangedVerbs, ChangedFlags, ChangedCoords, EndPath); } // Delete the elements from the path that we no longer want EndPath->InkPath.DeleteSection(FirstChangedIndex, NumElements); } // Next we have to add the new points, so build undo to remove them Action* UnAction; INT32 NumCoords = pNewPath->InkPath.GetNumCoords() - 1; ActionCode Act = RemovePathElementAction::Init(this, &UndoActions, NumCoords, FirstChangedIndex, (Action**)(&UnAction)); // if we failed to init the action, then fail this operation if (Act==AC_FAIL) return FALSE; // record details about the path in the action before we start inserting stuff ((RemovePathElementAction*)UnAction)->RecordPath(EndPath); // Insert stuff here EndPath->InkPath.InsertSection(FirstChangedIndex, NumCoords); // Get pointers to all the arrays of the original path PathVerb* Verbs = EndPath->InkPath.GetVerbArray(); DocCoord* Coords = EndPath->InkPath.GetCoordArray(); PathFlags* Flags = EndPath->InkPath.GetFlagArray(); // Get the pointer to all the arrays of the new path data PathVerb* NewVerbs = pNewPath->InkPath.GetVerbArray(); DocCoord* NewCoords = pNewPath->InkPath.GetCoordArray(); PathFlags* NewFlags = pNewPath->InkPath.GetFlagArray(); // Copy the new path data into the hole we have made FirstChangedIndex--; for (INT32 i=1; i<=NumCoords; i++) { Verbs[FirstChangedIndex+i] = NewVerbs[i]; Coords[FirstChangedIndex+i] = NewCoords[i]; Flags[FirstChangedIndex+i] = NewFlags[i]; } // set up retro stuff here SetRetroPath(EndPath, FirstChangedIndex, pNewPath->InkPath.GetNumCoords()); return TRUE; } /******************************************************************************************** > BOOL OpFreeHand::EditBrushLists(NodePath* pNewPath, INT32 FirstChangedIndex, INT32 NumElements) Author: Diccon_Yamanaka (Xara Group Ltd) Created: 19/3/2000 Inputs: pNewPath - The New curve that has just been draw FirstChangedIndex - The first coord in the path that is to be replaced NumElements - The number of elements in the path that need replacing Returns: TRUE if it worked, FALSE if not Purpose: When a path is edited that has a timestamping brush applied to it we those points are stored in a list that is generated when the path is drawn. If we subsequently want to edit that path we have to insert or remove points from that list. This function works out what points should be inserted or removed, and performs that action on the applied brush. ********************************************************************************************/ BOOL OpFreeHand::EditBrushLists(NodePath* pNewPath, INT32 FirstChangedIndex, INT32 NumElements) { // what we are going to do here is find out whether or not the path we are editing is brushed // and if so whether or not it uses pressure data. If so then we will generate a list of pressure // data and insert it. ERROR2IF(pNewPath == NULL, FALSE, "pNewPath is NULL in OpFreeHand::EditPressureList"); /* First up, find the nearest applied brush attribute. If there isn't one or it has no pressure list then we don't have to bother with all this */ AttrBrushType* pAttrBrush = NULL; pAttrBrush = EndPath->GetAppliedBrushAttribute(); if (pAttrBrush == NULL) return TRUE; // if we are attempting to edit an existing brush that does not have an existing pressure // cache then leave now if (!pAttrBrush->ContainsPressureCache()) return TRUE; // most of the action takes place in the attribute value so get that also BrushAttrValue* pVal = (BrushAttrValue*)pAttrBrush->GetAttributeValue(); if (pVal == NULL) return TRUE; /* next, record the following distances: - original path length; - distance to first changed point; - distance to last changed point; */ // double OriginalLength = EndPath->InkPath.GetPathLength(); MILLIPOINT DistToFirstPoint = -1; MILLIPOINT DistToLastPoint = -1; DocCoord* pCoords = EndPath->InkPath.GetCoordArray(); DocCoord FirstChangedPoint; DocCoord LastChangedPoint; if (pCoords == NULL) return FALSE; if (FirstChangedIndex + NumElements > EndPath->InkPath.GetNumCoords()) { ERROR3("Illegal number of coordinates"); return FALSE; } // get the coordinates from the array FirstChangedPoint = pCoords[FirstChangedIndex]; LastChangedPoint = pCoords[FirstChangedIndex + NumElements]; EndPath->InkPath.GetDistanceToPoint(FirstChangedPoint, &DistToFirstPoint); EndPath->InkPath.GetDistanceToPoint(LastChangedPoint, &DistToLastPoint); // Now find the indices into the pressure list that correspond to those distances INT32 StartPressureIndex = pVal->GetPressureListIndexAtDistance(DistToFirstPoint); if (StartPressureIndex == -1) { ERROR3("StartPressureIndex is -1 in OpDrawBrush::EditPressureList"); return FALSE; } INT32 EndPressureIndex = pVal->GetPressureListIndexAtDistance(DistToLastPoint); if (EndPressureIndex == -1 || EndPressureIndex <= StartPressureIndex) { ERROR3("EndPressureIndex is invalid in OpDrawBrush::EditPressureList"); return FALSE; } UINT32 NumObjects = EndPressureIndex - StartPressureIndex; CDistanceSampler* pSampler = OpDrawBrush::GeneratePressureData(pAttrBrush, DistToFirstPoint, DistToLastPoint, (MILLIPOINT)pNewPath->InkPath.GetPathLength()); if (pSampler == NULL) // did it fail, of so we must quit return FALSE; /* now get the pressure points list and ask the attribute to clear all the points between the edited distances*/ // Invalidate it as it was pAttrBrush->ClearCachedRect(); DocRect Rect = pAttrBrush->GetAttrBoundingRect(EndPath); EndPath->ReleaseCached(); Spread* pSpread = Document::GetSelectedSpread(); if (!DoInvalidateRegion(pSpread, Rect)) return FALSE; // now make a removepoints action RemovePressurePointsAction* pAction; if (RemovePressurePointsAction::Init(this, &UndoActions, pAttrBrush, (UINT32)StartPressureIndex, NumObjects, NULL, &pAction) == AC_FAIL) return FALSE; // now insert our new points into the attribute AddPressurePointsAction* pAddAction; // find out how many points we're inserting UINT32 NumAddPoints = pSampler->GetNumItems(); if (AddPressurePointsAction::Init(this, &UndoActions, pAttrBrush, pSampler, (UINT32)StartPressureIndex, NumAddPoints, &pAddAction) == AC_FAIL) return FALSE; // invalidate the whole thing3 pAttrBrush->ClearCachedRect(); Rect = pAttrBrush->GetAttrBoundingRect(EndPath); EndPath->ReleaseCached(); if (!DoInvalidateRegion(pSpread, Rect)) return FALSE; return TRUE; } /******************************************************************************************** > BOOL OpFreeHand::SimpleJoinBrush(NodePath* pInsertedNode, Path* pNewPath) Author: Diccon_Yamanaka (Xara Group Ltd) Created: 19/3/2000 Inputs: pInsertedNode - the nodepath that we have just inserted pNewPath - The New curve that has just been drawn Returns: TRUE if it worked, FALSE if not Purpose: Currently this only has to do work if we are editing a brush containing pressure data. In this case we must generate a new list of pressure info and insert it into the brush. ********************************************************************************************/ BOOL OpFreeHand::SimpleJoinBrush(NodePath* pInsertedNode, Path* pNewPath) { if (pInsertedNode == NULL || pNewPath == NULL) { ERROR3("Null inputs to OpDrawBrush::SimpleJoinBrush"); return FALSE; } // first check to see if the edited path has a brush applied to it, // if it doesn't then we'll leave NodeAttribute* pAttr; AttrBrushType* pAttrBrush; pInsertedNode->FindAppliedAttribute(CC_RUNTIME_CLASS(AttrBrushType), &pAttr); if (pAttr == NULL) return TRUE; pAttrBrush = (AttrBrushType*)pAttr; // if our brush has a 'no brush' handle then likewise if (pAttrBrush->GetBrushHandle() == BrushHandle_NoBrush) return TRUE; // if our brush is not either timestamping or has a pressure list then we will leave aswell if (!pAttrBrush->ContainsPressureCache() && !pAttrBrush->IsTimeStamping()) return TRUE; // Invalidate the brush rect NodePath* EditPath = StartPath; if (EditPath == NULL) EditPath = EndPath; if (EditPath != NULL) { pAttrBrush->ClearCachedRect(); DocRect BRect = pAttrBrush->GetAttrBoundingRect(EditPath); EditPath->ReleaseCached(); Spread* pSpread = Document::GetSelectedSpread(); if (pSpread != NULL) DoInvalidateRegion(pSpread, BRect); } else return FALSE; // find out what type of join we have SimpleJoinType JoinType = GetSimpleJoinType(pNewPath, &(pInsertedNode->InkPath)); if (JoinType == JOINTYPE_NONE) { ERROR3("No join type in OpDrawBrush::SimpleJoinBrush"); return TRUE; } // we need to know the distance along the original path where we want to insert the // new points. As this is a simple join it will either be at the beginning or the end. // we also need to know the distance to the end of the new points, as this is required // for the undo. MILLIPOINT NewPathLength = (MILLIPOINT)pNewPath->GetPathLength(); MILLIPOINT OldPathLength = -1; if (StartPath != NULL) OldPathLength = (MILLIPOINT)StartPath->InkPath.GetPathLength(); else { if (EndPath != NULL) OldPathLength = (MILLIPOINT)EndPath->InkPath.GetPathLength(); } MILLIPOINT InsertDistance = -1; MILLIPOINT EndInsertDistance = -1; INT32 StartIndex = 0; //for the pressure insert // ask the attribute for its pressure cache CDistanceSampler* pAttrData = pAttrBrush->GetPressureCache(); if (pAttrData == NULL) return FALSE; // according to the type of join we will want to insert our new data at the beginning or the // end, we may also want to reverse it. We need the distance values to give to the pressure // generator function switch (JoinType) { case JOINTYPE_NEWSTART_TO_OLDSTART: InsertDistance = -1; EndInsertDistance = 0; // indicates that we are inserting at the start StartIndex = 0; break; case JOINTYPE_NEWSTART_TO_OLDEND: InsertDistance = OldPathLength; // indicates that we are inserting at the end EndInsertDistance = -1; StartIndex = pAttrData->GetInternalIndexFromDistance(OldPathLength); if (StartIndex==-1) StartIndex = pAttrData->GetNumItems(); break; case JOINTYPE_NEWEND_TO_OLDSTART: InsertDistance = -1; EndInsertDistance = 0; StartIndex = 0; break; case JOINTYPE_NEWEND_TO_OLDEND: InsertDistance = OldPathLength; EndInsertDistance = -1; StartIndex = pAttrData->GetInternalIndexFromDistance(OldPathLength); if (StartIndex==-1) StartIndex = pAttrData->GetNumItems(); break; default: ERROR3("Unknown join type in OpDrawBrush::SimpleJoinBrush"); return FALSE; } // get ourselves a data cache to insert courtesy of our static function CDistanceSampler* pSampler = OpDrawBrush::GeneratePressureData(pAttrBrush, InsertDistance, EndInsertDistance, NewPathLength); if (pSampler == NULL) return FALSE; // make the action to insert the pressure list AddPressurePointsAction* pAction; UINT32 NumPoints = pSampler->GetNumItems(); if (AddPressurePointsAction::Init(this, &UndoActions, pAttrBrush, pSampler, StartIndex, NumPoints, &pAction) == AC_FAIL) return FALSE; // Invalidate the brush rect again if (EditPath != NULL) { pAttrBrush->ClearCachedRect(); DocRect BRect = pAttrBrush->GetAttrBoundingRect(EditPath); EditPath->ReleaseCached(); Spread* pSpread = Document::GetSelectedSpread(); if (pSpread != NULL) DoInvalidateRegion(pSpread, BRect); } return TRUE; } /******************************************************************************************** > BOOL OpFreeHand::RetroSmoothBrush(NodePath* pNewPath) Author: Diccon_Yamanaka (Xara Group Ltd) Created: 19/3/2000 Inputs: pNewPath - The New curve that has just been drawn Returns: TRUE Purpose: As above, the base class version does nothing ********************************************************************************************/ BOOL OpFreeHand::RetroSmoothBrush(NodePath* pNewNodePath) { return TRUE; } /******************************************************************************************** > BOOL OpFreeHand::ReverseBrushPressure() Author: Diccon_Yamanaka (Xara Group Ltd) Created: 19/3/2000 Inputs: - Returns: TRUE Purpose: The base class version does nothing, derived class should reverse its pressure cache ********************************************************************************************/ BOOL OpFreeHand::ReverseBrushPressure() { return TRUE; } /******************************************************************************************** > BOOL OpFreeHand::GetSimpleJoinType(NodePath* pNewNodePath) Author: Diccon_Yamanaka (Xara Group Ltd) Created: 21/6/2000 Inputs: pNewNodePath - The New Path that we want to join to an existing path ReversedPath Returns: the type of join, (an enumerated type, see the header file) Purpose: This function works out how the new path joins onto the existing path ********************************************************************************************/ SimpleJoinType OpFreeHand::GetSimpleJoinType(Path* pNewPath, Path* pExistingPath) { if (pNewPath == NULL || pExistingPath == NULL) return JOINTYPE_NONE; // we must join to one of these paths if (StartPath == NULL && EndPath == NULL) return JOINTYPE_NONE; // get the new coordinate array DocCoord* pNewCoords = pNewPath->GetCoordArray(); if (pNewCoords == NULL) return JOINTYPE_NONE; INT32 NumCoords = pNewPath->GetNumCoords(); // get the start and end coordinates of the new path DocCoord NewStart = pNewCoords[0]; DocCoord NewEnd = pNewCoords[NumCoords-1]; SimpleJoinType ReturnVal = JOINTYPE_NONE; /* DocCoord* pOldCoords = pExistingPath->GetCoordArray(); if (pOldCoords != NULL) { INT32 NumOldCoords = pExistingPath->GetNumCoords(); DocCoord OldStart = pOldCoords[0]; DocCoord OldEnd = pOldCoords[NumOldCoords -1]; // ok we don't have to be dead on here MILLIPOINT NewStartToOldStart = NewStart.Distance(OldStart); MILLIPOINT NewStartToOldEnd = NewStart.Distance(OldEnd); // for some reason these must be reversed if (NewStartToOldStart < NewStartToOldEnd) ReturnVal = JOINTYPE_NEWSTART_TO_OLDSTART; else ReturnVal = JOINTYPE_NEWSTART_TO_OLDEND; } return Retural; */ if (StartPath != NULL) { DocCoord* pOldCoords = StartPath->InkPath.GetCoordArray(); if (pOldCoords != NULL) { INT32 NumOldCoords = StartPath->InkPath.GetNumCoords(); DocCoord OldStart = pOldCoords[0]; DocCoord OldEnd = pOldCoords[NumOldCoords -1]; // ok we don't have to be dead on here MILLIPOINT NewStartToOldStart = (MILLIPOINT)NewStart.Distance(OldStart); MILLIPOINT NewStartToOldEnd = (MILLIPOINT)NewStart.Distance(OldEnd); if (NewStartToOldStart < NewStartToOldEnd) ReturnVal = JOINTYPE_NEWSTART_TO_OLDSTART; else ReturnVal = JOINTYPE_NEWSTART_TO_OLDEND; } } // if endpath is not null then we are joining the end of the new path to one of the // ends of the old path if (EndPath != NULL) { DocCoord* pOldCoords = EndPath->InkPath.GetCoordArray(); if (pOldCoords != NULL) { INT32 NumOldCoords = EndPath->InkPath.GetNumCoords(); DocCoord OldStart = pOldCoords[0]; DocCoord OldEnd = pOldCoords[NumOldCoords-1]; // ok we don't have to be dead on here const MILLIPOINT NewEndToOldStart = (MILLIPOINT)NewEnd.Distance(OldStart); const MILLIPOINT NewEndToOldEnd = (MILLIPOINT)NewEnd.Distance(OldEnd); if (NewEndToOldStart < NewEndToOldEnd) ReturnVal = JOINTYPE_NEWEND_TO_OLDSTART; else ReturnVal = JOINTYPE_NEWEND_TO_OLDEND; } } return ReturnVal; } /******************************************************************************************** > BOOL OpFreeHand::Declare() Author: Rik_Heywood (Xara Group Ltd) Created: 5/7/93 Returns: TRUE if all went OK, False otherwise Purpose: Adds the operation to the list of all known operations ********************************************************************************************/ BOOL OpFreeHand::Declare() { return (RegisterOpDescriptor( 0, _R(IDS_FREE_HAND_TOOL), CC_RUNTIME_CLASS(OpFreeHand), OPTOKEN_FREEHAND, OpFreeHand::GetState, 0, /* help ID */ _R(IDBBL_FREEHANDTOOLOP), 0 /* bitmap ID */)); } /******************************************************************************************** > OpState OpFreeHand::GetState(String_256* Description, OpDescriptor*) Author: Rik_Heywood (Xara Group Ltd) Created: 5/7/93 Outputs: Description - GetState fills this string with an approriate description of the current state of the push tool Returns: The state of the operation, so that menu items (ticks and greying can be done properly Purpose: Find out the state of the operation at the specific time ********************************************************************************************/ OpState OpFreeHand::GetState(String_256* Description, OpDescriptor*) { OpState Blobby; return Blobby; } /******************************************************************************************** > NodeBlend* OpFreeHand::GetParentOfNodePath() Author: Diccon_Yamanaka (Xara Group Ltd) Created: 26/9/99 inputs : - outputs: returns: pointer to the first nodeblend in the selection if successful, NULL otherwise Purpose: To determine if the current selection is a blend on a curve *********************************************************************************************/ NodeGroup* OpFreeHand::GetParentOfNodePath() { NodeGroup* pParent = NULL; if (m_pNewNodePath == NULL) // if no blendpath node then there won't be a group parent return NULL; if (!m_pNewNodePath->IsANodeBlendPath()) return NULL; pParent = (NodeGroup*)m_pNewNodePath->FindParent(); ERROR2IF(!(pParent->IS_KIND_OF(NodeGroup)), NULL, "Node is not NodeGroup"); return pParent; } /******************************************************************************************** > void OpFreeHand::InsertChangeBlendStepsAction(NodeBlend* pNodeBlend) Author: Diccon_Yamanaka (Xara Group Ltd) Created: 1/9/99 Inputs: the node blend to change Outputs: - Returns: - Purpose: To adjust the number of steps in a blend on a path as a result of the path being edited (which is why its here rather than in the blend code). First the number of steps is calculated from the new path distance and if different a new action is inserted Errors: Error2 if pNodeBlend is null SeeAlso: called from OpNodePathEditBlob::DragFinished or OpNodePathAddEndpoint::CompleteThisPath ********************************************************************************************/ BOOL OpFreeHand::InsertChangeBlendStepsAction(NodeBlend* pNodeBlend) { ERROR2IF(pNodeBlend==NULL, FALSE, "NodeBlend pointer is NULL"); // do we wish to keep the distance between steps constant? If so then determine // how many steps we need with the new length and make an action. if (pNodeBlend->GetEditState() == EDIT_DISTANCE) { UINT32 NewNumSteps = 0; UINT32 OldNumSteps = pNodeBlend->GetNumBlendSteps(); double DistanceEntered = pNodeBlend->GetDistanceEntered(); NodeBlendPath* pNodeBlendPath = pNodeBlend->GetNodeBlendPath(0); double NewPathLength = pNodeBlendPath->GetPathLength(); NewNumSteps = (UINT32)(NewPathLength/DistanceEntered); BOOL ok = TRUE; if (OldNumSteps != NewNumSteps) { ChangeBlendStepsAction* pAction; ok = (InvalidateBoundsAction::Init(this,&UndoActions,pNodeBlend,TRUE) != AC_FAIL); if (ok) ok = ChangeBlendStepsAction::Init(this,&UndoActions,pNodeBlend,OldNumSteps,DistanceEntered, &pAction) != AC_FAIL; if (ok) ok = DoInvalidateNodeRegion(pNodeBlend,TRUE,FALSE); if (ok) ok = (InvalidateBoundsAction::Init(this,&UndoActions,pNodeBlend,TRUE) != AC_FAIL); pNodeBlend->SetNumBlendSteps(NewNumSteps); // added 9/9/99 now we are shifting the last object along the curve to ensure precise // step distances. To do this we must set the proportion of the curve used in the // NodeBlender objects. if (ok) { double PathDistanceUsed = NewNumSteps * DistanceEntered; double PathProportion = PathDistanceUsed / NewPathLength; // double PathDistanceUnused = NewPathLength - PathDistanceUsed; if (PathProportion != 1.0) { //ChangeBlenderAction* pBlenderAction; ChangeBlenderOpParam BlenderParam; BlenderParam.m_ChangeType = CHANGEBLENDER_PATHEND; NodeBlender* pNodeBlender = pNodeBlend->FindFirstBlender(); INT32 NumBlenders = pNodeBlend->GetNumBlenders(); while (pNodeBlender != NULL) { NumBlenders--; if (NumBlenders ==0) { BlenderParam.m_NewPathEnd = PathProportion; ok = ChangeBlenderAction::Init(this, &UndoActions, pNodeBlender, BlenderParam); if (ok) { DocCoord NewPoint; double ExtraParam = 0.0; //passed to the function but not used afterwards ok = pNodeBlender->GetPointOnNodeBlendPath(1.0,&NewPoint,&ExtraParam); if (ok) { NodeRenderableInk* pEnd = pNodeBlender->GetNodeEnd(); NodeBlend* pNodeBlend = pNodeBlender->GetNodeBlend(); ok = ((pEnd != NULL) && (pNodeBlend != NULL)); if (ok) ok = pNodeBlend->TransformNodeToPoint(pEnd,&NewPoint,this,ExtraParam); } } } pNodeBlender = pNodeBlend->FindNextBlender(pNodeBlender); } } // end if (pathproportion.. } // end if(ok) } } return TRUE; } /******************************************************************************************** > BOOL OpFreeHand::InvalidateBrushRegion(NodePath* pNodePath) Author: Diccon_Yamanaka (Xara Group Ltd) Created: 26/9/99 inputs : pNodePath - the nodepath that we have edited outputs: returns: TRUE if successful, or if we are not really editing a brush, FALSE if something went wrong Purpose: We may be editing a brushed path with the FHT so this function makes sure the correct region is invalidated *********************************************************************************************/ BOOL OpFreeHand::InvalidateBrushRegion(NodePath* pNodePath) { ERROR2IF(pNodePath == NULL, FALSE, "Nodepath is NULL in OpFreeHand::InvalidateBrushRegion"); AttrBrushType* pAttrBrush; NodeAttribute* pAttr = NULL; BrushHandle Handle = BrushHandle_NoBrush; pNodePath->FindAppliedAttribute(CC_RUNTIME_CLASS(AttrBrushType), &pAttr); if (pAttr != NULL) { pAttrBrush = (AttrBrushType*)pAttr; Handle = pAttrBrush->GetBrushHandle(); } if (Handle == BrushHandle_NoBrush) return TRUE; pAttrBrush->ClearCachedRect(); pAttrBrush->FlushCache(); DocRect BRect = pNodePath->GetBoundingRect(); //pAttrBrush->GetAttrBoundingRect(pNodePath); pNodePath->ReleaseCached(); pAttrBrush->ClearCachedRect(); Spread* pSpread = Document::GetSelectedSpread(); if (pSpread != NULL) return DoInvalidateRegion(pSpread, BRect); else return FALSE; }