opfree.cpp

Go to the documentation of this file.
00001 // $Id: opfree.cpp 1282 2006-06-09 09:46:49Z alex $
00002 /* @@tag:xara-cn@@ DO NOT MODIFY THIS LINE
00003 ================================XARAHEADERSTART===========================
00004  
00005                Xara LX, a vector drawing and manipulation program.
00006                     Copyright (C) 1993-2006 Xara Group Ltd.
00007        Copyright on certain contributions may be held in joint with their
00008               respective authors. See AUTHORS file for details.
00009 
00010 LICENSE TO USE AND MODIFY SOFTWARE
00011 ----------------------------------
00012 
00013 This file is part of Xara LX.
00014 
00015 Xara LX is free software; you can redistribute it and/or modify it
00016 under the terms of the GNU General Public License version 2 as published
00017 by the Free Software Foundation.
00018 
00019 Xara LX and its component source files are distributed in the hope
00020 that it will be useful, but WITHOUT ANY WARRANTY; without even the
00021 implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
00022 See the GNU General Public License for more details.
00023 
00024 You should have received a copy of the GNU General Public License along
00025 with Xara LX (see the file GPL in the root directory of the
00026 distribution); if not, write to the Free Software Foundation, Inc., 51
00027 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
00028 
00029 
00030 ADDITIONAL RIGHTS
00031 -----------------
00032 
00033 Conditional upon your continuing compliance with the GNU General Public
00034 License described above, Xara Group Ltd grants to you certain additional
00035 rights. 
00036 
00037 The additional rights are to use, modify, and distribute the software
00038 together with the wxWidgets library, the wxXtra library, and the "CDraw"
00039 library and any other such library that any version of Xara LX relased
00040 by Xara Group Ltd requires in order to compile and execute, including
00041 the static linking of that library to XaraLX. In the case of the
00042 "CDraw" library, you may satisfy obligation under the GNU General Public
00043 License to provide source code by providing a binary copy of the library
00044 concerned and a copy of the license accompanying it.
00045 
00046 Nothing in this section restricts any of the rights you have under
00047 the GNU General Public License.
00048 
00049 
00050 SCOPE OF LICENSE
00051 ----------------
00052 
00053 This license applies to this program (XaraLX) and its constituent source
00054 files only, and does not necessarily apply to other Xara products which may
00055 in part share the same code base, and are subject to their own licensing
00056 terms.
00057 
00058 This license does not apply to files in the wxXtra directory, which
00059 are built into a separate library, and are subject to the wxWindows
00060 license contained within that directory in the file "WXXTRA-LICENSE".
00061 
00062 This license does not apply to the binary libraries (if any) within
00063 the "libs" directory, which are subject to a separate license contained
00064 within that directory in the file "LIBS-LICENSE".
00065 
00066 
00067 ARRANGEMENTS FOR CONTRIBUTION OF MODIFICATIONS
00068 ----------------------------------------------
00069 
00070 Subject to the terms of the GNU Public License (see above), you are
00071 free to do whatever you like with your modifications. However, you may
00072 (at your option) wish contribute them to Xara's source tree. You can
00073 find details of how to do this at:
00074   http://www.xaraxtreme.org/developers/
00075 
00076 Prior to contributing your modifications, you will need to complete our
00077 contributor agreement. This can be found at:
00078   http://www.xaraxtreme.org/developers/contribute/
00079 
00080 Please note that Xara will not accept modifications which modify any of
00081 the text between the start and end of this header (marked
00082 XARAHEADERSTART and XARAHEADEREND).
00083 
00084 
00085 MARKS
00086 -----
00087 
00088 Xara, Xara LX, Xara X, Xara X/Xtreme, Xara Xtreme, the Xtreme and Xara
00089 designs are registered or unregistered trademarks, design-marks, and/or
00090 service marks of Xara Group Ltd. All rights in these marks are reserved.
00091 
00092 
00093       Xara Group Ltd, Gaddesden Place, Hemel Hempstead, HP2 6EX, UK.
00094                         http://www.xara.com/
00095 
00096 =================================XARAHEADEREND============================
00097  */
00098 // The Freehand Tool operation
00099 
00100 /*
00101 */
00102 
00103 
00104 #include "camtypes.h"
00105 #include "opfree.h"
00106 
00107 //#include "resource.h"
00108 //#include "mario.h"
00109 //#include "rik.h"
00110 //#include "viewrc.h"
00111 //#include "docview.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00112 #include "paper.h"
00113 //#include "document.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00114 //#include "mainfrm.h"
00115 //#include "ensure.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00116 #include "csrstack.h"
00117 #include "nodepath.h"
00118 //#include "paths.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00119 #include "fitcurve.h"
00120 //#include "app.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00121 //#include "spread.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00122 #include "freehand.h"
00123 #include "pen.h"
00124 #include "progress.h"
00125 //#include "attrmgr.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00126 #include "blobs.h"
00127 #include "pathedit.h"
00128 //#include "fixmem.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00129 #include "keypress.h"
00130 //#include "freehres.h"
00131 #include "objchge.h"
00132 #include "bubbleid.h"
00133 
00134 #include "lineattr.h"       // Variable-width Path stroking headers
00135 #include "pathtrap.h"
00136 #include "ppstroke.h"
00137 #include "pressure.h"
00138 #include "strkattr.h"
00139 #include "valfunc.h"
00140 #include "nodeblnd.h"
00141 #include "blndtool.h"
00142 #include "ndbldpth.h"
00143 #include "nodebldr.h"
00144 #include "opdrbrsh.h"
00145 #include "ophist.h"
00146 #include "brshattr.h"
00147 
00148 // Set things up so that the tool will be listed in the Dialog box
00149 DECLARE_SOURCE("$Revision: 1282 $");
00150 
00151 // Declare the class
00152 CC_IMPLEMENT_DYNCREATE( OpFreeHand, SelOperation )
00153 
00154 // Improved memory tracking
00155 #define new CAM_DEBUG_NEW
00156 
00157 
00158 /********************************************************************************************
00159 
00160 >   OpFreeHand::OpFreeHand()
00161 
00162     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
00163     Created:    20/7/93
00164     Purpose:    Constructor. This simply sets a few of the operation flags.
00165 
00166 ********************************************************************************************/
00167 
00168 OpFreeHand::OpFreeHand()
00169 {
00170     // Set all our cursors to NULL to start with
00171     pFreeHandCursor = NULL;
00172     pJoinCursor = NULL;
00173     pStraightCursor = NULL;
00174     pRubOutCursor = NULL;
00175     pModifyCursor = NULL;
00176     
00177     // Set other default values
00178     TrackData = NULL;
00179     StartSpread = NULL;
00180     PreviousSpread = NULL;
00181     Smoothness = 512;
00182     LineSegmentCount = 0;
00183     CanLineJoin = FALSE;
00184     IsStraightLineMode = FALSE;
00185     AddPressureToPath = FALSE;
00186     FreeHandPressure = 0;
00187 
00188     // The paths that we are joined to, or NULL if we are joined to none
00189     pJoinInfo = NULL;
00190     StartPath = NULL;
00191     EndPath = NULL;
00192     CloseTo = 0;
00193     Mu = 0.0;
00194     IsEndNearEndpoint = FALSE;
00195     CurrentCursorID = 0;
00196 
00197     m_pNewNodePath = NULL;
00198 
00199 }
00200 
00201 
00202 
00203 /********************************************************************************************
00204 
00205 >   void OpFreeHand::DoDrag( DocCoord Anchor, Spread *pSpread, INT32 Smooth, 
00206                                 NodePath* path, Path* ToolPath )
00207 
00208     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
00209     Created:    5/7/93
00210     Inputs:     Anchor  - The position of the mouse at the start of the Drag
00211                 pSpread - The spread that the drag started on
00212                 Smooth  - Closeness of fit value
00213                 pFreeHandInfo - Pointer to the info about the Joining set up by the
00214                 freehand tool
00215                 ToolPath - The path to store the mouse tracking info in
00216     Purpose:    Starts up the drag operation by storing all start positions and setting
00217                 up a Path to store the mouse movement in
00218 
00219 ********************************************************************************************/
00220 
00221 void OpFreeHand::DoDrag(DocCoord Anchor, Spread *pSpread, INT32 Smooth,
00222                         FreeHandJoinInfo* pFreeHandInfo, Path* ToolPath)
00223 {
00224     TRACEUSER( "Diccon", _T("Do Drag Free, StartPos = %d, %d\n"), Anchor.x, Anchor.y);
00225 // WEBSTER - markn 25/4/97
00226 // No pen stuff required in Webster
00227 // Taken out by vector stroking code Neville 2/10/97
00228 #ifdef VECTOR_STROKING
00229     // Tell the pressure pen that we're starting a new stroke
00230     CCPen *pPen = GetApplication()->GetPressurePen();
00231     if (pPen != NULL)
00232         pPen->StartStroke();
00233 #endif // VECTOR_STROKING
00234 
00235     // Snap the starting coord if we are not joining to something else
00236     if ((pFreeHandInfo==NULL) || (pFreeHandInfo->pJoinPath==NULL))
00237         DocView::SnapCurrent(pSpread, &Anchor, FALSE, TRUE);
00238 
00239     // Make a note of various starting conditions
00240     TrackData   = ToolPath;
00241     Smoothness  = Smooth;
00242     pJoinInfo   = pFreeHandInfo;
00243     StartPath   = pJoinInfo->pJoinPath;
00244     EndPath     = NULL;
00245 
00246     // Make a mental note of the start point
00247     StartPoint    = Anchor;
00248     StartSpread   = pSpread;
00249     PreviousSpread= pSpread;
00250     PreviousPoint = Anchor;
00251     LineSegmentCount = 0;
00252     IsStraightLineMode = FALSE;
00253     CanLineJoin = FALSE;
00254 
00255     // Prepare the Track data path and add in the initial click point to the path
00256     if (!PrepareTrackDataPath())
00257     {
00258         // We failed to get the memory to initialise the track data
00259         InformError(_R(IDS_OUT_OF_MEMORY), _R(IDS_OK));
00260         FailAndExecute();
00261         End();
00262         return;
00263     }
00264         
00265     // Create some cursors that I might need
00266     if (!LoadCursors())
00267     {
00268         // The cursors did not load, so fail?
00269         FailAndExecute();
00270         End();
00271         return;
00272     }
00273 
00274     // Push my new cursor onto the stack
00275     CurrentCursorID = CursorStack::GPush(pFreeHandCursor, TRUE);
00276     MyCurrentCursor = pFreeHandCursor;
00277 
00278     // And tell the Dragging system that we need drags to happen
00279     StartDrag( DRAGTYPE_NOSCROLL );
00280 }
00281 
00282 
00283 
00284 /********************************************************************************************
00285 
00286 >   BOOL OpFreeHand::PrepareTrackDataPath()
00287 
00288     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
00289     Created:    25/10/94
00290     Returns:    TRUE if it worked, FALSE if it failed
00291     Purpose:    Tries to initialise the path that we will store the track data in. It also
00292                 sets up the pressure info stuff it there is pressure info coming in.
00293                 If the path initialise ok, then it inserts the initial position (including
00294                 its pressure) into the path. This function will only fail if there
00295                 is no memory to init the path with.
00296 
00297 ********************************************************************************************/
00298 
00299 BOOL OpFreeHand::PrepareTrackDataPath()
00300 {
00301     TrackData->ClearPath();
00302     TrackData->FindStartOfPath();
00303 
00304     // Check to see if we want to store pressure, and if so initialise the path.
00305     AddPressureToPath = FALSE;
00306 
00307 // WEBSTER - markn 25/4/97
00308 // No pen stuff required in Webster
00309 // Taken out by vector stroking code Neville 2/10/97
00310 #ifdef VECTOR_STROKING
00311     
00312     CCPen *pPen = GetApplication()->GetPressurePen();
00313     if (IsBrushOp() && pPen != NULL && pPen->GetPressureMode() != PressureMode_None)
00314     {
00315         // Yep, we want the pressure info
00316         AddPressureToPath = TRUE;
00317 
00318         // Lets gets the pressure for the first point in the path.
00319         FreeHandPressure = pPen->GetPenPressure();
00320 
00321         // try and initialise the extra info in the path and fail if not
00322         if (!TrackData->InitExtraInfo(CI_PRESSURE))
00323             return FALSE;
00324     }
00325     
00326 #endif // VECTOR_STROKING
00327 
00328     // insert the initial coord into the path and fail if it fails
00329     if (!TrackData->InsertMoveTo(StartPoint))
00330         return FALSE;
00331 
00332     // and add pressure info if needed
00333     if (AddPressureToPath)
00334         TrackData->AddExtraInfo(CI_PRESSURE, FreeHandPressure);
00335 
00336     // and tell someone that we did it
00337     return TRUE;
00338 }
00339 
00340 
00341 /********************************************************************************************
00342 
00343 >   BOOL OpFreeHand::LoadCursors()
00344 
00345     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
00346     Created:    5/5/94
00347     Returns:    TRUE if it worked, FALSE if not
00348     Purpose:    Tries to Load all the cursors used by the Free Hand Operation.
00349 
00350 ********************************************************************************************/
00351 
00352 BOOL OpFreeHand::LoadCursors()
00353 {
00354     // Try to load all the cursors
00355     if (FreeHandTool::FreehandPtrCrosshair)
00356     {
00357         pFreeHandCursor = new Cursor(TOOLID_FREEHAND, _R(IDC_FREEHANDTOOLCURSOR_X));
00358         pJoinCursor = new Cursor(TOOLID_FREEHAND, _R(IDC_FREEHANDJOINCURSOR_X));
00359         pRubOutCursor = new Cursor(TOOLID_FREEHAND, _R(IDC_FREEHANDRUBOUTCUR_X));
00360         pStraightCursor = new Cursor(TOOLID_FREEHAND, _R(IDC_FREEHANDLINECURSOR_X));
00361         pModifyCursor = new Cursor(TOOLID_FREEHAND, _R(IDC_FREEHANDMODIFY_X));
00362     }
00363     else
00364     {
00365         pFreeHandCursor = new Cursor(TOOLID_FREEHAND, _R(IDC_FREEHANDTOOLCURSOR));
00366         pJoinCursor = new Cursor(TOOLID_FREEHAND, _R(IDC_FREEHANDJOINCURSOR));
00367         pRubOutCursor = new Cursor(TOOLID_FREEHAND, _R(IDC_FREEHANDRUBOUTCUR));
00368         pStraightCursor = new Cursor(TOOLID_FREEHAND, _R(IDC_FREEHANDLINECURSOR));
00369         pModifyCursor = new Cursor(TOOLID_FREEHAND, _R(IDC_FREEHANDMODIFY));
00370     }
00371 
00372     // See if any of them failed
00373     if ((pRubOutCursor==NULL)   || (!pRubOutCursor->IsValid()) ||
00374         (pJoinCursor==NULL)     || (!pJoinCursor->IsValid()) ||
00375         (pFreeHandCursor==NULL) || (!pFreeHandCursor->IsValid()) ||
00376         (pStraightCursor==NULL) || (!pStraightCursor->IsValid()) ||
00377         (pModifyCursor==NULL)   || (!pModifyCursor->IsValid()))
00378     {
00379         // They did, so Tidy up and exit
00380         delete pFreeHandCursor;
00381         delete pJoinCursor;
00382         delete pStraightCursor;
00383         delete pRubOutCursor;
00384         delete pModifyCursor;
00385 
00386         // Set them all to NULL
00387         pFreeHandCursor = NULL;
00388         pJoinCursor = NULL;
00389         pStraightCursor = NULL;
00390         pRubOutCursor = NULL;
00391         pModifyCursor = NULL;
00392 
00393         // fail
00394         return FALSE;
00395     }
00396 
00397     // It worked
00398     return TRUE;
00399 }
00400 
00401 
00402 
00403 /********************************************************************************************
00404 
00405 >   void OpFreeHand::RemoveCursors()
00406 
00407     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
00408     Created:    25/10/94
00409     Purpose:    Gets rid of all this operations cursors. It takes the cursor I was using
00410                 off the top of the stack and deletes all the cursors I had allocated.
00411 
00412 ********************************************************************************************/
00413 
00414 void OpFreeHand::RemoveCursors()
00415 {
00416     // Get rid out my cursor from the top of the cursor stack
00417     CursorStack::GPop(CurrentCursorID);
00418     MyCurrentCursor = NULL;
00419     CurrentCursorID = 0;
00420 
00421     // destroy all the cursors that I had allocated
00422     delete pFreeHandCursor;
00423     delete pJoinCursor;
00424     delete pRubOutCursor;
00425     delete pStraightCursor;
00426     delete pModifyCursor;
00427 
00428     // and make sure that they all point to something sensible
00429     pFreeHandCursor = NULL;
00430     pJoinCursor = NULL;
00431     pStraightCursor = NULL;
00432     pRubOutCursor = NULL;
00433     pModifyCursor = NULL;
00434 }
00435 
00436 
00437 
00438 
00439 
00440 /********************************************************************************************
00441 
00442 >   void OpFreeHand::SetCursorOnMove(ClickModifiers ClickMods, Spread* pSpread, DocCoord* PointerPos)
00443 
00444     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
00445     Created:    25/10/94
00446     Inputs:     ClickMods - Which modifiers were pressed
00447                 pSpread - The spread that the mouse is over
00448     Outputs:    PointerPos - The position of the mouse
00449     Purpose:    Decides which cursor to display depending on which modifiers are pressed and
00450                 where the mouse is. PointerPos can be changed in this function
00451 
00452 ********************************************************************************************/
00453 
00454 void OpFreeHand::SetCursorOnMove(ClickModifiers ClickMods, Spread* pSpread, DocCoord* PointerPos)
00455 {
00456     // If the mouse is over an EndPoint, use the Join Cursor
00457     if (CheckMouseOverSelectedPath(PointerPos, pSpread, &IsEndNearEndpoint))
00458     {
00459         // Ok the cursor is near the path
00460         if (IsEndNearEndpoint)
00461         {
00462             // it was near one of the paths open ends
00463             SetCursorAndStatus(JOIN_CURSOR);
00464             return;
00465         }
00466 
00467         // Must be near the middle of the path
00468         // We can only do a modify join if we started the path on the same path
00469         if ((StartPath==EndPath) && (pJoinInfo->IsNearEndPoint==FALSE))
00470         {
00471             SetCursorAndStatus(MODIFY_CURSOR);
00472             return;
00473         }
00474     }
00475 
00476     // See Which of the modifiers are down...   
00477     if (ClickMods.Alternative1)
00478     {
00479         // if Alt is down we are in Straight Line mode
00480         SetCursorAndStatus(STRAIGHTLINE_CURSOR);
00481     }
00482     else
00483     {
00484         if (ClickMods.Adjust)
00485         {
00486             // If Shift is down we are in RubOut mode
00487             SetCursorAndStatus(RUBOUT_CURSOR);
00488         }
00489         else
00490         {
00491             // No Modifiers so use the normal cursor
00492             SetCursorAndStatus(NORMAL_CURSOR);
00493         }
00494     }
00495 }
00496 
00497 
00498 
00499 /********************************************************************************************
00500 
00501 >   void OpFreeHand::DragPointerMove( DocCoord PointerPos, ClickModifiers ClickMods, 
00502                                   Spread *pSpread, BOOL bSolidDrag)
00503 
00504     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
00505     Created:    5/7/93
00506     Inputs:     PointerPos - The current position of the mouse in Doc Coords
00507                 ClickMods - Which key modifiers are being pressed
00508                 pSpread - The spread that the mouse is moving over
00509     Purpose:    Handles the event of the mouse moving during a drag
00510     SeeAlso:    ClickModifiers
00511 
00512 ********************************************************************************************/
00513 
00514 void OpFreeHand::DragPointerMove( DocCoord PointerPos, ClickModifiers ClickMods, 
00515                                   Spread *pSpread, BOOL bSolidDrag)
00516 {
00517     // Find out the view
00518     DocView* pView = DocView::GetSelected();
00519 
00520     // Lets whip out the current pressure value
00521     if (AddPressureToPath)
00522         FreeHandPressure = ClickMods.Pressure;
00523 
00524     // If drag has moved onto a different spread, convert the coord to be relative to the
00525     // original spread.
00526     if (pSpread != StartSpread)
00527         PointerPos = MakeRelativeToSpread(StartSpread, pSpread, PointerPos);
00528 
00529     // Change the Cursor to display the appropriate thing.
00530     SetCursorOnMove(ClickMods, StartSpread, &PointerPos);
00531 
00532     // Are we in Straight Line Mode? We won't allow straight line mode when recording pressure
00533     if (ClickMods.Alternative1)
00534     {
00535         // Snap and Constrain if needed
00536         DocView::SnapCurrent(pSpread, &PointerPos);
00537         if (ClickMods.Constrain)
00538             DocView::ConstrainToAngle(PreviousPoint, &PointerPos);
00539 
00540         // If we are in the Straight Line mode rub out the line
00541         if (IsStraightLineMode)
00542             RenderEorStraightLine(NULL, pSpread);
00543         else
00544         {
00545             // We are just entering straight line mode, so switch the dragging mode
00546             // so that the document auto scrolls at the edges of the screen
00547             if (pView!=NULL)
00548                 pView->ChangeDragType(DRAGTYPE_AUTOSCROLL);
00549         }
00550 
00551         // Update the mouse position and render the EORed straight line
00552         StraightLinePos = PointerPos;
00553         RenderEorStraightLine(NULL, pSpread);
00554         
00555         // Finally note that we are in this mode
00556         IsStraightLineMode = TRUE;
00557     }
00558     else
00559     {
00560         // See if we were in Straight Line mode before
00561         if (IsStraightLineMode)
00562         {
00563             // We do not want to rub the straight line off, so do no drawing
00564             // Add the straight line into the path and set the flags as needed
00565             AddStraightLine();
00566             PreviousPoint = StraightLinePos;
00567 
00568             // We are just leaving straight line mode, so go back to not scrolling
00569             if (pView!=NULL)
00570                 pView->ChangeDragType(DRAGTYPE_NOSCROLL);
00571 
00572             // No longer in Straight Line mode, so set the flag
00573             IsStraightLineMode = FALSE;
00574         }
00575         else
00576         {
00577             // Either rub out sections of the path, or add new points to it
00578             if (ClickMods.Adjust)
00579                 RubOutPath(PointerPos, StartSpread);
00580             else
00581                 AddPointsToPath(PointerPos, StartSpread);
00582         }
00583     }
00584 
00585     // Set the last spread to something appropriate
00586     PreviousSpread = pSpread;
00587 }
00588 
00589 
00590 
00591 
00592 
00593 /********************************************************************************************
00594 
00595 >   void OpFreeHand::DragPointerIdle(DocCoord PointerPos, ClickModifiers ClickMods,
00596                                      Spread* pSpread, BOOL bSolidDrag)
00597 
00598     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
00599     Created:    12/5/94
00600     Inputs:     PointerPos - The position of the mouse
00601                 ClickMods - The modifiers that are held down at the time of the idle event
00602                 pSpread - The spread that the mouse was over at the event
00603     Purpose:    This function checks the Adjust Click modifier while the mouse is not
00604                 moving to see if it is released during a straight line mode edit. This
00605                 means that the shift key can be toggled in mid drag to create a straight
00606                 line shape
00607 
00608 ********************************************************************************************/
00609 
00610 void OpFreeHand::DragPointerIdle(DocCoord PointerPos, ClickModifiers ClickMods, Spread* pSpread, BOOL bSolidDrag)
00611 {
00612     // If we are in straight line mode, but the alt button has just come up, then
00613     // end the straight line we are drawing
00614     if (IsStraightLineMode && !ClickMods.Alternative1)
00615     {
00616         // Add the straight line into the path and set the flags as needed
00617         AddStraightLine();
00618         PreviousPoint = StraightLinePos;
00619 
00620         // No longer in Straight Line mode, so set the flag
00621         IsStraightLineMode = FALSE;
00622     }
00623 }
00624 
00625 
00626 
00627 
00628 /********************************************************************************************
00629 
00630 >   void OpFreeHand::DragFinished( DocCoord PointerPos, ClickModifiers ClickMods,
00631                                Spread *pSpread, BOOL Success, BOOL bSolidDrag)
00632 
00633     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
00634     Created:    5/7/93
00635     Inputs:     PointerPos - The position of the mouse at the end of the drag
00636                 ClickMods - the key modifiers being pressed
00637                 pSpread - The spread that the drag finished on
00638                 Success - TRUE if the drag was terminated properly, FALSE if it
00639                 was ended with the escape key being pressed
00640     Purpose:    Handles the drag finishing by rubbing out an EOR on the screen and
00641                 adding the path to the tree and building all the undo that will be
00642                 needed
00643     SeeAlso:    ClickModifiers
00644 
00645 ********************************************************************************************/
00646 
00647 void OpFreeHand::DragFinished( DocCoord PointerPos, ClickModifiers ClickMods,
00648                                Spread *pSpread, BOOL Success, BOOL bSolidDrag)
00649 {
00650     // Erase the whole EORed line
00651     DocRect ClipRect(0,0,0,0);
00652     RenderDragBlobs(ClipRect, StartSpread, bSolidDrag);
00653 
00654     // Get rid of all the cursors
00655     RemoveCursors();
00656 
00657     // Put the hour glass up as we have to and end the drag
00658     BeginSlowJob();
00659     EndDrag();
00660 
00661     // Add the new path to the tree if it was valid
00662     BOOL Worked = FALSE;
00663     if (Success)
00664     {
00665         // were we in the middle of drawing a straight line
00666         if (IsStraightLineMode)
00667         {
00668             // we were so add it in to the track data
00669             AddStraightLine();
00670             PreviousPoint = StraightLinePos;
00671             IsStraightLineMode = FALSE;
00672         }
00673 
00674         // try and smooth the path and insert it into the tree
00675         Worked = CompleteOperation();
00676 
00677         // DY 9/99 Check to see if we are editing a blend on a curve, if so we may wish to 
00678         // change the number of steps in the blend (making use of the path distance).
00679         NodeGroup* pParent = GetParentOfNodePath();           
00680         if (pParent!= NULL)
00681         {
00682             if (pParent->IS_KIND_OF(NodeBlend))
00683             {
00684                 InsertChangeBlendStepsAction((NodeBlend*)pParent);               
00685 
00686                 ObjChangeFlags cFlags(FALSE,TRUE);
00687                 ObjChangeParam ObjChange(OBJCHANGE_FINISHED,cFlags,NULL,this);
00688                 Worked = UpdateChangedNodes(&ObjChange);
00689             }
00690         
00691         }
00692     }
00693 // WEBSTER - markn 25/4/97
00694 // No pen stuff required in Webster
00695 // Taken out by vector stroking code Neville 2/10/97
00696 #ifdef VECTOR_STROKING
00697     // Inform the pressure pen that we've finished the stroke
00698     CCPen *pPen = GetApplication()->GetPressurePen();
00699     if (pPen != NULL)
00700         pPen->EndStroke();
00701 #endif // VECTOR_STROKING
00702 
00703 
00704 
00705     // If the operation failed, then tidy up
00706     if (Worked==FALSE)
00707         FailAndExecute();
00708     else    
00709     {
00710         // Update all the parents of the effected paths.
00711         ObjChangeFlags cFlags;
00712         ObjChangeParam ObjChange(OBJCHANGE_STARTING,cFlags,NULL,this);
00713         ObjChange.Define(OBJCHANGE_FINISHED,cFlags,NULL,this);
00714         UpdateChangedNodes(&ObjChange);
00715     }
00716     // End the operation properly
00717     End();
00718 }
00719 
00720 
00721 
00722 /********************************************************************************************
00723 
00724 >   BOOL OpFreeHand::DragKeyPress(KeyPress* pKeyPress, BOOL bSolidDrag)
00725 
00726     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
00727     Created:    16/11/94
00728     Inputs:     pKeyPress - pointer to a keypress object
00729     Returns:    TRUE if it handled the keypress, FALSE otherwise
00730     Purpose:    Find out if any of the key modifiers are being pressed when the mouse is
00731                 not moving.
00732 
00733 ********************************************************************************************/
00734 
00735 BOOL OpFreeHand::DragKeyPress(KeyPress* pKeyPress, BOOL bSolidDrag)
00736 {
00737     // Need something to see if there was a usful keypress
00738 //  BOOL IsKeyPress = FALSE;
00739 
00740     // and something to keep them in
00741     ClickModifiers ClickMods;
00742     ClickMods.Adjust = FALSE;
00743     ClickMods.Menu = FALSE;
00744     ClickMods.Constrain = FALSE;
00745     ClickMods.Alternative1 = FALSE;
00746     ClickMods.Alternative2 = FALSE;
00747     ClickMods.Pressure = 0;
00748 
00749     // See if was one of the key we are interested in
00750     if (pKeyPress->IsConstrain() || pKeyPress->IsAdjust() || pKeyPress->IsAlternative() || pKeyPress->IsRelease())
00751     {
00752         // Set up the click modifier
00753         ClickMods.Constrain = pKeyPress->IsConstrainPressed();
00754         ClickMods.Adjust = pKeyPress->IsAdjustPressed();
00755         ClickMods.Alternative1 = pKeyPress->IsAlternativePressed();
00756 
00757         // fake a mouse move message
00758         DragPointerMove(PreviousPoint, ClickMods, PreviousSpread, FALSE);
00759 
00760         // Tell the keypress people that we used the keypress
00761         return TRUE;
00762     }
00763 
00764     // Did not use the click
00765     return FALSE;
00766 }
00767 
00768 
00769 
00770 
00771 /********************************************************************************************
00772 
00773 >   BOOL OpFreeHand::CompleteOperation()
00774 
00775     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
00776     Created:    25/10/94
00777     Returns:    TRUE if it worked, FALSE if not
00778     Purpose:    Does all the steps required to build a new path, smooth it, try to join it
00779                 with others and finally insert it into the tree.
00780 
00781 ********************************************************************************************/
00782 
00783 BOOL OpFreeHand::CompleteOperation()
00784 {
00785     // Create a new path to be inserted into the tree
00786     NodePath* pNewNodePath = NULL;
00787     if (!CreateNewPath(&pNewNodePath))
00788         return FALSE;
00789 
00790     if (!SmoothNewCurve(pNewNodePath))
00791     {
00792         // Get rid of the path we created and fail
00793         delete pNewNodePath;
00794         return FALSE;
00795     }
00796 
00797     if (!InsertSmoothCurveWithUndo(pNewNodePath))
00798     {
00799         // Get rid of the path we created, and fail
00800         pNewNodePath->CascadeDelete();
00801         delete pNewNodePath;
00802         return FALSE;
00803     }
00804 
00805     // Mark the selection as no longer valid (the Bounding Box will have changed)
00806     SelRange* Selection = GetApplication()->Selection;
00807     if (Selection)
00808         Selection->Update();
00809 
00810     
00811     // All worked
00812     return TRUE;
00813 }
00814 
00815 
00816 
00817 /********************************************************************************************
00818 
00819 >   BOOL OpFreeHand::CreateNewPath(NodePath** pNewPath)
00820 
00821     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
00822     Created:    25/10/94
00823     Outputs:    pNewPath - This will be a pointer to an initialised NodePath or NULL
00824     Returns:    TRUE if it worked, FALSE if something went wrong
00825     Purpose:    Builds a new Node Path ready to have a Curve fitted into it. If anything
00826                 goes wrong (not enough memory, the path was not worth fitting etc) then
00827                 this function fails.
00828 
00829 ********************************************************************************************/
00830 
00831 BOOL OpFreeHand::CreateNewPath(NodePath** pNewPath)
00832 {
00833     // Get a pointer to the path and set it to NULL
00834     NodePath*& pFreeHandPath = *pNewPath;
00835     pFreeHandPath = NULL;
00836 
00837     // Only bother if they had actually drawn something
00838     if (LineSegmentCount==0)
00839         return FALSE;
00840 
00841     // See if the path ever got away from the initial snap coord
00842     DocCoord* Coords = TrackData->GetCoordArray();
00843     INT32 NumCoords = TrackData->GetNumCoords();
00844     BOOL IsAllSame = TRUE;
00845 
00846     // loop through all the coords comparing them with the first one
00847     for (INT32 i=0; (i<NumCoords) && (IsAllSame); i++)
00848     {
00849         // if this coord is different from the first one, then we are ok
00850         if (Coords[0] != Coords[i])
00851             IsAllSame = FALSE;
00852     }
00853 
00854     // If it did not (ie all the coordinates were the same, then chuck it away)
00855     if (IsAllSame)
00856         return FALSE;
00857 
00858     // Init the path with the default memory block sizes
00859     pFreeHandPath = new NodePath;
00860     if (pFreeHandPath==NULL)
00861         return FALSE;
00862 
00863     // Ok, we got a new NodePath, try and init the Path inside it
00864     if (!pFreeHandPath->SetUpPath(24, 12))
00865     {
00866         // The path failed to init, so get rid of it and fail
00867         delete pFreeHandPath;
00868         pFreeHandPath = NULL;
00869         return FALSE;
00870     }
00871 
00872     // Must have worked
00873     return TRUE;
00874 }
00875 
00876 
00877 
00878 /********************************************************************************************
00879 
00880 >   BOOL OpFreeHand::SmoothNewCurve(NodePath* pNewNodePath)
00881 
00882     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
00883     Created:    25/10/94
00884     Inputs:     pNewNodePath - The NodePath that we want to smooth the track data into
00885     Returns:    TRUE if it worked, FALSE if something went wrong
00886     Purpose:    Sets up the curve fitter and finally smooths the data in the Track data
00887                 buffer into the new node path.
00888 
00889 ********************************************************************************************/
00890 
00891 BOOL OpFreeHand::SmoothNewCurve(NodePath* pNewNodePath)
00892 {
00893     // To work out how much we need to smooth the curve by, we must know what
00894     // zoom factor we are currently at
00895     DocView* pDocView = DocView::GetSelected();
00896     if (pDocView==NULL)
00897         return FALSE;
00898 
00899     // When this path is built it will actually replace a section from the middle of the
00900     // original path, so we must make sure that is starts and ends on the original
00901     // The start was done before the drag started, the end can be done now.
00902     if ((StartPath!=NULL) && (StartPath==EndPath) && (pJoinInfo->IsNearEndPoint==FALSE))
00903     {
00904         // Find out the coord on the original path where the endpoint got closest to
00905         PreviousPoint = EndPath->InkPath.ClosestPointTo(Mu, CloseTo);
00906 
00907         // Add it to the path data
00908         if (TrackData->InsertLineTo(PreviousPoint))
00909         {
00910             // and add pressure info if needed
00911             if (AddPressureToPath)
00912                 TrackData->AddExtraInfo(CI_PRESSURE, FreeHandPressure);
00913         }
00914     }
00915 
00916     // Find out how flat they need the curve. This number represents
00917     // how far off the original track data the curve can get before it
00918     // is split and turned into more curves. The smaller the number
00919     // the tighter the fit.
00920     double ScaleFactor = (pDocView->GetViewScale()).MakeDouble();
00921     double ErrorLevel = (64 + (160*Smoothness)) / ScaleFactor;
00922     ErrorLevel = ErrorLevel * ErrorLevel;
00923     
00924     // FitCurve needs the number of points in the path, which is the 
00925     // number of Line Segments + 1
00926     CurveFitObject CurveFitter(&(pNewNodePath->InkPath), ErrorLevel);
00927 
00928     // Try to initialise the curve fitter
00929     if (!CurveFitter.Initialise(TrackData, LineSegmentCount+1))
00930         return FALSE;
00931 
00932     // All went ok, so fit the curve to the track data and return success
00933     CurveFitter.FitCurve();
00934     return TRUE;
00935 }
00936 
00937 
00938 
00939 
00940 /********************************************************************************************
00941 
00942 >   BOOL OpFreeHand::InsertSmoothCurveWithUndo(NodePath* pNewNodePath)
00943 
00944     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
00945     Created:    25/10/94
00946     Inputs:     pNewNodePath - The NodePath that we are trying to insert into the tree
00947     Returns:    TRUE if it worked, FALSE if it failed
00948     Purpose:    Starts the Selection operation (recording the selection state etc) and
00949                 trys to join our new path with any others touching it, before inserting
00950                 it into the tree.
00951 
00952 ********************************************************************************************/
00953 
00954 BOOL OpFreeHand::InsertSmoothCurveWithUndo(NodePath* pNewNodePath)
00955 {
00956     // Start the selection operation.
00957     // We don't want to save the end selection status because it will be
00958     // restored automatically when the operation is redone
00959     if (!DoStartSelOp(FALSE))
00960         return FALSE;
00961 
00962     // Now we have to go round testing if we are joined to any other paths
00963     // and building the appropriate undo info
00964     if ((StartPath!=NULL) && (StartPath==EndPath) &&
00965         (pJoinInfo->IsNearEndPoint==FALSE) && (IsEndNearEndpoint==FALSE))
00966     {
00967         // This is a case for replacing a section of an existing path
00968         // with the new smoothed path
00969         if (!TryToReplaceSection(pNewNodePath))
00970             return FALSE;
00971     }
00972     else
00973     {
00974         // If the new curve did not start and end on the same path we made need to make a few adjustments
00975         if (pJoinInfo->IsNearEndPoint==FALSE)
00976             StartPath = NULL;
00977 
00978         // this is a case for inserting the path in the normal way
00979         // Joining it to other paths that it touches etc
00980         if (!TryToJoinNewPathWithOthers(pNewNodePath))
00981             return FALSE;
00982     }
00983 
00984     // Clear the Node path that we made, as it will have been copied into the tree by now
00985     pNewNodePath->CascadeDelete();
00986     delete pNewNodePath;
00987 
00988     // It all worked ok
00989     return TRUE;
00990 }
00991 
00992 
00993 
00994 /********************************************************************************************
00995 
00996 >   void OpFreeHand::RubOutPath(DocCoord Pos, Spread* pSpread)
00997 
00998     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
00999     Created:    13/4/94
01000     Inputs:     Pos - The position of the mouse
01001                 pSpread - Pointer to the spread that we are drawing on
01002     Purpose:    Tries to rub out sections of the path that have already been drawn.
01003 
01004 ********************************************************************************************/
01005 
01006 void OpFreeHand::RubOutPath(DocCoord Pos, Spread* pSpread)
01007 {
01008     // If the mouse has not moved then do not do anything
01009     if (Pos==PreviousPoint)
01010         return;
01011 
01012     // Get hold of the blob manager for finding out the size of a blob
01013     BlobManager* pBlobMgr = GetApplication()->GetBlobManager();
01014     if (pBlobMgr==NULL)
01015         return;
01016 
01017     // Find out about all the coords in the path already
01018     INT32 NumCoords = TrackData->GetNumCoords();
01019     DocCoord* Coords = TrackData->GetCoordArray();
01020 
01021     // Get line width information
01022 //  INT32 LineWidth = GetCurrentLineWidth();
01023 
01024     // Find out about the area round the mouse position to consider for rubbing out
01025     DocRect BlobRect;
01026     pBlobMgr->GetBlobRect(Pos, &BlobRect);
01027 
01028     // We want to look at the last few coords in the path. So work out how far back to look
01029     INT32 StopLookingAt = 1;
01030 
01031     // Loop through the last few coords to see if the mouse is near them
01032     for (INT32 i=NumCoords-2; i>StopLookingAt; i--)
01033     {
01034         if (BlobRect.ContainsCoord(Coords[i]))
01035         {
01036             // Rub out the line from this coord to the end of the line
01037             RenderRegion* pRegion = DocView::RenderOnTop( NULL, pSpread, UnclippedEOR );
01038             while (pRegion)
01039             {
01040                 for (INT32 n = i+1; n < NumCoords; n++)
01041                     RenderLine(pRegion, TrackData, n);
01042 
01043                 // Get the Next render region
01044                 pRegion = DocView::GetNextOnTop( NULL );
01045             }
01046 
01047             // Rub out the actual path Data
01048             if (!TrackData->DeleteFromElement(i+1))
01049             {
01050                 InformError(_R(IDS_PATH_DELETE_ERROR));
01051                 return;
01052             }
01053 
01054             // Change the operations parameters
01055             PreviousPoint = Coords[i];
01056             LineSegmentCount = i;
01057 
01058             // this mouse message is finished with
01059             return;
01060         }
01061     }
01062 }
01063 
01064 
01065 
01066 /********************************************************************************************
01067 
01068 >   void OpFreeHand::AddPointsToPath(DocCoord Pos, Spread* pSpread)
01069 
01070     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
01071     Created:    13/4/94
01072     Inputs:     Pos - The position of the mouse
01073                 pSpread - Pointer to the spread that we are drawing on
01074     Purpose:    Adds a new point to the path of mouse moves. When the drag
01075                 finishes the mouse moves will be smoothed
01076 
01077 ********************************************************************************************/
01078 
01079 void OpFreeHand::AddPointsToPath(DocCoord PointerPos, Spread* pSpread)
01080 {
01081     // If this coord is the same as the last one, then do not bother adding to the track data
01082     if (PreviousPoint == PointerPos)
01083         return;
01084  
01085     // Insert the move
01086     if (TrackData->InsertLineTo(PointerPos))
01087     {
01088         // and add pressure info if needed
01089         if (AddPressureToPath)
01090             TrackData->AddExtraInfo(CI_PRESSURE, FreeHandPressure);
01091     }
01092     else
01093     {
01094         // Oh no, we ran out of mem in the middle of making a new path
01095         // Tidy up and report back to HQ
01096         EndDrag();
01097 
01098         // Tell the World that it all went horribly wrong
01099         InformError();
01100 
01101         // Remove it from the tree and delete it
01102         TrackData->ClearPath();
01103         
01104         // End this operation and return
01105         FailAndExecute();
01106         End();
01107         return;
01108     }
01109     
01110     // Now we have to render a new bit of EORed data to the screen
01111     RenderLine(NULL, pSpread, TrackData, TrackData->GetNumCoords()-1);
01112 
01113     // Make a note of the coord, inc the line count and mark joining to the start as active.
01114     PreviousPoint = PointerPos;
01115     LineSegmentCount++;
01116     CanLineJoin = TRUE;
01117 }
01118 
01119 
01120 /********************************************************************************************
01121 
01122 >   void OpFreeHand::AddStraightLine()
01123 
01124     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
01125     Created:    22/4/94
01126     Purpose:    Adds a straight line to the Path, that has been made by the Straight Line
01127                 Mode. This means it has to set some flags in the path to stop the curve
01128                 fitter from smoothing this straight line segment out
01129 
01130 ********************************************************************************************/
01131 
01132 void OpFreeHand::AddStraightLine()
01133 {
01134     PathFlags Flags;
01135     Flags.Spare1 = TRUE;
01136     
01137     // If this coord is the same as the last one, then do not bother adding to the track data
01138     if (PreviousPoint == StraightLinePos)
01139         return;
01140 
01141     // add the line segment into the path
01142     if (TrackData->InsertLineTo(StraightLinePos, &Flags))
01143     {
01144         // and add pressure info if needed
01145         if (AddPressureToPath)
01146             TrackData->AddExtraInfo(CI_PRESSURE, FreeHandPressure);
01147     }
01148     else
01149     {
01150         // Oh no, we ran out of mem in the middle of making a new path
01151         // Tidy up and report back to HQ
01152         EndDrag();
01153 
01154         // Tell the World that it all went horribly wrong
01155         InformError();
01156 
01157         // Remove it from the tree and delete it
01158         TrackData->ClearPath();
01159         
01160         // End this operation and return
01161         FailAndExecute();
01162         End();
01163         return;
01164     }
01165 
01166     // Count this bit of line
01167     LineSegmentCount++;
01168     CanLineJoin = TRUE;
01169 }
01170 
01171 
01172 /********************************************************************************************
01173 
01174 >   BOOL OpFreeHand::CheckMouseOverSelectedPath(DocCoord* PointerPos, Spread* pSpread,
01175                                                 BOOL* IsNearEndpoint)
01176 
01177     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
01178     Created:    12/4/94
01179     Inputs:     PointerPos - The position of the mouse
01180                 pSpread - the spread that the cursor is over
01181     Outputs:    IsNearEndPoint - if this function returns TRUE this param is valid, otherwise
01182                 it is not. It will be TRUE if the cursor was found to be close to an
01183                 open end of the path, or FALSE if it was near the middle of the path
01184     Returns:    TRUE if the pointer was over the end of a selected line, FALSE if it was not
01185     Purpose:    Checks to see if the mouse is over the endpoint of any of the
01186                 selected paths in the document and if it is, it makes a note of
01187                 the path and which end it was attached to
01188 
01189 ********************************************************************************************/
01190 
01191 BOOL OpFreeHand::CheckMouseOverSelectedPath(DocCoord* PointerPos, Spread* pSpread, BOOL* IsNearEndpoint)
01192 {
01193     // Assume we do not find a path
01194     EndPath = NULL;
01195 
01196     // Find out about the blob manager so we can find the size of a blob
01197     BlobManager* pBlobMgr = GetApplication()->GetBlobManager();
01198     if (pBlobMgr==NULL)
01199         return FALSE;
01200 
01201     // Find out about the size of a blob
01202     DocRect BlobRect;
01203     pBlobMgr->GetBlobRect(*PointerPos, &BlobRect);
01204 
01205     // Find the selected range of objects
01206     SelRange* Selected = GetApplication()->FindSelection();
01207     Node* pNode = Selected->FindFirst();
01208 
01209     // see if the selection is on the same spread as the drag
01210     if (pNode!=NULL)
01211     {
01212         // Get the spread and return if it is different from the one with the cursor
01213         Spread* pNodeSpread = pNode->FindParentSpread();
01214         if (pNodeSpread!=pSpread)
01215             return FALSE;       
01216     }
01217 
01218     // Walk through the selection
01219     while (pNode!=NULL)
01220     {
01221         if (pNode->IsNodePath())
01222         {
01223             // Now we know it's a NodePath, get a pointer to the Path object within it, so
01224             // we can find any endpoints
01225             Path* ThisPath = &(((NodePath*)pNode)->InkPath);
01226             
01227             // See if it touches any point in this path
01228             INT32 CloseSlot = 0;
01229             if (ThisPath->IsNearOpenEnd(BlobRect, &CloseSlot))
01230             {
01231                 // Yep, better work out where and set the path where is all happens
01232                 ThisPath->SetPathPosition(CloseSlot);
01233                 *PointerPos = ThisPath->GetCoord();
01234                 *IsNearEndpoint = TRUE;
01235                 EndPath = (NodePath*)pNode;
01236                 return TRUE;
01237             }
01238 
01239             // See if it is near the middle of the path
01240             // This is only relavent if this node is the same as the one we started on
01241             // and we started in the middle of it
01242             if ((StartPath!=NULL) && (StartPath==pNode) && (pJoinInfo->IsNearEndPoint==FALSE))
01243             {
01244                 INT32 Range = BlobRect.Width() / 2;
01245                 if (ThisPath->IsPointCloseTo(*PointerPos, Range*Range, &CloseTo, &Mu))
01246                 {
01247                     // OK, we are near the path we started on
01248                     // see if we are in the same sub-path as we started on
01249                     if (AreSlotsInSameSubPath(CloseTo, pJoinInfo->CloseSlot, ThisPath))
01250                     {
01251                         // Yep, better work out where and set the path where is all happens
01252                         *IsNearEndpoint = FALSE;
01253                         EndPath = (NodePath*)pNode;
01254                         return TRUE;
01255                     }
01256                 }
01257             }
01258         }   
01259 
01260         // Now find the next selected node
01261         pNode = Selected->FindNext(pNode);
01262     }
01263 
01264     // we will see if this point is anywhere near the start point
01265     if ((BlobRect.ContainsCoord(StartPoint)) && (StartPath==NULL))
01266     {
01267         // Note the path that we are starting at and modify the coord
01268         // This will make the start and end points the same, causing the path to be closed
01269         *PointerPos = StartPoint;
01270 
01271         // Wait till the cursor has moved out of the original snap range before we start joining
01272         if (CanLineJoin==TRUE)
01273         {
01274             *IsNearEndpoint = TRUE;
01275             return TRUE;
01276         }
01277     }
01278 
01279     // Did not find a join
01280     return FALSE;
01281 }
01282 
01283 
01284 
01285 /********************************************************************************************
01286 
01287 >   BOOL OpFreeHand::AreSlotsInSameSubPath(INT32 Slot1, INT32 Slot2, Path* pPath)
01288 
01289     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
01290     Created:    2/6/95
01291     Inputs:     Slot1, Slot2 - The slot numbers of the 2 points in question.
01292                 pPath - the path that the slots live in
01293     Returns:    TRUE if both these slots live in the same sub-path of pPath,
01294                 FALSE if they are in different sub-paths, or there was an error.
01295     Purpose:    This function is used when editing lines with the freehand tool. If the
01296                 drag was started in the middle of a path and the cursor is over that path
01297                 again, we need to know if we can complete the edit. This is only possible if
01298                 the start and end points are both on the same sub-path in the path.
01299                 This function is used to determine if the 2 points are indeed in the same
01300                 sub-path to allow the tool to display correct cursor etc.
01301 
01302 ********************************************************************************************/
01303 
01304 BOOL OpFreeHand::AreSlotsInSameSubPath(INT32 Slot1, INT32 Slot2, Path* pPath)
01305 {
01306     // saftey check
01307     ERROR2IF(pPath==NULL, FALSE, "NULL pointer passed to OpFreeHand::AreSlotsInSameSubPath");
01308 
01309     // We need to look at the verb array in the path.
01310     PathVerb* pVerbs = pPath->GetVerbArray();
01311     INT32 NumSlots = pPath->GetNumCoords();
01312 
01313     // find the largest and smallest slot numbers
01314     INT32 First = Slot1<Slot2 ? Slot1 : Slot2;
01315     INT32 Last = Slot1<Slot2 ? Slot2 : Slot1;
01316 
01317     // Make sure that the slot numbers make sense
01318     ERROR2IF(Last>NumSlots, FALSE, "Bad Slot numbers in OpFreeHand::AreSlotsInSameSubPath");
01319 
01320     // loop from the first to the last slot looking for MoveTos
01321     while (Last>First)
01322     {
01323         // See if the current last slot is a MoveTo
01324         if (pVerbs[Last]==PT_MOVETO)
01325             return FALSE;
01326 
01327         // move the last point back
01328         Last--;
01329     }
01330 
01331     // We did not find any MoveTos between the points
01332     return TRUE;    
01333 }
01334 
01335 
01336 
01337 /********************************************************************************************
01338 
01339 >   void OpFreeHand::SetCursorAndStatus(CursorType CurType)
01340 
01341     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
01342     Created:    9/5/94
01343     Inputs:     CurType - The type of cursor to change to
01344     Purpose:    Chanages the cursor to the type requested and sets the status bar message
01345                 to the appropriate message for the mode we are in.
01346 
01347 ********************************************************************************************/
01348 
01349 void OpFreeHand::SetCursorAndStatus(CursorType CurType)
01350 {
01351     // Now, if FlipCursor = TRUE, we flip it!
01352     Cursor* WhichCursor = NULL;
01353     String_256 StatusMsg("");
01354 
01355     switch (CurType)
01356     {
01357         case NORMAL_CURSOR:
01358             WhichCursor = pFreeHandCursor;
01359             StatusMsg.Load(_R(IDS_FREEHANDDRAG));
01360             break;
01361 
01362         case JOIN_CURSOR:
01363             WhichCursor = pJoinCursor;
01364             StatusMsg.Load(_R(IDS_FREEHANDDRAGJOIN));
01365             break;
01366 
01367         case STRAIGHTLINE_CURSOR:
01368             WhichCursor = pStraightCursor;
01369             StatusMsg.Load(_R(IDS_FREEHANDSTRAIGHT));
01370             break;
01371 
01372         case RUBOUT_CURSOR:
01373             WhichCursor = pRubOutCursor;
01374             StatusMsg.Load(_R(IDS_FREEHANDRUBOUT));
01375             break;
01376 
01377         case MODIFY_CURSOR:
01378             WhichCursor = pModifyCursor;
01379             StatusMsg.Load(_R(IDS_FREEHANDMODIFY));
01380             break;
01381 
01382         default:
01383             return;
01384     }
01385 
01386     // Change Status bar message and the cursor
01387     GetApplication()->UpdateStatusBarText(&StatusMsg);
01388     if (WhichCursor != MyCurrentCursor)
01389     {
01390         // set this cursor as the current cursor and immediately display it
01391         CursorStack::GSetTop(WhichCursor, CurrentCursorID);
01392 
01393         // remember this is our current cursor
01394         MyCurrentCursor = WhichCursor;
01395     }
01396 }
01397 
01398 
01399 /********************************************************************************************
01400 
01401 >   BOOL OpFreeHand::TryToJoinNewPathWithOthers(NodePath* FreePath)
01402 
01403     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
01404     Created:    4/5/94
01405     Inputs:     FreePath - The new path that we will try to join with other paths
01406     Returns:    TRUE if it worked, FALSE if it did not
01407     Purpose:    Tries to Join the new path with any of the existing paths
01408 
01409 ********************************************************************************************/
01410 
01411 BOOL OpFreeHand::TryToJoinNewPathWithOthers(NodePath* FreePath)
01412 {
01413     ObjChangeFlags cFlagsDelete(TRUE);
01414     ObjChangeFlags cFlagsReplace(FALSE,TRUE);
01415     ObjChangeFlags cFlagsChanging;
01416     ObjChangeParam ObjChange;
01417     
01418     if (StartPath != NULL && EndPath != NULL && StartPath != EndPath)
01419     {
01420         // Cope with the case when the two separate paths are being joined together with this freehand line.
01421         // In this case, the EndPath is hidden (effectively deleted), and the StartPath is replaced in the tree
01422         // by hiding it and inserting the resultant NodePath in its place
01423 
01424         // Is it ok to delete the end path?
01425         ObjChange.Define(OBJCHANGE_STARTING,cFlagsDelete,EndPath,this);
01426         if (!EndPath->AllowOp(&ObjChange))
01427             return TRUE;    // Pretend that we've done it.
01428 
01429         // Is it ok to replace the start path?
01430         ObjChange.Define(OBJCHANGE_STARTING,cFlagsReplace,StartPath,this);
01431         if (!StartPath->AllowOp(&ObjChange))
01432             return TRUE;    // Pretend that we've done it.
01433     }
01434     else if (StartPath != NULL || EndPath != NULL)
01435     {
01436         // Some joining will take place with either in StartPath or EndPath
01437 
01438         // Either the StartPath or the EndPath will be hidden and replaced by another NodePath
01439         ObjChange.Define(OBJCHANGE_STARTING,cFlagsChanging,StartPath,this);
01440 
01441         // If we have a start path, the op will happen to the start path
01442         if (StartPath != NULL && !StartPath->AllowOp(&ObjChange))
01443             return TRUE;    // Pretend that we've done it.
01444 
01445         // If we DONT have a start path, the op will happen to the end path
01446         if (StartPath == NULL && !EndPath->AllowOp(&ObjChange))
01447             return TRUE;    // Pretend that we've done it.
01448     }
01449 
01450     if (StartPath == NULL)
01451     {
01452         if (EndPath == NULL)
01453         {
01454             // Trivial Case - No Joining needed
01455             NodePath* JoinPath = MakeCopy(FreePath);
01456             if (JoinPath == NULL)
01457                 return FALSE;
01458 
01459             // Try to close it
01460             JoinPath->InkPath.TryToClose();
01461 
01462             // Apply some attributes
01463             if (!ApplyAttributes(JoinPath, GetWorkingDoc()))
01464                 return FALSE;
01465 
01466 
01467             if (!AddPressureAttribute(JoinPath))
01468                 return(FALSE);
01469 
01470             // Insert the node and invalidate the region 
01471             if (!DoInsertNewNode(JoinPath, StartSpread, FALSE))
01472                 return FALSE;
01473             
01474             if (!DoInvalidateNodeRegion(JoinPath, TRUE))
01475                 return FALSE;
01476 
01477             m_pNewNodePath = JoinPath;
01478             // Tell the freehand tool that it has a previous path
01479             SetRetroPath(JoinPath, 0, JoinPath->InkPath.GetNumCoords());
01480         }
01481         else
01482         {
01483             if (EndPath->InkPath.IsComplexPath())
01484             {
01485                 // Complex Join (End of new path Joined)
01486                 if (!ComplexJoin(FreePath, EndPath))
01487                     return FALSE;
01488             }
01489             else
01490             {
01491                 // Simple Join (End of new path Joined)
01492                 if (!SimpleJoin(FreePath, EndPath))
01493                     return FALSE;
01494             }
01495         }
01496     }
01497     else
01498     {
01499         if (EndPath == NULL)
01500         {
01501             if (StartPath->InkPath.IsComplexPath())
01502             {
01503                 // Complex Join (Start of new path Joined)
01504                 if (!ComplexJoin(FreePath, StartPath))
01505                     return FALSE;
01506             }
01507             else
01508             {
01509                 // Simple Join (Start of New Path Joined)
01510                 if (!SimpleJoin(FreePath, StartPath))
01511                     return FALSE;
01512             }
01513         }
01514         else
01515         {
01516             if (StartPath == EndPath)
01517             {
01518                 if (StartPath->InkPath.IsComplexPath())
01519                 {
01520                     // Complex to Same Complex Join
01521                     if (!ComplexToComplexJoin(FreePath, StartPath))
01522                         return FALSE;
01523                 }
01524                 else
01525                 {
01526                     // Simple to Same Simple Join (Start = End)
01527                     if (!SimpleJoin(FreePath, StartPath))
01528                         return FALSE;
01529                 }
01530             }
01531             else
01532             {
01533                 // Compex To Complex Join (Start and End different and complex)
01534                 // Complex To Simple Join (Start and End different and start complex)
01535                 // Complex To Simple Join (Start and End different and end complex)
01536                 // Simple To Simple Join (Start and End Different)
01537                 if (!VeryComplexToComplexJoin(FreePath, StartPath, EndPath))
01538                     return FALSE;
01539             }
01540         }
01541     }
01542 
01543     // Worked
01544     return TRUE;
01545 }
01546 
01547 
01548 
01549 /********************************************************************************************
01550 
01551 >   BOOL OpFreeHand::SimpleJoin(NodePath* FreePath, NodePath* OldPath)
01552 
01553     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
01554     Created:    5/5/94
01555     Inputs:     FreePath - The New freehand Path that is to be joined
01556                 OldPath - The path that we are trying to Join to
01557     Returns:    TRUE if it worked, FALSE if it failed
01558     Purpose:    Joins a Simple path with the new freehand path and builds all the undo that
01559                 is needed in the process.
01560 
01561 ********************************************************************************************/
01562 
01563 BOOL OpFreeHand::SimpleJoin(NodePath* FreePath, NodePath* OldPath)
01564 {
01565     // First we should make a note of the region that we will need redrawing
01566     DocRect InvalidRect = FreePath->GetBlobBoundingRect();
01567     
01568     DocRect OriginalRect = OldPath->GetBoundingRect();
01569 //  Spread* pSpread = Document::GetSelectedSpread();
01570 
01571     InvalidateBrushRegion(OldPath);
01572 
01573 
01574     // Need to know where in the joined path that the new section will be
01575     INT32 NumSlots = FreePath->InkPath.GetNumCoords();
01576     INT32 StartSlot = 0;
01577 //  INT32 OldNumSlots = OldPath->InkPath.GetNumCoords();
01578     
01579 //  DocCoord* pCoords = FreePath->InkPath.GetCoordArray();
01580 //  DocCoord* pOldCoords = OldPath->InkPath.GetCoordArray();
01581 
01582     // Make a copy of the node we are joining with
01583     NodePath* JoinPath = MakeCopy(OldPath);
01584     if (JoinPath == NULL)
01585         return FALSE;
01586 
01587     // first find out if we are editing a brush (ugh, yes I know)
01588     AttrBrushType* pAttrBrush;
01589     NodeAttribute* pAttr = NULL;
01590     BrushHandle Handle = BrushHandle_NoBrush;
01591     JoinPath->FindAppliedAttribute(CC_RUNTIME_CLASS(AttrBrushType), &pAttr);
01592     if (pAttr != NULL)
01593     {
01594         pAttrBrush = (AttrBrushType*)pAttr;
01595         Handle = pAttrBrush->GetBrushHandle();
01596     }
01597 
01598     // find out if the two paths join at the start
01599     BOOL PathReversed = FALSE;
01600 
01601     // before Joining them
01602     BOOL NewPathReversed = FALSE;
01603     
01604     if (Handle != BrushHandle_NoBrush)
01605         SimpleJoinBrush(JoinPath, &(FreePath->InkPath));
01606     
01607     if (!JoinPath->InkPath.SimpleJoin(&FreePath->InkPath, &StartSlot, &NewPathReversed, &PathReversed))
01608         return FALSE;
01609 
01610     // Insert the new object
01611     if (!InsertNewNode(JoinPath, InvalidRect, OldPath))
01612         return FALSE;
01613 
01614     // Reverse the original mouse move data if the fitted path was reversed
01615     if (NewPathReversed)
01616         TrackData->Reverse();
01617     
01618     // if we are a brush then we don't ever want the path reversed, 
01619     if (Handle != BrushHandle_NoBrush && PathReversed)
01620     {
01621         TrackData->Reverse();
01622         JoinPath->InkPath.Reverse();
01623         StartSlot = 0;
01624     }
01625 
01626     // Tell the freehand tool all about it
01627     SetRetroPath(JoinPath, StartSlot, NumSlots);
01628     
01629     m_pNewNodePath = JoinPath;
01630 
01631     InvalidateBrushRegion(JoinPath);
01632     
01633     // It Worked!
01634     return TRUE;
01635 }
01636 
01637 
01638 
01639 /********************************************************************************************
01640 
01641 >   BOOL OpFreeHand::ComplexJoin(NodePath* FreePath, Nodepath* OldPath)
01642 
01643     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
01644     Created:    5/5/94
01645     Inputs:     FreePath - The New freehand Path that is to be joined
01646                 OldPath - The path that we are trying to Join to
01647     Returns:    TRUE if it worked, FALSE if it failed
01648     Purpose:    Joins a Complex path with the new freehand path and builds all the undo that
01649                 is needed in the process.
01650 
01651 ********************************************************************************************/
01652 
01653 BOOL OpFreeHand::ComplexJoin(NodePath* FreePath, NodePath* OldPath)
01654 {
01655     // First we should make a note of the region that we will need redrawing
01656     DocRect InvalidRect = FreePath->GetBlobBoundingRect();
01657 
01658     // Need to know where in the joined path that the new section will be
01659     INT32 NumSlots = FreePath->InkPath.GetNumCoords();
01660     INT32 StartSlot;
01661 
01662     // Make a copy of the node we are joining with
01663     NodePath* JoinPath = MakeCopy(OldPath);
01664     if (JoinPath == NULL)
01665         return FALSE;
01666 
01667     // before Joining them
01668     BOOL NewPathReversed = FALSE;
01669     if (!JoinPath->InkPath.ComplexJoin(&FreePath->InkPath, &StartSlot, &NewPathReversed))
01670         return FALSE;
01671 
01672     // Insert the new object
01673     if (!InsertNewNode(JoinPath, InvalidRect, OldPath))
01674         return FALSE;
01675 
01676     // Reverse the original mouse move data if the fitted path was reversed
01677     if (NewPathReversed)
01678         TrackData->Reverse();
01679 
01680     // Tell the freehand tool all about it
01681     SetRetroPath(JoinPath, StartSlot, NumSlots);
01682     
01683     m_pNewNodePath = JoinPath;
01684     // It Worked!
01685     return TRUE;
01686 }
01687 
01688 
01689 
01690 /********************************************************************************************
01691 
01692 >   BOOL OpFreeHand::ComplexToComplexJoin(NodePath* FreePath, NodePath* OldPath)
01693 
01694     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
01695     Created:    5/5/94
01696     Inputs:     FreePath - The New freehand Path that is to be joined
01697                 OldPath - The path that we are trying to Join to
01698     Returns:    TRUE if it worked, FALSE if it failed
01699     Purpose:    Joins a Complex path with the new freehand path and builds all the undo that
01700                 is needed in the process. The new freehand path joins two of the complex 
01701                 paths sub-paths together.
01702 
01703 ********************************************************************************************/
01704 
01705 BOOL OpFreeHand::ComplexToComplexJoin(NodePath* FreePath, NodePath* OldPath)
01706 {
01707     // First we should make a note of the region that we will need redrawing
01708     DocRect InvalidRect = FreePath->GetBlobBoundingRect();
01709 
01710     // Need to know where in the joined path that the new section will be
01711     INT32 NumSlots = FreePath->InkPath.GetNumCoords();
01712     INT32 StartSlot;
01713 
01714     // Make a copy of the of the path we are Joining to
01715     NodePath* JoinPath = MakeCopy(OldPath);
01716     if (JoinPath==NULL)
01717         return FALSE;
01718 
01719     // Join the new path with the one already there
01720     BOOL NewPathReversed = FALSE;
01721     if (!JoinPath->InkPath.ComplexToSameComplexJoin(&FreePath->InkPath, &StartSlot, &NewPathReversed))
01722         return FALSE;
01723 
01724     // Insert the new object
01725     if (!InsertNewNode(JoinPath, InvalidRect, OldPath))
01726         return FALSE;
01727 
01728     // Reverse the original mouse move data if the fitted path was reversed
01729     if (NewPathReversed)
01730         TrackData->Reverse();
01731 
01732     // Tell the freehand tool all about it
01733     SetRetroPath(JoinPath, StartSlot, NumSlots);
01734 
01735     // It Worked!
01736     return TRUE;
01737 }
01738 
01739 
01740 
01741 /********************************************************************************************
01742 
01743 >   BOOL OpFreeHand::VeryComplexToComplexJoin(NodePath* FreePath, NodePath* TopPath, NodePath* BotPath)
01744 
01745     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
01746     Created:    5/5/94
01747     Inputs:     FreePath - The New freehand Path that is to be joined
01748                 TopPath -   The path to join with the new freehand path. This is the path
01749                             that is highest in the Z order of the two paths
01750                 BotPath -   The path to join with the new freehand path. This is the path
01751                             that is lowest in the Z order of the two paths
01752     Returns:    TRUE if it worked, FALSE if it failed
01753     Purpose:    Joins the new freehand path with two other paths (Which may or may not be
01754                 complex) and builds all the undo info that is needed.
01755 
01756 ********************************************************************************************/
01757 
01758 BOOL OpFreeHand::VeryComplexToComplexJoin(NodePath* FreePath, NodePath* TopPath, NodePath* BotPath)
01759 {
01760     // First we should make a note of the region that we will need redrawing
01761     DocRect InvalidRect = FreePath->GetBlobBoundingRect();
01762 
01763     // Need to know where in the joined path that the new section will be
01764     INT32 NumSlots = FreePath->InkPath.GetNumCoords();
01765     INT32 StartSlot;
01766 
01767     // Make a copy of the of the path we are Joining to
01768     NodePath* JoinPath = MakeCopy(TopPath);
01769     if (JoinPath==NULL)
01770         return FALSE;
01771 
01772     // Merge in the other complex path
01773     if (!JoinPath->InkPath.MergeTwoPaths(BotPath->InkPath))
01774         return FALSE;
01775 
01776     // Join the new path with the one already there
01777     BOOL NewPathReversed = FALSE;
01778     if (!JoinPath->InkPath.ComplexToSameComplexJoin(&FreePath->InkPath, &StartSlot, &NewPathReversed))
01779         return FALSE;
01780 
01781     // Insert the new object
01782     if (!InsertNewNode(JoinPath, InvalidRect, TopPath, BotPath))
01783         return FALSE;
01784 
01785     // Reverse the original mouse move data if the fitted path was reversed
01786     if (NewPathReversed)
01787         TrackData->Reverse();
01788 
01789     // Tell the freehand tool all about it              
01790     SetRetroPath(JoinPath, StartSlot, NumSlots);
01791 
01792     // It Worked
01793     return TRUE;
01794 }
01795 
01796 
01797 
01798 /********************************************************************************************
01799 
01800 >   BOOL OpFreeHand::ApplyAttributes(NodePath* NewPath, Document *pDocument)
01801 
01802     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
01803     Created:    4/5/94
01804     Inputs:     NewPath - The NodePath to apply the current attributes to
01805                 pDocument - the document this path will be placed into.
01806     Returns:    TRUE if it worked, FALSE if it did not
01807     Purpose:    Applies the current attributes to the Node passed in. It has to find the
01808                 current document to do this and will ENSURE if it fails to find one.
01809 
01810 ********************************************************************************************/
01811 
01812 BOOL OpFreeHand::ApplyAttributes(NodePath* NewPath, Document *pDocument)
01813 {
01814     // Find the current document to get the current attributes from
01815     ENSURE(pDocument!=NULL, "Null Document while setting attributes for new NodePath");
01816     if (pDocument!=NULL)
01817     {
01818         // Apply the current attributes to the path
01819         if (pDocument->GetAttributeMgr().ApplyCurrentAttribsToNode((NodeRenderableInk*)NewPath))
01820         {
01821             // ok special case exception here, if we have a brush attribute then delete it
01822             Node* pNode = NewPath->FindFirstChild(CC_RUNTIME_CLASS(AttrBrushType));
01823             if (pNode)
01824             {
01825                 pNode->CascadeDelete();
01826                 delete pNode;
01827             }
01828 
01829             return TRUE;
01830         }
01831     }
01832 
01833     // We failed to apply the attributes, so fail
01834     return FALSE;
01835 }
01836 
01837 
01838 
01839 /********************************************************************************************
01840 
01841 >   BOOL OpFreeHand::AddPressureAttribute(NodePath *NewPath)
01842 
01843     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
01844     Created:    21/1/97
01845     Inputs:     NewPath - The NodePath to apply the attributes to
01846 
01847     Returns:    TRUE if it worked, FALSE if it did not
01848 
01849     Purpose:    If pressure recording is enabled, this method builds attributes to 
01850                 apply to the path in order to attach the pressure as a variable-width
01851                 stroking description.
01852 
01853                 This attaches an AttrStrokeType to control stroking, and an AttrVariableWidth
01854                 to record the pressure values
01855 
01856 ********************************************************************************************/
01857 
01858 BOOL OpFreeHand::AddPressureAttribute(NodePath *pNewPath)
01859 {
01860     if (IsBrushOp())
01861         return TRUE;
01862 // Taken out by vector stroking code Neville 2/10/97
01863 #ifdef VECTOR_STROKING
01864 
01865     ERROR3IF(GetWorkingDoc() == NULL, "No working doc");
01866     ERROR3IF(pNewPath == NULL, "Illegal NULL param");
01867     ERROR3IF(TrackData == NULL, "Trackdata is NULL");
01868 
01869     // If pressure recording is disabled, then we don't need to do anything
01870     if (TrackData == NULL || !AddPressureToPath)
01871         return(TRUE);
01872 
01873     // --- Generate a smoothed pressure function from the recorded pressure samples
01874     // If this fails, then we still return TRUE, and the path just loses pressure information
01875     PressureSmoother Bob;
01876     ValueFunction *pValFunc = Bob.Smooth(TrackData, GetCurrentLineWidth());
01877     if (pValFunc == NULL)
01878         return(TRUE);
01879 
01880     // We only want to override the stroke type if it is a simple constant-width line
01881     // - if there is any new-style stroke type applied, we won't try to override it
01882     BOOL ApplyType = FALSE;
01883     
01884     // BLOCK
01885     {
01886         Node *pNode = pNewPath->FindFirstChild();
01887         if (pNode != NULL)
01888             pNode = pNode->FindNext(CC_RUNTIME_CLASS(AttrStrokeType));
01889 
01890         ApplyType = (pNode == NULL);    // If there are no StrokeType attrs applied already, we must apply one
01891         if (!ApplyType)
01892         {
01893             // If there is another StrokeType, but it will not stroke using the variable width info,
01894             // (i.e. has a NULL PathProcessor attached to it) we will need to replace it.
01895             StrokeTypeAttrValue *pSAV = (StrokeTypeAttrValue *) ((AttrStrokeType *)pNode)->GetAttributeValue();
01896             if (pSAV->GetPathProcessor() == NULL)
01897                 ApplyType = TRUE;
01898         }
01899     }
01900 
01901     // --- Create a simple variable-width path stroke attribute
01902     AttrStrokeType *pStrokeType = NULL;
01903     if (ApplyType)
01904     {
01905         pStrokeType = new AttrStrokeType;
01906         if (pStrokeType == NULL)
01907             return(TRUE);               // Return TRUE - we'll only lose the pressure info
01908 
01909         PathProcessorStroke *pProcessor = new PathProcessorStroke;
01910         if (pProcessor == NULL)
01911         {
01912             delete pStrokeType;
01913             return(TRUE);               // Return TRUE - we'll only lose the pressure info
01914         }
01915 
01916         // And attach the stroker to the attribute
01917         ((StrokeTypeAttrValue *)pStrokeType->GetAttributeValue())->SetPathProcessor(pProcessor);
01918     }
01919 
01920     // --- Create a pressure table variable-width attribute
01921     AttrVariableWidth *pVarWidth = new AttrVariableWidth;
01922     if (pVarWidth != NULL)
01923         ((VariableWidthAttrValue *)pVarWidth->GetAttributeValue())->SetWidthFunction(pValFunc);
01924 
01925     if (pVarWidth == NULL)
01926     {
01927         if (pStrokeType != NULL)
01928             delete pStrokeType;
01929         return(TRUE);               // Return TRUE - we'll only lose the pressure info
01930     }
01931 
01932     // --- Now apply the attributes to the path, removing any of a similar type which
01933     // are already applied to it.
01934     if (ApplyType)
01935     {
01936         DoRemoveAttrTypeFromSubtree(pNewPath, pStrokeType->GetRuntimeClass());
01937         pStrokeType->AttachNode(pNewPath, FIRSTCHILD, FALSE, FALSE);
01938     }
01939 
01940     // And apply the variable width attribute
01941     DoRemoveAttrTypeFromSubtree(pNewPath, pVarWidth->GetRuntimeClass());
01942     pVarWidth->AttachNode(pNewPath, FIRSTCHILD, FALSE, FALSE);
01943 
01944 #endif // VECTOR_STROKING
01945 
01946     return(TRUE);
01947 }
01948 
01949 
01950 
01951 /********************************************************************************************
01952 
01953 >   NodePath* OpFreeHand::MakeCopy(Node* pOriginalNode)
01954 
01955     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
01956     Created:    3/5/94
01957     Inputs:     pOriginalNode - The node to do a 'deep' copy of
01958     Returns:    The Copied node (NULL if it failed)
01959     Purpose:    Copies the node pOriginalNode and all its children an returns a pointer
01960                 to them. It makes sure that the Node is a NodePath (Which it should be).
01961 
01962 ********************************************************************************************/
01963 
01964 NodePath* OpFreeHand::MakeCopy(Node* pOriginalNode)
01965 {
01966     // Make a copy of the node we are joining with
01967     Node* pNode;
01968     if (!pOriginalNode->NodeCopy(&pNode))
01969         return NULL;
01970     
01971     // Check that the new node is OK
01972     ENSURE(pNode!=NULL, "Copied node seems to be NULL in TryToJoinNewPathWithOthers()");
01973     ENSURE(pNode->IsKindOf(CC_RUNTIME_CLASS(NodePath)), "Copied node was not a Path");
01974 
01975     // return it
01976     return (NodePath*) pNode;
01977 }
01978 
01979 
01980 /********************************************************************************************
01981 
01982 >   BOOL OpFreeHand::InsertNewNode(NodePath* pNewNode, DocRect& Invalid,
01983                                    Node* pOldNode, Node* pOtherOld = NULL)
01984 
01985     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
01986     Created:    3/5/94
01987     Inputs:     pNewNode - The Node to add to the tree
01988                 Invalid - The region that needs to be invalidated
01989                 pOldNode - The node that we joined to and will be hidden
01990                 pOtherOld - We may need to hide 2 other paths. This should be NULL(default)
01991                 or a pointer to a Node
01992     Returns:    TRUE if it worked, FALSE if not
01993     Purpose:    Inserts a new path into the tree and hides the original node that it was
01994                 Joined to. It will also invalidate the region of the new path that is
01995                 different.
01996 
01997 ********************************************************************************************/
01998 
01999 BOOL OpFreeHand::InsertNewNode(NodePath* pNewNode, DocRect& Invalid, Node* pOldNode, Node* pOtherOld)
02000 {
02001     // Insert the new object
02002     if (!DoInsertNewNode(pNewNode, pOldNode, NEXT, FALSE))
02003         return FALSE;
02004 
02005     // Invalidate the appropriate region.
02006     // if the filled state of the path has changed, then we need to invalidate the whole thing
02007     ENSURE(pOldNode->IsKindOf(CC_RUNTIME_CLASS(NodePath)), "Joining to something that was not a path");
02008 
02009 //  if (pNewNode->InkPath.IsFilled == ((NodePath*)pOldNode)->InkPath.IsFilled)
02010 //  {
02011         // Invalidate only the new bit
02012 //      Spread* pSpread = pOldNode->FindParentSpread();
02013 //      if (!DoInvalidateRegion(pSpread, Invalid))
02014 //          return FALSE;
02015 //  }
02016 //  else
02017 
02018     // invalidate the whole thing
02019     if (!DoInvalidateNodeRegion(pNewNode, TRUE))
02020         return FALSE;       
02021 
02022     // Hide the original
02023     if (!DoHideNode(pOldNode, TRUE))
02024         return FALSE;
02025 
02026     // and hide the other original node if it existed
02027     if (pOtherOld != NULL)
02028         if (!DoHideNode(pOtherOld, TRUE))
02029             return FALSE;
02030 
02031     // Make sure that the Selection Cache is refreshed
02032     SelRange* Selection = GetApplication()->Selection;
02033     if (Selection)
02034         Selection->Update();
02035 
02036     // It worked
02037     return TRUE;
02038 }
02039 
02040 
02041 
02042 
02043 
02044 /********************************************************************************************
02045 
02046 >   void OpFreeHand::SetRetroPath(NodePath* pNodePath, INT32 Start, INT32 Len)
02047 
02048     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
02049     Created:    23/5/94
02050     Inputs:     pNodePath - The new path that we want to set up for retro fitting
02051                 Start - The Start slot of the new section
02052                 Len - The number of slots the new section takes up
02053     Purpose:    Calls the freehand tool with details about the new path, so that future
02054                 changes in the smoothness will re-build the new path
02055 
02056 ********************************************************************************************/
02057 
02058 void OpFreeHand::SetRetroPath(NodePath* pNodePath, INT32 Start, INT32 Len)
02059 {
02060     // Go find the current tool
02061     Tool* pTool = Tool::GetCurrent();
02062     if (pTool)
02063     {
02064         // Make sure that it is the freehand tool, and if so set the previous path
02065         if (pTool->GetID()==TOOLID_FREEHAND)
02066             ((FreeHandTool*)pTool)->SetPreviousPath(pNodePath, Start, Len);
02067     }
02068 }
02069 
02070 
02071 
02072 
02073 /********************************************************************************************
02074 
02075 >   void OpFreeHand::RenderLine(RenderRegion *pRender, Path *pPath, INT32 CoordIndex, BOOL StartIndex = FALSE)
02076 
02077     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
02078     Created:    28/1/97
02079     Inputs:     pRender - the RenderReigon to draw into
02080                 pPath - The path to draw a line segment from
02081                 CoodIndex - The index of the end coordinate of the line to render. This
02082                             will render an EORed line from Coords[CoordIndex-1] to
02083                             Coords[CoordIndex]
02084                 StartIndex - is Index the start index?  If not then we simply render between
02085                 Index-1 and Index, otherwise we render between Index and NumCoords -1
02086     Purpose:    Renders an EORed line between 2 coordinates in the path.
02087                 May render pressure information feedback using the pressure stored with
02088                 the path corrdinates.
02089 
02090 ********************************************************************************************/
02091 
02092 void OpFreeHand::RenderLine(RenderRegion *pRender, Path *pPath, INT32 Index, BOOL StartIndex)
02093 {
02094     ERROR3IF(pRender == NULL || pPath == NULL, "illegal NUL params");
02095     ERROR3IF(Index < 1 || Index >= pPath->GetNumCoords(), "Out of range coordinate");
02096 
02097     INT32 EndIndex = Index;
02098     if (!StartIndex)
02099     {
02100         EndIndex = Index;
02101         
02102     }
02103     else
02104         EndIndex = pPath->GetNumCoords() - 1;
02105 
02106     Index--;
02107     DocCoord *Coord = pPath->GetCoordArray();
02108 
02109     // Always draw the blue centreline
02110     pRender->SetLineColour(COLOUR_XORNEW);
02111     pRender->DrawLine(Coord[Index], Coord[EndIndex]);
02112     
02113 // Taken out by vector stroking code Neville 2/10/97
02114 #ifdef VECTOR_STROKING
02115 
02116     // If we're showing pressure info, and the points are not coincident, and the average
02117     // line width is large enough to avoid hideous EOR mess, render parallel width lines
02118     if (!AddPressureToPath)
02119         return;
02120 
02121     PathWidth *Width = pPath->GetWidthArray();
02122     if (Width == NULL)
02123         return;
02124 
02125     const INT32 CurrentLineWidth = GetCurrentLineWidth();
02126     const double WidthMul = (double)CurrentLineWidth / (double)EXTRAVALUEMAX;
02127     INT32 StartWidth    = (INT32) ((double)Width[Index-1] * WidthMul);
02128     INT32 EndWidth  = (INT32) ((double)Width[Index]   * WidthMul);
02129 
02130     if (StartWidth > CurrentLineWidth)      // Damage control in case of rampant pens giving
02131         StartWidth = CurrentLineWidth;      // out of range values. You may laugh, but that's
02132     if (EndWidth > CurrentLineWidth)        // exactly what the pen code was doing until I
02133         EndWidth = CurrentLineWidth;        // just fixed it. This just stops wild rampancy
02134 
02135     if (Coord[Index-1] != Coord[Index] && (StartWidth + EndWidth) > pRender->GetPixelWidth() * 8)
02136     {
02137         pRender->SetLineColour(COLOUR_MIDGREY);
02138 
02139         // Get the normal to the centreline and normalise it to unit length
02140         NormCoord Normal( Coord[Index-1].y - Coord[Index].y, -(Coord[Index-1].x - Coord[Index].x));
02141         Normal.Normalise();
02142 
02143         // Now plot the two parallel edges
02144         DocCoord P1(Coord[Index-1].x + (INT32)(Normal.x * StartWidth), Coord[Index-1].y + (INT32)(Normal.y * StartWidth));
02145         DocCoord P2(Coord[Index].x   + (INT32)(Normal.x * EndWidth),   Coord[Index].y   + (INT32)(Normal.y * EndWidth));
02146         pRender->DrawLine(P1, P2);
02147 
02148         DocCoord P3(Coord[Index-1].x - (INT32)(Normal.x * StartWidth), Coord[Index-1].y - (INT32)(Normal.y * StartWidth));
02149         DocCoord P4(Coord[Index].x   - (INT32)(Normal.x * EndWidth),   Coord[Index].y   - (INT32)(Normal.y * EndWidth));
02150         pRender->DrawLine(P3, P4);
02151     }
02152 #endif // VECTOR_STROKING
02153 }
02154 
02155 
02156 
02157 /********************************************************************************************
02158 
02159 >   void OpFreeHand::RenderLine(DocRect* Rect, Spread* pSpread, Path *pPath, INT32 CoordIndex,
02160                                 BOOL StartIndex = FALSE)
02161 
02162     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
02163     Created:    28/1/97
02164     Inputs:     pRender - the RenderReigon to draw into
02165                 pPath - The path to draw a line segment from
02166                 CoodIndex - The index of the end coordinate of the line to render. This
02167                             will render an EORed line from Coords[CoordIndex-1] to
02168                             Coords[CoordIndex]
02169                 StartIndex - is Index the start index?  If not then we simply render between
02170                 Index-1 and Index, otherwise we render between Index and NumCoords -1
02171     Purpose:    Renders an EORed line between 2 coordinates in the path.
02172                 May render pressure information feedback using the pressure stored with
02173                 the path corrdinates.
02174 
02175 ********************************************************************************************/
02176 
02177 void OpFreeHand::RenderLine(DocRect* Rect, Spread* pSpread, Path *pPath, INT32 Index, BOOL StartIndex)
02178 {
02179     RenderRegion* pRegion = DocView::RenderOnTop(Rect, pSpread, UnclippedEOR );
02180     while ( pRegion )
02181     {
02182         RenderLine(pRegion, pPath, Index, StartIndex);
02183 
02184         // get the next region to draw in
02185         pRegion = DocView::GetNextOnTop(Rect);
02186     }   
02187 }
02188 
02189 
02190 
02191 
02192 /********************************************************************************************
02193 
02194 >   void OpFreeHand::RenderEorStraightLine(RenderRegion *pRender,
02195                                         DocCoord *pStart, INT32 StartWidth,
02196                                         DocCoord *pEnd, INT32 EndWidth)
02197     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
02198     Created:    22/1/97
02199     Inputs:     pRender - the render region to render intp
02200                 pStart - The start of the line (non-NULL)
02201                 StartWidth - The line width (in millipoints) at pStart
02202                 pEnd - The end of the line (non-NULL)
02203                 EndWidth- The line width (in millipoints) at pEnd
02204 
02205     Purpose:    Simply renders a straight-line segment to the given render region
02206 
02207                 However, it may also elect to show width information by rendering
02208                 parallel straight line segments on either side of the line
02209 
02210 ********************************************************************************************/
02211 
02212 void OpFreeHand::RenderEorStraightLine(RenderRegion *pRender,
02213                                         DocCoord *pStart, INT32 StartWidth,
02214                                         DocCoord *pEnd, INT32 EndWidth)
02215 {
02216     ERROR3IF(pRender == NULL || pStart == NULL || pEnd == NULL, "illegal NUL params");
02217 
02218     // Always draw the blue centreline
02219     pRender->SetLineColour(COLOUR_XORNEW);
02220     pRender->DrawLine(*pStart, *pEnd);
02221 
02222 // Taken out by vector stroking code Neville 2/10/97
02223 #ifdef VECTOR_STROKING
02224 
02225     // If we're showing pressure info, and the points are not coincident, and
02226     // the average line width is large enough to avoid hideous EOR mess, render parallel width lines
02227     if (AddPressureToPath && *pStart != *pEnd && (StartWidth + EndWidth) > pRender->GetPixelWidth() * 8)
02228     {
02229         pRender->SetLineColour(COLOUR_MIDGREY);
02230 
02231         // Get the normal to the centreline and normalise it to unit length
02232         NormCoord Normal( pStart->y - pEnd->y, -(pStart->x - pEnd->x));
02233         Normal.Normalise();
02234 
02235         // Now plot the two parallel edges
02236         DocCoord P1(pStart->x + (INT32)(Normal.x * StartWidth), pStart->y + (INT32)(Normal.y * StartWidth));
02237         DocCoord P2(pEnd->x   + (INT32)(Normal.x * EndWidth),   pEnd->y   + (INT32)(Normal.y * EndWidth));
02238         pRender->DrawLine(P1, P2);
02239 
02240         DocCoord P3(pStart->x - (INT32)(Normal.x * StartWidth), pStart->y - (INT32)(Normal.y * StartWidth));
02241         DocCoord P4(pEnd->x   - (INT32)(Normal.x * EndWidth),   pEnd->y   - (INT32)(Normal.y * EndWidth));
02242         pRender->DrawLine(P3, P4);
02243     }
02244 
02245 #endif // VECTOR_STROKING
02246 }
02247 
02248 
02249 
02250 /********************************************************************************************
02251 
02252 >   void OpFreeHand::RenderEorStraightLine(DocRect* Rect, Spread* pSpread,
02253                                             DocCoord *pStart, DocCoord *pEnd)
02254     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
02255     Created:    22/4/94
02256     Inputs:     Rect - The cliprect
02257                 pSpread - The spread to draw to
02258                 pStart - The start of the line (NULL means "PreviousPoint")
02259                 pEnd - The end of the line (NULL means "StraightLinePos")
02260                 
02261     Purpose:    Renders the EORed rubber band straight line that is used in Straight Line
02262                 Mode. It simply draws a single Straight Line segment
02263 
02264 ********************************************************************************************/
02265 
02266 void OpFreeHand::RenderEorStraightLine(DocRect* Rect, Spread* pSpread, DocCoord *pStart, DocCoord *pEnd)
02267 {
02268     if (pStart == NULL)
02269         pStart = &PreviousPoint;
02270     if (pEnd == NULL)
02271         pEnd = &StraightLinePos;
02272 
02273     INT32 LineWidth = GetCurrentLineWidth();
02274 
02275     RenderRegion* pRegion = DocView::RenderOnTop(Rect, pSpread, UnclippedEOR );
02276     while ( pRegion )
02277     {
02278         RenderEorStraightLine(pRegion, pStart, LineWidth, pEnd, LineWidth);
02279 
02280         // get the next region to draw in
02281         pRegion = DocView::GetNextOnTop(Rect);
02282     }
02283 }
02284 
02285 
02286 
02287 /********************************************************************************************
02288 
02289 >   INT32 OpFreeHand::GetCurrentLineWidth(void)
02290 
02291     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
02292     Created:    22/1/97
02293 
02294     Returns:    A line width in millipoints.
02295                 NOTE that this is half the normal linewidth value (i.e. it's the line radius)
02296 
02297     Purpose:    Determines the current line width (the width of any future
02298                 lines the user will draw) by asking the attribute manager.
02299 
02300 ********************************************************************************************/
02301 
02302 INT32 OpFreeHand::GetCurrentLineWidth(void)
02303 {
02304     ERROR3IF(GetWorkingDoc() == NULL, "No working doc!");
02305     AttrLineWidth *pWidthAttr = (AttrLineWidth *) GetWorkingDoc()->GetAttributeMgr().
02306                     GetCurrentAttribute(CC_RUNTIME_CLASS(NodeRenderableInk), CC_RUNTIME_CLASS(AttrLineWidth));
02307     INT32 LineWidth = 1000;
02308     if (pWidthAttr != NULL)
02309         LineWidth = pWidthAttr->Value.LineWidth / 2;
02310 
02311     return(LineWidth);
02312 }
02313 
02314 
02315 
02316 /********************************************************************************************
02317 
02318 >   virtual void OpFreeHand::RenderDragBlobs( DocRect Rect, Spread* pSpread, BOOL bSolidDrag )
02319 
02320     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
02321     Created:    6/9/93
02322     Inputs:     Rect - The Rectangle that needs to be re-rendered
02323                 pSpread - The spread on which the rendering is to take place
02324     Purpose:    EORs the whole path onto the screen a segment at a time. It needs to 
02325                 do it a segment at a time as that is how it is drawn as the path is
02326                 created. This has to redraw it identically or there will be bits of
02327                 EORed stuff left all over the place.
02328     SeeAlso:    OpFreeHand::RenderLineSegment()
02329 
02330 ********************************************************************************************/
02331 
02332 void OpFreeHand::RenderDragBlobs( DocRect Rect, Spread* pSpread, BOOL bSolidDrag )
02333 {
02334     // Work out which bits we need to draw
02335     DocRect* pRect;
02336     if (Rect.IsEmpty())
02337         pRect = NULL;
02338     else
02339         pRect = &Rect;
02340 
02341     // Get information on the path we've got to render
02342     INT32 NumCoords = TrackData->GetNumCoords();
02343 //  DocCoord* Coords = TrackData->GetCoordArray();
02344 
02345     // Find out the current line width
02346 //  INT32 LineWidth = GetCurrentLineWidth();
02347 
02348     // Render into all the available rectangles
02349     RenderRegion* pRegion = DocView::RenderOnTop(pRect, pSpread, UnclippedEOR );
02350     while ( pRegion )
02351     {
02352         // Rather than just calling DrawPath, we render each line ourselves - this allows the variable
02353         // width indicator lines to be rendered as well, and ensures all the EORing remains consistent.
02354         for (INT32 n = 1; n < NumCoords; n++)
02355             RenderLine(pRegion, TrackData, n);
02356 
02357         // get the next region to draw in
02358         pRegion = DocView::GetNextOnTop(pRect);
02359     }
02360 
02361     if (IsStraightLineMode)
02362         RenderEorStraightLine(pRect, pSpread);
02363 }
02364 
02365 
02366 
02367 
02368 /********************************************************************************************
02369 
02370 >   BOOL OpFreeHand::TryToReplaceSection(NodePath* pNewNodePath)
02371 
02372     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
02373     Created:    27/10/94
02374     Inputs:     pNewNodePath - The New Path that we want to use instead of a section of
02375                 an existing path
02376     Purpose:    Replaces a section from the path that the drag was started and finished on
02377                 with the newly drawn curve section.
02378 
02379 ********************************************************************************************/
02380 
02381 BOOL OpFreeHand::TryToReplaceSection(NodePath* pNewNodePath)
02382 {
02383     // The elements that the split occured on
02384     INT32 FirstElement, SecondElement, NewElements;
02385 
02386     ObjChangeFlags cFlags;
02387     ObjChangeParam ObjChange(OBJCHANGE_STARTING,cFlags,EndPath,this);
02388     if (!EndPath->AllowOp(&ObjChange, TRUE))
02389         return TRUE;    // Pretend that we've done it.
02390 
02391     // Invalidate it as it was
02392     if (!DoInvalidateNodeRegion(EndPath, TRUE))
02393         return FALSE;       
02394 
02395     // Split old path at start and end points
02396     if (!SplitAtPoint(StartPoint, &FirstElement, &NewElements))
02397         return FALSE;
02398 
02399     if (!SplitAtPoint(PreviousPoint, &SecondElement, &NewElements))
02400         return FALSE;
02401     
02402     // We may need to adjust the first element if the second element was
02403     // before it in the path
02404     if (SecondElement<=FirstElement)
02405     {
02406         // Swap the elements around and move the first one back a bit
02407         INT32 Temp = FirstElement + (NewElements/2);
02408         FirstElement = SecondElement;
02409         SecondElement = Temp;
02410 
02411         // We will also need to reverse the original curve here
02412         // (as well as the original track data)
02413         pNewNodePath->InkPath.Reverse();
02414         TrackData->Reverse();
02415         ReverseBrushPressure(); // if we are a brush we will want to reverse our pressure data
02416     }
02417 
02418     // Go and see if which part of the path we should be replacing
02419     if (!EndPath->InkPath.IsComplexPath() && EndPath->InkPath.IsFilled)
02420     {
02421         // Here we need to see if we will replace the wrong bit of the path and try and do something
02422         // about it if we are. The wrong bit is defined as follows. If you were to draw a filled shape
02423         // such as an ellipse and modified it by drawing a small loop around one of the control points
02424         // you should get an ellipse with a small notch cut out of it. However, if you do this around
02425         // the Start/End control point you end up with no ellipse and just a small notch.
02426         // To get round this, we always try to replace the smallest number of control points possible.
02427         INT32 HalfElements = EndPath->InkPath.GetNumCoords() / 2;
02428         INT32 ReplaceElements = SecondElement - FirstElement;
02429         if (HalfElements < ReplaceElements)
02430         {
02431             // We were trying to replace more than half of the elements in the path, so actually
02432             // replace the other, large half of the path
02433             
02434             // Here we have to change the the start element of the path, so we have
02435             // to hide the original and make a copy of it
02436             BOOL IsOk = FALSE;
02437             Node* pCopyNode = NULL;
02438             CALL_WITH_FAIL(EndPath->NodeCopy(&pCopyNode), this, IsOk);
02439 
02440             // See if we managed to make a copy of the node
02441             if (!IsOk)
02442                 return FALSE;
02443 
02444             // Insert the copy
02445             if (!DoInsertNewNode((NodeRenderableBounded*)pCopyNode, EndPath, NEXT, FALSE))
02446                 return FALSE;
02447 
02448             // Hide the original
02449             if (!DoHideNode(EndPath, TRUE))
02450                 return FALSE;
02451 
02452             // Copy the pointer back to where it used to be
02453             EndPath = (NodePath*) pCopyNode;
02454             ERROR3IF(!EndPath->IS_KIND_OF(NodePath), "Copy of a Node path was not a node path");
02455 
02456             // Make the Last replace element the start/end of the path
02457             if (EndPath->InkPath.ChangeStartElement(SecondElement-1))
02458             {
02459                 // It worked, so modify the Start and End Elements to match
02460                 FirstElement = 1;
02461                 SecondElement = EndPath->InkPath.GetNumCoords() - ReplaceElements;
02462 
02463                 // We will also need to reverse the original curve here
02464                 // (as well as the original track data)
02465                 pNewNodePath->InkPath.Reverse();
02466                 TrackData->Reverse();
02467             }
02468         }
02469 
02470     }
02471 
02472     // replace section with new path
02473     INT32 NumElements = SecondElement - FirstElement;
02474     if (!ReplaceMiddleOfPath(pNewNodePath, FirstElement, NumElements))
02475         return FALSE;
02476 
02477     // Mark the nodes bounding rect as invalid
02478     EndPath->InvalidateBoundingRect();
02479     
02480     TRACEUSER( "Diccon", _T("Replacing middle of Path\n"));
02481     InvalidateBrushRegion(EndPath);
02482     // and invalidate how it is now
02483     if (!DoInvalidateNodeRegion(EndPath, TRUE))
02484         return FALSE;       
02485 
02486     // Failed to do it
02487     return TRUE;
02488 }
02489 
02490 
02491 
02492 
02493 
02494 /********************************************************************************************
02495 
02496 >   BOOL OpFreeHand::SplitAtPoint(const DocCoord& SplitPoint)
02497 
02498     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
02499     Created:    31/10/94
02500     Inputs:     SplitPoint - The coord that we want the split to happen close to
02501     Outputs:    SplitAt - The Slot number of the new end point
02502                 NewElements - The number of new elements inserted into the path
02503     Returns:    TRUE if it was able to split the path ok, FALSE if it failed
02504     Purpose:    Splits the path, building undo info, at the coord supplied.
02505 
02506 ********************************************************************************************/
02507 
02508 BOOL OpFreeHand::SplitAtPoint(const DocCoord& SplitPoint, INT32* SplitAt, INT32* NewElements)
02509 {
02510     // First we need to get some space to put any new coords etc that will be generated
02511     INT32 SplitElement;
02512     UINT32 NumElements;
02513     PathVerb    NewVerbs[6];
02514     DocCoord    NewCoords[6];
02515     PathFlags   NewFlags[6];
02516 
02517     // First we had better go and see if this point is close to any of the existing coords
02518     BlobManager* pBlobMgr = GetApplication()->GetBlobManager();
02519     if (pBlobMgr!=NULL)
02520     {
02521         // There was a blob manager, so we could test the coords
02522         // Find out about the area round the mouse position to consider for rubbing out
02523         DocRect BlobRect;
02524         DocCoord BlobPoint = SplitPoint;
02525         pBlobMgr->GetBlobRect(BlobPoint, &BlobRect);
02526 
02527         // Find out about all the coords in the path already
02528         PathFlags* Flags = EndPath->InkPath.GetFlagArray();
02529         DocCoord* Coords = EndPath->InkPath.GetCoordArray();
02530         INT32 NumCoords = EndPath->InkPath.GetNumCoords();
02531 
02532         // Loop through all the coords in the path
02533         for (INT32 i=0; i<NumCoords; i++)
02534         {
02535             // Is this coord close to the point specified
02536             if ((Flags[i].IsEndPoint==TRUE) && (BlobRect.ContainsCoord(Coords[i])))
02537             {
02538                 // Yep, set the outputs to the correct values and return Success
02539                 *SplitAt = i+1;
02540                 *NewElements = 0;
02541 
02542                 // Make sure that the rotate flags are unset around the coord
02543                 Flags[i].IsRotate = FALSE;
02544                 if (i>0)
02545                     Flags[i-1].IsRotate = FALSE;
02546 
02547                 if (i<NumCoords-1)
02548                     Flags[i+1].IsRotate = FALSE;
02549 
02550                 // And tell caller that it worked
02551                 return TRUE;
02552             }
02553         }
02554     }
02555 
02556     // Try and split the path. This returns FALSE if the path did not need splitting
02557     if (EndPath->InkPath.SplitAtPoint(SplitPoint, &SplitElement, &NumElements, NewVerbs, NewCoords))
02558     {
02559         // Make a note of where the new endpoint will be
02560         *SplitAt = SplitElement+(NumElements/2);
02561         *NewElements = NumElements;
02562 
02563         // The path was split, so we know where, and how, so let's party on the path
02564         PathVerb* Verbs = EndPath->InkPath.GetVerbArray();
02565         PathFlags* Flags = EndPath->InkPath.GetFlagArray();
02566         DocCoord* Coords = EndPath->InkPath.GetCoordArray();
02567 //      UINT32  NumCoords = EndPath->InkPath.GetNumCoords();    
02568         INT32 NumToChange;
02569 
02570         // We're adding something, either a line or curve - check which
02571         if ((Verbs[SplitElement] & ~PT_CLOSEFIGURE) == PT_BEZIERTO)
02572         {
02573             // Adding a curve - number to change = 6
02574             NumToChange = 6;
02575 
02576             // Initialise the flags appropriately
02577             for (INT32 i=0; i<6; i++)
02578             {
02579                 // copy the flags from the old curve (repeat the flag in the first 3 elements)
02580                 NewFlags[i] = Flags[SplitElement+(i%3)];
02581                 NewFlags[i].IsSelected = FALSE;
02582 
02583                 // Want it to be a cusp in the middle
02584                 if ((i>0) && (i<4))
02585                 {
02586                     NewFlags[i].IsSmooth = FALSE;
02587                     NewFlags[i].IsRotate = FALSE;
02588                 }
02589             }
02590 
02591             // If this was happening at the end of a path,
02592             // then make sure the close figure flag is set correctly
02593             if (Verbs[SplitElement+2] & PT_CLOSEFIGURE)
02594                 NewVerbs[5] |= PT_CLOSEFIGURE;
02595         }
02596         else
02597         {
02598             // The slit happened in a straight line section
02599             NumToChange = 2;
02600             NewFlags[0] = NewFlags[1] = Flags[SplitElement];
02601             NewFlags[0].IsSmooth = NewFlags[0].IsRotate = FALSE;
02602             NewFlags[0].IsSelected = FALSE;
02603 
02604             // Make sure that the close figure flag is maintained
02605             if (Verbs[SplitElement] & PT_CLOSEFIGURE)
02606                 NewVerbs[1] |= PT_CLOSEFIGURE;
02607         }
02608 
02609         // Start performing actions to insert the split point with full undo
02610         Action* UnAction;
02611         ActionCode Act = RemovePathElementAction::Init(this, &UndoActions, NumToChange/2,
02612                                                     SplitElement, (Action**)(&UnAction));
02613 
02614         // if we failed to init the action, then fail this operation
02615         if (Act==AC_FAIL)
02616             return FALSE;
02617 
02618         // record details about the path in the action before we start inserting stuff
02619         ((RemovePathElementAction*)UnAction)->RecordPath(EndPath);
02620 
02621         // Move to the place in the path where the split took place
02622         EndPath->InkPath.SetPathPosition(SplitElement);
02623         PathFlags InsertFlags;
02624         BOOL      InsertWorked;
02625 
02626         // insert a curve or a line, depending on where the split happened
02627         if (NumToChange==6)
02628             InsertWorked = EndPath->InkPath.InsertCurveTo(NewCoords[0], NewCoords[1], NewCoords[2], &InsertFlags);
02629         else
02630             InsertWorked = EndPath->InkPath.InsertLineTo(NewCoords[0], &InsertFlags);
02631 
02632         // see if the insertion worked
02633         if (!InsertWorked)
02634             return FALSE;
02635                 
02636         // Build an action to record the changes we're about to make to the path
02637         ModifyPathAction* ModAction;
02638         Act = ModifyPathAction::Init(this, &UndoActions, NumToChange, (Action**)(&ModAction));
02639         if (Act==AC_FAIL)
02640             return FALSE;
02641     
02642         // If the action is happy...
02643         if (Act!=AC_NORECORD)
02644         {
02645             // Some pointers to what has changed
02646             PathVerb* ChangedVerbs;
02647             PathFlags* ChangedFlags;
02648             DocCoord* ChangedCoords;
02649             INT32* ChangedIndices;
02650 
02651             // memory for the details about what has changed
02652             ALLOC_WITH_FAIL(ChangedVerbs, (PathVerb*) CCMalloc(NumToChange * sizeof(PathVerb)), this);
02653             ALLOC_WITH_FAIL(ChangedFlags, (PathFlags*) CCMalloc(NumToChange* sizeof(PathFlags)), this);
02654             ALLOC_WITH_FAIL(ChangedCoords, (DocCoord*) CCMalloc(NumToChange* sizeof(DocCoord)), this);
02655             ALLOC_WITH_FAIL(ChangedIndices, (INT32*) CCMalloc(NumToChange* sizeof(INT32)), this);
02656 
02657             // See of all the allocations worked
02658             if (!ChangedVerbs || !ChangedFlags || !ChangedCoords || !ChangedIndices)
02659             {
02660                 // They did not, so free them up and fail
02661                 if (ChangedVerbs) CCFree(ChangedVerbs);
02662                 if (ChangedFlags) CCFree(ChangedFlags);
02663                 if (ChangedCoords) CCFree(ChangedCoords);
02664                 if (ChangedIndices) CCFree(ChangedIndices);
02665 
02666                 return FALSE;
02667             }
02668 
02669             // Reread the pointers, in case they've changed
02670             Verbs = EndPath->InkPath.GetVerbArray();
02671             Flags = EndPath->InkPath.GetFlagArray();
02672             Coords = EndPath->InkPath.GetCoordArray();
02673 
02674             // Now record the arrays...
02675             for (INT32 i=0; i<NumToChange; i++)
02676             {
02677                 // record which elements changed and what they used to be
02678                 ChangedIndices[i] = SplitElement+i;
02679                 ChangedVerbs[i] = Verbs[SplitElement+i];
02680                 ChangedFlags[i] = Flags[SplitElement+i];
02681                 ChangedCoords[i] = Coords[SplitElement+i];
02682             }
02683 
02684             // and store the info in the action
02685             ModAction->StoreArrays(ChangedVerbs, ChangedFlags, ChangedCoords, ChangedIndices, EndPath);
02686         }
02687 
02688         // Reread the pointers, in case they've changed
02689         Verbs = EndPath->InkPath.GetVerbArray();
02690         Flags = EndPath->InkPath.GetFlagArray();
02691         Coords = EndPath->InkPath.GetCoordArray();
02692 
02693         // Now that the undo rigmarole has been done, let's change the path
02694         for (INT32 i=0; i<NumToChange; i++)
02695         {
02696             Verbs[SplitElement+i] = NewVerbs[i];
02697             Flags[SplitElement+i] = NewFlags[i];
02698             Coords[SplitElement+i] = NewCoords[i];
02699         }
02700     }
02701     else
02702     {
02703         // else we tried to split at an existing control point (endpoint)
02704         // That means that there were no new endpoints
02705         *NewElements = 0;
02706         *SplitAt = SplitElement;
02707 
02708         // See if we can find the control point in question
02709         DocCoord* Coords = EndPath->InkPath.GetCoordArray();
02710         INT32 NumCoords = EndPath->InkPath.GetNumCoords();
02711 
02712         // see if the split point is over the last point on the curve
02713         if (Coords[NumCoords-1]==SplitPoint)
02714             *SplitAt = NumCoords;
02715     }   
02716 
02717     // all worked
02718     return TRUE;
02719 }
02720 
02721 
02722 
02723 
02724 /********************************************************************************************
02725 
02726 >   BOOL OpFreeHand::ReplaceMiddleOfPath(NodePath* pNewPath, INT32 FirstChangedIndex,
02727                                         INT32 NumElements)
02728 
02729     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
02730     Created:    1/11/94
02731     Inputs:     pNewPath - The New curve that has just been draw
02732                 FirstChangedIndex - The first coord in the path that is to be replaced
02733                 NumElements - The number of elements in the path that need replacing
02734     Returns:    TRUE if it worked, FALSE if not
02735     Purpose:    This will replace a section of the path with a new path, building undo
02736                 as it goes. This function will also set up the Retro Curve fitting data
02737                 so that it is able to retro fit the new section of the path.
02738 
02739 ********************************************************************************************/
02740 
02741 BOOL OpFreeHand::ReplaceMiddleOfPath(NodePath* pNewPath, INT32 FirstChangedIndex, INT32 NumElements)
02742 {
02743     // if we are a brush then do our special smoothing
02744     //etroSmoothBrush(pNewPath);
02745 
02746     // if we are editing a brush with timestamp or pressure lists 
02747     EditBrushLists(pNewPath, FirstChangedIndex, NumElements);
02748     
02749     // If there is something to remove from this path, the do it
02750     if (NumElements>0)
02751     {
02752         // Make ourselves an insert action as we are about to delete the
02753         // old bits of the path we no longer want
02754         ActionCode Act;
02755         InsertPathElementAction* ModAction;
02756         Act = InsertPathElementAction::Init(this, &UndoActions, NumElements, FirstChangedIndex,
02757                                            (Action**)(&ModAction));
02758     
02759         // Did we init the action ok
02760         if (Act == AC_FAIL)
02761             return FALSE;
02762 
02763         // If the action will let us, record the details of what we want to delete
02764         if (Act!=AC_NORECORD)
02765         {
02766             // We need to store info about all the elements we are going to delete
02767             PathVerb* ChangedVerbs;
02768             DocCoord* ChangedCoords;
02769             PathFlags* ChangedFlags;
02770 
02771             // Get some memory to put them in
02772             ALLOC_WITH_FAIL(ChangedVerbs, (PathVerb*)CCMalloc(NumElements*sizeof(PathVerb)), this);
02773             ALLOC_WITH_FAIL(ChangedCoords, (DocCoord*)CCMalloc(NumElements*sizeof(DocCoord)), this);
02774             ALLOC_WITH_FAIL(ChangedFlags, (PathFlags*)CCMalloc(NumElements*sizeof(PathFlags)), this);
02775 
02776             // Make sure that we got the memory
02777             if (!ChangedVerbs || !ChangedCoords || !ChangedFlags)
02778             {
02779                 if (ChangedVerbs) CCFree(ChangedVerbs);
02780                 if (ChangedCoords) CCFree(ChangedCoords);
02781                 if (ChangedFlags) CCFree(ChangedFlags);
02782                 return FALSE;
02783             }
02784 
02785             // Get pointers to all the arrays of the path
02786             PathVerb* Verbs = EndPath->InkPath.GetVerbArray();
02787             DocCoord* Coords = EndPath->InkPath.GetCoordArray();
02788             PathFlags* Flags = EndPath->InkPath.GetFlagArray();
02789 
02790             // Now copy the data from the path into the arrays
02791             for (INT32 i=0; i<NumElements; i++)
02792             {
02793                 ChangedVerbs[i] = Verbs[FirstChangedIndex+i];
02794                 ChangedCoords[i] = Coords[FirstChangedIndex+i];
02795                 ChangedFlags[i] = Flags[FirstChangedIndex+i];
02796             }
02797 
02798             // And ask the action to make a note of them
02799             ModAction->RecordPath(ChangedVerbs, ChangedFlags, ChangedCoords, EndPath);
02800         }
02801 
02802         // Delete the elements from the path that we no longer want
02803         EndPath->InkPath.DeleteSection(FirstChangedIndex, NumElements);
02804     }
02805 
02806 
02807     // Next we have to add the new points, so build undo to remove them
02808     Action* UnAction;
02809     INT32 NumCoords = pNewPath->InkPath.GetNumCoords() - 1;
02810     ActionCode Act = RemovePathElementAction::Init(this, &UndoActions, NumCoords,
02811                                                 FirstChangedIndex, (Action**)(&UnAction));
02812 
02813     // if we failed to init the action, then fail this operation
02814     if (Act==AC_FAIL)
02815         return FALSE;
02816 
02817     // record details about the path in the action before we start inserting stuff
02818     ((RemovePathElementAction*)UnAction)->RecordPath(EndPath);
02819 
02820     // Insert stuff here
02821     EndPath->InkPath.InsertSection(FirstChangedIndex, NumCoords);
02822 
02823     // Get pointers to all the arrays of the original path
02824     PathVerb* Verbs = EndPath->InkPath.GetVerbArray();
02825     DocCoord* Coords = EndPath->InkPath.GetCoordArray();
02826     PathFlags* Flags = EndPath->InkPath.GetFlagArray();
02827 
02828     // Get the pointer to all the arrays of the new path data
02829     PathVerb* NewVerbs = pNewPath->InkPath.GetVerbArray();
02830     DocCoord* NewCoords = pNewPath->InkPath.GetCoordArray();
02831     PathFlags* NewFlags = pNewPath->InkPath.GetFlagArray();
02832 
02833     // Copy the new path data into the hole we have made
02834     FirstChangedIndex--;
02835     for (INT32 i=1; i<=NumCoords; i++)
02836     {
02837         Verbs[FirstChangedIndex+i] = NewVerbs[i];
02838         Coords[FirstChangedIndex+i] = NewCoords[i];
02839         Flags[FirstChangedIndex+i] = NewFlags[i];
02840     }
02841 
02842     // set up retro stuff here
02843     SetRetroPath(EndPath, FirstChangedIndex, pNewPath->InkPath.GetNumCoords());
02844 
02845     return TRUE;
02846 }
02847 
02848 
02849 
02850 /********************************************************************************************
02851 
02852 >   BOOL OpFreeHand::EditBrushLists(NodePath* pNewPath, INT32 FirstChangedIndex,
02853                                         INT32 NumElements)
02854 
02855     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
02856     Created:    19/3/2000
02857     Inputs:     pNewPath - The New curve that has just been draw
02858                 FirstChangedIndex - The first coord in the path that is to be replaced
02859                 NumElements - The number of elements in the path that need replacing
02860     Returns:    TRUE if it worked, FALSE if not
02861     Purpose:    When a path is edited that has a timestamping brush applied to it we those 
02862                 points are stored in a list that is generated when the path is drawn.  If we 
02863                 subsequently want to edit that path we have to insert or remove points from 
02864                 that list.  This function works out what points should be inserted or removed,
02865                 and performs that action on the applied brush.
02866 
02867 ********************************************************************************************/
02868 
02869 BOOL OpFreeHand::EditBrushLists(NodePath* pNewPath, INT32 FirstChangedIndex, INT32 NumElements)
02870 {
02871     // what we are going to do here is find out whether or not the path we are editing is brushed
02872     // and if so whether or not it uses pressure data.  If so then we will generate a list of pressure
02873     // data and insert it.
02874     ERROR2IF(pNewPath == NULL, FALSE, "pNewPath is NULL in OpFreeHand::EditPressureList");
02875 
02876     /* First up, find the nearest applied brush attribute. If there isn't one or
02877     it has no pressure list then we don't have to bother with all this */
02878     AttrBrushType* pAttrBrush = NULL;
02879 
02880     pAttrBrush = EndPath->GetAppliedBrushAttribute();
02881     if (pAttrBrush == NULL)
02882         return TRUE;
02883 
02884     // if we are attempting to edit an existing brush that does not have an existing pressure
02885     // cache then leave now
02886     if (!pAttrBrush->ContainsPressureCache())
02887         return TRUE;
02888     
02889     // most of the action takes place in the attribute value so get that also
02890     BrushAttrValue* pVal = (BrushAttrValue*)pAttrBrush->GetAttributeValue();
02891     if (pVal == NULL)
02892         return TRUE;
02893 
02894     /* next, record the following distances:
02895     - original path length;
02896     - distance to first changed point;
02897     - distance to last changed point;
02898     */
02899 
02900 //  double OriginalLength = EndPath->InkPath.GetPathLength();
02901     MILLIPOINT DistToFirstPoint = -1;
02902     MILLIPOINT DistToLastPoint = -1;
02903     DocCoord* pCoords = EndPath->InkPath.GetCoordArray();
02904 
02905     DocCoord FirstChangedPoint;
02906     DocCoord LastChangedPoint;
02907 
02908     if (pCoords == NULL)
02909         return FALSE;
02910     if (FirstChangedIndex + NumElements > EndPath->InkPath.GetNumCoords())
02911     {
02912         ERROR3("Illegal number of coordinates");
02913         return FALSE;
02914     }
02915 
02916     // get the coordinates from the array
02917     FirstChangedPoint = pCoords[FirstChangedIndex];
02918     LastChangedPoint  =  pCoords[FirstChangedIndex + NumElements];
02919 
02920     EndPath->InkPath.GetDistanceToPoint(FirstChangedPoint, &DistToFirstPoint);
02921     EndPath->InkPath.GetDistanceToPoint(LastChangedPoint, &DistToLastPoint);
02922 
02923     // Now find the indices into the pressure list that correspond to those distances
02924     INT32 StartPressureIndex = pVal->GetPressureListIndexAtDistance(DistToFirstPoint);
02925     if (StartPressureIndex == -1)
02926     {
02927         ERROR3("StartPressureIndex is -1 in OpDrawBrush::EditPressureList");
02928         return FALSE;
02929     }
02930     
02931     INT32 EndPressureIndex = pVal->GetPressureListIndexAtDistance(DistToLastPoint);
02932     if (EndPressureIndex == -1 || EndPressureIndex <= StartPressureIndex)
02933     {
02934         ERROR3("EndPressureIndex is invalid in OpDrawBrush::EditPressureList");
02935         return FALSE;
02936     }
02937     UINT32 NumObjects = EndPressureIndex - StartPressureIndex;
02938 
02939     CDistanceSampler* pSampler = OpDrawBrush::GeneratePressureData(pAttrBrush, DistToFirstPoint, DistToLastPoint,
02940                                                 (MILLIPOINT)pNewPath->InkPath.GetPathLength()); 
02941 
02942     if (pSampler == NULL)  // did it fail, of so we must quit
02943         return FALSE;
02944     /* now get the pressure points list and ask the attribute to clear all the points
02945     between the edited distances*/
02946 
02947     // Invalidate it as it was
02948     pAttrBrush->ClearCachedRect();
02949     DocRect Rect = pAttrBrush->GetAttrBoundingRect(EndPath);
02950     EndPath->ReleaseCached();
02951     Spread* pSpread = Document::GetSelectedSpread();
02952     if (!DoInvalidateRegion(pSpread, Rect))
02953         return FALSE;
02954     // now make a removepoints action
02955     RemovePressurePointsAction* pAction; 
02956     
02957     if (RemovePressurePointsAction::Init(this, &UndoActions, pAttrBrush, (UINT32)StartPressureIndex, NumObjects, NULL, &pAction) == AC_FAIL)
02958         return FALSE;
02959 
02960     // now insert our new points into the attribute
02961     AddPressurePointsAction* pAddAction;
02962 
02963     // find out how many points we're inserting
02964     UINT32 NumAddPoints = pSampler->GetNumItems();
02965     
02966     if (AddPressurePointsAction::Init(this, &UndoActions, pAttrBrush, pSampler, (UINT32)StartPressureIndex, NumAddPoints,  &pAddAction) == AC_FAIL)
02967         return FALSE;
02968     
02969     // invalidate the whole thing3
02970     pAttrBrush->ClearCachedRect();
02971     Rect = pAttrBrush->GetAttrBoundingRect(EndPath);    
02972     EndPath->ReleaseCached();
02973     if (!DoInvalidateRegion(pSpread, Rect))
02974         return FALSE;
02975     return TRUE;
02976     
02977 }
02978 
02979 
02980 /********************************************************************************************
02981 
02982 >   BOOL OpFreeHand::SimpleJoinBrush(NodePath* pInsertedNode, Path* pNewPath)
02983 
02984     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
02985     Created:    19/3/2000
02986     Inputs:     pInsertedNode - the nodepath that we have just inserted
02987                 pNewPath - The New curve that has just been drawn
02988                 
02989     Returns:    TRUE if it worked, FALSE if not
02990     Purpose:    Currently this only has to do work if we are editing a brush containing pressure
02991                 data.  In this case we must generate a new list of pressure info and insert it
02992                 into the brush.
02993 
02994 ********************************************************************************************/
02995 
02996 BOOL OpFreeHand::SimpleJoinBrush(NodePath* pInsertedNode, Path* pNewPath)
02997 {
02998     if (pInsertedNode == NULL || pNewPath == NULL)
02999     {
03000         ERROR3("Null inputs to OpDrawBrush::SimpleJoinBrush");
03001         return FALSE;
03002     }
03003     
03004     // first check to see if the edited path has a brush applied to it,
03005     // if it doesn't then we'll leave
03006     NodeAttribute* pAttr;
03007     AttrBrushType* pAttrBrush;
03008     pInsertedNode->FindAppliedAttribute(CC_RUNTIME_CLASS(AttrBrushType), &pAttr);
03009     if (pAttr == NULL)
03010         return TRUE;
03011 
03012     pAttrBrush = (AttrBrushType*)pAttr;
03013     // if our brush has a 'no brush' handle then likewise
03014     if (pAttrBrush->GetBrushHandle() == BrushHandle_NoBrush)
03015         return TRUE;
03016 
03017     // if our brush is not either timestamping or has a pressure list then we will leave aswell
03018     if (!pAttrBrush->ContainsPressureCache() && !pAttrBrush->IsTimeStamping())
03019         return TRUE;
03020 
03021     // Invalidate the brush rect
03022     NodePath* EditPath = StartPath;
03023     if (EditPath == NULL)
03024         EditPath = EndPath;
03025 
03026     if (EditPath != NULL)
03027     {
03028         pAttrBrush->ClearCachedRect();
03029         DocRect BRect = pAttrBrush->GetAttrBoundingRect(EditPath);
03030         EditPath->ReleaseCached();
03031         Spread* pSpread = Document::GetSelectedSpread();
03032         if (pSpread != NULL)
03033             DoInvalidateRegion(pSpread, BRect);
03034     }
03035     else
03036         return FALSE;
03037 
03038     // find out what type of join we have 
03039     SimpleJoinType JoinType = GetSimpleJoinType(pNewPath, &(pInsertedNode->InkPath));
03040     if (JoinType == JOINTYPE_NONE)
03041     {
03042         ERROR3("No join type in OpDrawBrush::SimpleJoinBrush");
03043         return TRUE;
03044     }
03045 
03046     // we need to know the distance along the original path where we want to insert the
03047     // new points.  As this is a simple join it will either be at the beginning or the end.
03048     // we also need to know the distance to the end of the new points, as this is required
03049     // for the undo.
03050     MILLIPOINT NewPathLength = (MILLIPOINT)pNewPath->GetPathLength();
03051     MILLIPOINT OldPathLength = -1;
03052     if (StartPath != NULL)
03053         OldPathLength = (MILLIPOINT)StartPath->InkPath.GetPathLength();
03054     else 
03055     {
03056         if (EndPath != NULL)
03057             OldPathLength = (MILLIPOINT)EndPath->InkPath.GetPathLength();
03058     }
03059 
03060     MILLIPOINT InsertDistance = -1;
03061     MILLIPOINT EndInsertDistance = -1;
03062     INT32 StartIndex = 0; //for the pressure insert
03063 
03064     // ask the attribute for its pressure cache
03065     CDistanceSampler* pAttrData = pAttrBrush->GetPressureCache();
03066     if (pAttrData == NULL)
03067         return FALSE;
03068     // according to the type of join we will want to insert our new data at the beginning or the
03069     // end, we may also want to reverse it.  We need the distance values to give to the pressure
03070     // generator function
03071     switch (JoinType)
03072     {
03073         case JOINTYPE_NEWSTART_TO_OLDSTART:
03074             InsertDistance = -1;
03075             EndInsertDistance = 0; // indicates that we are inserting at the start
03076             StartIndex = 0; 
03077             break;
03078         case JOINTYPE_NEWSTART_TO_OLDEND:
03079             InsertDistance = OldPathLength; // indicates that we are inserting at the end
03080             EndInsertDistance = -1;
03081             StartIndex = pAttrData->GetInternalIndexFromDistance(OldPathLength);
03082             if (StartIndex==-1) StartIndex = pAttrData->GetNumItems();
03083             break;
03084         case JOINTYPE_NEWEND_TO_OLDSTART:  
03085             InsertDistance = -1;
03086             EndInsertDistance = 0;
03087             StartIndex = 0; 
03088             break;
03089         case JOINTYPE_NEWEND_TO_OLDEND:
03090             InsertDistance = OldPathLength;
03091             EndInsertDistance = -1;
03092             StartIndex = pAttrData->GetInternalIndexFromDistance(OldPathLength);    
03093             if (StartIndex==-1) StartIndex = pAttrData->GetNumItems();
03094             
03095             break;
03096         default:
03097             ERROR3("Unknown join type in OpDrawBrush::SimpleJoinBrush");
03098             return FALSE;
03099     }
03100     
03101     
03102     // get ourselves a data cache to insert courtesy of our static function
03103     CDistanceSampler* pSampler = OpDrawBrush::GeneratePressureData(pAttrBrush, InsertDistance, EndInsertDistance, NewPathLength);
03104     if (pSampler == NULL)
03105         return FALSE;
03106     
03107 
03108     // make the action to insert the pressure list
03109     
03110     AddPressurePointsAction* pAction;
03111     UINT32 NumPoints = pSampler->GetNumItems();
03112 
03113     if (AddPressurePointsAction::Init(this, &UndoActions, pAttrBrush, pSampler, 
03114                                     StartIndex, NumPoints, &pAction) == AC_FAIL)
03115         return FALSE;
03116     
03117     // Invalidate the brush rect again
03118     if (EditPath != NULL)
03119     {
03120         pAttrBrush->ClearCachedRect();
03121         DocRect BRect = pAttrBrush->GetAttrBoundingRect(EditPath);
03122         EditPath->ReleaseCached();
03123         Spread* pSpread = Document::GetSelectedSpread();
03124         if (pSpread != NULL)
03125             DoInvalidateRegion(pSpread, BRect);
03126     }
03127 
03128     return TRUE;
03129 }
03130 
03131 
03132 /********************************************************************************************
03133 
03134 >   BOOL OpFreeHand::RetroSmoothBrush(NodePath* pNewPath)
03135 
03136     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
03137     Created:    19/3/2000
03138     Inputs:     pNewPath - The New curve that has just been drawn
03139             
03140     Returns:    TRUE 
03141     Purpose:    As above, the base class version does nothing
03142 
03143 ********************************************************************************************/
03144 
03145 BOOL OpFreeHand::RetroSmoothBrush(NodePath* pNewNodePath)
03146 {
03147     return TRUE;
03148 }
03149 
03150 
03151 
03152 /********************************************************************************************
03153 
03154 >   BOOL OpFreeHand::ReverseBrushPressure()
03155 
03156     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
03157     Created:    19/3/2000
03158     Inputs:     -
03159             
03160     Returns:    TRUE 
03161     Purpose:    The base class version does nothing, derived class should reverse its pressure cache
03162 
03163 ********************************************************************************************/
03164 
03165 BOOL OpFreeHand::ReverseBrushPressure()
03166 {
03167     return TRUE;
03168 }
03169 
03170 /********************************************************************************************
03171 
03172 >   BOOL OpFreeHand::GetSimpleJoinType(NodePath* pNewNodePath)
03173 
03174     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
03175     Created:    21/6/2000
03176     Inputs:     pNewNodePath - The New Path that we want to join to an existing path
03177                 ReversedPath
03178     Returns:    the type of join, (an enumerated type, see the header file)
03179     Purpose:    This function works out how the new path joins onto the existing path
03180 
03181 ********************************************************************************************/
03182 
03183 SimpleJoinType OpFreeHand::GetSimpleJoinType(Path* pNewPath, Path* pExistingPath)
03184 {
03185     if (pNewPath == NULL || pExistingPath == NULL)
03186         return JOINTYPE_NONE;
03187 
03188     // we must join to one of these paths
03189     if (StartPath == NULL && EndPath == NULL)
03190         return JOINTYPE_NONE;
03191     // get the new coordinate array
03192     DocCoord* pNewCoords = pNewPath->GetCoordArray();
03193     if (pNewCoords == NULL)
03194         return JOINTYPE_NONE;
03195 
03196     INT32 NumCoords = pNewPath->GetNumCoords();
03197     // get the start and end coordinates of the new path
03198     DocCoord NewStart = pNewCoords[0];
03199     DocCoord NewEnd   = pNewCoords[NumCoords-1];
03200     
03201     SimpleJoinType ReturnVal = JOINTYPE_NONE;
03202     /*
03203     DocCoord* pOldCoords = pExistingPath->GetCoordArray();
03204     if (pOldCoords != NULL)
03205     {
03206         INT32 NumOldCoords = pExistingPath->GetNumCoords();
03207         DocCoord OldStart = pOldCoords[0];
03208         DocCoord OldEnd = pOldCoords[NumOldCoords -1];
03209 
03210         // ok we don't have to be dead on here
03211         MILLIPOINT NewStartToOldStart = NewStart.Distance(OldStart);
03212         MILLIPOINT NewStartToOldEnd   = NewStart.Distance(OldEnd);
03213     
03214         // for some reason these must be reversed
03215         if (NewStartToOldStart < NewStartToOldEnd)
03216             ReturnVal = JOINTYPE_NEWSTART_TO_OLDSTART;
03217         else
03218             ReturnVal = JOINTYPE_NEWSTART_TO_OLDEND;
03219     }
03220     return Retural;
03221     */
03222     if (StartPath != NULL)
03223     {
03224         DocCoord* pOldCoords = StartPath->InkPath.GetCoordArray();
03225         if (pOldCoords != NULL)
03226         {
03227             INT32 NumOldCoords = StartPath->InkPath.GetNumCoords();
03228             DocCoord OldStart = pOldCoords[0];
03229             DocCoord OldEnd = pOldCoords[NumOldCoords -1];
03230 
03231             // ok we don't have to be dead on here
03232             MILLIPOINT NewStartToOldStart = (MILLIPOINT)NewStart.Distance(OldStart);
03233             MILLIPOINT NewStartToOldEnd   = (MILLIPOINT)NewStart.Distance(OldEnd);
03234         
03235             
03236             if (NewStartToOldStart < NewStartToOldEnd)
03237                 ReturnVal = JOINTYPE_NEWSTART_TO_OLDSTART;
03238             else
03239                 ReturnVal = JOINTYPE_NEWSTART_TO_OLDEND;
03240         }
03241     }
03242 
03243     // if endpath is not null then we are joining the end of the new path to one of the
03244     // ends of the old path
03245     if (EndPath != NULL)
03246     {
03247         DocCoord* pOldCoords = EndPath->InkPath.GetCoordArray();
03248         if (pOldCoords != NULL)
03249         {
03250             INT32 NumOldCoords = EndPath->InkPath.GetNumCoords();
03251             DocCoord OldStart = pOldCoords[0];
03252             DocCoord OldEnd = pOldCoords[NumOldCoords-1];
03253 
03254             // ok we don't have to be dead on here
03255             const MILLIPOINT NewEndToOldStart = (MILLIPOINT)NewEnd.Distance(OldStart);
03256             const MILLIPOINT NewEndToOldEnd   = (MILLIPOINT)NewEnd.Distance(OldEnd);
03257         
03258             if (NewEndToOldStart < NewEndToOldEnd)
03259                 ReturnVal = JOINTYPE_NEWEND_TO_OLDSTART;
03260             else
03261                 ReturnVal = JOINTYPE_NEWEND_TO_OLDEND;
03262         }
03263     }
03264 
03265     return ReturnVal;       
03266 }
03267 /********************************************************************************************
03268 
03269 >   BOOL OpFreeHand::Declare()
03270 
03271     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
03272     Created:    5/7/93
03273     Returns:    TRUE if all went OK, False otherwise
03274     Purpose:    Adds the operation to the list of all known operations
03275 
03276 ********************************************************************************************/
03277 
03278 BOOL OpFreeHand::Declare()
03279 {
03280     return (RegisterOpDescriptor(
03281                                 0, 
03282                                 _R(IDS_FREE_HAND_TOOL),
03283                                 CC_RUNTIME_CLASS(OpFreeHand), 
03284                                 OPTOKEN_FREEHAND,
03285                                 OpFreeHand::GetState,
03286                                 0,  /* help ID */
03287                                 _R(IDBBL_FREEHANDTOOLOP),
03288                                 0   /* bitmap ID */));
03289 }
03290 
03291 
03292 /********************************************************************************************
03293 
03294 >   OpState OpFreeHand::GetState(String_256* Description, OpDescriptor*)
03295 
03296     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
03297     Created:    5/7/93
03298     Outputs:    Description - GetState fills this string with an approriate description
03299                 of the current state of the push tool
03300     Returns:    The state of the operation, so that menu items (ticks and greying can be
03301                 done properly
03302     Purpose:    Find out the state of the operation at the specific time
03303 
03304 ********************************************************************************************/
03305 
03306 OpState OpFreeHand::GetState(String_256* Description, OpDescriptor*)
03307 {
03308     OpState Blobby;
03309     
03310     return Blobby;
03311 }
03312 
03313 /********************************************************************************************
03314 
03315 >   NodeBlend* OpFreeHand::GetParentOfNodePath()
03316 
03317     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
03318     Created:    26/9/99
03319     inputs :    -
03320     outputs:    
03321     returns:    pointer to the first nodeblend in the selection if successful, 
03322                 NULL otherwise
03323     Purpose:    To determine if the current selection is a blend on a curve
03324 
03325 
03326 *********************************************************************************************/
03327 
03328 NodeGroup* OpFreeHand::GetParentOfNodePath()
03329 {
03330     NodeGroup* pParent = NULL;
03331     if (m_pNewNodePath == NULL)  // if no blendpath node then there won't be a group parent
03332         return NULL;
03333     if (!m_pNewNodePath->IsANodeBlendPath())
03334         return NULL;
03335 
03336     pParent = (NodeGroup*)m_pNewNodePath->FindParent();
03337     
03338     ERROR2IF(!(pParent->IS_KIND_OF(NodeGroup)), NULL, "Node is not NodeGroup");
03339     return pParent;
03340 
03341 }
03342 
03343 
03344 /********************************************************************************************
03345 
03346 >   void OpFreeHand::InsertChangeBlendStepsAction(NodeBlend* pNodeBlend)
03347 
03348     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
03349     Created:    1/9/99
03350     Inputs:     the node blend to change
03351     Outputs:    -
03352     Returns:    -
03353     Purpose:    To adjust the number of steps in a blend on a path as a result of the path
03354                 being edited (which is why its here rather than in the blend code).
03355                 First the number of steps is calculated from the new path distance and if
03356                 different a new action is inserted
03357     Errors:     Error2 if pNodeBlend is null
03358     SeeAlso:    called from OpNodePathEditBlob::DragFinished or 
03359                 OpNodePathAddEndpoint::CompleteThisPath
03360 
03361 ********************************************************************************************/
03362 BOOL OpFreeHand::InsertChangeBlendStepsAction(NodeBlend* pNodeBlend)
03363 {
03364     
03365     ERROR2IF(pNodeBlend==NULL, FALSE, "NodeBlend pointer is NULL");
03366     // do we wish to keep the distance between steps constant?  If so then determine
03367     // how many steps we need with the new length and make an action.
03368     if (pNodeBlend->GetEditState() == EDIT_DISTANCE)
03369     {
03370         UINT32 NewNumSteps = 0;
03371         UINT32 OldNumSteps = pNodeBlend->GetNumBlendSteps();
03372         double DistanceEntered = pNodeBlend->GetDistanceEntered();
03373         NodeBlendPath* pNodeBlendPath = pNodeBlend->GetNodeBlendPath(0);
03374         double NewPathLength = pNodeBlendPath->GetPathLength();
03375         
03376         NewNumSteps = (UINT32)(NewPathLength/DistanceEntered);
03377         
03378 
03379         BOOL ok = TRUE;  
03380     
03381         if (OldNumSteps != NewNumSteps)
03382         {
03383             ChangeBlendStepsAction* pAction;
03384                     ok = (InvalidateBoundsAction::Init(this,&UndoActions,pNodeBlend,TRUE) != AC_FAIL);
03385             if (ok) ok = ChangeBlendStepsAction::Init(this,&UndoActions,pNodeBlend,OldNumSteps,DistanceEntered, &pAction) != AC_FAIL;
03386             if (ok) ok = DoInvalidateNodeRegion(pNodeBlend,TRUE,FALSE);
03387             if (ok) ok = (InvalidateBoundsAction::Init(this,&UndoActions,pNodeBlend,TRUE) != AC_FAIL);
03388             pNodeBlend->SetNumBlendSteps(NewNumSteps);
03389 
03390             // added 9/9/99 now we are shifting the last object along the curve to ensure precise
03391             // step distances.  To do this we must set the proportion of the curve used in the 
03392             // NodeBlender objects.
03393             if (ok)
03394             {
03395                 double PathDistanceUsed = NewNumSteps * DistanceEntered;
03396                 double PathProportion = PathDistanceUsed / NewPathLength;
03397 //              double PathDistanceUnused = NewPathLength - PathDistanceUsed;
03398 
03399                 if (PathProportion != 1.0)
03400                 {
03401                     //ChangeBlenderAction* pBlenderAction;
03402                     ChangeBlenderOpParam BlenderParam;
03403                     BlenderParam.m_ChangeType = CHANGEBLENDER_PATHEND;
03404                             
03405                 
03406                     NodeBlender* pNodeBlender = pNodeBlend->FindFirstBlender();
03407                     INT32 NumBlenders = pNodeBlend->GetNumBlenders();
03408                         
03409                     while (pNodeBlender != NULL)
03410                     {
03411                         NumBlenders--;
03412                         if (NumBlenders ==0)
03413                         {                                   
03414                             BlenderParam.m_NewPathEnd = PathProportion;
03415                             ok = ChangeBlenderAction::Init(this, &UndoActions, pNodeBlender, BlenderParam);
03416                             if (ok)
03417                             {
03418                                 DocCoord NewPoint;
03419                                 double ExtraParam = 0.0;  //passed to the function but not used afterwards
03420                                 ok = pNodeBlender->GetPointOnNodeBlendPath(1.0,&NewPoint,&ExtraParam);
03421 
03422                                 if (ok)
03423                                 {
03424                                     NodeRenderableInk* pEnd = pNodeBlender->GetNodeEnd();
03425                                     NodeBlend* pNodeBlend = pNodeBlender->GetNodeBlend();
03426 
03427                                     ok = ((pEnd != NULL) && (pNodeBlend != NULL));
03428                     
03429                                     if (ok) 
03430                                         ok = pNodeBlend->TransformNodeToPoint(pEnd,&NewPoint,this,ExtraParam);
03431                                 }
03432                             }
03433                         }   
03434                         pNodeBlender = pNodeBlend->FindNextBlender(pNodeBlender);
03435                     }
03436                 } // end if (pathproportion..
03437             } // end if(ok)
03438 
03439         }
03440     }
03441 
03442     return TRUE;
03443 }
03444 
03445 
03446 
03447 /********************************************************************************************
03448 
03449 >   BOOL OpFreeHand::InvalidateBrushRegion(NodePath* pNodePath)
03450 
03451     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
03452     Created:    26/9/99
03453     inputs :    pNodePath - the nodepath that we have edited
03454     outputs:    
03455     returns:    TRUE if successful, or if we are not really editing a brush, FALSE if something
03456                 went wrong
03457     Purpose:    We may be editing a brushed path with the FHT so this function makes sure the
03458                 correct region is invalidated
03459 
03460 
03461 *********************************************************************************************/
03462 
03463 BOOL OpFreeHand::InvalidateBrushRegion(NodePath* pNodePath)
03464 {
03465     ERROR2IF(pNodePath == NULL, FALSE, "Nodepath is NULL in OpFreeHand::InvalidateBrushRegion");
03466     
03467     AttrBrushType* pAttrBrush;
03468     NodeAttribute* pAttr = NULL;
03469     BrushHandle Handle = BrushHandle_NoBrush;
03470     pNodePath->FindAppliedAttribute(CC_RUNTIME_CLASS(AttrBrushType), &pAttr);
03471     if (pAttr != NULL)
03472     {
03473         pAttrBrush = (AttrBrushType*)pAttr;
03474         Handle = pAttrBrush->GetBrushHandle();
03475     }
03476     if (Handle == BrushHandle_NoBrush)
03477         return TRUE;
03478 
03479     pAttrBrush->ClearCachedRect();
03480     pAttrBrush->FlushCache();
03481     DocRect BRect = pNodePath->GetBoundingRect(); //pAttrBrush->GetAttrBoundingRect(pNodePath);
03482     pNodePath->ReleaseCached();
03483     pAttrBrush->ClearCachedRect();
03484     Spread* pSpread = Document::GetSelectedSpread();
03485     if (pSpread != NULL)
03486         return DoInvalidateRegion(pSpread, BRect);  
03487     else 
03488         return FALSE;
03489 }

Generated on Sat Nov 10 03:47:47 2007 for Camelot by  doxygen 1.4.4