pathedit.cpp

Go to the documentation of this file.
00001 // $Id: pathedit.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 // Contains general purpose operations to be used in path editing
00099 
00100 /*
00101 */
00102 
00103 #include "camtypes.h"
00104 #include "pathedit.h"
00105 
00106 // Code headers
00107 //#include "app.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00108 //#include "attrmgr.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00109 #include "blobs.h"
00110 #include "csrstack.h"
00111 //#include "cursor.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00112 //#include "document.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00113 //#include "docview.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00114 //#include "fixmem.h"               // for CCMalloc and CCFree - in camtypes.h [AUTOMATICALLY REMOVED]
00115 //#include "group.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00116 #include "lineattr.h"           // for getting the line width of a NodePath
00117 #include "nodepath.h"
00118 #include "ndtxtpth.h"
00119 //#include "nodeattr.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00120 #include "objchge.h"
00121 #include "pathops.h"
00122 #include "progress.h"
00123 //#include "tool.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00124 //#include "tranform.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00125 #include "attrmap.h"
00126 //#include "spread.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00127 //#include "bubbleid.h"
00128 #include "ndbldpth.h"
00129 #include "nodeblnd.h"
00130 //#include "blndtool.h"
00131 #include "nodebldr.h"
00132 #include "opdrbrsh.h"
00133 // Resource headers
00134 #include "helpids.h"
00135 //#include "jim.h"
00136 //#include "peter.h"
00137 //#include "resource.h"    
00138 //#include "barsdlgs.h"
00139 //#include "rik.h"
00140 //#include "simon.h"
00141 //#include "viewrc.h"
00142 //#include "phil.h"
00143 //#include "becomea.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00144 #include "brshattr.h"
00145 #include "opbevel.h"
00146 #include "nodemold.h"
00147 #include "ndmldpth.h"
00148 #include "samplist.h"
00149 #include "brshattr.h"
00150 #include "ophist.h"
00151 #include "blndtool.h"
00152 
00153 #define PATRACE if(IsUserName("Peter")) TRACE
00154 
00155 // Useful macro, given a pointer to a CCObject (or an object derived
00156 // from it) and a string format, it will output the class name of the object. 
00157 // Example usage PACLASSNAME(CurrentNode,"Node class = %s\n");
00158 // N.B. No checking is node or parameters.  Note semi-colon on end.
00159 #define PACLASSNAME(a,b) CCRuntimeClass* Blob; Blob = a->GetRuntimeClass(); PATRACE(b,Blob->m_lpszClassName)
00160 
00161 // Operations
00162 CC_IMPLEMENT_DYNCREATE( OpJoinShapes, SelOperation)
00163 CC_IMPLEMENT_DYNCREATE( OpBreakShapes, SelOperation) 
00164 CC_IMPLEMENT_DYNCREATE( OpDeletePoints, SelOperation)       // Deleting of path endpoints
00165 CC_IMPLEMENT_DYNCREATE( OpNodePathEditBlob, SelOperation)   // Dragging of a path endpoint
00166 CC_IMPLEMENT_DYNCREATE( OpToggleSmooth, SelOperation)       // Toggling smooth/cuspness of endpoints
00167 CC_IMPLEMENT_DYNCREATE( OpNodePathEditControlBlob, OpNodePathEditBlob)  // Dragging of a Bezier control point
00168 CC_IMPLEMENT_DYNCREATE( OpReshapeOrAddPoint, OpNodePathEditBlob)        // Reshaping a path OR adding an endpoint
00169 CC_IMPLEMENT_DYNCREATE( OpNodePathAddEndpoint, OpNodePathEditBlob)      // Adding an endpoint to the end of the path
00170 CC_IMPLEMENT_DYNCREATE( OpNewPath, OpNodePathEditBlob)
00171 CC_IMPLEMENT_DYNCREATE( OpCloseNodePaths, OpNodePathAddEndpoint)        // Closing all selected open paths
00172 
00173 // Actions
00174 CC_IMPLEMENT_DYNCREATE( ModifyPathAction, Action)
00175 CC_IMPLEMENT_DYNCREATE( RemovePathElementAction, Action)
00176 CC_IMPLEMENT_DYNCREATE( InsertPathElementAction, Action)
00177 CC_IMPLEMENT_DYNCREATE( ModifyElementAction, Action)
00178 CC_IMPLEMENT_DYNCREATE( ModifyFlagsAction, Action)
00179 CC_IMPLEMENT_DYNCREATE( RecalcBoundsAction, Action)
00180 CC_IMPLEMENT_DYNCREATE( RecordBoundsAction, Action)
00181 CC_IMPLEMENT_DYNCREATE( ModifyFilledAction, Action)
00182 CC_IMPLEMENT_DYNCREATE( SavePathArraysAction, Action)
00183 //CC_IMPLEMENT_DYNCREATE( StorePathSubSelStateAction, Action)
00184 
00185 // Declare smart memory handling in Debug builds
00186 #define new CAM_DEBUG_NEW
00187 
00188 /********************************************************************************************
00189 
00190 >   OpNodePathEditBlob::OpNodePathEditBlob()
00191 
00192     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
00193     Created:    20/7/93
00194     Purpose:    Constructor. This simply sets a few of the operation flags.
00195 
00196 ********************************************************************************************/
00197 
00198 OpNodePathEditBlob::OpNodePathEditBlob()
00199 {
00200     EndSnapped = FALSE; 
00201     SnapToAnother = FALSE;  
00202     pMoveCursor = NULL;
00203     SnapToPath = NULL;
00204     DragStarted = FALSE;
00205     SnapToLineOrCurve = FALSE;
00206     DragPoint = -1;
00207     UpdatePoint = -1;
00208     ConstrainPoint = DocCoord(-1,-1);
00209     ConstrainPrevPoint = DocCoord(-1,-1);
00210     ConstrainNextPoint = DocCoord(-1,-1);
00211     MultiplePaths = FALSE;
00212 }
00213 
00214 /********************************************************************************************
00215 
00216 >   void OpNodePathEditBlob::DoStartDragEdit(NodePath* OrigPath, DocCoord Anchor, Spread *pSpread)
00217 
00218     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com> - latterly Peter
00219     Created:    5/7/93
00220     Inputs:     OrigPath - Pointer to the path we are about to edit
00221                 Anchor - The position of the mouse at the start of the Drag
00222                 pSpread - The spread that the path is on
00223     Purpose:    This is called to start a drag operation on an endpoint on a path
00224 
00225 ********************************************************************************************/
00226 
00227 void OpNodePathEditBlob::DoStartDragEdit(NodePath* OrigPath, DocCoord Anchor, Spread *pSpread)
00228 {
00229     BOOL Success = TRUE;
00230     
00231     // We had better take a note of the starting point of the drag
00232     LastMousePos = Anchor;
00233     StartMousePos = Anchor;
00234     StartSpread  = pSpread;
00235 
00236     SelRange* theSelection = GetApplication ()->FindSelection ();
00237 
00238     BOOL selectionContainsMoulds = FALSE;
00239 
00240     if (theSelection)
00241     {
00242         // we need to do some special processing to handle moulds ....
00243 
00244         Node* pCurrentNode = (Node*) theSelection->FindFirst ();
00245 
00246         while (pCurrentNode)
00247         {
00248             if (IS_A (pCurrentNode, NodeMould))
00249             {
00250                 selectionContainsMoulds = TRUE;
00251                 pCurrentNode = NULL;
00252             }
00253             else
00254             {
00255                 pCurrentNode = (Node*) theSelection->FindNext (pCurrentNode);
00256             }
00257         }
00258     }
00259 
00260     if (!selectionContainsMoulds)
00261     {
00262         BevelTools::BuildListOfSelectedNodes(&OriginalPaths, CC_RUNTIME_CLASS(NodePath));
00263     }
00264     else
00265     {
00266         // now go and get those moulds baby !
00267         BevelTools::BuildListOfSelectedNodes(&OriginalPaths, CC_RUNTIME_CLASS(NodeMouldPath));
00268 
00269         // now, we also have to rescan the selection (again!) BUT this time without
00270         // the moulds
00271 
00272         Node* pCurrentNode = (Node*) theSelection->FindFirst ();
00273 
00274         while (pCurrentNode)
00275         {
00276             if (IS_A (pCurrentNode, NodeMould))
00277             {
00278                 pCurrentNode = (Node*) theSelection->FindNext (pCurrentNode);
00279             }
00280             else
00281             {
00282                 if (IS_A (pCurrentNode, NodePath))
00283                 {
00284                     NodeListItem* pInsert = new NodeListItem ();
00285                     
00286                     if (pInsert)    // and insert into the paths list ....
00287                     {
00288                         pInsert->pNode = pCurrentNode;
00289                         OriginalPaths.AddHead (pInsert);
00290                     }
00291                 }
00292                 
00293                 pCurrentNode = (Node*) theSelection->FindNext (pCurrentNode);
00294             }
00295         }
00296     }
00297 
00298     if (OriginalPaths.GetCount () == 1)
00299     //if (TRUE)
00300     {
00301         MultiplePaths = FALSE;
00302         
00303         OriginalPath = OrigPath;
00304 
00305         // Now calculate DragPoint and UpdatePoint
00306         if (Success)
00307         {
00308             PathFlags* Flags = OriginalPath->InkPath.GetFlagArray();
00309             PathVerb* Verbs = OriginalPath->InkPath.GetVerbArray();
00310             INT32 NumCoords = OriginalPath->InkPath.GetNumCoords();
00311 
00312             for (INT32 i=0;i<NumCoords;i++)
00313             {
00314                 if (Flags[i].IsEndPoint && Flags[i].IsSelected 
00315                                 && !(OriginalPath->InkPath.IsSubPathClosed(i) && (Verbs[i] == PT_MOVETO)) )
00316                 {
00317                     // If you are dragging a closepoint then you are actually dragging two points
00318                     // but we need to update the line tool as the user thinks they are dragging one.
00319                     // If we are on the opening moveto then just skip the tests.
00320                     if (DragPoint != -1)
00321                     {
00322                         UpdatePoint = -1;
00323                         DragPoint = -1;
00324                         break;
00325                     }
00326                     else
00327                     {
00328                         UpdatePoint = i;
00329                         DragPoint = i;
00330                     }
00331                 }
00332             }
00333             // On exit from that loop, DragPoint = -1 if there are multiple selected endpoints,
00334             // otherwise DragPoint is the index to the selected endpoint.  UpdatePoint is the
00335             // index of the point displaied in the Line tool
00336         }
00337 
00338         // Set the constrain point
00339         if (DragPoint != -1)
00340         {
00341             ConstrainPoint = OriginalPath->InkPath.GetCoordArray()[DragPoint];
00342 
00343             // Get the previous endpoint
00344             INT32 OtherEndpoint = DragPoint;
00345             if (OriginalPath->InkPath.FindPrevEndPoint(&OtherEndpoint))
00346                 ConstrainPrevPoint = OriginalPath->InkPath.GetCoordArray()[OtherEndpoint];
00347             else
00348                 ConstrainPrevPoint = ConstrainPoint;
00349 
00350             // Get the next endpoint
00351             OtherEndpoint = DragPoint;
00352             if (OriginalPath->InkPath.FindNextEndPoint(&OtherEndpoint))
00353                 ConstrainNextPoint = OriginalPath->InkPath.GetCoordArray()[OtherEndpoint];
00354             else
00355                 ConstrainNextPoint = ConstrainPoint;
00356         }
00357         else
00358         {
00359             ConstrainPoint = Anchor;
00360             ConstrainPrevPoint = Anchor;
00361             ConstrainNextPoint = Anchor;
00362         }
00363 
00364         // We also need to make a version of the path that we can change
00365         Success = BuildEditPath();
00366 
00367         // Create and send a change message about this path edit
00368         // This one is handled by moulds in their OnChildChange() function
00369         if (Success)
00370             Success = (EditObjChange.ObjChangeStarting(OrigPath,this,&EditPath,StartSpread,TRUE) == CC_OK);
00371 
00372         // Create and display the cursors for this operation
00373         if (Success)
00374             Success = CreateCursors();
00375         if (Success)
00376             ChangeCursor(pCrossHairCursor);
00377         
00378     //  // Render the bits of the path that are different
00379         DocRect EditPathBBox = EditPath.GetBoundingRect();
00380     //  if (Success)
00381     //      RenderPathEditBlobs(EditPathBBox, pSpread);
00382 
00383         // Tell the Dragging system that we need drags to happen
00384         if (Success)
00385             Success = StartDrag(DRAGTYPE_AUTOSCROLL, &EditPathBBox, &LastMousePos);
00386 
00387         if (!Success)
00388         {
00389             InformError();
00390             FailAndExecute();
00391             End();
00392         }
00393     }
00394     else
00395     {
00396         // lets try and keep things the same for the blobs parent as in the one selection case
00397         // BUT lets also try and do our extra stuff ....  I expect time MUCK UPS to occur !!!!
00398 
00399         MultiplePaths = TRUE;
00400         
00401         OriginalPath = OrigPath;
00402 
00403         // we need to make OrigPath the first one in our linked list ....
00404 
00405         NodeListItem* pCurrentOrig = (NodeListItem*) OriginalPaths.GetHead ();
00406 
00407         while (pCurrentOrig)
00408         {
00409             NodePath* pOrigPath = (NodePath*) (pCurrentOrig->pNode);
00410 
00411             if (pOrigPath == OrigPath)
00412             {
00413                 NodeListItem* newHead = (NodeListItem*) OriginalPaths.RemoveItem (pCurrentOrig);
00414                 
00415                 OriginalPaths.AddHead (newHead);
00416 
00417                 pCurrentOrig = NULL;
00418             }
00419             else
00420             {
00421                 pCurrentOrig = (NodeListItem*) OriginalPaths.GetNext (pCurrentOrig);
00422             }
00423         }
00424 
00425         // Now calculate DragPoint and UpdatePoint
00426         if (Success)
00427         {
00428             NodeListItem* pCurrentOrig = (NodeListItem*) OriginalPaths.GetHead ();
00429 
00430             // CGS:  while loop taken out since we can only be 'drag sensitive' to the point
00431             // that is actually being dragged
00432 
00433             //while (pCurrentOrig)
00434             {
00435                 NodePath* pOrigPath = (NodePath*) (pCurrentOrig->pNode);
00436                 
00437                 PathFlags* Flags = pOrigPath->InkPath.GetFlagArray();
00438                 PathVerb* Verbs = pOrigPath->InkPath.GetVerbArray();
00439                 INT32 NumCoords = pOrigPath->InkPath.GetNumCoords();
00440 
00441                 for (INT32 i=0;i<NumCoords;i++)
00442                 {
00443                     if (Flags[i].IsEndPoint && Flags[i].IsSelected 
00444                                     && !(pOrigPath->InkPath.IsSubPathClosed(i) && (Verbs[i] == PT_MOVETO)) )
00445                     {
00446                         // If you are dragging a closepoint then you are actually dragging two points
00447                         // but we need to update the line tool as the user thinks they are dragging one.
00448                         // If we are on the opening moveto then just skip the tests.
00449                         if (DragPoint != -1)
00450                         {
00451                             UpdatePoint = -1;
00452                             DragPoint = -1;
00453                             break;
00454                         }
00455                         else
00456                         {
00457                             UpdatePoint = i;
00458                             DragPoint = i;
00459                         }
00460                     }
00461                 }
00462 
00463             //  pCurrentOrig = (NodeListItem*) OriginalPaths.GetNext (pCurrentOrig);
00464             }
00465             // On exit from that loop, DragPoint = -1 if there are multiple selected endpoints,
00466             // otherwise DragPoint is the index to the selected endpoint.  UpdatePoint is the
00467             // index of the point displaied in the Line tool
00468         }
00469 
00470         // Set the constrain point - do this only for the path that was clicked on
00471         // Any guesses as to why?
00472 
00473         // Answer:  cause its the one that is being dragged!
00474         if (DragPoint != -1)
00475         {
00476             NodeListItem* pCurrentOrig = (NodeListItem*) OriginalPaths.GetHead ();
00477             NodePath* pOrigPath = (NodePath*) (pCurrentOrig->pNode);
00478             
00479             ConstrainPoint = pOrigPath->InkPath.GetCoordArray()[DragPoint];
00480 
00481             // Get the previous endpoint
00482             INT32 OtherEndpoint = DragPoint;
00483             if (pOrigPath->InkPath.FindPrevEndPoint(&OtherEndpoint))
00484                 ConstrainPrevPoint = pOrigPath->InkPath.GetCoordArray()[OtherEndpoint];
00485             else
00486                 ConstrainPrevPoint = ConstrainPoint;
00487 
00488             // Get the next endpoint
00489             OtherEndpoint = DragPoint;
00490             if (pOrigPath->InkPath.FindNextEndPoint(&OtherEndpoint))
00491                 ConstrainNextPoint = pOrigPath->InkPath.GetCoordArray()[OtherEndpoint];
00492             else
00493                 ConstrainNextPoint = ConstrainPoint;
00494         }
00495         else
00496         {
00497             ConstrainPoint = Anchor;
00498             ConstrainPrevPoint = Anchor;
00499             ConstrainNextPoint = Anchor;
00500         }
00501 
00502         // We also need to make versions of the paths that we can change
00503         Success = BuildEditPaths();
00504 
00505         // Create and send a change message about this path edit
00506         // This one is handled by moulds in their OnChildChange() function
00507         if (Success)
00508         {
00509             NodeListItem* pCurrentOrig = (NodeListItem*) OriginalPaths.GetHead ();
00510             NodeListItem* pCurrentEdit = (NodeListItem*) EditPaths.GetHead ();
00511 
00512             while ((pCurrentOrig) && (pCurrentEdit))
00513             {
00514                 NodePath* pOrigPath = (NodePath*) (pCurrentOrig->pNode);
00515                 Path* pEditPath = (Path*) (pCurrentEdit->pNode);
00516                 
00518                 
00519                 ObjChangePathEdit* NewEditObjChange = new ObjChangePathEdit;
00520                 
00521                 Success = (NewEditObjChange->ObjChangeStarting(pOrigPath,this,pEditPath,StartSpread,TRUE) == CC_OK);
00522 
00523                 NodeListItem* pInsert = new NodeListItem;
00524                 pInsert->pNode = (Node*) NewEditObjChange;
00525         
00526                 ObjChanges.AddTail (pInsert);
00527 
00529 
00530                 BOOL* NewDragStartVal = new BOOL;
00531                 *NewDragStartVal = FALSE;
00532                 
00533                 NodeListItem* pInsert2 = new NodeListItem;
00534                 pInsert2->pNode = (Node*) NewDragStartVal;
00535 
00536                 PathsDragStarted.AddTail (pInsert2);
00537 
00539 
00540                 if (Success)
00541                 {
00542                     pCurrentOrig = (NodeListItem*) OriginalPaths.GetNext (pCurrentOrig);
00543                     pCurrentEdit = (NodeListItem*) EditPaths.GetNext (pCurrentEdit);
00544                 }
00545                 else
00546                 {
00547                     pCurrentOrig = NULL;
00548                     pCurrentEdit = NULL;
00549                 }
00550 
00552                 
00553                 //delete (NewEditObjChange);
00554         //      delete (NewDragStartVal);
00555             }
00556         }
00557 
00558         // Create and display the cursors for this operation (only once - obviously)
00559         if (Success)
00560             Success = CreateCursors();
00561         if (Success)
00562             ChangeCursor(pCrossHairCursor);
00563         
00564     //  // Render the bits of the path that are different
00565         DocRect EditPathBBox;// = EditPath.GetBoundingRect();
00566 
00567         SelRange* Selected = GetApplication()->FindSelection();
00568 
00569         EditPathBBox = Selected->GetBlobBoundingRect ();
00570 
00571     //  if (Success)
00572     //      RenderPathEditBlobs(EditPathBBox, pSpread);
00573 
00574         // Tell the Dragging system that we need drags to happen
00575         if (Success)
00576             Success = StartDrag(DRAGTYPE_AUTOSCROLL, &EditPathBBox, &LastMousePos);
00577 
00578         if (!Success)
00579         {
00580             InformError();
00581             FailAndExecute();
00582             End();
00583         }
00584     }
00585 }
00586 
00587 
00588 
00589 /********************************************************************************************
00590 
00591 >   void OpNodePathEditBlob::DragPointerMove( DocCoord PointerPos, ClickModifiers ClickMods, Spread*, BOOL bSolidDrag)
00592 
00593     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
00594     Created:    5/7/93
00595     Inputs:     PointerPos - The current position of the mouse in Doc Coords
00596                 ClickMods - Which key modifiers are being pressed
00597     Purpose:    This is called every time the mouse moves, during a drag.
00598     SeeAlso:    ClickModifiers
00599 
00600 ********************************************************************************************/
00601 
00602 void OpNodePathEditBlob::DragPointerMove( DocCoord PointerPos, ClickModifiers ClickMods, Spread *pSpread, BOOL bSolidDrag)
00603 {
00604     if (!MultiplePaths)
00605     {
00606         // If drag has moved onto a different spread, convert the coord to be relative to the
00607         // original spread.
00608         if (pSpread != StartSpread)
00609             PointerPos = MakeRelativeToSpread(StartSpread, pSpread, PointerPos);
00610 
00611         Path TempPath;
00612 
00613         // inform the parent a change is happening
00614         if (EditObjChange.ChangeMask.EorBlobs)
00615         {
00616             ChangeCode Chge = EditObjChange.RenderCurrentBlobs(OriginalPath,this,&EditPath,StartSpread,TRUE);
00617             if (Chge!=CC_OK)
00618                 return;
00619             // Create a local copy of the edit path
00620             if (!TempPath.Initialise(EditPath.GetNumCoords(), 12))
00621                 return;
00622             TempPath.CopyPathDataFrom(&EditPath);
00623         }
00624 
00625         // Rub out the old EORed version of the path
00626         if (DragStarted)
00627             RenderPathEditBlobs( EditPath.GetBoundingRect(), StartSpread );
00628         else
00629             DragStarted = TRUE;
00630 
00631         // Here is where we do all the snapping to grids and endpoints
00632         // and constrain. First we check for snapping to line ends, then
00633         // we check constrain, then we check grid snapping
00634 
00635         EndSnapped = FALSE;         // TRUE if this point snaps to the other end
00636         SnapToAnother = FALSE;      // TRUE if this point snaps to another line end
00637         SnapToLineOrCurve = FALSE;  // No snapped other line
00638 
00639         // We might be snapping to an endpoint if the point we're dragging around is either
00640         // a moveto, or is the last point in the subpath. We will also only snap if there is
00641         // only one point selected on the path
00642 
00643         DocRect PointerBlobRect;
00644         GetApplication()->GetBlobManager()->GetBlobRect(PointerPos,&PointerBlobRect);
00645 
00646         DocCoord* Coords = EditPath.GetCoordArray();
00647 //      PathFlags* Flags = EditPath.GetFlagArray();
00648         PathVerb* Verbs = EditPath.GetVerbArray();
00649         INT32 NumCoords = EditPath.GetNumCoords();
00650 
00651         INT32 i;
00652         if (DragPoint != -1)
00653         {
00654             // Look to see if the selected point is a real endpoint
00655             if (Verbs[DragPoint] == PT_MOVETO)
00656             {
00657                 // This is the start element of the path
00658                 i = DragPoint;
00659                 if (!EditPath.IsSubPathClosed(i))
00660                 {
00661                     INT32 NextEndpoint = i;
00662                     if (EditPath.FindNextEndPoint(&NextEndpoint))
00663                     {
00664                         EditPath.FindEndElOfSubPath(&i);        // i indexes to the end element
00665 
00666                         // If this sub-path is one element long then closing it is not allowed
00667                         if ((i != NextEndpoint) && PointerBlobRect.ContainsCoord(Coords[i]))
00668                         {
00669                             PointerPos.x = Coords[i].x + LastMousePos.x - Coords[DragPoint].x;
00670                             PointerPos.y = Coords[i].y + LastMousePos.y - Coords[DragPoint].y;
00671                             EndSnapped = TRUE;
00672                         }
00673                     }
00674                 }
00675             }
00676             else if (DragPoint+1 == NumCoords || Verbs[DragPoint+1] == PT_MOVETO)
00677             {
00678                 // This is the last element in the sub-path
00679                 i = DragPoint;
00680                 if (!EditPath.IsSubPathClosed(i))
00681                 {
00682                     INT32 PrevEndpoint = i;
00683                     if (EditPath.FindPrevEndPoint(&PrevEndpoint))
00684                     {
00685                         EditPath.FindStartOfSubPath(&i);            // i is the index of the start of the sub-path
00686             
00687                         // If this sub-path is one element long then closing it is not allowed
00688                         if ((i != PrevEndpoint) && PointerBlobRect.ContainsCoord(Coords[i]))
00689                         {
00690                             PointerPos.x = Coords[i].x + LastMousePos.x - Coords[DragPoint].x;
00691                             PointerPos.y = Coords[i].y + LastMousePos.y - Coords[DragPoint].y;
00692                             EndSnapped = TRUE;
00693                         }
00694                     }
00695                 }
00696             }
00697         }
00698         
00699         // That detects snapping to the opposite endpoint, but what about snapping to another
00700         // endpoint altogether? Time to check for that eventuality
00701         if (!EndSnapped && DragPoint != -1 && (Verbs[DragPoint] == PT_MOVETO || DragPoint+1 == NumCoords || Verbs[DragPoint+1] == PT_MOVETO) )
00702         {
00703             // Get a snapshot of the selection
00704             SelRange* Selected = GetApplication()->FindSelection();
00705             Node* pNode = Selected->FindFirst();
00706             INT32 SubIndex,SubEnd;
00707             while (pNode)
00708             {
00709                 if ((pNode->FindParentSpread() == StartSpread) && 
00710                     (pNode->GetRuntimeClass() == CC_RUNTIME_CLASS(NodePath)) /*&&
00711                     (pNode != OriginalPath)*/ )
00712                 {
00713                     Path* ThisPath = &(((NodePath*)pNode)->InkPath);
00714                     INT32 ThisNum = ThisPath->GetNumCoords();
00715                     SubIndex = 0;
00716                     SubEnd = 0;
00717                     while (SubIndex<ThisNum)        
00718                     {
00719                         DocCoord tempStart,tempEnd;
00720                         ThisPath->SetPathPosition(SubIndex);    // Set the path's internal pos index
00721                         if (ThisPath->GetSubPathEnds(&tempStart, &tempEnd))
00722                         {
00723                             SubEnd = ThisPath->GetPathPosition();
00724                             // Now SubIndex is the index of the first, and SubEnd is the index
00725                             // of the last element (N.B. might be a curveto, in which case add 2)
00726                             if (ThisPath->GetVerb() == PT_BEZIERTO)
00727                                 SubEnd+=2;
00728 
00729                             BOOL ClosedPath = (ThisPath->GetVerbArray()[SubEnd] & PT_CLOSEFIGURE) ;
00730 
00731                             // Now compare start and end with pointer position, but not if this
00732                             // subpath is the currently dragging subpath
00733                             if ( !ClosedPath &&
00734                                  ( (pNode != OriginalPath) ||
00735                                    ((pNode == OriginalPath) && (DragPoint != SubIndex) && (DragPoint != SubEnd)) ) )
00736                             {
00737                                 if (PointerBlobRect.ContainsCoord(tempStart))
00738                                 {
00739                                     break;
00740                                 }
00741                                 if (PointerBlobRect.ContainsCoord(tempEnd))
00742                                 {
00743                                     SubIndex = SubEnd;
00744                                     break;
00745                                 }
00746                             }
00747                         }
00748                         else
00749                         {
00750                             SubEnd = ThisPath->GetPathPosition();
00751                         }
00752                         SubIndex = SubEnd+1;        // Points at start of next subpath
00753                                                     // or top of whole path
00754                     }
00755                     // if SubIndex < ThisNum we must have found a matching coord
00756                     // so that's what we'll snap to
00757                     if (SubIndex < ThisNum)
00758                         break;              // Exit the outer while condition
00759                 }
00760                 pNode = Selected->FindNext(pNode);
00761             }
00762             // At this point, if pNode == NULL we haven't snapped to anything
00763             // otherwise pNode is the path we've snapped to, and SubIndex is the index
00764             // of the point we've snapped to
00765             if (pNode)
00766             {
00767                 // if the snap to path is the path being edited check that it has the same number of points!
00768                 if (((NodePath*)pNode == OriginalPath) && (OriginalPath->InkPath.GetNumCoords() != EditPath.GetNumCoords()))
00769                 {
00770 //                  PATRACE( _T("Attempted to join an edited path!\n"));
00771                     // The problem is that if the OriginalPath has been edited by adding an endpoint then
00772                     // SnapToIndex will be wrong as it counted along the original path, not the edited one....
00773                 }
00774                 else
00775                 {
00776                     SnapToAnother = TRUE;
00777                     SnapToPath = (NodePath*)pNode;
00778                     SnapToIndex = SubIndex;
00779                     SnapToPath->InkPath.SetPathPosition(SnapToIndex);
00780                     PointerPos.x = SnapToPath->InkPath.GetCoord().x + LastMousePos.x - Coords[DragPoint].x;
00781                     PointerPos.y = SnapToPath->InkPath.GetCoord().y + LastMousePos.y - Coords[DragPoint].y;
00782                 }
00783             }
00784         }
00785 
00786         // We don't allow closed paths consiting of two straight segments
00787         if (EndSnapped && NumCoords < 4)
00788             EndSnapped = FALSE;
00789             
00790         if ((EndSnapped || SnapToAnother) && (pMoveCursor != NULL))
00791             ChangeCursor(pCloseCursor);
00792         else
00793             ChangeCursor(pCrossHairCursor);
00794         
00795         // Now constrain the mouse point
00796         ERROR3IF(ConstrainPoint == DocCoord(-1,-1),"ConstrainPoint wasn't set");
00797         ERROR3IF(ConstrainPrevPoint == DocCoord(-1,-1),"ConstrainPrevPoint wasn't set");
00798         ERROR3IF(ConstrainNextPoint == DocCoord(-1,-1),"ConstrainNextPoint wasn't set");
00799         if (!EndSnapped && ClickMods.Constrain)
00800         {
00801             if (ClickMods.Adjust)
00802             {
00803                 if (ClickMods.Alternative1 || ClickMods.Alternative2)
00804                     DocView::ConstrainToAngle(ConstrainNextPoint, &PointerPos);
00805                 else
00806                     DocView::ConstrainToAngle(ConstrainPrevPoint, &PointerPos);
00807             }
00808             else
00809                 DocView::ConstrainToAngle(ConstrainPoint, &PointerPos);
00810         }
00811 
00812         // Only snap to grid if we didn't snap to the end of a path
00813         if (!EndSnapped)
00814             DocView::SnapCurrent(StartSpread,&PointerPos);
00815 
00816         // This is the bit where we go off and re-calculate the paths position,
00817         // based on how much the mouse has moved
00818         DocCoord Offset;
00819         Offset.x = PointerPos.x - LastMousePos.x;
00820         Offset.y = PointerPos.y - LastMousePos.y;
00821 
00822         RecalculatePath(Offset, EndSnapped, DragPoint);
00823 
00824         if (EditObjChange.ChangeMask.EorBlobs)
00825         {
00826             ChangeCode Chge = EditObjChange.RenderChangedBlobs(OriginalPath,this,&EditPath,StartSpread,TRUE);
00827             if (Chge!=CC_OK)
00828             {
00829                 // replace the old edit path
00830                 EditPath.CopyPathDataFrom(&TempPath);
00831                 RenderPathEditBlobs( EditPath.GetBoundingRect(), StartSpread );
00832                 return;
00833             }
00834         }
00835 
00836         // Update the Last Mouse Position
00837         LastMousePos = PointerPos;
00838 
00839         // Draw in the new version of the path
00840         RenderPathEditBlobs( EditPath.GetBoundingRect(), StartSpread );
00841 
00842         // If there is just one selected point being dragged then we should show
00843         // its new position in the line tool infobar.
00844         if (UpdatePoint != -1)
00845             BROADCAST_TO_ALL(PathEditedMsg(&EditPath, pSpread, UpdatePoint));
00846 
00847         SetStatusLineHelp();
00848     }
00849     else
00850     {   
00851         NodeListItem* pCurrentOrig = (NodeListItem*) OriginalPaths.GetHead ();
00852         NodeListItem* pCurrentEdit = (NodeListItem*) EditPaths.GetHead ();
00853         NodeListItem* pCurrentChange = (NodeListItem*) ObjChanges.GetHead ();
00854         NodeListItem* pCurrentDragS = (NodeListItem*) PathsDragStarted.GetHead ();
00855 
00856         DocCoord Offset(0,0);
00857 
00858         while ((pCurrentOrig) && (pCurrentEdit) && (pCurrentChange) && (pCurrentDragS))
00859         {
00860             NodePath* pOrigPath = (NodePath*) (pCurrentOrig->pNode);
00861             Path* pEditPath = (Path*) (pCurrentEdit->pNode);
00862             ObjChangePathEdit* pObjChange = (ObjChangePathEdit*) (pCurrentChange->pNode);
00863             BOOL* pDragStarted = (BOOL*) (pCurrentDragS->pNode);
00864 
00866 
00867             // If drag has moved onto a different spread, convert the coord to be relative to the
00868             // original spread.
00869             if (pSpread != StartSpread)
00870                 PointerPos = MakeRelativeToSpread(StartSpread, pSpread, PointerPos);
00871 
00872             Path TempPath;
00873 
00874             // inform the parent a change is happening
00875             if (pObjChange->ChangeMask.EorBlobs)
00876             {
00877                 ChangeCode Chge = pObjChange->RenderCurrentBlobs(pOrigPath,this,pEditPath,StartSpread,TRUE);
00878                 if (Chge!=CC_OK)
00879                     return;
00880                 // Create a local copy of the edit path
00881                 if (!TempPath.Initialise(pEditPath->GetNumCoords(), 12))
00882                     return;
00883                 TempPath.CopyPathDataFrom(pEditPath);
00884             }
00885 
00886             // Rub out the old EORed version of the path
00887             if (*pDragStarted == TRUE)
00888             {
00889                 RenderPathEditBlobs(pEditPath, /*pEditPath->GetBoundingRect(),*/ StartSpread );
00890             }
00891             else
00892             {
00893                 *pDragStarted = TRUE;
00894 //              pCurrentDragS->pNode = (Node*) pDragStarted;
00895             }
00896 
00897             DocRect PointerBlobRect;
00898             GetApplication()->GetBlobManager()->GetBlobRect(PointerPos,&PointerBlobRect);
00899 
00900             DocCoord* Coords = pEditPath->GetCoordArray();
00901 //          PathFlags* Flags = pEditPath->GetFlagArray();
00902             PathVerb* Verbs = pEditPath->GetVerbArray();
00903             INT32 NumCoords = pEditPath->GetNumCoords();
00904 
00905             if (pCurrentOrig == (NodeListItem*) OriginalPaths.GetHead ())
00906             {
00907                 // CGS:  all of the operations to do with snapping to grid, snapping to opposite
00908                 // end point and snapping to another lines endpoint are relevant only for
00909                 // that control point that the drag was initiated from ....
00910 
00911                 // Here is where we do all the snapping to grids and endpoints
00912                 // and constrain. First we check for snapping to line ends, then
00913                 // we check constrain, then we check grid snapping
00914 
00915                 EndSnapped = FALSE;         // TRUE if this point snaps to the other end
00916                 SnapToAnother = FALSE;      // TRUE if this point snaps to another line end
00917                 SnapToLineOrCurve = FALSE;  // No snapped other line
00918 
00919                 // We might be snapping to an endpoint if the point we're dragging around is either
00920                 // a moveto, or is the last point in the subpath. We will also only snap if there is
00921                 // only one point selected on the path
00922 
00923                 INT32 i;
00924                 if (DragPoint != -1)
00925                 {
00926                     // Look to see if the selected point is a real endpoint
00927                     if (Verbs[DragPoint] == PT_MOVETO)
00928                     {
00929                         // This is the start element of the path
00930                         i = DragPoint;
00931                         if (!pEditPath->IsSubPathClosed(i))
00932                         {
00933                             INT32 NextEndpoint = i;
00934                             if (pEditPath->FindNextEndPoint(&NextEndpoint))
00935                             {
00936                                 pEditPath->FindEndElOfSubPath(&i);      // i indexes to the end element
00937 
00938                                 // If this sub-path is one element long then closing it is not allowed
00939                                 if ((i != NextEndpoint) && PointerBlobRect.ContainsCoord(Coords[i]))
00940                                 {
00941                                     PointerPos.x = Coords[i].x + LastMousePos.x - Coords[DragPoint].x;
00942                                     PointerPos.y = Coords[i].y + LastMousePos.y - Coords[DragPoint].y;
00943                                     EndSnapped = TRUE;
00944                                 }
00945                             }
00946                         }
00947                     }
00948                     else if (DragPoint+1 == NumCoords || Verbs[DragPoint+1] == PT_MOVETO)
00949                     {
00950                         // This is the last element in the sub-path
00951                         i = DragPoint;
00952                         if (!pEditPath->IsSubPathClosed(i))
00953                         {
00954                             INT32 PrevEndpoint = i;
00955                             if (pEditPath->FindPrevEndPoint(&PrevEndpoint))
00956                             {
00957                                 pEditPath->FindStartOfSubPath(&i);          // i is the index of the start of the sub-path
00958                     
00959                                 // If this sub-path is one element long then closing it is not allowed
00960                                 if ((i != PrevEndpoint) && PointerBlobRect.ContainsCoord(Coords[i]))
00961                                 {
00962                                     PointerPos.x = Coords[i].x + LastMousePos.x - Coords[DragPoint].x;
00963                                     PointerPos.y = Coords[i].y + LastMousePos.y - Coords[DragPoint].y;
00964                                     EndSnapped = TRUE;
00965                                 }
00966                             }
00967                         }
00968                     }
00969                 }
00970             
00971                 // That detects snapping to the opposite endpoint, but what about snapping to another
00972                 // endpoint altogether? Time to check for that eventuality
00973                 if (!EndSnapped && DragPoint != -1 && (Verbs[DragPoint] == PT_MOVETO || DragPoint+1 == NumCoords || Verbs[DragPoint+1] == PT_MOVETO) )
00974                 {
00975                     // Get a snapshot of the selection
00976                     SelRange* Selected = GetApplication()->FindSelection();
00977 
00978                     // do something extra in here ....
00979 
00980                     Node* pNode = Selected->FindFirst();
00981                     INT32 SubIndex,SubEnd;
00982                     while (pNode)
00983                     {
00985                         if ((pNode->FindParentSpread() == StartSpread) && 
00986                             (pNode->GetRuntimeClass() == CC_RUNTIME_CLASS(NodePath)) /*&&
00987                     (pNode != OriginalPath)*/ )
00988                         {
00990                             Path* ThisPath = &(((NodePath*)pNode)->InkPath);
00991                             INT32 ThisNum = ThisPath->GetNumCoords();
00992                             SubIndex = 0;
00993                             SubEnd = 0;
00994                             while (SubIndex<ThisNum)        
00995                             {
00996                                 DocCoord tempStart,tempEnd;
00997                                 ThisPath->SetPathPosition(SubIndex);    // Set the path's internal pos index
00998                                 if (ThisPath->GetSubPathEnds(&tempStart, &tempEnd))
00999                                 {
01000                                     SubEnd = ThisPath->GetPathPosition();
01001                                     // Now SubIndex is the index of the first, and SubEnd is the index
01002                                     // of the last element (N.B. might be a curveto, in which case add 2)
01003                                     if (ThisPath->GetVerb() == PT_BEZIERTO)
01004                                         SubEnd+=2;
01005 
01006                                     BOOL ClosedPath = (ThisPath->GetVerbArray()[SubEnd] & PT_CLOSEFIGURE) ;
01007 
01008                                     // Now compare start and end with pointer position, but not if this
01009                                     // subpath is the currently dragging subpath
01010                                     if ( !ClosedPath &&
01011                                          ( (pNode != OriginalPath) ||
01012                                            ((pNode == OriginalPath) && (DragPoint != SubIndex) && (DragPoint != SubEnd)) ) )
01013                                     {
01014                                         if (PointerBlobRect.ContainsCoord(tempStart))
01015                                         {
01016                                             break;
01017                                         }
01018                                         if (PointerBlobRect.ContainsCoord(tempEnd))
01019                                         {
01020                                             SubIndex = SubEnd;
01021                                             break;
01022                                         }
01023                                     }
01024                                 }
01025                                 else
01026                                 {
01027                                     SubEnd = ThisPath->GetPathPosition();
01028                                 }
01029                                 SubIndex = SubEnd+1;        // Points at start of next subpath
01030                                                             // or top of whole path
01031                             }
01032                             // if SubIndex < ThisNum we must have found a matching coord
01033                             // so that's what we'll snap to
01034                             if (SubIndex < ThisNum)
01035                                 break;              // Exit the outer while condition
01036                         }
01037                         pNode = Selected->FindNext(pNode);
01038                     }
01039                     // At this point, if pNode == NULL we haven't snapped to anything
01040                     // otherwise pNode is the path we've snapped to, and SubIndex is the index
01041                     // of the point we've snapped to
01042                     if (pNode)
01043                     {
01044                         // if the snap to path is the path being edited check that it has the same number of points!
01045                         if (((NodePath*)pNode == pOrigPath) && (pOrigPath->InkPath.GetNumCoords() != pEditPath->GetNumCoords()))
01046                         {
01047 //                          PATRACE( _T("Attempted to join an edited path!\n"));
01048                             // The problem is that if the OriginalPath has been edited by adding an endpoint then
01049                             // SnapToIndex will be wrong as it counted along the original path, not the edited one....
01050                         }
01051                         else
01052                         {
01053                             SnapToAnother = TRUE;
01054                             SnapToPath = (NodePath*)pNode;
01055                             SnapToIndex = SubIndex;
01056                             SnapToPath->InkPath.SetPathPosition(SnapToIndex);
01057                             PointerPos.x = SnapToPath->InkPath.GetCoord().x + LastMousePos.x - Coords[DragPoint].x;
01058                             PointerPos.y = SnapToPath->InkPath.GetCoord().y + LastMousePos.y - Coords[DragPoint].y;
01059                         }
01060                     }
01061                 }
01062 
01064 
01065                 // only do the following for the node that the drag was started from ....
01066 
01067                 //if (pCurrentOrig == (NodeListItem*) OriginalPaths.GetHead ())
01068                 //{
01069                     // We don't allow closed paths consiting of two straight segments
01070                     if (EndSnapped && NumCoords < 4)
01071                         EndSnapped = FALSE;
01072                         
01073                     if ((EndSnapped || SnapToAnother) && (pMoveCursor != NULL))
01074                         ChangeCursor(pCloseCursor);
01075                     else
01076                         ChangeCursor(pCrossHairCursor);
01077                 //}
01078                 
01079                 // Now constrain the mouse point
01080                 ERROR3IF(ConstrainPoint == DocCoord(-1,-1),"ConstrainPoint wasn't set");
01081                 ERROR3IF(ConstrainPrevPoint == DocCoord(-1,-1),"ConstrainPrevPoint wasn't set");
01082                 ERROR3IF(ConstrainNextPoint == DocCoord(-1,-1),"ConstrainNextPoint wasn't set");
01083                 if (!EndSnapped && ClickMods.Constrain)
01084                 {
01085                     if (ClickMods.Adjust)
01086                     {
01087                         if (ClickMods.Alternative1 || ClickMods.Alternative2)
01088                             DocView::ConstrainToAngle(ConstrainNextPoint, &PointerPos);
01089                         else
01090                             DocView::ConstrainToAngle(ConstrainPrevPoint, &PointerPos);
01091                     }
01092                     else
01093                         DocView::ConstrainToAngle(ConstrainPoint, &PointerPos);
01094                 }
01095 
01096                 // Only snap to grid if we didn't snap to the end of a path
01097                 if (!EndSnapped)
01098                     DocView::SnapCurrent(StartSpread,&PointerPos);
01099             }
01100 
01101             // This is the bit where we go off and re-calculate the paths position,
01102             // based on how much the mouse has moved
01103 
01104             if (pCurrentOrig == (NodeListItem*) OriginalPaths.GetHead ())
01105             {
01106                 Offset.x = PointerPos.x - LastMousePos.x;
01107                 Offset.y = PointerPos.y - LastMousePos.y;
01108             }
01109 
01110             // need to consider the validness of EndSnapped and DragPoint as well ....
01111             // CSG says:  we MUST pass EndSnapped and DragPoint as passed in below,
01112             // since these two values are reserved for the path that the drag was iniated
01113             // on (i.e.  the head of our linked list).  If we don't do this, then we get
01114             // loads of nasty asserts and errors going off.
01115             RecalculatePaths (pEditPath, Offset, FALSE /*EndSnapped*/, -1 /*DragPoint*/);
01116 
01117             if (pObjChange->ChangeMask.EorBlobs)
01118             {
01119                 ChangeCode Chge = pObjChange->RenderChangedBlobs(pOrigPath,this,pEditPath,StartSpread,TRUE);
01120                 if (Chge!=CC_OK)
01121                 {
01122                     // replace the old edit path
01123                     pEditPath->CopyPathDataFrom(&TempPath);
01124                     RenderPathEditBlobs( pEditPath/*->GetBoundingRect()*/, StartSpread );
01125                     return;
01126                 }
01127             }
01128 
01129             // Update the Last Mouse Position
01130             LastMousePos = PointerPos;
01131 
01132             // Draw in the new version of the path
01133             RenderPathEditBlobs( pEditPath/*->GetBoundingRect()*/, StartSpread );
01134 
01135             // If there is just one selected point being dragged then we should show
01136             // its new position in the line tool infobar.
01137 //          if (UpdatePoint != -1)
01138 //              BROADCAST_TO_ALL(PathEditedMsg(pEditPath, pSpread, UpdatePoint));
01139 
01141 
01142             pCurrentOrig = (NodeListItem*) OriginalPaths.GetNext (pCurrentOrig);
01143             pCurrentEdit = (NodeListItem*) EditPaths.GetNext (pCurrentEdit);
01144             pCurrentChange = (NodeListItem*) ObjChanges.GetNext (pCurrentChange);
01145             pCurrentDragS = (NodeListItem*) PathsDragStarted.GetNext (pCurrentDragS);
01146         }
01147         
01148         SetStatusLineHelp();
01149     }
01150 }
01151 
01152 
01153 
01154 /********************************************************************************************
01155 
01156 >   void OpNodePathEditBlob::DragFinished( DocCoord PointerPos, ClickModifiers ClickMods, Spread*, BOOL Success, BOOL bSolidDrag)
01157 
01158     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
01159     Created:    5/7/93
01160     Inputs:     PointerPos - The position of the mouse at the end of the drag
01161                 ClickMods - the key modifiers being pressed
01162                 Success - TRUE if the drag was terminated properly, FALSE if it
01163                 was ended with the escape key being pressed
01164     Purpose:    This is called when a drag operation finishes.
01165     SeeAlso:    ClickModifiers
01166 
01167 ********************************************************************************************/
01168 
01169 void OpNodePathEditBlob::DragFinished( DocCoord PointerPos, ClickModifiers ClickMods, 
01170                                        Spread* pSpread, BOOL Success, BOOL bSolidDrag)
01171 {
01172     if (!MultiplePaths)
01173     {
01174         if (DragStarted)
01175             RenderDraggingBlobs( EditPath.GetBoundingRect(), StartSpread );
01176         DestroyCursors();
01177         EndDrag();
01178         BeginSlowJob();
01179         ChangeCode Chge;                                       
01180 
01181         BOOL NotFailed = Success;
01182         
01183         // DY, if we are editing a brushed nodepath then we need a couple of extra actions
01184         AttrBrushType* pAttrBrush = GetAppliedBrush();
01185 
01186         if (NotFailed)
01187         {
01188             if (!DragStarted)
01189             {
01190                 INT32 i;
01191                 if (OriginalPath->InkPath.FindNearestPoint( PointerPos, 
01192                                                         POINTFLAG_ENDPOINTS | 
01193                                                         POINTFLAG_CONTROLPOINTS | 
01194                                                         POINTFLAG_ENDSFIRST,
01195                                                         &i)
01196                 )
01197                 {
01198                     // Deselect all but the clicked endpoint
01199                     OriginalPath->InkPath.RenderPathSelectedControlBlobs(StartSpread);
01200                     OriginalPath->InkPath.ClearSubSelection();
01201                     PathFlags* pFlags = OriginalPath->InkPath.GetFlagArray();
01202                     pFlags[i].IsSelected = TRUE;
01203                     OriginalPath->InkPath.EnsureSelection(TRUE);
01204                     OriginalPath->InkPath.RenderPathSelectedControlBlobs(StartSpread);
01205 
01206                     // Send a sel-changed message so tools update (hello jason  8-)
01207                     GetApplication()->FindSelection()->Update();
01208 
01209                     // Kill the op so we dont appear in the undo history
01210                     FailAndExecute();
01211                     End();
01212                     return;
01213                 }
01214             }
01215             else
01216             {
01217                 NotFailed = DoStartSelOp(TRUE,TRUE);
01218 
01219                 // Create and send a change message about this path edit
01220                 if (NotFailed)
01221                 {
01222                     ObjChangeFlags cFlags;
01223                     cFlags.TransformNode = TRUE;
01224                     ObjChangeParam ObjChange(OBJCHANGE_STARTING,cFlags,OriginalPath,this);
01225                     NotFailed = OriginalPath->AllowOp(&ObjChange, TRUE);
01226                 }
01227 
01228     //          // Store the paths sub-selection state
01229     //          if (NotFailed)
01230     //              NotFailed = (StorePathSubSelStateAction::DoRecord(this, &UndoActions, &OriginalPath->InkPath) != AC_FAIL);
01231 
01232                 // Check we are dealing with a node path. If not, we shouldn't optimise the redraw.
01233                 // Really we should ask the node whether it can cope with optimised redraw. This means
01234                 // whether its correct simply to redraw the changed rectangular section around the moved
01235                 // coordinate. For envelope and perspective moulds whose shapes are govened by derived
01236                 // path objects its not correct to redraw optimally as this can result in sections of the
01237                 // object being left undrawn when the whole surface / contents have changed.
01238                 BOOL OptimiseRedraw = CopyNeedToRenderFlags() && !(EndSnapped || SnapToAnother);
01239                 if (!IS_A(OriginalPath,NodePath))
01240                     OptimiseRedraw = FALSE;
01241                 
01242                 // if we are editing a brush we don't want to optimise either, as it redraws incorrectly
01243                 if (pAttrBrush != NULL)
01244                     OptimiseRedraw = FALSE;
01245                 // We had better copy the path back over the original and re-calc the bounding box
01246                 // Force a re-draw of the place where the path used to be
01247                 if (NotFailed)
01248                     NotFailed = RecalcBoundsAction::DoRecalc(this, &UndoActions, OriginalPath, OptimiseRedraw) != AC_FAIL;
01249 
01250 
01251                 //if we are editing a brushed nodepath we need to have this here for the undo to work
01252                 if (pAttrBrush != NULL && pAttrBrush->IsTimeStamping() && NotFailed)
01253                 {
01254                     UpdateBrushAction* pAction;
01255                     NotFailed = (UpdateBrushAction::Init(this, &UndoActions,  OriginalPath, &pAction) != AC_FAIL);
01256                 }
01257                 // If the EndSnapped flag is set, we have to set the PT_CLOSEFIGURE flag in the last
01258                 // element of the copied path
01259                 if (NotFailed && EndSnapped)
01260                     SnapEndsTogether();
01261         
01262                 // Go and copy the edited path back over the original path
01263                 if (NotFailed)
01264                     NotFailed = CopyEditedPathBack();
01265 
01266                 // If the ends snapped, set the filled bit on the path
01267                 if (NotFailed)
01268                     NotFailed = FillPathIfEndsSnapped();
01269                 if (NotFailed)
01270                     NotFailed = JoinWithOtherPath();
01271 
01272                 if (NotFailed)
01273                 {
01274                     // Recalculate the path's bounding box
01275                     OriginalPath->InvalidateBoundingRect();
01276                     SelRange *Sel = GetApplication()->FindSelection();
01277                     Sel->UpdateBounds();
01278 
01279                     // Expand the pasteboard as necessary to include the new object bounds
01280                     DocRect NewBounds = Sel->GetBoundingRect(TRUE);
01281                     ERROR3IF(pSpread == NULL, "Unexpectedly NULL spread pointer");
01282                     pSpread->ExpandPasteboardToInclude(NewBounds);
01283 
01284                     // Force a redraw of the place where the path is now.
01285                     NotFailed = RecordBoundsAction::DoRecord(this, &UndoActions, OriginalPath, OptimiseRedraw) != AC_FAIL;
01286                 }
01287             }
01288         }
01289 
01290         // DY 9/99 Check to see if we are editing a blend on a curve, if so we may wish to 
01291         // change the number of steps in the blend (making use of the path distance).
01292         NodeGroup* pParent = GetGroupParentOfCurve();
01293         
01294         if (pParent != NULL)
01295         {
01296             if (pParent->IS_KIND_OF(NodeBlend))
01297             {
01298                 if (NotFailed)
01299                     InsertChangeBlendStepsAction((NodeBlend*)pParent);               
01300             }
01301         
01302         }
01303 
01304     
01305 
01306         // Inform all the parents of this node that it has been changed.
01307         if (NotFailed)
01308         {
01309             ObjChangeFlags ChgeFlags;
01310             EditObjChange.Define(OBJCHANGE_FINISHED,ChgeFlags,OriginalPath,this,&EditPath,StartSpread);
01311             NotFailed = UpdateChangedNodes(&EditObjChange);
01312         }
01313         else
01314         {
01315             if (EditObjChange.ChangeMask.Finished)
01316                 Chge=EditObjChange.ObjChangeFailed(OriginalPath,this,&EditPath,StartSpread,TRUE);
01317         }
01318 
01319         
01320         if (!NotFailed) 
01321             FailAndExecute();
01322 
01323         OriginalPaths.DeleteAll ();
01324 
01325         GetApplication()->UpdateSelection();
01326         End();
01327     }
01328     else
01329     {
01330         NodeListItem* pCurrentOrig = (NodeListItem*) OriginalPaths.GetHead ();
01331         NodeListItem* pCurrentEdit = (NodeListItem*) EditPaths.GetHead ();
01332         NodeListItem* pCurrentChange = (NodeListItem*) ObjChanges.GetHead ();
01333         NodeListItem* pCurrentDragS = (NodeListItem*) PathsDragStarted.GetHead ();
01334 
01335         DocCoord Offset;
01336 
01337         DestroyCursors();
01338         EndDrag();
01339         BeginSlowJob();
01340         ChangeCode Chge;           
01341 
01342         BOOL NotFailed = Success;
01343         
01344         // DY, if we are editing a brushed nodepath then we need a couple of extra actions
01345         AttrBrushType* pAttrBrush = GetAppliedBrush();
01346 
01347         if (NotFailed)
01348             NotFailed = DoStartSelOp(TRUE,TRUE);
01349 
01350         while ((pCurrentOrig) && (pCurrentEdit) && (pCurrentChange) && (pCurrentDragS))
01351         {
01352             NodePath* pOrigPath = (NodePath*) (pCurrentOrig->pNode);
01353             Path* pEditPath = (Path*) (pCurrentEdit->pNode);
01354 //          ObjChangePathEdit* pObjChange = (ObjChangePathEdit*) (pCurrentChange->pNode);
01355             BOOL* pDragStarted = (BOOL*) (pCurrentDragS->pNode);
01356 
01357             if (*pDragStarted == TRUE)
01358             {
01359                 RenderDraggingBlobs( pEditPath/*EditPath.GetBoundingRect()*/, StartSpread );
01360             }
01361 
01362             if (NotFailed)
01363             {
01364                 if (*pDragStarted == FALSE)
01365                 {
01366                     INT32 i;
01367                     if (pOrigPath->InkPath.FindNearestPoint(PointerPos, 
01368                                                             POINTFLAG_ENDPOINTS | 
01369                                                             POINTFLAG_CONTROLPOINTS | 
01370                                                             POINTFLAG_ENDSFIRST,
01371                                                             &i)
01372                     )
01373                     {
01374                         // Deselect all but the clicked endpoint
01375                         pOrigPath->InkPath.RenderPathSelectedControlBlobs(StartSpread);
01376                         pOrigPath->InkPath.ClearSubSelection();
01377                         PathFlags* pFlags = pOrigPath->InkPath.GetFlagArray();
01378                         pFlags[i].IsSelected = TRUE;
01379                         pOrigPath->InkPath.EnsureSelection(TRUE);
01380                         pOrigPath->InkPath.RenderPathSelectedControlBlobs(StartSpread);
01381 
01382                         // Send a sel-changed message so tools update (hello jason  8-)
01383                         GetApplication()->FindSelection()->Update();
01384 
01385                         // Kill the op so we dont appear in the undo history
01386                         FailAndExecute();
01387                         End();
01388                         return;
01389                     }
01390                 }
01391                 else
01392                 {
01393                     //NotFailed = DoStartSelOp(TRUE,TRUE);
01394 
01395                     // Create and send a change message about this path edit
01396                     if (NotFailed)
01397                     {
01398                         ObjChangeFlags cFlags;
01399                         cFlags.TransformNode = TRUE;
01400                         ObjChangeParam ObjChange(OBJCHANGE_STARTING,cFlags,pOrigPath,this);
01401                         NotFailed = pOrigPath->AllowOp(&ObjChange, TRUE);
01402                     }
01403 
01404         //          // Store the paths sub-selection state
01405         //          if (NotFailed)
01406         //              NotFailed = (StorePathSubSelStateAction::DoRecord(this, &UndoActions, &OriginalPath->InkPath) != AC_FAIL);
01407 
01408                     // Check we are dealing with a node path. If not, we shouldn't optimise the redraw.
01409                     // Really we should ask the node whether it can cope with optimised redraw. This means
01410                     // whether its correct simply to redraw the changed rectangular section around the moved
01411                     // coordinate. For envelope and perspective moulds whose shapes are govened by derived
01412                     // path objects its not correct to redraw optimally as this can result in sections of the
01413                     // object being left undrawn when the whole surface / contents have changed.
01414                     BOOL OptimiseRedraw = CopyNeedToRenderFlags(pOrigPath, pEditPath) && !(EndSnapped || SnapToAnother);
01415                     if (!IS_A(pOrigPath,NodePath))
01416                         OptimiseRedraw = FALSE;
01417                     
01418                     // if we are editing a brush we don't want to optimise either, as it redraws incorrectly
01419                     if (pAttrBrush != NULL)
01420                         OptimiseRedraw = FALSE;
01421 
01422                     // We had better copy the path back over the original and re-calc the bounding box
01423                     // Force a re-draw of the place where the path used to be
01424                     if (NotFailed)
01425                         NotFailed = RecalcBoundsAction::DoRecalc(this, &UndoActions, pOrigPath, OptimiseRedraw) != AC_FAIL;
01426 
01427 
01428                     //if we are editing a brushed nodepath we need to have this here for the undo to work
01429                     if (pAttrBrush != NULL && pAttrBrush->IsTimeStamping() && NotFailed)
01430                     {
01431                         UpdateBrushAction* pAction;
01432                         NotFailed = (UpdateBrushAction::Init(this, &UndoActions,  pOrigPath, &pAction) != AC_FAIL);
01433                     }
01434                     
01435                     if (pCurrentOrig == (NodeListItem*) OriginalPaths.GetHead ())
01436                     {
01437                         // If the EndSnapped flag is set, we have to set the PT_CLOSEFIGURE flag in the last
01438                         // element of the copied path
01439                         if (NotFailed && EndSnapped)
01440                         {   
01441                             SnapEndsTogether (pEditPath);
01442                         }
01443                     }
01444                 
01445                     // Go and copy the edited path back over the original path
01446                     if (NotFailed)
01447                         NotFailed = CopyEditedPathBack(pOrigPath, pEditPath);
01448 
01449                     if (pCurrentOrig == (NodeListItem*) OriginalPaths.GetHead ())
01450                     {
01451                         // If the ends snapped, set the filled bit on the path
01452                         if (NotFailed)
01453                             NotFailed = FillPathIfEndsSnapped(pOrigPath);
01454                     }
01455 
01456                     // CGS:  BUT, we need to delay the snapping of a control point to another
01457                     // paths until here.  These is because the path that we are snapping to
01458                     // could have also of been dragged; and it is NOT guaranteed to have been
01459                     // updated until now ....
01460 
01461                     BOOL useHeadPath = FALSE;
01462                     NodePath*   pHeadPath = NULL;
01463                     NodePath**  hHeadPath = NULL;
01464 
01465                     if (pOrigPath == SnapToPath)
01466                     {
01467                         if (NotFailed)
01468                         {
01469                             NodeListItem* pHeadOrig = (NodeListItem*) OriginalPaths.GetHead ();
01470                             /*NodePath**/ pHeadPath = (NodePath*) (pHeadOrig->pNode);
01471                             hHeadPath = &pHeadPath;
01472 
01473                             NotFailed = JoinWithOtherPath(hHeadPath);
01474                             
01475                             if (NotFailed)
01476                             {
01477                                 useHeadPath = TRUE;
01478                             }
01479                         }
01480                     }
01481 
01482                     if (NotFailed)
01483                     {
01484                         // Recalculate the path's bounding box
01485                         pOrigPath->InvalidateBoundingRect();
01486                         SelRange *Sel = GetApplication()->FindSelection();
01487                         Sel->UpdateBounds();
01488 
01489                         // Expand the pasteboard as necessary to include the new object bounds
01490                         DocRect NewBounds = Sel->GetBoundingRect();
01491                         ERROR3IF(pSpread == NULL, "Unexpectedly NULL spread pointer");
01492                         pSpread->ExpandPasteboardToInclude(NewBounds);
01493 
01494                         if (!useHeadPath)
01495                         {
01496                             // Force a redraw of the place where the path is now.
01497                             NotFailed = RecordBoundsAction::DoRecord(this, &UndoActions, pOrigPath, OptimiseRedraw) != AC_FAIL;
01498                         }
01499                         else
01500                         {
01501                             //NodeListItem* pHeadOrig = (NodeListItem*) OriginalPaths.GetHead ();
01502                             //NodePath* pHeadPath = (NodePath*) (pHeadOrig->pNode);
01503                             
01504                             // Force a redraw of the place where the path is now.
01505                             NotFailed = RecordBoundsAction::DoRecord(this, &UndoActions, (*hHeadPath), OptimiseRedraw) != AC_FAIL;
01506                         }
01507                     }
01508                 }
01509             }
01510 
01511             // DY 9/99 Check to see if we are editing a blend on a curve, if so we may wish to 
01512             // change the number of steps in the blend (making use of the path distance).
01513             NodeGroup* pParent = GetGroupParentOfCurve(pOrigPath);
01514             
01515             if (pParent != NULL)
01516             {
01517                 if (pParent->IS_KIND_OF(NodeBlend))
01518                 {
01519                     if (NotFailed)
01520                         InsertChangeBlendStepsAction((NodeBlend*)pParent);               
01521                 }
01522             }
01523 
01524             // Inform all the parents of this node that it has been changed.
01525             if (NotFailed)
01526             {
01527                 ObjChangeFlags ChgeFlags;
01528                 EditObjChange.Define(OBJCHANGE_FINISHED,ChgeFlags,pOrigPath,this,pEditPath,StartSpread);
01529                 NotFailed = UpdateChangedNodes(&EditObjChange);
01530             }
01531             else
01532             {
01533                 if (EditObjChange.ChangeMask.Finished)
01534                     Chge=EditObjChange.ObjChangeFailed(pOrigPath,this,pEditPath,StartSpread,TRUE);
01535             }
01536 
01537             pCurrentOrig = (NodeListItem*) OriginalPaths.GetNext (pCurrentOrig);
01538             pCurrentEdit = (NodeListItem*) EditPaths.GetNext (pCurrentEdit);
01539             pCurrentChange = (NodeListItem*) ObjChanges.GetNext (pCurrentChange);
01540             pCurrentDragS = (NodeListItem*) PathsDragStarted.GetNext (pCurrentDragS);
01541         }
01542         
01543         if (!NotFailed) 
01544             FailAndExecute();
01545 
01546         GetApplication()->UpdateSelection();
01547 
01548         // now we need to handle destruction ourselves ....
01549 
01550 //      pCurrentOrig = (NodeListItem*) OriginalPaths.GetHead ();
01551         pCurrentEdit = (NodeListItem*) EditPaths.GetHead ();
01552         pCurrentChange = (NodeListItem*) ObjChanges.GetHead ();
01553         pCurrentDragS = (NodeListItem*) PathsDragStarted.GetHead ();
01554 
01555         while (/*(pCurrentOrig) &&*/ (pCurrentEdit) && (pCurrentChange) && (pCurrentDragS))
01556         {
01557 //          NodePath* pOrigPath = (NodePath*) (pCurrentOrig->pNode);
01558             Path* pEditPath = (Path*) (pCurrentEdit->pNode);
01559             ObjChangePathEdit* pObjChange = (ObjChangePathEdit*) (pCurrentChange->pNode);
01560             BOOL* pDragStarted = (BOOL*) (pCurrentDragS->pNode);
01561 
01562     //      delete (pCurrentOrig->pNode);
01563             delete (pEditPath);
01564             delete (pObjChange);
01565             delete (pDragStarted);
01566             
01567 //          pCurrentOrig = (NodeListItem*) OriginalPaths.GetNext (pCurrentOrig);
01568             pCurrentEdit = (NodeListItem*) EditPaths.GetNext (pCurrentEdit);
01569             pCurrentChange = (NodeListItem*) ObjChanges.GetNext (pCurrentChange);
01570             pCurrentDragS = (NodeListItem*) PathsDragStarted.GetNext (pCurrentDragS);
01571         }
01572 
01573         OriginalPaths.DeleteAll ();
01574         EditPaths.DeleteAll ();
01575         ObjChanges.DeleteAll ();
01576         PathsDragStarted.DeleteAll ();
01577 
01578         End();
01579     }
01580 }
01581 
01582 
01583 /********************************************************************************************
01584 
01585 >   void OpNodePathEditBlob::InsertChangeBlendStepsAction(NodeBlend* pNodeBlend)
01586 
01587     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
01588     Created:    1/9/99
01589     Inputs:     the node blend to change
01590     Outputs:    -
01591     Returns:    -
01592     Purpose:    To adjust the number of steps in a blend on a path as a result of the path
01593                 being edited (which is why its here rather than in the blend code).
01594                 First the number of steps is calculated from the new path distance and if
01595                 different a new action is inserted
01596     Errors:     Error2 if pNodeBlend is null
01597     SeeAlso:    called from OpNodePathEditBlob::DragFinished or 
01598                 OpNodePathAddEndpoint::CompleteThisPath
01599 
01600 ********************************************************************************************/
01601 BOOL OpNodePathEditBlob::InsertChangeBlendStepsAction(NodeBlend* pNodeBlend)
01602 {
01603     ERROR2IF(pNodeBlend==NULL, FALSE, "NodeBlend pointer is NULL");
01604     // do we wish to keep the distance between steps constant?  If so then determine
01605     // how many steps we need with the new length and make an action.
01606     if (pNodeBlend->GetEditState() == EDIT_DISTANCE)
01607     {
01608         UINT32 NewNumSteps = 0;
01609         UINT32 OldNumSteps = pNodeBlend->GetNumBlendSteps();
01610         double StepDistance = pNodeBlend->GetDistanceEntered();
01611         double NewPathLength = EditPath.GetPathLength();
01612         NewNumSteps = (UINT32)(NewPathLength/StepDistance);
01613         BOOL ok = TRUE;
01614         
01615     
01616         if (OldNumSteps != NewNumSteps)
01617         {
01618             ChangeBlendStepsAction* pAction;
01619             if (ok) ok = (InvalidateBoundsAction::Init(this,&UndoActions,pNodeBlend,TRUE) != AC_FAIL);
01620             if (ok) ok = ChangeBlendStepsAction::Init(this,&UndoActions,pNodeBlend,OldNumSteps,StepDistance,&pAction) != AC_FAIL;
01621             if (ok) ok = DoInvalidateNodeRegion(pNodeBlend,TRUE,FALSE);
01622             if (ok) ok = (InvalidateBoundsAction::Init(this,&UndoActions,pNodeBlend,TRUE) != AC_FAIL);
01623             pNodeBlend->SetNumBlendSteps(NewNumSteps);
01624         }
01625 
01626         // added 9/9/99 now we are shifting the last object along the curve to ensure precise
01627         // step distances.  To do this we must set the proportion of the curve used in the 
01628         // NodeBlender objects.
01629         if (ok)
01630         {
01631             double PathDistanceUsed = NewNumSteps * StepDistance;
01632             double PathProportion = PathDistanceUsed / NewPathLength;
01633 //          double PathDistanceUnused = NewPathLength - PathDistanceUsed;
01634 
01635             if (PathProportion != 1.0)
01636             {
01637                 ChangeBlenderOpParam BlenderParam;
01638                 BlenderParam.m_ChangeType = CHANGEBLENDER_PATHEND;
01639                             
01640                 NodeBlender* pNodeBlender = pNodeBlend->FindFirstBlender();
01641                 INT32 NumBlenders = pNodeBlend->GetNumBlenders();
01642                         
01643                 while (pNodeBlender != NULL)
01644                 {
01645                     NumBlenders--;
01646                     if (NumBlenders ==0)
01647                     {                                   
01648                         BlenderParam.m_NewPathEnd = PathProportion;
01649                         ok = ChangeBlenderAction::Init(this, &UndoActions, pNodeBlender, BlenderParam);
01650                         if (ok)
01651                         {
01652                             DocCoord NewPoint;
01653                             double ExtraParam = 0.0;  //passed to the function but not used afterwards
01654                             ok = pNodeBlender->GetPointOnNodeBlendPath(1.0,&NewPoint,&ExtraParam);
01655                             if (ok)
01656                             {
01657                                 NodeRenderableInk* pEnd = pNodeBlender->GetNodeEnd();
01658                                 NodeBlend* pNodeBlend = pNodeBlender->GetNodeBlend();
01659                                 ok = ((pEnd != NULL) && (pNodeBlend != NULL));
01660                     
01661                                 if (ok) 
01662                                     ok = pNodeBlend->TransformNodeToPoint(pEnd,&NewPoint,this,ExtraParam);
01663                             }
01664                         }
01665                     }   
01666                     pNodeBlender = pNodeBlend->FindNextBlender(pNodeBlender);
01667                 }
01668             } // end if (pathproportion..
01669         } // end if(ok)
01670     }
01671     return TRUE;
01672 }
01673 
01674 /********************************************************************************************
01675 
01676 >   void OpNodePathEditBlob::SnapEndsTogether()
01677 
01678     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
01679     Created:    19/7/94
01680     Inputs:     -
01681     Outputs:    -
01682     Returns:    -
01683     Purpose:    Sets the closefigure flag in the last element in the subpath. Also turns
01684                 off the rotate bit in the points we're snapping together if their
01685                 smoothing bits are also turned off. This prevents us creating a cusp that
01686                 has rotate flags set.
01687     Errors:     -
01688     SeeAlso:    -
01689 
01690 ********************************************************************************************/
01691 
01692 void OpNodePathEditBlob::SnapEndsTogether()
01693 {
01694     // If ends are snapping, there can only be one selected point on the path...
01695     // Get a pointer to the path's verbs
01696     PathVerb* Verbs = EditPath.GetVerbArray();
01697     PathFlags* Flags = EditPath.GetFlagArray();
01698     INT32 NumCoords = EditPath.GetNumCoords();
01699 
01700     // Find the selected point
01701     INT32               pos;
01702     for( pos = 0; pos < NumCoords && !( Flags[pos].IsEndPoint && Flags[pos].IsSelected); pos++ );
01703 
01704     // Now pos is the index of the selected point
01705 
01706     // This next may seem like a lot of faffing around, but we don't know whether the 
01707     // selected point is the start or the end of the path, so this code doesn't care
01708     // (thus it's not very optimal, but it shouldn't matter).
01709     
01710     // Move to the start of the edit path
01711     INT32               startpos = pos;
01712     EditPath.FindStartOfSubPath(&startpos);
01713     // find the end element (might be a curve or a line)
01714     pos=startpos;
01715     EditPath.FindEndOfSubPath(&pos);
01716 
01717     // if the last element is a bezier, pos points at the first control point
01718     // so we have to add 2 to move to the endpoint of the curve
01719     if (Verbs[pos] == PT_BEZIERTO)
01720         pos+=2;
01721     
01722     // Now simply set the PT_CLOSEFIGURE flag in this verb
01723     Verbs[pos] |= PT_CLOSEFIGURE;
01724     
01725     // make sure the first and last points are selected, along with adjacent ctrl points
01726     Flags[pos].IsSelected = TRUE;
01727     // if it's a bezier, select previous control point and check state of smooth/rotate bits
01728     if ((Verbs[pos] & ~PT_CLOSEFIGURE) == PT_BEZIERTO)
01729     {
01730         Flags[pos-1].IsSelected = TRUE;
01731         
01732         // if the smooth flag is not set, clear the rotate flag as well, otherwise
01733         // We'll have something that looks like a cusp, but as soon as you drag a control
01734         // point, the opposite point will rotate around.
01735         if (!Flags[pos-1].IsSmooth)
01736             Flags[pos-1].IsRotate = FALSE;
01737 
01738         // Do the same for the endpoint...
01739         if (!Flags[pos].IsSmooth)
01740             Flags[pos].IsRotate = FALSE;
01741     }
01742     pos = startpos;
01743     Flags[pos].IsSelected = TRUE;
01744     if (Verbs[pos+1] == PT_BEZIERTO)
01745     {
01746         Flags[pos+1].IsSelected = TRUE;
01747 
01748         // As above, clear the rotate bit if the smooth bit is clear
01749         if (!Flags[pos+1].IsSmooth)
01750             Flags[pos+1].IsRotate = FALSE;
01751         // Do the same for the endpoint...
01752         if (!Flags[pos].IsSmooth)
01753             Flags[pos].IsRotate = FALSE;
01754     }
01755 }
01756 
01757 /********************************************************************************************
01758 
01759 >   void OpNodePathEditBlob::SnapEndsTogether(Path* pEditPath)
01760 
01761     Author:     Chris_Snook (Xara Group Ltd) <camelotdev@xara.com> (based upon Jim)
01762     Created:    17/5/00
01763     Inputs:     -
01764     Outputs:    -
01765     Returns:    -
01766     Purpose:    Sets the closefigure flag in the last element in the subpath. Also turns
01767                 off the rotate bit in the points we're snapping together if their
01768                 smoothing bits are also turned off. This prevents us creating a cusp that
01769                 has rotate flags set.
01770     Errors:     -
01771     SeeAlso:    -
01772 
01773 ********************************************************************************************/
01774 
01775 void OpNodePathEditBlob::SnapEndsTogether(Path* pEditPath)
01776 {
01777     // If ends are snapping, there can only be one selected point on the path...
01778     // Get a pointer to the path's verbs
01779     PathVerb* Verbs = pEditPath->GetVerbArray();
01780     PathFlags* Flags = pEditPath->GetFlagArray();
01781     INT32 NumCoords = pEditPath->GetNumCoords();
01782 
01783     // Find the selected point
01784     INT32               pos;
01785     for ( pos = 0;pos<NumCoords && !(Flags[pos].IsEndPoint && Flags[pos].IsSelected);pos++);
01786 
01787     // Now pos is the index of the selected point
01788 
01789     // This next may seem like a lot of faffing around, but we don't know whether the 
01790     // selected point is the start or the end of the path, so this code doesn't care
01791     // (thus it's not very optimal, but it shouldn't matter).
01792     
01793     // Move to the start of the edit path
01794     INT32 startpos = pos;
01795     pEditPath->FindStartOfSubPath(&startpos);
01796     // find the end element (might be a curve or a line)
01797     pos=startpos;
01798     pEditPath->FindEndOfSubPath(&pos);
01799 
01800     // if the last element is a bezier, pos points at the first control point
01801     // so we have to add 2 to move to the endpoint of the curve
01802     if (Verbs[pos] == PT_BEZIERTO)
01803         pos+=2;
01804     
01805     // Now simply set the PT_CLOSEFIGURE flag in this verb
01806     Verbs[pos] |= PT_CLOSEFIGURE;
01807     
01808     // make sure the first and last points are selected, along with adjacent ctrl points
01809     Flags[pos].IsSelected = TRUE;
01810     // if it's a bezier, select previous control point and check state of smooth/rotate bits
01811     if ((Verbs[pos] & ~PT_CLOSEFIGURE) == PT_BEZIERTO)
01812     {
01813         Flags[pos-1].IsSelected = TRUE;
01814         
01815         // if the smooth flag is not set, clear the rotate flag as well, otherwise
01816         // We'll have something that looks like a cusp, but as soon as you drag a control
01817         // point, the opposite point will rotate around.
01818         if (!Flags[pos-1].IsSmooth)
01819             Flags[pos-1].IsRotate = FALSE;
01820 
01821         // Do the same for the endpoint...
01822         if (!Flags[pos].IsSmooth)
01823             Flags[pos].IsRotate = FALSE;
01824     }
01825     pos = startpos;
01826     Flags[pos].IsSelected = TRUE;
01827     if (Verbs[pos+1] == PT_BEZIERTO)
01828     {
01829         Flags[pos+1].IsSelected = TRUE;
01830 
01831         // As above, clear the rotate bit if the smooth bit is clear
01832         if (!Flags[pos+1].IsSmooth)
01833             Flags[pos+1].IsRotate = FALSE;
01834         // Do the same for the endpoint...
01835         if (!Flags[pos].IsSmooth)
01836             Flags[pos].IsRotate = FALSE;
01837     }
01838 }
01839 
01840 /********************************************************************************************
01841 
01842 >   BOOL OpNodePathEditBlob::JoinWithOtherPath()
01843 
01844     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
01845     Created:    5/10/94
01846     Returns:    FALSE if it failed (lack of memory maybe) TRUE otherwise
01847     Purpose:    Looks at the member variables dealing with snapping to another path and joins
01848                 the paths together. It will always join the other path to the original path, 
01849                 keeping the original path's attributes intact.
01850                 NOTE: This routine will alter the OriginalPath member variable if it joins paths, 
01851                 because it has to make a copy of the path for undo purposes. 
01852     Errors:     If it runs out of memory then it will return FALSE
01853 
01854 ********************************************************************************************/
01855 
01856 BOOL OpNodePathEditBlob::JoinWithOtherPath()
01857 {
01858     // If SnapToAnother is false there's no snapping to be done, so just return successfully
01859     if (!SnapToAnother)
01860         return TRUE;
01861 
01862     // If we're joining with another path, the rules say we can only have one selected point.
01863     PathFlags* Flags = OriginalPath->InkPath.GetFlagArray();
01864     INT32 NumCoords = OriginalPath->InkPath.GetNumCoords();
01865 
01866     NodePath* OldSnapPath = SnapToPath;
01867     
01868     INT32 MainIndex;
01869     for (MainIndex=0; MainIndex<NumCoords; MainIndex++)
01870     {
01871         if (Flags[MainIndex].IsEndPoint && Flags[MainIndex].IsSelected)
01872             break;
01873     }
01874 
01875     // on exit, MainIndex is the selected endpoint, or = NumCoords
01876 
01877     ENSURE(MainIndex<NumCoords,"There isn't a selected endpoint in the original path (JoinWithAnotherPath)");
01878     
01879     // We're joining to another path, so the easiest way to handle undo is to make a deep copy
01880     // of the original path, then join that with the othe path, put it back into the tree
01881     // and delete the old one. Simple.
01882     Node* pnp;
01883     OriginalPath->NodeCopy(&pnp);   // Make a deep copy of the original path, with attributes
01884     NodePath* MainPath = (NodePath*)pnp;
01885 
01886     if (!MainPath)
01887         return FALSE;
01888 
01889     // Now that we have a copy of this path, let's join it with the other path
01890 
01891     if (SnapToAnother)
01892     {
01893         // If we're snapping to a subpath in the same path, change SnapToPath because we're
01894         // no longer joining to OriginalPath.
01895         if (SnapToPath == OriginalPath)
01896             SnapToPath = MainPath;
01897         
01898         // If we're snapping to another, the first thing I have to do is change the control
01899         // point of the point in the other path that we're snapping to, because it might
01900         // have been smoothed.
01901 
01902         if (SnapToLineOrCurve)      // TRUE if the path has been smoothed
01903         {
01904             PathVerb* OtherVerbs = SnapToPath->InkPath.GetVerbArray();
01905             PathFlags* OtherFlags = SnapToPath->InkPath.GetFlagArray();
01906             DocCoord* OtherCoords = SnapToPath->InkPath.GetCoordArray();
01907 
01908             if (OtherVerbs[SnapToIndex] == PT_MOVETO)
01909             {
01910                 // Don't need to undo if SnapToPath == MainPath
01911                 if (SnapToPath == MainPath)
01912                     OtherCoords[SnapToIndex+1] = SnapToCoords[1];
01913                 else
01914                     DoAlterPathElement( SnapToPath, 
01915                                         SnapToIndex+1, 
01916                                         SnapToCoords[1], 
01917                                         OtherFlags[SnapToIndex+1],
01918                                         OtherVerbs[SnapToIndex+1]);
01919             }
01920             else
01921             {
01922                 if (SnapToPath == MainPath)
01923                     OtherCoords[SnapToIndex-1] = SnapToCoords[1];
01924                 else
01925                     DoAlterPathElement( SnapToPath, 
01926                                         SnapToIndex-1, 
01927                                         SnapToCoords[1], 
01928                                         OtherFlags[SnapToIndex-1],
01929                                         OtherVerbs[SnapToIndex-1]);
01930             }
01931         }
01932 
01933         if (!MainPath->InkPath.JoinToAnother(&(SnapToPath->InkPath), MainIndex, SnapToIndex))
01934         {
01935             MainPath->CascadeDelete();
01936             delete MainPath;
01937             return FALSE;
01938         }
01939     }
01940 
01941     // We've joined successfully, so let's insert this new node into the tree next to the original
01942     if (!DoInsertNewNode(MainPath, OriginalPath, NEXT, TRUE, FALSE))
01943     {
01944         MainPath->CascadeDelete();
01945         delete MainPath;
01946         return FALSE;
01947     }
01948 
01949     // Hide the original path
01950     if (!DoHideNode(OriginalPath, TRUE))
01951         return FALSE;
01952 
01953     // Hide the node we joined to
01954     if (OldSnapPath != OriginalPath)
01955     {
01956         if (!DoHideNode(OldSnapPath, TRUE))
01957             return FALSE;
01958     }
01959 
01960     // Change the OriginalPath member variable so that subsequent operations work properly
01961     OriginalPath = MainPath;
01962 
01963     return TRUE;
01964 }
01965 
01966 
01967 
01968 /********************************************************************************************
01969 
01970 >   BOOL OpNodePathEditBlob::JoinWithOtherPath(NodePath** pOrigPath)
01971 
01972     Author:     Chris_Snook (Xara Group Ltd) <camelotdev@xara.com> (based upon Jim)
01973     Created:    17/5/00
01974     Returns:    FALSE if it failed (lack of memory maybe) TRUE otherwise
01975     Purpose:    Looks at the member variables dealing with snapping to another path and joins
01976                 the paths together. It will always join the other path to the original path, 
01977                 keeping the original path's attributes intact.
01978                 NOTE: This routine will alter the OriginalPath member variable if it joins paths, 
01979                 because it has to make a copy of the path for undo purposes. 
01980     Errors:     If it runs out of memory then it will return FALSE
01981 
01982 ********************************************************************************************/
01983 
01984 BOOL OpNodePathEditBlob::JoinWithOtherPath(NodePath** pOrigPath)
01985 {
01986     // If SnapToAnother is false there's no snapping to be done, so just return successfully
01987     if (!SnapToAnother)
01988         return TRUE;
01989 
01990     // If we're joining with another path, the rules say we can only have one selected point.
01991     PathFlags* Flags = (*pOrigPath)->InkPath.GetFlagArray();
01992     INT32 NumCoords = (*pOrigPath)->InkPath.GetNumCoords();
01993 
01994     NodePath* OldSnapPath = SnapToPath;
01995     
01996     INT32 MainIndex;
01997     for (MainIndex=0; MainIndex<NumCoords; MainIndex++)
01998     {
01999         if (Flags[MainIndex].IsEndPoint && Flags[MainIndex].IsSelected)
02000             break;
02001     }
02002 
02003     // on exit, MainIndex is the selected endpoint, or = NumCoords
02004 
02005     ENSURE(MainIndex<NumCoords,"There isn't a selected endpoint in the original path (JoinWithAnotherPath)");
02006     
02007     // We're joining to another path, so the easiest way to handle undo is to make a deep copy
02008     // of the original path, then join that with the othe path, put it back into the tree
02009     // and delete the old one. Simple.
02010     Node* pnp;
02011     (*pOrigPath)->NodeCopy(&pnp);   // Make a deep copy of the original path, with attributes
02012     NodePath* MainPath = (NodePath*)pnp;
02013 
02014     if (!MainPath)
02015         return FALSE;
02016 
02017     // Now that we have a copy of this path, let's join it with the other path
02018 
02019     if (SnapToAnother)
02020     {
02021         // If we're snapping to a subpath in the same path, change SnapToPath because we're
02022         // no longer joining to OriginalPath.
02023         if (SnapToPath == (*pOrigPath))
02024             SnapToPath = MainPath;
02025         
02026         // If we're snapping to another, the first thing I have to do is change the control
02027         // point of the point in the other path that we're snapping to, because it might
02028         // have been smoothed.
02029 
02030         if (SnapToLineOrCurve)      // TRUE if the path has been smoothed
02031         {
02032             PathVerb* OtherVerbs = SnapToPath->InkPath.GetVerbArray();
02033             PathFlags* OtherFlags = SnapToPath->InkPath.GetFlagArray();
02034             DocCoord* OtherCoords = SnapToPath->InkPath.GetCoordArray();
02035 
02036             if (OtherVerbs[SnapToIndex] == PT_MOVETO)
02037             {
02038                 // Don't need to undo if SnapToPath == MainPath
02039                 if (SnapToPath == MainPath)
02040                     OtherCoords[SnapToIndex+1] = SnapToCoords[1];
02041                 else
02042                     DoAlterPathElement( SnapToPath, 
02043                                         SnapToIndex+1, 
02044                                         SnapToCoords[1], 
02045                                         OtherFlags[SnapToIndex+1],
02046                                         OtherVerbs[SnapToIndex+1]);
02047             }
02048             else
02049             {
02050                 if (SnapToPath == MainPath)
02051                     OtherCoords[SnapToIndex-1] = SnapToCoords[1];
02052                 else
02053                     DoAlterPathElement( SnapToPath, 
02054                                         SnapToIndex-1, 
02055                                         SnapToCoords[1], 
02056                                         OtherFlags[SnapToIndex-1],
02057                                         OtherVerbs[SnapToIndex-1]);
02058             }
02059         }
02060 
02061         if (!MainPath->InkPath.JoinToAnother(&(SnapToPath->InkPath), MainIndex, SnapToIndex))
02062         {
02063             MainPath->CascadeDelete();
02064             delete MainPath;
02065             return FALSE;
02066         }
02067     }
02068 
02069     // We've joined successfully, so let's insert this new node into the tree next to the original
02070     if (!DoInsertNewNode(MainPath, (*pOrigPath), NEXT, TRUE, FALSE))
02071     {
02072         MainPath->CascadeDelete();
02073         delete MainPath;
02074         return FALSE;
02075     }
02076 
02077     // Hide the original path
02078     if (!DoHideNode((*pOrigPath), TRUE))
02079         return FALSE;
02080 
02081     // Hide the node we joined to
02082     if (OldSnapPath != (*pOrigPath))
02083     {
02084         if (!DoHideNode(OldSnapPath, TRUE))
02085             return FALSE;
02086     }
02087 
02088     // Change the pOrigPath variable so that subsequent operations work properly
02089     (*pOrigPath) = MainPath;
02090 
02091     return TRUE;
02092 }
02093 
02094 
02095 
02096 /********************************************************************************************
02097 >   BOOL OpNodePathEditBlob::BuildEditPaths ()
02098 
02099     Author:     Chris_Snook (Xara Group Ltd) <camelotdev@xara.com> (based upon Rik/Peter)
02100     Created:    17/02/94
02101     Returns:    TRUE if it managed to build the paths, FALSE if it failed
02102     Purpose:    Builds a copy of each path in the selection that we can edit, without
02103                 destroying the original data.  Also sets the NeedToRender flags for EOR display.
02104     Errors:     If it runs out of memory then it will return FALSE
02105 ********************************************************************************************/
02106 
02107 BOOL OpNodePathEditBlob::BuildEditPaths ()
02108 {
02109     NodeListItem* pCurrent = (NodeListItem*) OriginalPaths.GetHead ();
02110 
02111     while (pCurrent)
02112     {
02113         NodePath* pCurrentPath = (NodePath*) (pCurrent->pNode);
02114         Path* NewEditPath = new Path ();
02115 
02116         UINT32 NumCoords = pCurrentPath->InkPath.GetNumCoords();
02117 
02118         if (!NewEditPath->Initialise(NumCoords, 24))
02119             return FALSE;
02120         if (!NewEditPath->CopyPathDataFrom(&(pCurrentPath->InkPath)))
02121             return FALSE;
02122 
02123         // Go though all the coords, with scary amounts of looking back and forwards
02124         UINT32 LastEndPoint = 0;                // The EndPoint before this one
02125         BOOL SetNextEndPoint = FALSE;       // TRUE if we want to set the next EndPoint to render
02126         BOOL SetNextNextEndPoint = FALSE;   // TRUE if we want the one after the next one to render
02127         PathFlags* Flags = NewEditPath->GetFlagArray();
02128         
02129         for (UINT32 i=0; i<NumCoords; i++)
02130         {
02131             // Make all the flags FALSE by default
02132             Flags[i].NeedToRender = FALSE;
02133 
02134             if (Flags[i].IsEndPoint)
02135             {
02136                 // if the endpoint 2 elements back was selected and the last element was smooth
02137                 // then we need to mark this point for rendering
02138                 if (SetNextNextEndPoint)
02139                 {
02140                     Flags[i].NeedToRender = TRUE;
02141                     SetNextNextEndPoint = FALSE;
02142                 }
02143 
02144                 // We have found an Endpoint, do we want to mark this one as renderable
02145                 if (SetNextEndPoint)
02146                 {
02147                     // As the last element was selected, this element needs to render
02148                     Flags[i].NeedToRender = TRUE;
02149                     SetNextEndPoint = FALSE;
02150 
02151                     // If the smooth flag is set then the next item needs to render as well
02152                     if (Flags[i].IsRotate || Flags[i].IsSmooth)
02153                         SetNextNextEndPoint = TRUE;
02154                 }
02155 
02156                 // If its selected, then its renderable
02157                 if (Flags[i].IsSelected)
02158                 {
02159                     Flags[i].NeedToRender = TRUE;
02160                     if (Flags[LastEndPoint].IsRotate || Flags[LastEndPoint].IsSmooth)
02161                         Flags[LastEndPoint].NeedToRender = TRUE;
02162 
02163                     // Set the flag for the next endpoint
02164                     SetNextEndPoint = TRUE;
02165                 }
02166 
02167                 LastEndPoint = i;
02168             }
02169         }
02170 
02171         NodeListItem* pInsert = new NodeListItem;
02172         pInsert->pNode = (Node*) NewEditPath;
02173         
02174         EditPaths.AddTail (pInsert);//NewEditPath);
02175         
02176         pCurrent = (NodeListItem*) OriginalPaths.GetNext (pCurrent);
02177 
02178         //delete (pInsert);
02179 
02180 //      delete (NewEditPath);               // cleanup that is necessary
02181     }
02182     
02183     return (TRUE);
02184 }
02185 
02186 
02187 /********************************************************************************************
02188 >   BOOL OpNodePathEditBlob::BuildEditPath()
02189 
02190     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com> - later attacked by Peter
02191     Created:    17/02/94
02192     Returns:    TRUE if it managed to build the path, FALSE if it failed
02193     Purpose:    Builds a copy of the path that we can edit, without destroying the original
02194                 data.  Also sets the NeedToRender flags for EOR display.
02195     Errors:     If it runs out of memory then it will return FALSE
02196 ********************************************************************************************/
02197 BOOL OpNodePathEditBlob::BuildEditPath()
02198 {
02199     // Make a copy of the original path
02200     UINT32 NumCoords = OriginalPath->InkPath.GetNumCoords();
02201     if (!EditPath.Initialise(NumCoords, 24))
02202         return FALSE;
02203     if (!EditPath.CopyPathDataFrom(&(OriginalPath->InkPath)))
02204         return FALSE;
02205 
02206     // Go though all the coords, with scary amounts of looking back and forwards
02207     UINT32 LastEndPoint = 0;                // The EndPoint before this one
02208     BOOL SetNextEndPoint = FALSE;       // TRUE if we want to set the next EndPoint to render
02209     BOOL SetNextNextEndPoint = FALSE;   // TRUE if we want the one after the next one to render
02210     PathFlags* Flags = EditPath.GetFlagArray();
02211     for (UINT32 i=0; i<NumCoords; i++)
02212     {
02213         // Make all the flags FALSE by default
02214         Flags[i].NeedToRender = FALSE;
02215 
02216         if (Flags[i].IsEndPoint)
02217         {
02218             // if the endpoint 2 elements back was selected and the last element was smooth
02219             // then we need to mark this point for rendering
02220             if (SetNextNextEndPoint)
02221             {
02222                 Flags[i].NeedToRender = TRUE;
02223                 SetNextNextEndPoint = FALSE;
02224             }
02225 
02226             // We have found an Endpoint, do we want to mark this one as renderable
02227             if (SetNextEndPoint)
02228             {
02229                 // As the last element was selected, this element needs to render
02230                 Flags[i].NeedToRender = TRUE;
02231                 SetNextEndPoint = FALSE;
02232 
02233                 // If the smooth flag is set then the next item needs to render as well
02234                 if (Flags[i].IsRotate || Flags[i].IsSmooth)
02235                     SetNextNextEndPoint = TRUE;
02236             }
02237 
02238             // If its selected, then its renderable
02239             if (Flags[i].IsSelected)
02240             {
02241                 Flags[i].NeedToRender = TRUE;
02242                 if (Flags[LastEndPoint].IsRotate || Flags[LastEndPoint].IsSmooth)
02243                     Flags[LastEndPoint].NeedToRender = TRUE;
02244 
02245                 // Set the flag for the next endpoint
02246                 SetNextEndPoint = TRUE;
02247             }
02248 
02249             LastEndPoint = i;
02250         }
02251     }
02252 
02253     return TRUE;
02254 }
02255 
02256 
02257 
02258 /********************************************************************************************
02259 
02260 >   BOOL OpNodePathEditBlob::CopyEditedPathBack()
02261 
02262     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
02263     Created:    17/02/94
02264     Returns:    TRUE if it worked, FALSE if it failed
02265     Purpose:    Copies the contents of the edited path back into the original path
02266 
02267 ********************************************************************************************/
02268 
02269 BOOL OpNodePathEditBlob::CopyEditedPathBack()
02270 {
02271     // Now to do some undo information. To do this, I have to look at each element in the path
02272     // and each element in the copy, and see which ones differ. I then have to work out how many
02273     // elements that was, and create a path to contain those elements, along with an array of indices 
02274     // telling me where those elements came from
02275     INT32 NumElements = OriginalPath->InkPath.GetNumCoords();
02276     PathVerb* SourceVerbs = OriginalPath->InkPath.GetVerbArray();
02277     DocCoord* SourceCoords = OriginalPath->InkPath.GetCoordArray();
02278     PathFlags* SourceFlags = OriginalPath->InkPath.GetFlagArray();
02279     PathVerb* DestVerbs = EditPath.GetVerbArray();
02280     DocCoord* DestCoords = EditPath.GetCoordArray();
02281     PathFlags* DestFlags = EditPath.GetFlagArray();
02282 
02283     // DY now we want to keep track of the indexes of the changed section
02284     INT32 ChangedElements = 0;
02285     INT32 FirstChanged = -1;
02286     INT32 LastChanged  = -1;
02287 
02288     INT32 i;
02289     for (i=0;i<NumElements;i++)
02290     {
02291         if (SourceVerbs[i] != DestVerbs[i] || SourceCoords[i] != DestCoords[i] || 
02292             SourceFlags[i] != DestFlags[i] )
02293         {
02294             ChangedElements++;
02295 
02296         //  if (SourceCoords[i] != DestCoords[i])
02297             {
02298                 // if we have not yet set the first changed index then set it
02299                 if (FirstChanged == -1)
02300                     FirstChanged = i;
02301 
02302                 // always set the last changed index
02303                 LastChanged = i;
02304             }
02305         }
02306 
02307     }
02308     
02309 
02310     // ChangedElements is the number of elements in this path that will change.
02311     // We have to create three arrays to contain the changed elements, plus one array
02312     // to tell me where the elements should go (the indices)
02313 
02314     // I also have to create an action object to contain these arrays. I have to create the action
02315     // object first because that does all the work of deciding if there's enough memory in the 
02316     // undo buffer to store the action, and prompting the user accordingly of there isn't
02317 
02318     if (ChangedElements > 0)
02319     {
02320         // do the brush editing here
02321         // the following block deals with editing path blobs.  For some reason I could not get a
02322         // sensible value for the distance of the edited path from the changed indexes, however
02323         // it works quite nicely by finding the contraining points.
02324         if (DragPoint != -1)
02325         {
02326             // this section only applies for editing blobs (as reshaping has no drag point)
02327             FirstChanged = DragPoint;
02328             LastChanged = DragPoint;
02329             OriginalPath->InkPath.FindPrevEndPoint(&FirstChanged);
02330             OriginalPath->InkPath.FindNextEndPoint(&LastChanged);
02331         }
02332         
02333         AttrBrushType* pAttrBrush = GetAppliedBrush();
02334         if (pAttrBrush != NULL)
02335             EditBrushAttribute(FirstChanged, LastChanged, pAttrBrush);
02336 
02337         ModifyPathAction* ModAction;
02338     
02339         ActionCode Act;
02340         Act = ModifyPathAction::Init(this, &UndoActions, ChangedElements, (Action**)(&ModAction));
02341         if (Act == AC_FAIL)
02342         {
02343             FailAndExecute();
02344             End();
02345             return FALSE;
02346         }
02347 
02348         PathVerb* ChangedVerbs=NULL;
02349         DocCoord* ChangedCoords=NULL;
02350         PathFlags* ChangedFlags=NULL;
02351         INT32* ChangedIndices=NULL;
02352 
02353         // If the function returned AC_NO_RECORD we shouldn't record any undo information in the action
02354         // NOTE - during unwind all actions return AC_OK with a NULL ModAction pointer!!
02355         if ((Act!=AC_NORECORD) && (ModAction!=NULL))
02356         {
02357             // This next bit is a bit hellish. Any one of these four allocations can fail, in which case 
02358             // we have to tidy up afterwards. Cue a lot of nested ifs and elses.
02359 
02360             ALLOC_WITH_FAIL(ChangedVerbs,(PathVerb*) CCMalloc(ChangedElements * sizeof(PathVerb)),this);
02361             if (ChangedVerbs)
02362             {
02363                 ALLOC_WITH_FAIL(ChangedCoords,(DocCoord*) CCMalloc(ChangedElements * sizeof(DocCoord)),this);
02364                 if (ChangedCoords)
02365                 {
02366                     ALLOC_WITH_FAIL(ChangedFlags,(PathFlags*) CCMalloc(ChangedElements * sizeof(PathFlags)),this);
02367                     if (ChangedFlags)
02368                     {
02369                         ALLOC_WITH_FAIL(ChangedIndices,(INT32*) CCMalloc(ChangedElements * sizeof(INT32)),this);
02370                         if (!ChangedIndices)
02371                         {
02372                             CCFree( ChangedFlags );
02373                             CCFree( ChangedCoords );
02374                             CCFree( ChangedVerbs);
02375                             FailAndExecute();
02376                             End();
02377                             return FALSE;
02378                         }
02379                     }
02380                     else
02381                     {
02382                         CCFree( ChangedCoords );
02383                         CCFree( ChangedVerbs );
02384                         FailAndExecute();
02385                         End();
02386                         return FALSE;
02387 
02388                     }
02389                 }
02390                 else
02391                 {
02392                     CCFree( ChangedVerbs);
02393                     FailAndExecute();
02394                     End();
02395                     return FALSE;
02396 
02397                 }
02398             }
02399 
02400             // Now to put the undo data into the undo action
02401             INT32 index = 0;
02402             for (i=0;i<NumElements;i++)
02403             {
02404                 if (SourceVerbs[i] != DestVerbs[i] || 
02405                     SourceCoords[i] != DestCoords[i] || 
02406                     SourceFlags[i] != DestFlags[i]
02407                     )
02408                 {
02409                     ChangedVerbs[index] = SourceVerbs[i];
02410                     ChangedFlags[index] = SourceFlags[i];
02411                     ChangedCoords[index] = SourceCoords[i];
02412                     ChangedIndices[index] = i;
02413                     index++;
02414                 }
02415             }
02416 
02417             // Now we've allocated the arrays, let's tell the action about 'em
02418             ModAction->StoreArrays(ChangedVerbs, ChangedFlags, ChangedCoords, ChangedIndices, OriginalPath);
02419         }
02420     }
02421         
02422     if (!OriginalPath->InkPath.CopyPathDataFrom(&EditPath))
02423         return FALSE;
02424     else
02425         return TRUE;
02426 }
02427 
02428 
02429 
02430 /********************************************************************************************
02431 
02432 >   BOOL OpNodePathEditBlob::CopyEditedPathBack(NodePath* pOrigPath, Path* pEditPath)
02433 
02434     Author:     Chris_Snook (Xara Group Ltd) <camelotdev@xara.com> (based upon Will/Peter)
02435     Created:    20/4/2000
02436     Returns:    TRUE if it worked, FALSE if it failed
02437     Purpose:    Copies the contents of the edited path back into the original path
02438 
02439 ********************************************************************************************/
02440 
02441 BOOL OpNodePathEditBlob::CopyEditedPathBack(NodePath* pOrigPath, Path* pEditPath)
02442 {
02443     // Now to do some undo information. To do this, I have to look at each element in the path
02444     // and each element in the copy, and see which ones differ. I then have to work out how many
02445     // elements that was, and create a path to contain those elements, along with an array of indices 
02446     // telling me where those elements came from
02447     INT32 NumElements = pOrigPath->InkPath.GetNumCoords();
02448     PathVerb* SourceVerbs = pOrigPath->InkPath.GetVerbArray();
02449     DocCoord* SourceCoords = pOrigPath->InkPath.GetCoordArray();
02450     PathFlags* SourceFlags = pOrigPath->InkPath.GetFlagArray();
02451     PathVerb* DestVerbs = pEditPath->GetVerbArray();
02452     DocCoord* DestCoords = pEditPath->GetCoordArray();
02453     PathFlags* DestFlags = pEditPath->GetFlagArray();
02454 
02455     INT32 ChangedElements = 0;
02456     INT32 i;    
02457 
02458     for (i=0;i<NumElements;i++)
02459     {
02460         if (SourceVerbs[i] != DestVerbs[i] || 
02461             SourceCoords[i] != DestCoords[i] || 
02462             SourceFlags[i] != DestFlags[i]
02463             )
02464             ChangedElements++;
02465     }
02466 
02467     // ChangedElements is the number of elements in this path that will change.
02468     // We have to create three arrays to contain the changed elements, plus one array
02469     // to tell me where the elements should go (the indices)
02470 
02471     // I also have to create an action object to contain these arrays. I have to create the action
02472     // object first because that does all the work of deciding if there's enough memory in the 
02473     // undo buffer to store the action, and prompting the user accordingly of there isn't
02474 
02475     if (ChangedElements > 0)
02476     {
02477         ModifyPathAction* ModAction;
02478     
02479         ActionCode Act;
02480         Act = ModifyPathAction::Init(this, &UndoActions, ChangedElements, (Action**)(&ModAction));
02481         if (Act == AC_FAIL)
02482         {
02483             FailAndExecute();
02484             End();
02485             return FALSE;
02486         }
02487 
02488         PathVerb* ChangedVerbs=NULL;
02489         DocCoord* ChangedCoords=NULL;
02490         PathFlags* ChangedFlags=NULL;
02491         INT32* ChangedIndices=NULL;
02492 
02493         // If the function returned AC_NO_RECORD we shouldn't record any undo information in the action
02494         // NOTE - during unwind all actions return AC_OK with a NULL ModAction pointer!!
02495         if ((Act!=AC_NORECORD) && (ModAction!=NULL))
02496         {
02497             // This next bit is a bit hellish. Any one of these four allocations can fail, in which case 
02498             // we have to tidy up afterwards. Cue a lot of nested ifs and elses.
02499 
02500             ALLOC_WITH_FAIL(ChangedVerbs,(PathVerb*) CCMalloc(ChangedElements * sizeof(PathVerb)),this);
02501             if (ChangedVerbs)
02502             {
02503                 ALLOC_WITH_FAIL(ChangedCoords,(DocCoord*) CCMalloc(ChangedElements * sizeof(DocCoord)),this);
02504                 if (ChangedCoords)
02505                 {
02506                     ALLOC_WITH_FAIL(ChangedFlags,(PathFlags*) CCMalloc(ChangedElements * sizeof(PathFlags)),this);
02507                     if (ChangedFlags)
02508                     {
02509                         ALLOC_WITH_FAIL(ChangedIndices,(INT32*) CCMalloc(ChangedElements * sizeof(INT32)),this);
02510                         if (!ChangedIndices)
02511                         {
02512                             CCFree( ChangedFlags );
02513                             CCFree( ChangedCoords );
02514                             CCFree( ChangedVerbs);
02515                             FailAndExecute();
02516                             End();
02517                             return FALSE;
02518                         }
02519                     }
02520                     else
02521                     {
02522                         CCFree( ChangedCoords );
02523                         CCFree( ChangedVerbs );
02524                         FailAndExecute();
02525                         End();
02526                         return FALSE;
02527 
02528                     }
02529                 }
02530                 else
02531                 {
02532                     CCFree( ChangedVerbs);
02533                     FailAndExecute();
02534                     End();
02535                     return FALSE;
02536 
02537                 }
02538             }
02539 
02540             // Now to put the undo data into the undo action
02541             INT32 index = 0;
02542             for (i=0;i<NumElements;i++)
02543             {
02544                 if (SourceVerbs[i] != DestVerbs[i] || 
02545                     SourceCoords[i] != DestCoords[i] || 
02546                     SourceFlags[i] != DestFlags[i]
02547                     )
02548                 {
02549                     ChangedVerbs[index] = SourceVerbs[i];
02550                     ChangedFlags[index] = SourceFlags[i];
02551                     ChangedCoords[index] = SourceCoords[i];
02552                     ChangedIndices[index] = i;
02553                     index++;
02554                 }
02555             }
02556 
02557             // Now we've allocated the arrays, let's tell the action about 'em
02558             ModAction->StoreArrays(ChangedVerbs, ChangedFlags, ChangedCoords, ChangedIndices, pOrigPath);
02559         }
02560     }
02561         
02562     if (!pOrigPath->InkPath.CopyPathDataFrom(pEditPath))
02563         return FALSE;
02564     else
02565         return TRUE;
02566 }
02567 
02568 
02569 
02570 /********************************************************************************************
02571 
02572 >   void OpNodePathEditBlob::CopyNeedToRenderFlags()
02573 
02574     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
02575     Created:    10/03/95
02576     Inputs:     -
02577     Outputs:    (see below)
02578     Returns:    TRUE if the flags have been copied; FALSE if the paths were different lengths
02579     Purpose:    This function is used to copy the NeedToRender flags from EditPath to
02580                 OriginalPath.  This are then used in optimised region invalidation.
02581 
02582 ********************************************************************************************/
02583 BOOL OpNodePathEditBlob::CopyNeedToRenderFlags()
02584 {
02585     const INT32 OrigLength = OriginalPath->InkPath.GetNumCoords();
02586     const INT32 EditLength = EditPath.GetNumCoords();
02587 
02588     if (EditLength != OrigLength)
02589         return FALSE;
02590 
02591     PathFlags* EditFlags = EditPath.GetFlagArray();
02592     PathFlags* OrigFlags = OriginalPath->InkPath.GetFlagArray();
02593         
02594     for (INT32 loop = 0; loop < EditLength; loop ++)
02595     {
02596         OrigFlags[loop].NeedToRender = EditFlags[loop].NeedToRender;
02597     }
02598 
02599     return TRUE;
02600 }
02601 
02602 
02603 
02604 /********************************************************************************************
02605 
02606 >   BOOL OpNodePathEditBlob::CopyNeedToRenderFlags(NodePath* pOrigPath, Path* pEditPath)
02607 
02608     Author:     Chris_Snook (Xara Group Ltd) <camelotdev@xara.com> (based upon Peter)
02609     Created:    20/04/95
02610     Inputs:     -
02611     Outputs:    (see below)
02612     Returns:    TRUE if the flags have been copied; FALSE if the paths were different lengths
02613     Purpose:    This function is used to copy the NeedToRender flags from EditPath to
02614                 OriginalPath.  This are then used in optimised region invalidation.
02615 
02616 ********************************************************************************************/
02617 
02618 BOOL OpNodePathEditBlob::CopyNeedToRenderFlags(NodePath* pOrigPath, Path* pEditPath)
02619 {
02620     const INT32 OrigLength = pOrigPath->InkPath.GetNumCoords();
02621     const INT32 EditLength = pEditPath->GetNumCoords();
02622 
02623     if (EditLength != OrigLength)
02624         return FALSE;
02625 
02626     PathFlags* EditFlags = pEditPath->GetFlagArray();
02627     PathFlags* OrigFlags = pOrigPath->InkPath.GetFlagArray();
02628         
02629     for (INT32 loop = 0; loop < EditLength; loop ++)
02630     {
02631         OrigFlags[loop].NeedToRender = EditFlags[loop].NeedToRender;
02632     }
02633 
02634     return TRUE;
02635 }
02636 
02637 
02638 
02639 /********************************************************************************************
02640 
02641 >   void OpNodePathEditBlob::RecalculatePath(   DocCoord Offset, 
02642                                                 BOOL SnapEnds = FALSE,
02643                                                 INT32 SnapIndex = 0 )
02644 
02645     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
02646     Created:    9/11/93
02647     Inputs:     Offset - The Amount to move the selected blobs by before re-calcing everything
02648                 SnapEnds is TRUE if the start and endpoint should snap (affects smoothing)
02649                 SnapIndex is the index of the point that's being snapped, so we can only
02650                 snap one subpath out of many
02651     Scope:      Private
02652     Purpose:    This goes through the path, moves all the selected coords by the offset and
02653                 then performs some magic to smooth the rest of the path round the changes if
02654                 it needs it.
02655 
02656 ********************************************************************************************/
02657 
02658 void OpNodePathEditBlob::RecalculatePath( DocCoord Offset, BOOL SnapEnds , INT32 SnapIndex)
02659 {
02660     // Get the array that I need
02661     PathFlags* Flags  = EditPath.GetFlagArray();
02662     DocCoord*  Coords = EditPath.GetCoordArray();
02663 
02664     INT32 NumCoords = EditPath.GetNumCoords();
02665 
02666     // Go though all the points
02667     for (INT32 i=0; i<NumCoords; i++)
02668     {
02669         // and translate them by the drag offset if they are selected
02670         if (Flags[i].IsSelected)
02671             Coords[i].translate( Offset.x, Offset.y );
02672     }
02673 
02674     // And now smooth out the new path
02675     EditPath.SmoothCurve(TRUE, SnapEnds, SnapIndex);
02676 
02677     // Now to snap (possibly) to another path, if the SnapToAnother flag is set
02678     // This not only entails changing the part of the line we're dragging, but it might
02679     // also mean we have to set the SnapToCoords array to contain a snapped bezier segment  
02680     if (SnapToAnother)
02681     {
02682         // Get the array pointers
02683         PathVerb* Verbs = EditPath.GetVerbArray();
02684         Flags = EditPath.GetFlagArray();
02685         Coords = EditPath.GetCoordArray();
02686         PathVerb* OtherVerbs = SnapToPath->InkPath.GetVerbArray();
02687         PathFlags* OtherFlags = SnapToPath->InkPath.GetFlagArray();
02688         DocCoord* OtherCoords = SnapToPath->InkPath.GetCoordArray();
02689 
02690         // Because we're snapping, we must be on either the start or the end of a subpath
02691         // And the CloseFigure flag will never be set.
02692         // There are sixteen possibilities - for each line, we could be looking at
02693         // a curve or a line, at the start or end of each subpath. This logic enumerates
02694         // all of the possibilities. It may look tortuous, but it gets them all.
02695         if (Verbs[SnapIndex] == PT_MOVETO)
02696         {
02697             // We're dragging the start of a subpath. See if it's a curve
02698             if (Verbs[SnapIndex+1] == PT_BEZIERTO)
02699             {
02700                 // We are dragging a curve at the start of the path. See what we've snapped to
02701                 if (OtherVerbs[SnapToIndex] == PT_MOVETO)
02702                 {
02703                     // Snapping a curve at start to the start of the other one
02704                     if (OtherVerbs[SnapToIndex+1] == PT_BEZIERTO)
02705                     {
02706                         // Snapping curve at beginning with curve at beginning
02707                         // Let's smooth this bezier according to the snapping
02708                         if (Flags[SnapIndex+1].IsSmooth)
02709                             Coords[SnapIndex+1] = EditPath.SmoothControlPoint(  SnapIndex+1,
02710                                                                                 FALSE,  // start end not snapped
02711                                                                                 TRUE,   // Joined to another
02712                                                                                 TRUE,   // Other is a curve
02713                                                                                 OtherCoords[SnapToIndex+3]
02714                                                                                 );
02715                         // Note that the other control point is not affected by the snap
02716                         // Since the other segment is a curve, we have to set up the SnapToCoords array
02717                         // so that the render routine works properly
02718                         // Notice that the assignment in the if below is required
02719                         if ((SnapToLineOrCurve = OtherFlags[SnapToIndex+1].IsSmooth))
02720                         {
02721                             SnapToCoords[0] = OtherCoords[SnapToIndex];
02722                             SnapToCoords[1] = SnapToPath->InkPath.SmoothControlPoint(   SnapToIndex+1,
02723                                                                                         FALSE,
02724                                                                                         TRUE,
02725                                                                                         TRUE,
02726                                                                                         Coords[SnapIndex+3]
02727                                                                                         );
02728                             SnapToCoords[2] = OtherCoords[SnapToIndex+2];
02729                             SnapToCoords[3] = OtherCoords[SnapToIndex+3];
02730                         }
02731                     }
02732                     else
02733                     {
02734                         // Snapping curve at beginning with line at beginning
02735                         if (Flags[SnapIndex+1].IsSmooth)
02736                             Coords[SnapIndex+1] = EditPath.SmoothControlPoint(  SnapIndex+1,
02737                                                                                 FALSE,  // start end not snapped
02738                                                                                 TRUE,   // Joined to another
02739                                                                                 FALSE,  // Other is a line
02740                                                                                 OtherCoords[SnapToIndex+1]
02741                                                                                 );
02742                         // Since we're snapping to a line, we just need to set SnapToLineOrCurve to FALSE
02743                         SnapToLineOrCurve = FALSE;
02744 
02745                     }
02746                 }
02747                 else
02748                 {
02749                     // Snapping curve at beginning with the end of the other one
02750                     if (OtherVerbs[SnapToIndex] == PT_BEZIERTO)
02751                     {
02752                         // Snapping curve at beginning with curve at end of other one
02753                         // Smooth the editpath bezier if the flag says we can
02754                         if (Flags[SnapIndex+1].IsSmooth)
02755                             Coords[SnapIndex+1] = EditPath.SmoothControlPoint(  SnapIndex+1,
02756                                                                                 FALSE,
02757                                                                                 TRUE,
02758                                                                                 TRUE,
02759                                                                                 OtherCoords[SnapToIndex-3]
02760                                                                                 );
02761                         // Now set up SnapToCoords array
02762                         if ((SnapToLineOrCurve = OtherFlags[SnapToIndex-1].IsSmooth))   // yes the other path is a curve
02763                         {
02764                             SnapToCoords[0] = OtherCoords[SnapToIndex];
02765                             SnapToCoords[1] = SnapToPath->InkPath.SmoothControlPoint(   SnapToIndex-1,
02766                                                                                         FALSE,
02767                                                                                         TRUE,
02768                                                                                         TRUE,
02769                                                                                         Coords[SnapIndex+3]
02770                                                                                         );
02771                             SnapToCoords[2] = OtherCoords[SnapToIndex-2];
02772                             SnapToCoords[3] = OtherCoords[SnapToIndex-3];
02773                         }
02774                     }
02775                     else
02776                     {
02777                         // snapping curve at beginning with line at end of other one
02778                         if (Flags[SnapIndex+1].IsSmooth)
02779                             Coords[SnapIndex+1] = EditPath.SmoothControlPoint(  SnapIndex+1,
02780                                                                                 FALSE,
02781                                                                                 TRUE,
02782                                                                                 FALSE,
02783                                                                                 OtherCoords[SnapToIndex-1]
02784                                                                                 );
02785                         // Other is a line, so the SnapToCoords array isn't used
02786                         SnapToLineOrCurve = FALSE;
02787                     }
02788                 }
02789 
02790             }
02791             else
02792             {
02793                 // Dragging a line at the beginning. What are we snapping to
02794                 if (OtherVerbs[SnapToIndex] == PT_MOVETO)
02795                 {
02796                     // Snapping a line at start to the start of the other one
02797                     if (OtherVerbs[SnapToIndex+1] == PT_BEZIERTO)
02798                     {
02799                         // Snapping line at beginning with curve at beginning
02800                         // There's nothing to smooth in the edit path, but we have to
02801                         // set up the SnapToCoords array
02802                         if ((SnapToLineOrCurve = OtherFlags[SnapToIndex+1].IsSmooth))
02803                         {
02804                             SnapToCoords[0] = OtherCoords[SnapToIndex];
02805                             SnapToCoords[1] = SnapToPath->InkPath.SmoothControlPoint(   SnapToIndex+1,
02806                                                                                         FALSE,
02807                                                                                         TRUE,
02808                                                                                         FALSE,
02809                                                                                         Coords[SnapIndex+1]
02810                                                                                         );
02811                             SnapToCoords[2] = OtherCoords[SnapToIndex+2];
02812                             SnapToCoords[3] = OtherCoords[SnapToIndex+3];
02813                         }
02814                     }
02815                     {
02816                         // Snapping line at beginning with line at beginning
02817                         // Nothing whatever to smooth, just clear the flag
02818                         SnapToLineOrCurve = FALSE;
02819                     }
02820                 }
02821                 else
02822                 {
02823                     // Snapping line at beginning with the end of the other one
02824                     if (OtherVerbs[SnapToIndex] == PT_BEZIERTO)
02825                     {
02826                         // Snapping line at beginning with curve at end of other one
02827                         // Nothing to smooth in editpath, let's set up SnapToCoords array
02828                         if ((SnapToLineOrCurve = OtherFlags[SnapToIndex-1].IsSmooth))
02829                         {
02830                             SnapToCoords[0] = OtherCoords[SnapToIndex];
02831                             SnapToCoords[1] = SnapToPath->InkPath.SmoothControlPoint(   SnapToIndex-1,
02832                                                                                         FALSE,
02833                                                                                         TRUE,
02834                                                                                         FALSE,
02835                                                                                         Coords[SnapIndex+1]
02836                                                                                         );
02837                             SnapToCoords[2] = OtherCoords[SnapToIndex-2];
02838                             SnapToCoords[3] = OtherCoords[SnapToIndex-3];
02839                         }
02840                     }
02841                     else
02842                     {
02843                         // snapping line at beginning with line at end of other one
02844                         // Just clear the flag
02845                         SnapToLineOrCurve = FALSE;
02846                     }
02847                 }
02848 
02849             }
02850         }
02851         else
02852         {
02853             // We're snapping the end of the path, let's see what
02854             if (Verbs[SnapIndex] == PT_BEZIERTO)
02855             {
02856                 // Snapping a curve at the end to... what?
02857                 if (OtherVerbs[SnapToIndex] == PT_MOVETO)
02858                 {
02859                     // Snapping a curve at the end to the start of the other one
02860                     if (OtherVerbs[SnapToIndex+1] == PT_BEZIERTO)
02861                     {
02862                         // Snapping curve at the end with curve at beginning
02863                         if (Flags[SnapIndex-1].IsSmooth)
02864                             Coords[SnapIndex-1] = EditPath.SmoothControlPoint(  SnapIndex-1,
02865                                                                                 FALSE,
02866                                                                                 TRUE,
02867                                                                                 TRUE,
02868                                                                                 OtherCoords[SnapToIndex+3]
02869                                                                                 );
02870                         // Set up the SnapToCoords array
02871                         if ((SnapToLineOrCurve = OtherFlags[SnapToIndex+1].IsSmooth))
02872                         {
02873                             SnapToCoords[0] = OtherCoords[SnapToIndex];
02874                             SnapToCoords[1] = SnapToPath->InkPath.SmoothControlPoint(   SnapToIndex+1,
02875                                                                                         FALSE,
02876                                                                                         TRUE,
02877                                                                                         TRUE,
02878                                                                                         Coords[SnapIndex-3]
02879                                                                                         );
02880                             SnapToCoords[2] = OtherCoords[SnapToIndex+2];
02881                             SnapToCoords[3] = OtherCoords[SnapToIndex+3];
02882                         }
02883                     }
02884                     else
02885                     {
02886                         // Snapping curve at the end with line at beginning
02887                         if (Flags[SnapIndex-1].IsSmooth)
02888                             Coords[SnapIndex-1] = EditPath.SmoothControlPoint(  SnapIndex-1,
02889                                                                                 FALSE,
02890                                                                                 TRUE,
02891                                                                                 FALSE,
02892                                                                                 OtherCoords[SnapToIndex+1]
02893                                                                                 );
02894 
02895                         // Clear flag for SnapToCoords array
02896                         SnapToLineOrCurve = FALSE;
02897                     }
02898                 }
02899                 else
02900                 {
02901                     // Snapping curve at the end with the end of the other one
02902                     if (OtherVerbs[SnapToIndex] == PT_BEZIERTO)
02903                     {
02904                         // Snapping curve at the end with curve at end of other one
02905                         if (Flags[SnapIndex-1].IsSmooth)
02906                             Coords[SnapIndex-1] = EditPath.SmoothControlPoint(  SnapIndex-1,
02907                                                                                 FALSE,
02908                                                                                 TRUE,
02909                                                                                 TRUE,
02910                                                                                 OtherCoords[SnapToIndex-3]
02911                                                                                 );
02912                         // Init the SnapToCoords array
02913                         if ((SnapToLineOrCurve = OtherFlags[SnapToIndex-1].IsSmooth))
02914                         {
02915                             SnapToCoords[0] = OtherCoords[SnapToIndex];
02916                             SnapToCoords[1] = SnapToPath->InkPath.SmoothControlPoint(   SnapToIndex-1,
02917                                                                                         FALSE,
02918                                                                                         TRUE,
02919                                                                                         TRUE,
02920                                                                                         Coords[SnapIndex-3]
02921                                                                                         );
02922                             SnapToCoords[2] = OtherCoords[SnapToIndex-2];
02923                             SnapToCoords[3] = OtherCoords[SnapToIndex-3];
02924                         }
02925                     }
02926                     else
02927                     {
02928                         // snapping curve at the end with line at end of other one
02929                         if (Flags[SnapIndex-1].IsSmooth)
02930                             Coords[SnapIndex-1] = EditPath.SmoothControlPoint(  SnapIndex-1,
02931                                                                                 FALSE,
02932                                                                                 TRUE,
02933                                                                                 FALSE,
02934                                                                                 OtherCoords[SnapToIndex-1]
02935                                                                                 );
02936                         // Other is a line so clear the flag
02937                         SnapToLineOrCurve = FALSE;
02938                     }
02939                 }
02940 
02941             }
02942             else
02943             {
02944                 // Snapping a line at the end with something
02945                 if (OtherVerbs[SnapToIndex] == PT_MOVETO)
02946                 {
02947                     // Snapping a line at the end to the start of the other one
02948                     if (OtherVerbs[SnapToIndex+1] == PT_BEZIERTO)
02949                     {
02950                         // Snapping line at the end with curve at beginning
02951                         // Nothing to smooth in the edit path, but we should set up SnapToCoords array
02952                         if ((SnapToLineOrCurve = OtherFlags[SnapToIndex+1].IsSmooth))
02953                         {
02954                             SnapToCoords[0] = OtherCoords[SnapToIndex];
02955                             SnapToCoords[1] = SnapToPath->InkPath.SmoothControlPoint(   SnapToIndex+1,
02956                                                                                         FALSE,
02957                                                                                         TRUE,
02958                                                                                         FALSE,
02959                                                                                         Coords[SnapIndex-1]
02960                                                                                         );
02961                             SnapToCoords[2] = OtherCoords[SnapToIndex+2];
02962                             SnapToCoords[3] = OtherCoords[SnapToIndex+3];
02963                         }
02964                     }
02965                     {
02966                         // Snapping line at the end with line at beginning
02967                         // Just clear the flag
02968                         SnapToLineOrCurve = FALSE;
02969                     }
02970                 }
02971                 else
02972                 {
02973                     // Snapping line at the end with the end of the other one
02974                     if (OtherVerbs[SnapToIndex] == PT_BEZIERTO)
02975                     {
02976                         // Snapping line at the end with curve at end of other one
02977                         // Nothing to smooth in Editpath, but we have to do the array
02978                         if ((SnapToLineOrCurve = OtherFlags[SnapToIndex-1].IsSmooth))
02979                         {
02980                             SnapToCoords[0] = OtherCoords[SnapToIndex];
02981                             SnapToCoords[1] = SnapToPath->InkPath.SmoothControlPoint(   SnapToIndex-1,
02982                                                                                         FALSE,
02983                                                                                         TRUE,
02984                                                                                         FALSE,
02985                                                                                         Coords[SnapIndex-1]
02986                                                                                         );
02987                             SnapToCoords[2] = OtherCoords[SnapToIndex-2];
02988                             SnapToCoords[3] = OtherCoords[SnapToIndex-3];
02989                         }
02990                     }
02991                     else
02992                     {
02993                         // snapping line at the end with line at end of other one
02994                         // Just clear the flag
02995                         SnapToLineOrCurve = FALSE;
02996                     }
02997                 }
02998 
02999             }
03000         }
03001 
03002     }
03003 
03004 }
03005 
03006 
03007 
03008 /********************************************************************************************
03009 
03010 >   void OpNodePathEditBlob::RecalculatePaths(  DocCoord Offset, 
03011                                                 BOOL SnapEnds = FALSE,
03012                                                 INT32 SnapIndex = 0 )
03013 
03014     Author:     Chris_Snook (Xara Group Ltd) <camelotdev@xara.com>
03015     Created:    20/4/2000
03016     Inputs:     Offset - The Amount to move the selected blobs by before re-calcing everything
03017                 SnapEnds is TRUE if the start and endpoint should snap (affects smoothing)
03018                 SnapIndex is the index of the point that's being snapped, so we can only
03019                 snap one subpath out of many
03020     Scope:      Private
03021     Purpose:    This goes through the path, moves all the selected coords by the offset and
03022                 then performs some magic to smooth the rest of the path round the changes if
03023                 it needs it.
03024 
03025 ********************************************************************************************/
03026 
03027 void OpNodePathEditBlob::RecalculatePaths (Path* pEditPath, DocCoord Offset, BOOL SnapEnds , INT32 SnapIndex)
03028 {
03029     //NodeListItem* pCurrentOrig = (NodeListItem*) OriginalPaths.GetHead ();
03030     //NodeListItem* pCurrentEdit = (NodeListItem*) EditPaths.GetHead ();
03031 
03032     //while (/*(pCurrentOrig) &&*/ (pCurrentEdit))
03033     //{
03034         //NodePath* pOrigPath = (NodePath*) (pCurrentOrig->pNode);
03035     //  Path* pEditPath = (Path*) (pCurrentEdit->pNode);
03036     
03037         // Get the array that I need
03038         PathFlags* Flags  = pEditPath->GetFlagArray();
03039         DocCoord*  Coords = pEditPath->GetCoordArray();
03040 
03041         INT32 NumCoords = pEditPath->GetNumCoords();
03042 
03043         // Go though all the points
03044         for (INT32 i=0; i<NumCoords; i++)
03045         {
03046             // and translate them by the drag offset if they are selected
03047             if (Flags[i].IsSelected)
03048                 Coords[i].translate( Offset.x, Offset.y );
03049         }
03050 
03051         // And now smooth out the new path
03052         pEditPath->SmoothCurve(TRUE, SnapEnds, SnapIndex);
03053 
03054         //pCurrentOrig = (NodeListItem*) OriginalPaths.GetNext (pCurrentOrig);
03055     //  pCurrentEdit = (NodeListItem*) EditPaths.GetNext (pCurrentEdit);
03056     //}
03057 }
03058 
03059 
03060 
03061 /********************************************************************************************
03062 >   void OpNodePathEditBlob::RenderDragBlobs( DocRect Rect, SPread* pSpread, BOOL bSolidDrag)
03063 
03064     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
03065     Created:    6/9/93
03066     Purpose:    Renders the new version of the path to the window. It makes use of flags in
03067                 the path to determine which segments of the path need to be rendered.
03068 ********************************************************************************************/
03069 void OpNodePathEditBlob::RenderDragBlobs( DocRect Rect, Spread *pSpread, BOOL bSolidDrag )
03070 {
03071     RenderPathEditBlobs(Rect,pSpread);
03072     if (EditObjChange.ChangeMask.EorBlobs)
03073         EditObjChange.RenderCurrentBlobs(OriginalPath,this,&EditPath,StartSpread,TRUE);
03074 }
03075 
03076 
03077 
03078 /********************************************************************************************
03079 >   void OpNodePathEditBlob::RenderDraggingBlobs( DocRect Rect, Spread *pSpread )
03080 
03081     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
03082     Created:    1/5/95
03083     Purpose:    Call this function to render all the blobs on screen from this operation
03084 ********************************************************************************************/
03085 void OpNodePathEditBlob::RenderDraggingBlobs ( DocRect Rect, Spread *pSpread )
03086 {
03087     RenderPathEditBlobs(Rect, pSpread);
03088 }
03089 
03090 
03091 
03092 /********************************************************************************************
03093 >   void OpNodePathEditBlob::RenderDraggingBlobs ( Path* pEditPath, Spread *pSpread )
03094 
03095     Author:     Chris_Snook (Xara Group Ltd) <camelotdev@xara.com>
03096     Created:    20/4/2000
03097     Purpose:    Call this function to render all the blobs on screen from this operation
03098 ********************************************************************************************/
03099 
03100 void OpNodePathEditBlob::RenderDraggingBlobs ( Path* pEditPath, Spread *pSpread )
03101 {
03102     RenderPathEditBlobs(pEditPath, pSpread);
03103 }
03104 
03105 
03106 void OpNodePathEditBlob::RenderPathEditBlobs( DocRect Rect, Spread *pSpread )
03107 {
03108     // First we have to build a path to render
03109     Path RenderPath;
03110     if ( RenderPath.Initialise(24, 12) == FALSE )
03111     {
03112         // We did not get the memory, so don't draw anything
03113         return;
03114     }
03115 
03116     BOOL IsInCurve = FALSE;
03117     INT32 NumCoords = EditPath.GetNumCoords();
03118     for (INT32 i=0; i<NumCoords; i++)
03119     {
03120         // Dereference the pointers I need
03121         PathFlags* Flags  = EditPath.GetFlagArray();
03122         DocCoord*  Coords = EditPath.GetCoordArray();
03123         PathVerb*  Verbs  = EditPath.GetVerbArray();
03124 
03125         // We only consider the end points
03126         if (Flags[i].IsEndPoint)
03127         {
03128             if (IsInCurve)
03129             {
03130                 // we are already adding to the curve, so see if we should continue adding
03131                 if (Flags[i].NeedToRender)
03132                 {
03133                     switch (Verbs[i] & (~PT_CLOSEFIGURE))
03134                     {
03135                         case PT_MOVETO:
03136                             RenderPath.InsertMoveTo(Coords[i]);
03137                             break;
03138 
03139                         case PT_LINETO:
03140                             RenderPath.InsertLineTo(Coords[i]);
03141                             break;
03142 
03143                         case PT_BEZIERTO:
03144                             RenderPath.InsertCurveTo(Coords[i-2], Coords[i-1], Coords[i]);
03145                             break;
03146 
03147                         default:
03148                             break;
03149                     }
03150                 }
03151                 else
03152                 {
03153                     IsInCurve = FALSE;
03154                 }
03155             }
03156             else
03157             {
03158                 // We are not currently adding to the render path, so start
03159                 if (Flags[i].NeedToRender)
03160                 {
03161                     switch (Verbs[i] & (~PT_CLOSEFIGURE))
03162                     {
03163                     case PT_MOVETO:
03164                         RenderPath.InsertMoveTo(Coords[i]);
03165                         break;
03166                     case PT_BEZIERTO:
03167                         RenderPath.InsertMoveTo(Coords[i-3]);
03168                         RenderPath.InsertCurveTo(Coords[i-2],Coords[i-1],Coords[i]);
03169                         break;
03170                     case PT_LINETO:
03171                         RenderPath.InsertMoveTo(Coords[i-1]);
03172                         RenderPath.InsertLineTo(Coords[i]);
03173                         break;
03174                     }
03175                     IsInCurve = TRUE;
03176                 }
03177             }
03178         }
03179     }
03180 
03181     // And if we've snapped to another curve
03182     if (SnapToLineOrCurve)
03183     {
03184         // We are snapping to another curve, so render the curve
03185         RenderPath.InsertMoveTo(SnapToCoords[0]);
03186         RenderPath.InsertCurveTo(SnapToCoords[1],SnapToCoords[2],SnapToCoords[3]);
03187     }
03188     
03189     // Make sure that there is something to draw
03190     if (RenderPath.GetNumCoords()==0)
03191         return;
03192 
03193     // And this bit actually draws the path we have just made
03194     RenderRegion* pRegion = DocView::RenderOnTop( NULL, StartSpread, ClippedEOR );
03195     while (pRegion != NULL)
03196     {
03197         pRegion->SetLineColour( COLOUR_XORNEW );    // Set the line colour 
03198         pRegion->DrawPath( &RenderPath );           // Draw an EORed version of how the path will turn out
03199         pRegion = DocView::GetNextOnTop( &Rect );   // Get the next render region
03200     }
03201 }
03202 
03203 
03204 
03205 /********************************************************************************************
03206 >   void OpNodePathEditBlob::RenderPathEditBlobs( Path* pEditPath, Spread *pSpread )
03207 
03208     Author:     Chris_Snook (Xara Group Ltd) <camelotdev@xara.com>
03209     Created:    20/4/2000
03210     Purpose:    Call this function to render all the blobs on screen from this operation
03211 ********************************************************************************************/
03212 
03213 void OpNodePathEditBlob::RenderPathEditBlobs( Path* pEditPath, /*DocRect Rect,*/ Spread *pSpread )
03214 {
03215     // First we have to build a path to render
03216     Path RenderPath;
03217     if ( RenderPath.Initialise(24, 12) == FALSE )
03218     {
03219         // We did not get the memory, so don't draw anything
03220         return;
03221     }
03222 
03223     DocRect Rect = pEditPath->GetBoundingRect();
03224 
03225     BOOL IsInCurve = FALSE;
03226     INT32 NumCoords = pEditPath->GetNumCoords();
03227     for (INT32 i=0; i<NumCoords; i++)
03228     {
03229         // Dereference the pointers I need
03230         PathFlags* Flags  = pEditPath->GetFlagArray();
03231         DocCoord*  Coords = pEditPath->GetCoordArray();
03232         PathVerb*  Verbs  = pEditPath->GetVerbArray();
03233 
03234         // We only consider the end points
03235         if (Flags[i].IsEndPoint)
03236         {
03237             if (IsInCurve)
03238             {
03239                 // we are already adding to the curve, so see if we should continue adding
03240                 if (Flags[i].NeedToRender)
03241                 {
03242                     switch (Verbs[i] & (~PT_CLOSEFIGURE))
03243                     {
03244                         case PT_MOVETO:
03245                             RenderPath.InsertMoveTo(Coords[i]);
03246                             break;
03247 
03248                         case PT_LINETO:
03249                             RenderPath.InsertLineTo(Coords[i]);
03250                             break;
03251 
03252                         case PT_BEZIERTO:
03253                             RenderPath.InsertCurveTo(Coords[i-2], Coords[i-1], Coords[i]);
03254                             break;
03255 
03256                         default:
03257                             break;
03258                     }
03259                 }
03260                 else
03261                 {
03262                     IsInCurve = FALSE;
03263                 }
03264             }
03265             else
03266             {
03267                 // We are not currently adding to the render path, so start
03268                 if (Flags[i].NeedToRender)
03269                 {
03270                     switch (Verbs[i] & (~PT_CLOSEFIGURE))
03271                     {
03272                     case PT_MOVETO:
03273                         RenderPath.InsertMoveTo(Coords[i]);
03274                         break;
03275                     case PT_BEZIERTO:
03276                         RenderPath.InsertMoveTo(Coords[i-3]);
03277                         RenderPath.InsertCurveTo(Coords[i-2],Coords[i-1],Coords[i]);
03278                         break;
03279                     case PT_LINETO:
03280                         RenderPath.InsertMoveTo(Coords[i-1]);
03281                         RenderPath.InsertLineTo(Coords[i]);
03282                         break;
03283                     }
03284                     IsInCurve = TRUE;
03285                 }
03286             }
03287         }
03288     }
03289 
03290     // And if we've snapped to another curve
03291     if (SnapToLineOrCurve)
03292     {
03293         // We are snapping to another curve, so render the curve
03294         RenderPath.InsertMoveTo(SnapToCoords[0]);
03295         RenderPath.InsertCurveTo(SnapToCoords[1],SnapToCoords[2],SnapToCoords[3]);
03296     }
03297     
03298     // Make sure that there is something to draw
03299     if (RenderPath.GetNumCoords()==0)
03300         return;
03301 
03302     // And this bit actually draws the path we have just made
03303     RenderRegion* pRegion = DocView::RenderOnTop( NULL, StartSpread, ClippedEOR );
03304     while (pRegion != NULL)
03305     {
03306         pRegion->SetLineColour( COLOUR_XORNEW );    // Set the line colour 
03307         pRegion->DrawPath( &RenderPath );           // Draw an EORed version of how the path will turn out
03308         pRegion = DocView::GetNextOnTop( &Rect );   // Get the next render region
03309     }
03310 }
03311 
03312 
03313 
03314 /********************************************************************************************
03315 
03316 >   BOOL OpNodePathEditBlob::Init()
03317 
03318     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
03319     Created:    5/7/93
03320     Returns:    TRUE if all went OK, False otherwise
03321     Purpose:    Adds the operation to the list of all known operations
03322 
03323 ********************************************************************************************/
03324 
03325 BOOL OpNodePathEditBlob::Init()
03326 {
03327     return (RegisterOpDescriptor(   0, 
03328                                     _R(IDS_NODEPATH_EDIT),
03329                                     CC_RUNTIME_CLASS(OpNodePathEditBlob), 
03330                                     OPTOKEN_NODEPATH,
03331                                     OpNodePathEditBlob::GetState,
03332                                     0,                                      // help ID
03333                                     _R(IDBBL_NODEPATHOP),
03334                                     0 ) );                                  // bitmap ID */
03335 }
03336 
03337 
03338 /********************************************************************************************
03339 
03340 >   OpState OpNodePathEditBlob::GetState(String_256* Description, OpDescriptor*)
03341 
03342     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
03343     Created:    5/7/93
03344     Outputs:    Description - GetState fills this string with an approriate description
03345                 of the current state of the selector tool
03346     Returns:    The state of the operation, so that menu items (ticks and greying can be
03347                 done properly
03348     Purpose:    Find out the state of the operation at the specific time
03349 
03350 ********************************************************************************************/
03351 OpState OpNodePathEditBlob::GetState(String_256* Description, OpDescriptor*)
03352 {
03353     OpState Blobby;
03354     
03355     return Blobby;
03356 }
03357 
03358 
03359 
03360 /********************************************************************************************
03361 >   void OpNodePathEditBlob::SetStatusLineHelp()
03362 
03363     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
03364     Created:    4/1/95
03365     Purpose:    Updates the status line message to reflect the current situation
03366 ********************************************************************************************/
03367 void OpNodePathEditBlob::SetStatusLineHelp()
03368 {
03369     String_256 HelpString;
03370     if (EndSnapped)
03371         HelpString = String_256(_R(IDS_PATHDRAGCREATESHAPE));
03372     else
03373     {
03374         if (SnapToAnother)
03375             HelpString = String_256(_R(IDS_PATHDRAGJOINPATHS));
03376         else
03377             HelpString = String_256(_R(IDS_PATHDRAGFINISH));
03378     }
03379     GetApplication()->UpdateStatusBarText(&HelpString, FALSE);
03380 }
03381 
03382 
03383 
03384 
03385 
03386 
03387 
03388 /********************************************************************************************
03389 
03390 >   void OpNodePathEditControlBlob::DoStartDragEdit(NodePath* OrigPath, DocCoord Anchor,
03391                                                     Spread *pSpread, INT32 ControlIndex)
03392 
03393     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
03394     Created:    5/7/93
03395     Inputs:     OrigPath - The original Node path object
03396                 Anchor - The position of the mouse at the start of the Drag
03397                 pSpread - the spread that the drag started on
03398                 ControlIndex - The Coord number of the control point that we are dragging
03399     Purpose:    This is called when a Drag operation has been started on a control point
03400                 of a path
03401 
03402 ********************************************************************************************/
03403 void OpNodePathEditControlBlob::DoStartDragEdit(NodePath* OrigPath, DocCoord Anchor,
03404                                                 Spread *pSpread, INT32 ControlIndex)
03405 {
03406     // We had better take a note of the starting point of the drag
03407     LastMousePos = Anchor;
03408     StartMousePos = Anchor;
03409     StartSpread  = pSpread;
03410     OriginalPath = OrigPath;
03411     Index = ControlIndex;
03412     BOOL ok = TRUE;
03413 
03414     PathFlags* Flags = OrigPath->InkPath.GetFlagArray();
03415     PathVerb* Verbs = OrigPath->InkPath.GetVerbArray();
03416     DocCoord* Coords = OrigPath->InkPath.GetCoordArray();
03417 
03418     // Find the index of the end point that this control point is attached to
03419     if (Flags[Index-1].IsEndPoint)
03420         EndPointIndex = Index-1;
03421     else
03422         EndPointIndex = Index+1;
03423     ConstrainPoint = Coords[EndPointIndex];
03424     ConstrainPrevPoint = ConstrainPoint;
03425     ConstrainNextPoint = ConstrainPoint;
03426 
03427     // Find the opposite index
03428     OppositeIndex = OrigPath->InkPath.FindOppositeControlPoint(Index);
03429 
03430     // We also need to make a version of the path that we can change
03431     if (ok)
03432         ok = BuildEditPath();
03433 
03434     // Create and send a change message about this path edit
03435     if (ok)
03436         ok = EditObjChange.ObjChangeStarting(OrigPath,this,&EditPath,StartSpread,TRUE) == CC_OK;
03437 
03438     if (ok)
03439         ok = CreateCursors();
03440     if (ok)
03441         ChangeCursor(pCrossHairCursor);
03442 
03443     // Since we're editing control points, we have to clear the IsSmooth flags on this,
03444     // the adjacent point and the endpoint
03445     if (ok)
03446     {
03447         Flags = EditPath.GetFlagArray();
03448         Verbs = EditPath.GetVerbArray();
03449 
03450         Flags[ControlIndex].IsSmooth = FALSE;
03451         if (OppositeIndex >= 0)
03452             Flags[OppositeIndex].IsSmooth = FALSE;
03453         Flags[EndPointIndex].IsSmooth = FALSE;
03454     }
03455 
03456     // Render the bits of the path that are different
03457     if (ok)
03458     {
03459         DocRect EditPathBBox = EditPath.GetBoundingRect();
03460         RenderPathEditControlBlobs(EditPathBBox, pSpread);
03461         DragStarted = TRUE;
03462 
03463         // And tell the Dragging system that we need drags to happen
03464         ok = StartDrag(DRAGTYPE_AUTOSCROLL, &EditPathBBox, &LastMousePos);
03465     }
03466 
03467     if (!ok)
03468     {
03469         InformError();
03470         FailAndExecute();
03471         End();
03472     }
03473 }
03474 
03475 
03476 
03477 /********************************************************************************************
03478 
03479 >   void OpNodePathEditControlBlob::DragPointerMove( DocCoord PointerPos, 
03480                                                 ClickModifiers ClickMods, Spread *pSpread,
03481                                                 BOOL bSolidDrag)
03482 
03483     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
03484     Created:    5/7/93
03485     Inputs:     PointerPos - The current position of the mouse in Doc Coords
03486                 ClickMods - Which key modifiers are being pressed
03487                 pSpread - the spread that the cursor is over now
03488     Purpose:    This is called every time the mouse moves, during a drag.
03489     SeeAlso:    ClickModifiers
03490 
03491 ********************************************************************************************/
03492 
03493 void OpNodePathEditControlBlob::DragPointerMove( DocCoord PointerPos, ClickModifiers ClickMods, Spread *pSpread, BOOL bSolidDrag)
03494 {
03495     EndSnapped = FALSE;     // Always false in this operation
03496     
03497     // If drag has moved onto a different spread, convert the coord to be relative to the
03498     // original spread.
03499     if (pSpread != StartSpread)
03500         PointerPos = MakeRelativeToSpread(StartSpread, pSpread, PointerPos);
03501 
03502     Path TempPath;
03503 
03504     // inform the parent a change is happening
03505     if (EditObjChange.ChangeMask.EorBlobs)
03506     {
03507         ChangeCode Chge=EditObjChange.RenderCurrentBlobs(OriginalPath,this,&EditPath,StartSpread,TRUE);
03508         if (Chge!=CC_OK)
03509             return;
03510         // Create a local copy of the edit path
03511         if (!TempPath.Initialise(EditPath.GetNumCoords(), 12))
03512             return;
03513         TempPath.CopyPathDataFrom(&EditPath);
03514     }
03515 
03516     // Optionally constrain the new position
03517     ERROR3IF(ConstrainPoint == DocCoord(-1,-1),"ConstrainPoint wasn't set");
03518     if (ClickMods.Constrain)
03519         DocView::ConstrainToAngle(ConstrainPoint, &PointerPos);
03520     DocView::SnapCurrent(StartSpread, &PointerPos);
03521 
03522     // Rub out the old EORed version of the path
03523     RenderPathEditControlBlobs( EditPath.GetBoundingRect(), StartSpread );
03524 
03525     // This is the bit where we go off and re-calculate the paths position,
03526     // based on how much the mouse has moved
03527     DocCoord Offset;
03528     Offset.x = PointerPos.x - LastMousePos.x;
03529     Offset.y = PointerPos.y - LastMousePos.y;
03530     RecalculatePath( Offset );
03531 
03532     if (EditObjChange.ChangeMask.EorBlobs)
03533     {
03534         ChangeCode Chge=EditObjChange.RenderChangedBlobs(OriginalPath,this,&EditPath,StartSpread,TRUE);
03535         if (Chge!=CC_OK)
03536         {
03537             // replace the old edit path
03538             EditPath.CopyPathDataFrom(&TempPath);
03539             RenderPathEditControlBlobs( EditPath.GetBoundingRect(), StartSpread );
03540             return;
03541         }
03542     }
03543 
03544     // Update the last mouse position and draw in the new version of the path
03545     LastMousePos = PointerPos;
03546     RenderPathEditControlBlobs( EditPath.GetBoundingRect(), StartSpread );
03547 
03548     // Now we want to update the Line tool infobar if the position of control point being 
03549     // dragged is being shown.
03550     PathFlags* Flags = EditPath.GetFlagArray();
03551 //  DocCoord* Coords = EditPath.GetCoordArray();
03552     INT32  CurrentIndex;
03553 
03554     if (Flags[Index+1].IsEndPoint)
03555     {
03556         CurrentIndex = Index+1;
03557     }
03558     else
03559     {
03560         CurrentIndex = Index-1;
03561     }
03562 
03563     // We'll broadcast a message about the edit, thus hopping over the kernel/tool DLL boundary
03564     BROADCAST_TO_ALL(PathEditedMsg(&EditPath, pSpread, CurrentIndex));
03565 
03566     SetStatusLineHelp();
03567 }
03568 
03569 
03570 
03571 /********************************************************************************************
03572 >   BOOL OpNodePathEditControlBlob::BuildEditPath()
03573 
03574     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com> - later attacked by Peter
03575     Created:    17/02/94
03576     Returns:    TRUE if it managed to build the path, FALSE if it failed
03577     Purpose:    Builds a copy of the path that we can edit, without destroying the original
03578                 data.  Also sets the NeedToRenderFlags for the EOR rendering during the drag.
03579                 The parts of the path need to be rendered during the drag are the segment
03580                 that contains this control point and the segment with the opposite control
03581                 point if the rotate flag is set on the endpoint
03582     Errors:     If it runs out of memory then it will return FALSE
03583 ********************************************************************************************/
03584 BOOL OpNodePathEditControlBlob::BuildEditPath()
03585 {
03586     // Make a copy of the original path and clear its need to render flags
03587     const INT32 NumCoords = OriginalPath->InkPath.GetNumCoords();
03588     if (!EditPath.Initialise(NumCoords, 24))
03589         return FALSE;
03590     if (!EditPath.CopyPathDataFrom(&(OriginalPath->InkPath)))
03591         return FALSE;
03592     EditPath.ClearNeedToRender();
03593 
03594     PathFlags* Flags = EditPath.GetFlagArray();
03595 
03596     // Set the render flag on the endpoint after the dragged control point so that segment renders
03597     if (Flags[Index-1].IsEndPoint)
03598         Flags[Index+2].NeedToRender = TRUE;
03599     else
03600         Flags[Index+1].NeedToRender = TRUE;
03601 
03602     // Now do the same for the opposite control point, if the Rotate flag is set on the endpoint
03603     if (Flags[EndPointIndex].IsRotate && OppositeIndex>=0 && OppositeIndex<NumCoords)
03604     {   
03605         if (Flags[OppositeIndex-1].IsEndPoint)
03606             Flags[OppositeIndex+2].NeedToRender = TRUE;
03607         else
03608             Flags[OppositeIndex+1].NeedToRender = TRUE;
03609     }
03610 
03611     // Set the endpoints smooth flag to FALSE
03612     Flags[EndPointIndex].IsSmooth = FALSE;
03613 
03614     return TRUE;
03615 }
03616 
03617 
03618 
03619 /********************************************************************************************
03620 
03621 >   void OpNodePathEditControlBlob::RecalculatePath( DocCoord Offset )
03622 
03623     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
03624     Created:    9/11/93
03625     Inputs:     Offset - The Amount to move the selected blobs by before re-calcing everything
03626     Scope:      Private
03627     Purpose:    Will move the control point in question and then re-calculate the whole path
03628                 setting the redraw flag where needed.
03629 
03630 ********************************************************************************************/
03631 
03632 void OpNodePathEditControlBlob::RecalculatePath( DocCoord Offset )
03633 {
03634     // Find the coords in the path
03635     DocCoord* Coords = EditPath.GetCoordArray();
03636     PathFlags* Flags = EditPath.GetFlagArray();
03637     INT32 NumCoords = EditPath.GetNumCoords();
03638     if (Flags[EndPointIndex].IsRotate && OppositeIndex >=0 && OppositeIndex < NumCoords)
03639     {
03640         EditPath.CalcRotate(Coords[EndPointIndex], &Coords[Index], &Coords[OppositeIndex], Offset);
03641     }
03642     else
03643     {
03644         Coords[Index].x += Offset.x;
03645         Coords[Index].y += Offset.y;
03646     }
03647 }
03648 
03649 
03650 /********************************************************************************************
03651 
03652 >   virtual void OpNodePathEditControlBlob::RenderDragBlobs(DocRect Rect, Spread* pSpread, BOOL bSolidDrag)
03653 
03654     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
03655     Created:    25/8/94
03656     Purpose:    Renders the new version of the path to the window. It calls to the
03657                 base class function to render the path, then renders some blobs itself.
03658 
03659 ********************************************************************************************/
03660 
03661 void OpNodePathEditControlBlob::RenderDragBlobs(DocRect Rect, Spread *pSpread, BOOL bSolidDrag)
03662 {
03663     // Don't render anything if the last mouse position is the same as the start point
03664     if (LastMousePos == StartMousePos) return;
03665 
03666     // Now render the current edit path
03667     RenderPathEditControlBlobs(Rect,pSpread);
03668 
03669     // And report the change to our parents.
03670     if (EditObjChange.ChangeMask.EorBlobs)
03671         EditObjChange.RenderCurrentBlobs(OriginalPath,this,&EditPath,StartSpread,TRUE);
03672 }
03673 
03674 
03675 
03676 /********************************************************************************************
03677 >   void OpNodePathEditControlBlob::RenderDraggingBlobs( DocRect Rect, Spread *pSpread )
03678 
03679     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
03680     Created:    1/5/95
03681     Purpose:    Call this function to render all the blobs on screen from this operation
03682 ********************************************************************************************/
03683 void OpNodePathEditControlBlob::RenderDraggingBlobs ( DocRect Rect, Spread *pSpread )
03684 {
03685     RenderPathEditControlBlobs(Rect, pSpread);
03686 }
03687 
03688 
03689 
03690 /********************************************************************************************
03691 
03692 >   void OpNodePathEditControlBlob::RenderPathEditControlBlobs( DocRect Rect, Spread* pSpread )
03693 
03694     Author:     Mike_Kenny (Xara Group Ltd) <camelotdev@xara.com>
03695     Created:    16/01/95
03696     Inputs:     Rect    = a doc rect rectangle
03697                 pSpread = a pointer to a spread to render into
03698     Purpose:    Renders the new version of the path to the window. It calls to the
03699                 base class function to render the path, then renders some blobs itself.
03700 
03701 ********************************************************************************************/
03702 
03703 void OpNodePathEditControlBlob::RenderPathEditControlBlobs( DocRect Rect, Spread *pSpread )
03704 {
03705     if (LastMousePos == StartMousePos)
03706         return;
03707 
03708     DocCoord* Coords = EditPath.GetCoordArray();
03709     PathFlags* Flags = EditPath.GetFlagArray();
03710 //  PathVerb* Verbs = EditPath.GetVerbArray();
03711     INT32 NumCoords = EditPath.GetNumCoords();
03712 
03713 //  BOOL PrevIsEndPoint;
03714     DocCoord StartCoord = Coords[0];
03715 
03716     // And this bit actually draws the path we have just made
03717     RenderRegion* pRegion = DocView::RenderOnTop( NULL, StartSpread, ClippedEOR );
03718     while ( pRegion )
03719     {
03720         OpNodePathEditBlob::RenderPathEditBlobs(Rect, pSpread);
03721 
03722         EditPath.DrawControlBlob(pRegion, Coords[Index]);
03723         EditPath.DrawControlLine(pRegion, Coords[Index], Coords[EndPointIndex]);
03724         if (Flags[EndPointIndex].IsRotate && OppositeIndex>=0 && OppositeIndex<NumCoords && Flags[Index].IsSelected)
03725         {
03726             
03727             EditPath.DrawControlBlob(pRegion, Coords[OppositeIndex]);
03728             EditPath.DrawControlLine(pRegion, Coords[OppositeIndex], Coords[EndPointIndex]);
03729         }           
03730 
03731         // Get the Next render region
03732         pRegion = DocView::GetNextOnTop( &Rect );
03733         
03734     }
03735 }
03736 
03737 /********************************************************************************************
03738 
03739 >   BOOL OpNodePathEditControlBlob::Init()
03740 
03741     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
03742     Created:    5/7/93
03743     Returns:    TRUE if all went OK, False otherwise
03744     Purpose:    Adds the operation to the list of all known operations
03745 
03746 ********************************************************************************************/
03747 
03748 BOOL OpNodePathEditControlBlob::Init()
03749 {
03750     PORTNOTETRACE("other","OpNodePathEditControlBlob::Init - do nothing");
03751     return (RegisterOpDescriptor(
03752                                     0, 
03753                                     _R(IDS_NODEPATH_EDIT),
03754                                     CC_RUNTIME_CLASS(OpNodePathEditControlBlob), 
03755                                     OPTOKEN_NODEPATH,
03756                                     OpNodePathEditControlBlob::GetState,
03757                                     0,                                          // help ID
03758                                     _R(IDBBL_NODEPATHOP),
03759                                     0 ) );                                      // bitmap ID
03760 }
03761 
03762 
03763 /********************************************************************************************
03764 
03765 >   OpState OpNodePathEditControlBlob::GetState(String_256* Description, OpDescriptor*)
03766 
03767     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
03768     Created:    5/7/93
03769     Outputs:    Description - GetState fills this string with an approriate description
03770                 of the current state of the selector tool
03771     Returns:    The state of the operation, so that menu items (ticks and greying can be
03772                 done properly
03773     Purpose:    Find out the state of the operation at the specific time
03774 
03775 ********************************************************************************************/
03776 
03777 OpState OpNodePathEditControlBlob::GetState(String_256* Description, OpDescriptor*)
03778 {
03779     OpState Blobby;
03780     
03781     return Blobby;
03782 }
03783 
03784 
03785 
03786 /********************************************************************************************
03787 >   void OpNodePathEditControlBlob::SetStatusLineHelp()
03788 
03789     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
03790     Created:    4/1/96
03791     Purpose:    Updates the status line message to reflect the current situation
03792 ********************************************************************************************/
03793 void OpNodePathEditControlBlob::SetStatusLineHelp()
03794 {
03795     String_256 HelpString(_R(IDS_PATHDRAGFINISHCONTROL));
03796     GetApplication()->UpdateStatusBarText(&HelpString, FALSE);
03797 }
03798 
03799 
03800 /********************************************************************************************
03801 
03802 >   NodeGroup* OpNodePathEditBlob::GetGroupParentOfCurve()
03803 
03804     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
03805     Created:    26/9/99
03806     inputs :    -
03807     outputs:    
03808     returns:    pointer to the first nodeblend in the selection if successful, 
03809                 NULL otherwise
03810     Purpose:    To determine if the current selection is a blend on a curve
03811 
03812 
03813 *********************************************************************************************/
03814 
03815 NodeGroup* OpNodePathEditBlob::GetGroupParentOfCurve()
03816 {   
03817     if (OriginalPath == NULL)
03818         return NULL;
03819     Node* pParent = OriginalPath->FindParent();
03820 
03821     if (pParent != NULL)
03822     {
03823         if (pParent->IS_KIND_OF(NodeBlend))
03824         {
03825             return (NodeGroup*)pParent;
03826         }
03827         else
03828             return NULL;
03829     }
03830 
03831     return NULL;
03832 
03833 }
03834 
03835 
03836 
03837 /********************************************************************************************
03838 
03839 >   NodeGroup* OpNodePathEditBlob::GetGroupParentOfCurve(NodePath* pOrigPath)
03840 
03841     Author:     Chris_Snook (Xara Group Ltd) <camelotdev@xara.com> (based upon Diccon Yamanaka's)
03842     Created:    20/4/2000
03843     inputs :    -
03844     outputs:    
03845     returns:    pointer to the first nodeblend in the selection if successful, 
03846                 NULL otherwise
03847     Purpose:    To determine if the current selection is a blend on a curve
03848 
03849 
03850 *********************************************************************************************/
03851 
03852 NodeGroup* OpNodePathEditBlob::GetGroupParentOfCurve(NodePath* pOrigPath)
03853 {
03854     if (pOrigPath == NULL)
03855         return NULL;
03856     Node* pParent = pOrigPath->FindParent();
03857 
03858     if (pParent != NULL)
03859     {
03860         if (pParent->IS_KIND_OF(NodeBlend))
03861         {
03862             return (NodeGroup*)pParent;
03863         }
03864         else
03865             return NULL;
03866     }
03867 
03868     return NULL;
03869 
03870 }
03871 
03872 
03873 /********************************************************************************************
03874 
03875 >   AttrBrushType* OpNodePathEditBlob::GetAppliedBrush()
03876 
03877     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
03878     Created:    26/9/99
03879     inputs :    -
03880     outputs:    -
03881     returns:    pointer to the attrbrushtype that is applied to this nodepath, or NULL if there
03882                 isn't one.  Note that if the applied brush is the default 'no brush' then we also
03883                 return NULL
03884     Purpose:    To get the brush attribute that is applied to the nodepath we are editing
03885 
03886 
03887 *********************************************************************************************/
03888 
03889 AttrBrushType* OpNodePathEditBlob::GetAppliedBrush()
03890 {
03891     if (OriginalPath == NULL)
03892         return NULL;
03893 
03894     NodeAttribute* pAttr = NULL;
03895     AttrBrushType* pAttrBrush = NULL;
03896     OriginalPath->FindAppliedAttribute(CC_RUNTIME_CLASS(AttrBrushType), &pAttr);
03897     
03898     if (pAttr != NULL)
03899     {
03900         // just check that our applied brush isn't the default
03901         if (((AttrBrushType*)pAttr)->GetBrushHandle() != BrushHandle_NoBrush)
03902             pAttrBrush = (AttrBrushType*)pAttr;
03903     }
03904     return pAttrBrush;
03905 }
03906 
03907 
03908 /********************************************************************************************
03909 
03910 >   BOOL OpNodePathEditBlob::EditBrushAttribute(INT32 FirstIndex, INT32 LastIndex, AttrBrushType* pAttr)
03911 
03912     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
03913     Created:    14/6/2000
03914     inputs :    FirstIndex - first changed index of the path
03915                 LastIndex  - last changed index
03916                 pAttrBrush - the attribute to change
03917     outputs:    -
03918     returns:    TRUE if all went well
03919     Purpose:    If we use the shape editor to edit a nodepath with an applied brush attribute
03920                 and this brush attribute makes use of sampled pressure or time information then
03921                 the brush needs to resample its data.  So here we will insert a few actions to
03922                 make that happen.
03923 
03924 
03925 *********************************************************************************************/
03926 
03927 BOOL OpNodePathEditBlob::EditBrushAttribute(INT32 FirstIndex, INT32 LastIndex, AttrBrushType* pAttrBrush)
03928 {
03929     if (FirstIndex < 0 || LastIndex < 0 || LastIndex <= FirstIndex || pAttrBrush == NULL)
03930     {
03931         //ERROR3("Invalid inputs to OpNodePathEditBlob::EditBrushAttribute");
03932         return FALSE;
03933     }
03934     
03935     // currently we only have pressure implemented, and if this attribute does not use
03936     // pressure then theres nothing for us to do
03937     CDistanceSampler* pDistSamp = pAttrBrush->GetPressureCache();
03938     if (pDistSamp == NULL)
03939         return TRUE;  // not an error 
03940     
03941     /* Basically what we want to do here is resample the data that applies to the changed 
03942      section of the path, so that we have the same data applying over a different distance.
03943      The way to implement this in an undoable way is this:
03944      1) Find out the new length of the changed section of the path;
03945      2) Ask the sampler to copy out the current data between the changed points;
03946      4) Resample that data over the new length(the sampler can do this for us);
03947      4) Create an action to undoably remove the original section of data from the attribute
03948      5) Create an action to undoably insert the new resampled section;
03949     */
03950     MILLIPOINT ChangedLength = GetLengthOfPathSection(&EditPath, FirstIndex, LastIndex);
03951 //  MILLIPOINT OrigLength    = GetLengthOfPathSection(&(OriginalPath->InkPath), FirstIndex, LastIndex);
03952 
03953     // Get the distances to the changed indexes in the original path
03954     MILLIPOINT OrigStartDist = GetDistanceToPathIndex(&(OriginalPath->InkPath), FirstIndex);
03955     MILLIPOINT OrigEndDist   = GetDistanceToPathIndex(&(OriginalPath->InkPath), LastIndex);
03956 
03957     if (OrigStartDist == -1 || OrigEndDist == -1)
03958     {
03959         ERROR3("Error getting distance to index in OpNodePathEditBlob::EditBrushAttribute");
03960         return FALSE;
03961     }
03962     // we need another sampler to store the section data, needs to be allocated as we're putting
03963     // it into an action later
03964     CDistanceSampler* pSection = new CDistanceSampler;
03965     if (pSection == NULL)
03966         return FALSE;
03967 
03968     // Ask the sampler for its data section
03969     BOOL ok = pDistSamp->GetDataSection(OrigStartDist, OrigEndDist, pSection);
03970     if (!ok)
03971         return FALSE;
03972     
03973     // ask the section to resample itself
03974     if (!pSection->ReSample(ChangedLength))
03975     {
03976         delete pSection;
03977         return FALSE;
03978     }
03979     pSection->SetNumItemsFromArraySize();
03980 
03981     INT32 StartPressIndex = pDistSamp->GetInternalIndexFromDistance(OrigStartDist);
03982     INT32 EndPressIndex   = pDistSamp->GetInternalIndexFromDistance(OrigEndDist);
03983     INT32 NumIndexes = EndPressIndex - StartPressIndex;
03984     if (StartPressIndex == -1 || EndPressIndex == -1 || NumIndexes <= 0)
03985     {
03986         delete pSection;
03987         return FALSE;
03988     }
03989 
03990     // now make a removepoints action 
03991     RemovePressurePointsAction* pAction; 
03992     
03993     if (RemovePressurePointsAction::Init(this, &UndoActions, pAttrBrush, StartPressIndex, NumIndexes, NULL, &pAction) == AC_FAIL)
03994         return FALSE;
03995 
03996     // now insert our new points into the attribute
03997     AddPressurePointsAction* pAddAction;
03998 
03999     // find out how many points we're inserting
04000     size_t NumAddPoints = pSection->GetNumItems();
04001     
04002     if (AddPressurePointsAction::Init(this, &UndoActions, pAttrBrush, pSection, StartPressIndex, NumAddPoints,  &pAddAction) == AC_FAIL)
04003     {
04004         delete pSection;
04005         return FALSE;
04006     }
04007  
04008     return TRUE;
04009 }
04010 
04011 
04012 
04013 
04014 /********************************************************************************************
04015 
04016 >   MILLIPOINT OpNodePathEditBlob::GetLengthOfPathSection(Path* pPath, INT32 FirstIndex, INT32 LastIndex)
04017 
04018     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
04019     Created:    14/6/2000
04020     Inputs:     FirstIndex, LastIndex - the indexes marking the start and end of the section
04021                 of path we are interested in
04022                 pPath - the path we are interested in
04023     Returns:    The distance along the pPath between the two indexes, or -1 if something went wrong
04024     Purpose:    As above, if you want to know the distance between two points on the edit path
04025                 then this function is for you.
04026 
04027     Notes:      I can't believe no-one has written a Path function to do this.  There must be 
04028                 a better way to do this but currently I must get the two coordinates corresponding
04029                 to the indexes, and call GetDistanceToPoint for both of them and do a subtraction.
04030                 I will shift this into Path at some point but I don't want to hang around for
04031                 the rebuild right now. 
04032 
04033 ********************************************************************************************/
04034 
04035 MILLIPOINT OpNodePathEditBlob::GetLengthOfPathSection(Path* pPath, INT32 FirstIndex, INT32 LastIndex)
04036 {
04037     ERROR2IF(pPath == NULL, -1, "Path is NULL in OpNodePathEditBlob::GetLengthOfPathSection");
04038 
04039     // find out how many elements there are in EditPath
04040     INT32 NumElements = pPath->GetNumCoords();
04041 
04042     // check that our indexes are ok
04043     if (FirstIndex < 0 || LastIndex < 0 || LastIndex <= FirstIndex || FirstIndex >= NumElements
04044         || LastIndex > NumElements)
04045     {
04046         ERROR3("Invalid inputs to OpNodePathEditBlob::GetLengthOfEditSection");
04047         return -1;
04048     }
04049     
04050     // get the coordinates
04051     DocCoord* pCoords = pPath->GetCoordArray();
04052     ERROR2IF(pCoords == NULL, -1, "Failed to get coord array in OpNodePathEditBlob::GetLengthOfEditSection");
04053 
04054     DocCoord FirstCoord = pCoords[FirstIndex];
04055     DocCoord LastCoord  = pCoords[LastIndex];
04056 
04057     // Now get the distances along the path to these points
04058     MILLIPOINT FirstDistance = -1;
04059     MILLIPOINT LastDistance  = -1;
04060     
04061     BOOL ok = pPath->GetDistanceToPoint(FirstCoord, &FirstDistance);
04062     if (ok)
04063         ok = pPath->GetDistanceToPoint(LastCoord, &LastDistance);
04064 
04065     MILLIPOINT SectionLength = -1;
04066     if (ok)
04067         SectionLength = LastDistance - FirstDistance;
04068 
04069     return SectionLength;
04070 }
04071 
04072 
04073 /********************************************************************************************
04074 
04075 >   INT32 OpNodePathEditBlob::GetDistanceToPathIndex(Path* pPath, INT32 Index)
04076 
04077     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
04078     Created:    14/6/2000
04079     Inputs:     pPath - the path containing the index
04080                 Index - the index to which we wish to find the distance
04081     Returns:    The distance along pPath to Index, or -1 if theres a problem
04082     Purpose:    As above, 
04083 
04084     Notes:      Should really move this into Path at some point
04085 
04086 ********************************************************************************************/
04087 
04088 INT32 OpNodePathEditBlob::GetDistanceToPathIndex(Path* pPath, INT32 Index)
04089 {
04090     // some checks
04091     ERROR2IF(pPath == NULL, -1, "Path is NULL in OpNodePathEditBlob::GetDistanceToPathIndex");
04092     
04093     if (Index >= pPath->GetNumCoords())
04094     {
04095         ERROR3("Invalid index in OpNodePathEditBlob::GetDistanceToPathIndex");
04096         return -1;
04097     }
04098 
04099     // get the coordinates
04100     DocCoord* pCoords = pPath->GetCoordArray();
04101     ERROR2IF(pCoords == NULL, -1, "Failed to get coord array in OpNodePathEditBlob::GetLengthOfEditSection");
04102 
04103     DocCoord Coord = pCoords[Index];
04104 
04105     MILLIPOINT Distance = -1;
04106     if (!pPath->GetDistanceToPoint(Coord, &Distance))
04107         return -1;
04108 
04109     return Distance;
04110 }
04111 
04112 
04113 /********************************************************************************************
04114 
04115 >   BOOL OpNodePathAddEndpoint::Init()
04116 
04117     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
04118     Created:    17/6/94
04119     Returns:    TRUE if all went OK, False otherwise
04120     Purpose:    Adds the operation to the list of all known operations
04121 
04122 ********************************************************************************************/
04123 
04124 BOOL OpNodePathAddEndpoint::Init()
04125 {
04126     return (RegisterOpDescriptor(   0, 
04127                                     _R(IDS_NODEPATH_ADDENDPOINT),
04128                                     CC_RUNTIME_CLASS(OpNodePathAddEndpoint), 
04129                                     OPTOKEN_ADDENDPOINT,
04130                                     OpNodePathAddEndpoint::GetState,
04131                                     0,                                      // help ID
04132                                     _R(IDBBL_NODEPATHOP),
04133                                     0 ) );                                  // bitmap ID
04134 }
04135 
04136 
04137 /********************************************************************************************
04138 
04139 >   OpState OpNodePathAddEndpoint::GetState(String_256* Description, OpDescriptor*)
04140 
04141     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
04142     Created:    17/6/94
04143     Outputs:    Description - GetState fills this string with an approriate description
04144                 of the current state of the selector tool
04145     Returns:    The state of the operation, so that menu items (ticks and greying can be
04146                 done properly
04147     Purpose:    Find out the state of the operation at the specific time
04148 
04149 ********************************************************************************************/
04150 
04151 OpState OpNodePathAddEndpoint::GetState(String_256* Description, OpDescriptor*)
04152 {
04153     OpState Blobby;
04154     
04155     return Blobby;
04156 }
04157 
04158 
04159 
04160 /********************************************************************************************
04161 >   void OpNodePathAddEndpoint::SetStatusLineHelp()
04162 
04163     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
04164     Created:    4/1/95
04165     Purpose:    Updates the status line message to reflect the current situation
04166 ********************************************************************************************/
04167 void OpNodePathAddEndpoint::SetStatusLineHelp()
04168 {
04169     String_256 HelpString;
04170     if (EndSnapped)
04171         HelpString = String_256(_R(IDS_PATHDRAGCREATESHAPE));
04172     else
04173     {
04174         if (SnapToAnother)
04175             HelpString = String_256(_R(IDS_PATHDRAGJOINPATHS));
04176         else
04177             HelpString = String_256(_R(IDS_PATHDRAGADDSEGMENT));
04178     }
04179 
04180     GetApplication()->UpdateStatusBarText(&HelpString, FALSE);
04181 }
04182 
04183 
04184 
04185 /********************************************************************************************
04186 
04187 >   void OpNodePathAddEndpoint::DoStartDragEdit(NodePath* OrigPath, 
04188                                                 DocCoord Anchor, 
04189                                                 Spread *pSpread, 
04190                                                 INT32 PathIndex,
04191                                                 BOOL CurveOrLine,
04192                                                 BOOL CloseThePath = FALSE,
04193                                                 BOOL SmoothOrCusp = TRUE)
04194 
04195     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com> (via Rik)
04196     Created:    17/6/94
04197     Inputs:     OrigPath - Pointer to the path we are about to edit
04198                 Anchor - The position of the mouse at the start of the Drag
04199                 pSpread - The spread that the path is on
04200                 PathIndex is the index into the path of the element to which we're adding
04201                 CurveOrLine is TRUE if we're adding a curve segment, FALSE for a line
04202                 CloseThePath is true if this click is to close the path
04203                 SmoothOrCusp is TRUE if the new point is smooth, FALSE if it is a cusp.
04204     Purpose:    This is called when a Drag operation has been started when the user wants
04205                 to add a line or curve element to the start or end of an open subpath.
04206                 Anchor is the point we are adding (that's where the user clicked) and
04207                 PathIndex tells us the nearest endpoint in the path. The flag CloseThePath
04208                 tells the operation if it should close the path and select both the start and 
04209                 endpoints to drag around.
04210 
04211 ********************************************************************************************/
04212 
04213 void OpNodePathAddEndpoint::DoStartDragEdit(NodePath* OrigPath, 
04214                                             DocCoord Anchor, 
04215                                             Spread *pSpread,
04216                                             ClickModifiers Mods, 
04217                                             INT32 PathIndex,
04218                                             BOOL CurveOrLine,
04219                                             BOOL CloseThePath,
04220                                             BOOL SmoothOrCusp)
04221 {
04222     // Remember the index of the point to which we're adding this element
04223     InsertPosition = PathIndex;
04224     OriginalPath = OrigPath;
04225 
04226     // We had better take a note of the starting point of the drag
04227     StartMousePos = Anchor;
04228     LastMousePos = Anchor;
04229     StartSpread  = pSpread;
04230     IsPathClosing = CloseThePath;
04231     AddCurveFlag = CurveOrLine;
04232     AddSmoothFlag = SmoothOrCusp;
04233     
04234     // Now work out the position to constrain to.
04235     PathFlags*  Flags = OrigPath->InkPath.GetFlagArray();
04236     PathVerb*   Verbs = OrigPath->InkPath.GetVerbArray();
04237     DocCoord*   Coords = OrigPath->InkPath.GetCoordArray();
04238     if (Verbs[InsertPosition] == PT_MOVETO)
04239         ConstrainPoint = Coords[InsertPosition];
04240     else
04241     {
04242         INT32 Temp = InsertPosition;
04243         while (!Flags[Temp].IsEndPoint && (Temp < OrigPath->InkPath.GetNumCoords()))
04244             Temp++;
04245         ConstrainPoint = Coords[Temp];
04246     }
04247     ConstrainPrevPoint = ConstrainPoint;
04248     ConstrainNextPoint = ConstrainPoint;
04249 
04250     // Constrain the anchor and snap it to the grid
04251     ERROR3IF(ConstrainPoint == DocCoord(-1,-1),"DragConstrainPoint wasn't set");
04252     if (Mods.Constrain)
04253         DocView::ConstrainToAngle(ConstrainPoint, &LastMousePos);
04254     DocView::SnapCurrent(pSpread, &LastMousePos);
04255 
04256     BOOL ok = TRUE;
04257     
04258     // We also need to make a version of the path that we can change
04259     if (ok)
04260         ok = BuildEditPath(LastMousePos);
04261     UpdatePoint = DragPoint;
04262 
04263     // If the path is closing we don't want to start a drag as it leads to complications with snapping to ourselves
04264     if (CloseThePath)
04265     {
04266         if (ok)
04267             ok = Complete(StartMousePos);
04268     
04269         if (!ok)
04270         {
04271             InformError();
04272             FailAndExecute();
04273         }
04274 
04275         End();
04276         return;
04277     }
04278     else
04279     {
04280         // Create the drag cursors
04281         if (ok)
04282             ok = CreateCursors();
04283 
04284         // Render the bits of the path that are different
04285         if (ok)
04286         {
04287             DocRect EditPathBBox = EditPath.GetBoundingRect();
04288 //          RenderPathEditBlobs(EditPathBBox, pSpread);
04289 
04290             // And tell the Dragging system that we need drags to happen
04291             ok = StartDrag(DRAGTYPE_AUTOSCROLL, &EditPathBBox, &LastMousePos);
04292         }
04293 
04294         if (ok)
04295             ChangeCursor(pCrossHairCursor);
04296     }
04297 
04298     if (!ok)
04299     {
04300         InformError();
04301         FailAndExecute();
04302         End();
04303     }
04304 }
04305 
04306 /********************************************************************************************
04307 
04308 >   BOOL OpNodePathAddEndpoint::BuildEditPath(DocCoord NewPoint)
04309 
04310     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
04311     Created:    20/6/94
04312     Input:      NewPoint is the point that's being inserted
04313     Returns:    TRUE if it managed to build the path, FALSE if it failed
04314     Purpose:    Builds a copy of the path that we can edit, without destroying the original
04315                 data. This version has to add an extra element which we will be dragging 
04316                 around. It will look at the member variable IsPathClosing to see if the path
04317                 should be made to close.
04318     Errors:     If it runs out of memory then it will return FALSE
04319 
04320 ********************************************************************************************/
04321 
04322 BOOL OpNodePathAddEndpoint::BuildEditPath(DocCoord NewPoint)
04323 {
04324     // Find out how long the path is
04325     INT32 NumCoords = OriginalPath->InkPath.GetNumCoords();
04326 
04327     // Ask for a path with 24 free slots in it to be going on with and fail if we do not get it
04328     if (!EditPath.Initialise(NumCoords, 24))
04329         return FALSE;
04330 
04331     // Copy the path data from the original path to the editable one
04332     if (!EditPath.CopyPathDataFrom(&(OriginalPath->InkPath)))
04333         return FALSE;
04334 
04335     INT32 LastEndPoint = 0;             // The EndPoint before this one
04336     INT32 LastLastEndPoint = 0;         // The EndPoint before the last EndPoint
04337     BOOL SetNextEndPoint = FALSE;       // TRUE if we want to set the next EndPoint to render
04338     BOOL SetNextNextEndPoint = FALSE;   // TRUE if we want the one after the next one to render
04339 
04340     // Get pointers to the arrays that we are interested in
04341     PathFlags* Flags = EditPath.GetFlagArray();
04342     DocCoord* PathCoords = EditPath.GetCoordArray();
04343     PathVerb* PathVerbs = EditPath.GetVerbArray();
04344 
04345     // Clear all the selected bits in the path because we're adding a new point
04346     INT32                   i;
04347     for ( i=0; i<NumCoords; i++)
04348     {
04349         Flags[i].IsSelected = FALSE;
04350     }
04351 
04352 
04353     // Now add the new element to the correct place in the path. This we get from the
04354     // member variable InsertPosition.
04355 
04356     if (!InsertElement(NewPoint, &EditPath, InsertPosition, &NewPointIndex))
04357         return FALSE;
04358 
04359     // Read the length of the path again
04360     NumCoords = EditPath.GetNumCoords();
04361 
04362     // Get array pointers again, because the paths have moved
04363     Flags = EditPath.GetFlagArray();
04364     PathCoords = EditPath.GetCoordArray();
04365     PathVerbs = EditPath.GetVerbArray();
04366 
04367     // Now check the IsPathClosing flag to see if we should make the path closed
04368     if (IsPathClosing)
04369     {
04370         // When closing the path, we have to snap the new endpoint to the opposite 
04371         // endpoint. Thus, we need to know if this element was added to the start or end
04372         // of the path. This we can discover by looking at the previous element to
04373         // the element at NewPointIndex. If that is a MoveTo then the new point is
04374         // at the start, and we should snap the MoveTo coordinate to the coord at
04375         // the end of the path, otherwise it's at the end, and we have to snap the
04376         // coords of the final element to be the same as the coords of the initial
04377         // moveto element.
04378 
04379         if (PathVerbs[NewPointIndex-1] == PT_MOVETO)
04380         {
04381             // Snap the MoveTo coords to the coords of the endpoint
04382             for (i = NewPointIndex; i < NumCoords && PathVerbs[i] != PT_MOVETO; i++ );
04383             // i now points one element above the endpoint of the path, so decrement
04384             i--;
04385 
04386             // Copy the coords of the endpoint to the moveto
04387             PathCoords[NewPointIndex-1] = PathCoords[i];
04388 
04389             // And set the closed bit
04390             PathVerbs[i] |= PT_CLOSEFIGURE;
04391 
04392             // And select the other endpoint so it drags as well
04393             Flags[i].IsSelected = TRUE;
04394         }
04395         else
04396         {
04397             // Snap the endpoint coords to those of the moveto at the start, but first
04398             // set the CloseFigure flag in the verb.
04399             
04400             INT32 EndCoord = NewPointIndex;
04401             
04402             if ((PathVerbs[NewPointIndex] & ~PT_CLOSEFIGURE) == PT_BEZIERTO)
04403                 EndCoord = NewPointIndex + 2;
04404 
04405             PathVerbs[EndCoord] |= PT_CLOSEFIGURE;
04406 
04407             // scan back to the start of the path
04408             i = EndCoord;
04409             do
04410             {
04411                 i--;
04412             } while (PathVerbs[i] != PT_MOVETO);
04413 
04414             PathCoords[EndCoord] = PathCoords[i];
04415 
04416             // And select the moveto as well
04417             Flags[i].IsSelected = TRUE;
04418         }
04419     }
04420     
04421     EditPath.SmoothCurve();
04422 
04423     // Go though all the coords, with scary amounts of looking back and forwards
04424     for (i=0; i<NumCoords; i++)
04425     {
04426         // Make all the flags FALSE by default
04427         Flags[i].NeedToRender = FALSE;
04428 
04429         if (Flags[i].IsEndPoint)
04430         {
04431             // if the endpoint 2 elements back was selected and the last element was smooth
04432             // then we need to mark this point for rendering
04433             if (SetNextNextEndPoint)
04434             {
04435                 Flags[i].NeedToRender = TRUE;
04436                 SetNextNextEndPoint = FALSE;
04437             }
04438 
04439             // We have found an Endpoint, do we want to mark this one as renderable
04440             if (SetNextEndPoint)
04441             {
04442                 // As the last element was selected, this element needs to render
04443                 Flags[i].NeedToRender = TRUE;
04444                 SetNextEndPoint = FALSE;
04445 
04446                 // If the smooth flag is set then the next item needs to render as well
04447                 if (Flags[i].IsSmooth)
04448                     SetNextNextEndPoint = TRUE;
04449             }
04450 
04451             // If its selected, then its renderable
04452             if (Flags[i].IsSelected)
04453             {
04454                 Flags[i].NeedToRender = TRUE;
04455                 Flags[LastEndPoint].NeedToRender = TRUE;
04456 
04457                 // If the smooth flag is set then the re-draw area goes further
04458                 if (Flags[LastEndPoint].IsSmooth)
04459                     Flags[LastLastEndPoint].NeedToRender = TRUE;
04460 
04461                 // Set the flag for the next endpoint
04462                 SetNextEndPoint = TRUE;
04463             }
04464 
04465             // Make a note of the last endpoint, incase the next one is selected
04466             LastLastEndPoint = LastEndPoint;
04467             LastEndPoint = i;
04468         }
04469     }
04470 
04471     // It worked
04472     return TRUE;
04473 }
04474 
04475 /********************************************************************************************
04476 
04477 >   void OpNodePathAddEndpoint::DragFinished(DocCoord Pos, 
04478                                             ClickModifiers Mods, 
04479                                             Spread* pSpread, 
04480                                             BOOL Success,
04481                                             BOOL bSolidDrag)
04482 
04483     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
04484     Created:    22/6/94
04485     Inputs:     Pos = mouse position at end of drag
04486                 Mods = click modifiers (Adjust, etc.)
04487                 pSpread points at the spread the drag finished in
04488                 Success indicates whether the drag was aborted with Escape
04489     Outputs:    -
04490     Returns:    -
04491     Purpose:    This routine handles the end of the drag operation when adding an element
04492                 to the end of a path. It will insert an element into the nodepath, then
04493                 copy the contents of the temporary path into the nodepath
04494     Errors:     -
04495     SeeAlso:    -
04496 
04497 ********************************************************************************************/
04498 
04499 void OpNodePathAddEndpoint::DragFinished(   DocCoord Pos, 
04500                                             ClickModifiers Mods, 
04501                                             Spread* pSpread, 
04502                                             BOOL Success,
04503                                             BOOL bSolidDrag)
04504 {
04505     RenderPathEditBlobs( EditPath.GetBoundingRect(), StartSpread );
04506     EndDrag();
04507     DestroyCursors();
04508     if ( Success )
04509     {
04510         if (!Complete(Pos))
04511         {
04512             InformError();
04513             FailAndExecute();
04514         }
04515     }
04516     else
04517         FailAndExecute();
04518 
04519     End();  
04520     
04521 
04522 }
04523 
04524 
04525 
04526 /********************************************************************************************
04527 >   BOOL OpNodePathAddEndpoint::Complete(DocCoord AddElementPoint)
04528 
04529     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com> - From Jim code
04530     Created:    2/6/95
04531     Inputs:     AddElementPoint - the point at which the mouse finished
04532     Outputs:    -
04533     Returns:    TRUE/FALSE for success/failure
04534     Purpose:    This routine does all the actions, with undo, for appending the additional
04535                 endpoint onto the line, including the preparaty and concluding actions
04536     Errors:     -
04537     SeeAlso:    OpNodePathAddEndpoint::CompleteThisPath
04538 ********************************************************************************************/
04539 BOOL OpNodePathAddEndpoint::Complete(DocCoord AddElementPoint)
04540 {
04541     BeginSlowJob();
04542     BOOL ok = TRUE;
04543     
04544     // Start the sel operation, removes blobs at start and end
04545     if (ok)
04546         ok = DoStartSelOp(TRUE,TRUE);
04547 
04548     // Create and send a change message about this path edit so parents can refuse
04549     ObjChangeFlags cFlags;
04550     cFlags.TransformNode = TRUE;
04551     ObjChangeParam ObjChange(OBJCHANGE_STARTING,cFlags,OriginalPath,this);
04552     if (ok)
04553     {
04554         if (!OriginalPath->AllowOp(&ObjChange, TRUE))
04555         {
04556             FailAndExecute();
04557             return TRUE;
04558         }
04559     }
04560 
04561     // Do all the work involved in closing the path
04562     if (ok)
04563         ok = CompleteThisPath(AddElementPoint);
04564 
04565     // Inform all the parents of this node that it has been changed.
04566     if (ok)
04567     {
04568         ObjChange.Define(OBJCHANGE_FINISHED,ObjChangeFlags(),OriginalPath,this);
04569         ok = UpdateChangedNodes(&ObjChange);
04570     }
04571 
04572     return ok;
04573 }
04574 
04575 
04576 
04577 /********************************************************************************************
04578 >   BOOL OpNodePathAddEndpoint::CompleteThisPath(DocCoord AddElementPoint)
04579 
04580     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com> - From Jim code
04581     Created:    19/12/95
04582     Inputs:     -
04583     Outputs:    -
04584     Returns:    TRUE/FALSE for success/failure
04585     Purpose:    This routine does all the actions, with undo, for appending the additional
04586                 endpoint onto the line.
04587     Errors:     FailAndExecute will have been called in the event of a failure
04588     SeeAlso:    OpNodePathAddEndpoint::Complete
04589 ********************************************************************************************/
04590 BOOL OpNodePathAddEndpoint::CompleteThisPath(DocCoord AddElementPoint)
04591 {
04592     BOOL ok = TRUE;
04593 
04594     // Force a re-draw of the place where the path used to be
04595     if (ok)
04596         ok = (RecalcBoundsAction::DoRecalc(this, &UndoActions, OriginalPath) != AC_FAIL);
04597 
04598 //  // Store the paths sub-selection state
04599 //  if (ok)
04600 //      ok = (StorePathSubSelStateAction::DoRecord(this, &UndoActions, &OriginalPath->InkPath) != AC_FAIL);
04601 
04602     // Snap the endpoint that we are dragging with the other end of the sub-path
04603     if (ok && EndSnapped)
04604         SnapEndsTogether();
04605 
04606     // First thing to do is add a point into the path at the same place we added it
04607     // for the temporary path.
04608     if (ok)
04609         ok = InsertElement(AddElementPoint, &(OriginalPath->InkPath), InsertPosition, &NewPointIndex, TRUE, OriginalPath);
04610 
04611     // Copy the edited path back over the original path
04612     if (ok)
04613         ok = CopyEditedPathBack();
04614 
04615     if (IsPathClosing)
04616         EndSnapped = TRUE;
04617         
04618     // If the ends snapped, set the filled bit on the path
04619     if (ok)
04620         ok = FillPathIfEndsSnapped() && JoinWithOtherPath() ;
04621 
04622     if (ok)
04623     {
04624         // Update the bounding rectangle
04625         OriginalPath->InvalidateBoundingRect();
04626 
04627         // Announce this bounds change to the world
04628         GetApplication()->FindSelection()->UpdateBounds();
04629 
04630         // Force a redraw of the place where the path is now.
04631         ok = (RecordBoundsAction::DoRecord(this, &UndoActions, OriginalPath) != AC_FAIL);
04632     }
04633 
04634     // DY 9/99 are we editing a blend on a curve? If so we may wish to change the number of
04635     // steps in the blend
04636     NodeGroup* pParent = GetGroupParentOfCurve();
04637     
04638     if (pParent != NULL)
04639     {
04640         if (pParent->IS_KIND_OF(NodeBlend))
04641         {
04642             if (ok)
04643                 InsertChangeBlendStepsAction((NodeBlend*)pParent);               
04644         }
04645     
04646     }
04647     ObjChangeFlags cFlags(FALSE,TRUE);
04648     ObjChangeParam ObjChange(OBJCHANGE_FINISHED,cFlags,NULL,this);
04649     ok = UpdateChangedNodes(&ObjChange);
04650     
04651     return ok;
04652 }
04653 
04654 
04655 /********************************************************************************************
04656 
04657 >   BOOL OpNodePathAddEndpoint::EditBrushAttribute(INT32 FirstIndex, INT32 LastIndex, AttrBrushType* pAttr)
04658 
04659     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
04660     Created:    14/6/2000
04661     inputs :    FirstIndex - first changed index of the path
04662                 LastIndex  - the last changed index
04663                 pAttrBrush - the attribute to change
04664     outputs:    -
04665     returns:    TRUE if all went well
04666     Purpose:    If we use the shape editor to edit a nodepath with an applied brush attribute
04667                 and this brush attribute makes use of sampled pressure or time information then
04668                 the brush needs to resample its data.  So here we will insert a few actions to
04669                 make that happen.
04670     
04671     Notes:      Derived version does not do the remove points (as we are only adding here). Instead
04672                 it makes itself some new data and inserts it at the insert point.
04673 
04674 *********************************************************************************************/
04675 
04676 BOOL OpNodePathAddEndpoint::EditBrushAttribute(INT32 FirstIndex, INT32 LastIndex, AttrBrushType* pAttrBrush)
04677 {
04678     if (FirstIndex < 0 || LastIndex < 0 || LastIndex <= FirstIndex || pAttrBrush == NULL)
04679     {
04680         //ERROR3("Invalid inputs to OpNodePathEditBlob::EditBrushAttribute");
04681         return FALSE;
04682     }
04683     
04684     // currently we only have pressure implemented, and if this attribute does not use
04685     // pressure then theres nothing for us to do
04686     CDistanceSampler* pDistSamp = pAttrBrush->GetPressureCache();
04687     if (pDistSamp == NULL)
04688         return TRUE;  // not an error 
04689     
04690     // what we basically want to do here is to make a new section of data and insert ot
04691     // either at the beginning or the end
04692 
04693     MILLIPOINT SectionLength = GetLengthOfPathSection(&EditPath, FirstIndex, LastIndex);
04694     if (SectionLength == -1) // something went wrong
04695         return FALSE;
04696     // work out how many items we need
04697     INT32 NumItems = SectionLength / MIN_BRUSH_SPACING;
04698 
04699     // make a new CDistanceSampler
04700     CDistanceSampler* pNewSampler = new CDistanceSampler;
04701     if (pNewSampler == NULL)
04702         return FALSE;
04703 
04704     if (!pNewSampler->InitialiseData(NumItems))  // initialise the array 
04705     {
04706         delete pNewSampler;
04707         return FALSE;
04708     }
04709 
04710     // we are inserting either at the beginning or the end
04711     INT32 InsertIndex = 0;
04712     if (FirstIndex != 0)
04713         InsertIndex = LastIndex;
04714 
04715     // Get the distance to the insert index
04716     DocCoord* pCoord = EditPath.GetCoordArray();
04717     if (pCoord == NULL)
04718     {
04719         delete pNewSampler;
04720         return FALSE;
04721     }
04722     DocCoord InsertCoord = pCoord[InsertIndex];
04723     
04724     MILLIPOINT InsertDistance = -1;
04725     if (!EditPath.GetDistanceToPoint(InsertCoord, &InsertDistance))
04726     {
04727         delete pNewSampler;
04728         return FALSE;
04729     }
04730 
04731     // we want to get the pressure at this distance
04732     UINT32 InternalIndex = pDistSamp->GetInternalIndexFromDistance(InsertDistance);
04733     
04734     if (InternalIndex == (UINT32)-1) // we're over the end!
04735         InternalIndex = UINT32(pDistSamp->GetNumItems() - 1);
04736 
04737     pDistSamp->SetRetrievalSampleRate(1.0);
04738     CSampleItem TheItem;
04739 
04740     if (!pDistSamp->GetAt(InternalIndex, &TheItem))
04741     {
04742         ERROR3("Unable to retrieve pressure item in OpNodePathAddEndpoint::EditBrushAttribute"); 
04743         delete pNewSampler;
04744         return FALSE;
04745     }
04746 
04747     // whew, now we just have to fill up our new sampler with that item
04748     BOOL ok = pNewSampler->SetAt(0, TheItem);
04749     while (ok)
04750     {
04751         ok = pNewSampler->SetNext(TheItem);
04752         TRACEUSER( "Diccon", _T("Adding item pressure = %d\n"), TheItem.m_Pressure);
04753     }
04754 
04755     // now insert our new points into the attribute
04756     AddPressurePointsAction* pAddAction;
04757 
04758     // find out how many points we're inserting
04759     size_t NumAddPoints = pNewSampler->GetNumItems() + 1;
04760     
04761     if (AddPressurePointsAction::Init(this, &UndoActions, pAttrBrush, pNewSampler, InternalIndex, NumAddPoints,  &pAddAction) == AC_FAIL)
04762     {
04763         delete pNewSampler;
04764         return FALSE;
04765     }
04766  
04767     return TRUE;
04768 }
04769 
04770 
04771 
04772 /********************************************************************************************
04773 
04774 >   void OpNodePathEditBlob::ChangeCursor(Cursor* cursor)
04775 
04776     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
04777     Created:    19/9/94
04778     Inputs:     Pointer to the cursor to use now.
04779     Outputs:    -
04780     Returns:    -
04781     Purpose:    Changes the mouse pointer to a new shape.
04782     Errors:     -
04783     SeeAlso:    OpNodePathEditBlob::CreateCursors(), OpNodePathEditBlob::DestroyCursors()
04784 
04785 ********************************************************************************************/
04786 
04787 void OpNodePathEditBlob::ChangeCursor(Cursor* cursor)
04788 {
04789     if (cursor != MyCurrentCursor)
04790     {   // only change if this cursor is different from the current cursor
04791         if (MyCurrentCursor != NULL)
04792         {   // If one of our cursors is on the stack then get it off
04793             CursorStack::GPop(CurrentCursorID); 
04794         }
04795         MyCurrentCursor = cursor;
04796         CurrentCursorID = CursorStack::GPush(cursor);
04797     }
04798 }   
04799 
04800 
04801 /********************************************************************************************
04802 
04803 >   BOOL OpNodePathEditBlob::CreateCursors()
04804 
04805     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
04806     Created:    19/9/94
04807     Inputs:     -
04808     Outputs:    -
04809     Returns:    FALSE if the cursors wern't created, TRUE if they were.
04810     Purpose:    Creates the cursor objects for the path operations.
04811     Errors:     -
04812     SeeAlso:    OpNodePathEditBlob::DestroyCursors()
04813 
04814 ********************************************************************************************/
04815 
04816 BOOL OpNodePathEditBlob::CreateCursors()
04817 {
04818     if (pMoveCursor == NULL)
04819     {   // If already created then don't create a new set.
04820         MyCurrentCursor = NULL;
04821         pMoveCursor = new Cursor(TOOLID_BEZTOOL, _R(IDC_MOVEBEZIERCURSOR));
04822         pCloseCursor = new Cursor(TOOLID_BEZTOOL, _R(IDC_CLOSEPATHCURSOR));
04823         pCrossHairCursor = new Cursor(TOOLID_BEZTOOL, _R(IDC_CROSSHAIRCURSOR));
04824         // See if any of them failed
04825         if ((!pMoveCursor || !pMoveCursor->IsValid())
04826                 || (!pCloseCursor || !pCloseCursor->IsValid())
04827                 || (!pCrossHairCursor || !pCrossHairCursor->IsValid()))
04828         {   
04829             // They did fail, so clean up
04830             TRACE( _T("Cursors not created in OpNodePathEditBlob::CreateCursors\n"));
04831             delete pMoveCursor;
04832             delete pCloseCursor;
04833             delete pCrossHairCursor;
04834             pMoveCursor = NULL;
04835             return FALSE;           
04836         }
04837     }
04838     return TRUE;
04839 }
04840 
04841 
04842 
04843 /********************************************************************************************
04844 
04845 >   void OpNodePathEditBlob::DestroyCursors()
04846 
04847     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
04848     Created:    19/9/94
04849     Inputs:     -
04850     Outputs:    -
04851     Returns:    -
04852     Purpose:    Deletes the path operation cursors.
04853     Errors:     -
04854     SeeAlso:    OpNodePathEditBlob::CreateCursors()
04855 
04856 ********************************************************************************************/
04857 
04858 void OpNodePathEditBlob::DestroyCursors()
04859 {
04860     if (pMoveCursor != NULL)
04861     {   // If one is NULL then the rest don't exist
04862         if (MyCurrentCursor != NULL)
04863         {
04864             CursorStack::GPop(CurrentCursorID);         
04865         }
04866         delete pMoveCursor;
04867         delete pCloseCursor;
04868         delete pCrossHairCursor;
04869         pMoveCursor = NULL;
04870         MyCurrentCursor = NULL;
04871         CurrentCursorID = 0;
04872     }
04873 }
04874 
04875 
04876 
04877 /********************************************************************************************
04878 
04879 >   BOOL OpNodePathEditBlob::FillPathIfEndsSnapped()
04880 
04881     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
04882     Created:    21/7/94
04883     Inputs:     -
04884     Outputs:    -
04885     Returns:    TRUE if successful, FALSE otherwise
04886     Purpose:    Will look at the EndSnapped flag and set the IsFilled bit in the OriginalPath
04887                 Builds undo information. Returns FALSE if it couldn't build the Undo info
04888     Errors:     -
04889     SeeAlso:    -
04890 
04891 ********************************************************************************************/
04892 
04893 BOOL OpNodePathEditBlob::FillPathIfEndsSnapped()
04894 {
04895     // If the ends snapped, set the filled bit on the path
04896     if (EndSnapped && !(OriginalPath->InkPath.IsFilled))
04897     {
04898         ModifyFilledAction* pAction;
04899         if (ModifyFilledAction::Init(this, &UndoActions, TRUE, FALSE, OriginalPath, (Action**)(&pAction))== AC_FAIL)
04900             return FALSE;
04901         OriginalPath->InkPath.IsFilled = TRUE;
04902     }
04903     return TRUE;
04904 }
04905 
04906 /********************************************************************************************
04907 
04908 >   BOOL OpNodePathEditBlob::FillPathIfEndsSnapped(NodePath* pEditPath)
04909 
04910     Author:     Chris_Snook (Xara Group Ltd) <camelotdev@xara.com> (based upon Jim)
04911     Created:    17/5/00
04912     Inputs:     -
04913     Outputs:    -
04914     Returns:    TRUE if successful, FALSE otherwise
04915     Purpose:    Will look at the EndSnapped flag and set the IsFilled bit in the OriginalPath
04916                 Builds undo information. Returns FALSE if it couldn't build the Undo info
04917     Errors:     -
04918     SeeAlso:    -
04919 
04920 ********************************************************************************************/
04921 
04922 BOOL OpNodePathEditBlob::FillPathIfEndsSnapped(NodePath* pOrigPath)
04923 {
04924     // If the ends snapped, set the filled bit on the path
04925     if (EndSnapped && !(pOrigPath->InkPath.IsFilled))
04926     {
04927         ModifyFilledAction* pAction;
04928         if (ModifyFilledAction::Init(this, &UndoActions, TRUE, FALSE, pOrigPath, (Action**)(&pAction))== AC_FAIL)
04929             return FALSE;
04930         pOrigPath->InkPath.IsFilled = TRUE;
04931     }
04932     return TRUE;
04933 }
04934 
04935 /********************************************************************************************
04936 
04937 >   BOOL OpNodePathAddEndpoint::InsertElement(  DocCoord NewPoint, 
04938                                                 Path* DestPath, 
04939                                                 INT32 InsPos,
04940                                                 INT32* NewPosition,
04941                                                 BOOL RecordUndo = FALSE,
04942                                                 NodePath* UndoPath = NULL)
04943 
04944     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
04945     Created:    22/6/94
04946     Inputs:     NewPoint is the point that should be added
04947                 DestPath is the path to which the element should be added
04948                 InsPos is the index of the element which we're adding
04949                 to (the first or last element in the path).
04950                 RecordUndo is a flag telling the routine to build undo information (default FALSE)
04951                 UndoPath is the path that's being operated on, for the undo recording
04952     Outputs:    NewPosition is the index of the element
04953                 Also sets DragPoint
04954     Returns:    BOOL indicating success or failure
04955     Purpose:    Inserts a new element (either line or curve, depending in internal flags)
04956                 into the given path. It will set the selected bit in the element.
04957 
04958                 NOTE: This routine will move the path data around so any pointers to the arrays
04959                 in the path should be re-read after any call to this routine.
04960 
04961     Errors:     If no memory available to insert the point
04962     SeeAlso:    -
04963 
04964 ********************************************************************************************/
04965 
04966 BOOL OpNodePathAddEndpoint::InsertElement(  DocCoord NewPoint, 
04967                                             Path* DestPath, 
04968                                             INT32 InsPos,
04969                                             INT32* NewPosition,
04970                                             BOOL RecordUndo,
04971                                             NodePath* UndoPath)
04972 {
04973 
04974     Action* UnAction;       // pointer to action that might be created
04975     ActionCode Act;         // Action code that might be used
04976 
04977     // There are two possibilities here - that we're at the start of a path, or at the
04978     // end. The end case is easier - we just add an element after the end element. For a
04979     // point added to the start, we have to change the MoveToCoordinates to be the point
04980     // clicked, and add an element which goes from there to the original MoveTo position.
04981     // If we add a CurveTo element, we have to smooth the coordinates before the drag starts.
04982 
04983     DestPath->SetPathPosition(InsPos);
04984 
04985     if (DestPath->GetVerb() == PT_MOVETO)
04986     {
04987         DocCoord tempcoord = DestPath->GetEndPoint();       // Get coord of moveto
04988         DestPath->FindNext();                               // Skip to after the MoveTo
04989         *NewPosition = DestPath->GetPathPosition();         // remember index of point we're inserting
04990 
04991         // If we're undoing, create an action for this insert
04992         if (RecordUndo)
04993         {
04994             if (AddCurveFlag)
04995                 Act = RemovePathElementAction::Init(this, &UndoActions, 3,*NewPosition, (Action**)(&UnAction));
04996             else
04997                 Act = RemovePathElementAction::Init(this, &UndoActions, 1,*NewPosition, (Action**)(&UnAction));
04998             if (Act == AC_FAIL)
04999                 return FALSE;
05000 
05001             ((RemovePathElementAction*)UnAction)->RecordPath(UndoPath);
05002         }
05003 
05004         PathFlags tempflags;
05005 
05006         if (AddSmoothFlag)
05007         {
05008             tempflags.IsSmooth = TRUE;
05009             tempflags.IsRotate = TRUE;
05010         }
05011         else
05012         {
05013             tempflags.IsSmooth = TRUE;
05014             tempflags.IsRotate = FALSE;
05015         }
05016         
05017         if (AddCurveFlag)
05018         {
05019             // We insert a curve element with all three coords the same, since 
05020             // they will get smoothed
05021             if (!DestPath->InsertCurveTo(tempcoord,tempcoord,tempcoord,&tempflags))
05022                 return FALSE;
05023         }
05024         else
05025         {
05026             if (!DestPath->InsertLineTo(tempcoord, &tempflags))
05027                 return FALSE;
05028         }       
05029 
05030         // Read the pointers to the arrays.
05031         PathFlags* Flags = DestPath->GetFlagArray();
05032         DocCoord* PathCoords = DestPath->GetCoordArray();
05033         PathVerb* PathVerbs = DestPath->GetVerbArray();
05034 
05035         // Set the selected bit on the point we have to move (the MoveTo)
05036         if (RecordUndo)
05037         {
05038             DoChangeSelection(UndoPath, InsPos, TRUE);
05039             if (AddCurveFlag)
05040                 DoChangeSelection(UndoPath, InsPos+1, TRUE);
05041         }
05042         else
05043         {
05044             Flags[InsPos].IsSelected = TRUE;    
05045             if (AddCurveFlag)
05046                 Flags[InsPos+1].IsSelected = TRUE;
05047         }
05048 
05049         // Now to adjust the MoveTo coordinate, recording undo info if necessary
05050 
05051         if (RecordUndo)
05052         {
05053             Act = ModifyElementAction::Init(this, 
05054                                             &UndoActions,
05055                                             PathVerbs[InsPos],
05056                                             Flags[InsPos],
05057                                             PathCoords[InsPos],
05058                                             InsPos,
05059                                             UndoPath,
05060                                             (Action**)&UnAction);
05061         }
05062 
05063         PathCoords[InsPos] = NewPoint;
05064 
05065         // Make it smooth/rotate
05066         Flags[InsPos].IsSmooth = TRUE;
05067         Flags[InsPos].IsRotate = TRUE;
05068 
05069         DragPoint = InsPos;
05070 
05071         // Now smooth the path
05072 //      DestPath->SmoothCurve();
05073     }
05074     else
05075     {
05076         // Adding to the end of a path is easier - insert an element after this one
05077         DestPath->FindNext();
05078         *NewPosition = DestPath->GetPathPosition();
05079 
05080         // If we're undoing, create an action for this insert
05081         if (RecordUndo)
05082         {
05083             if (AddCurveFlag)
05084                 Act = RemovePathElementAction::Init(this, &UndoActions, 3,*NewPosition, (Action**)(&UnAction));
05085             else
05086                 Act = RemovePathElementAction::Init(this, &UndoActions, 1,*NewPosition, (Action**)(&UnAction));
05087             if (Act == AC_FAIL)
05088                 return FALSE;
05089 
05090             ((RemovePathElementAction*)UnAction)->RecordPath(UndoPath);
05091         }
05092 
05093         PathFlags tempflags;
05094         if (AddSmoothFlag)
05095         {
05096             tempflags.IsSmooth = TRUE;
05097             tempflags.IsRotate = TRUE;
05098         }
05099         else
05100         {
05101             tempflags.IsSmooth = TRUE;
05102             tempflags.IsRotate = FALSE;
05103         }
05104         if (AddCurveFlag)
05105         {
05106             if (!DestPath->InsertCurveTo(NewPoint,NewPoint,NewPoint,&tempflags))
05107                 return FALSE;
05108         }
05109         else
05110         {
05111             if (!DestPath->InsertLineTo(NewPoint,&tempflags))
05112                 return FALSE;
05113         }
05114 
05115         // Re-read the pointers to the arrays, in case they've changed
05116         PathFlags* Flags = DestPath->GetFlagArray();
05117 //      DocCoord* PathCoords = DestPath->GetCoordArray();
05118 //      PathVerb* PathVerbs = DestPath->GetVerbArray();
05119 
05120         if (AddCurveFlag)
05121         {
05122             if (RecordUndo)
05123             {
05124                 DoChangeSelection(UndoPath,(*NewPosition)+2, TRUE);     // Select the endpoint
05125                 DoChangeSelection(UndoPath, (*NewPosition)+1, TRUE);    // Select the control point as well
05126             }
05127             else
05128             {
05129                 Flags[(*NewPosition)+2].IsSelected = TRUE;
05130                 Flags[(*NewPosition)+1].IsSelected = TRUE;
05131             }
05132             DragPoint = (*NewPosition)+2;
05133     //      DestPath->SmoothCurve();
05134         }
05135         else
05136         {
05137             if (RecordUndo)
05138                 DoChangeSelection(UndoPath,(*NewPosition), TRUE);
05139             else
05140                 Flags[(*NewPosition)].IsSelected = TRUE;
05141             DragPoint = *NewPosition;
05142         }
05143     }
05144 
05145     return TRUE;
05146 }
05147 
05148 
05149 /********************************************************************************************
05150 >   OpToggleSmooth::OpToggleSmooth()
05151 
05152     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
05153     Created:    30/6/94
05154     Purpose:    Constructor for toggle smooth/cusp operation
05155 ********************************************************************************************/
05156 OpToggleSmooth::OpToggleSmooth()
05157 {
05158     // Would initialise member variables here if there were any!
05159 }
05160 
05161 
05162 
05163 /********************************************************************************************
05164 >   BOOL OpToggleSmooth::Init()
05165 
05166     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
05167     Created:    30/6/94
05168     Returns:    TRUE if all went OK, FALSE otherwise
05169     Purpose:    Adds the operation to the list of all known operations
05170 ********************************************************************************************/
05171 BOOL OpToggleSmooth::Init()
05172 {
05173     return (RegisterOpDescriptor(   0, 
05174                                     _R(IDS_NODEPATH_EDIT),
05175                                     CC_RUNTIME_CLASS(OpToggleSmooth), 
05176                                     OPTOKEN_NODEPATH,
05177                                     OpToggleSmooth::GetState,
05178                                     0,                                  // help ID
05179                                     _R(IDBBL_NODEPATHOP),
05180                                     0 ) );                              // bitmap ID
05181 }
05182 
05183 
05184 
05185 /********************************************************************************************
05186 >   OpState OpToggleSmooth::GetState(String_256* Description, OpDescriptor*)
05187 
05188     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
05189     Created:    30/6/94
05190     Outputs:    -
05191     Returns:    The state of the operation, so that menu items (ticks and greying can be
05192                 done properly
05193     Purpose:    Find out the state of the operation at the specific time
05194 ********************************************************************************************/
05195 OpState OpToggleSmooth::GetState(String_256* Description, OpDescriptor*)
05196 {
05197     OpState Blobby;     // Ungreyed, unticked
05198     return Blobby;
05199 }
05200 
05201 
05202 
05203 /********************************************************************************************
05204 >   void OpToggleSmooth::DoTogglePoint(NodePath* ThisNode, INT32 Index, Spread* pSpread, BOOL AllSmooth, BOOL DontMoveOnCusp)
05205 
05206     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>/Mike/Peter
05207     Created:    4/7/94
05208     Inputs:     ThisNode is the nodepath containing the point we want to toggle, if it
05209                 is NULL then all selected paths should be scanned.
05210                 Index is the index into the path of the point clicked - if part of a
05211                 curve, it points at the endpoint, not the first control point.
05212                 If Index is -1 then all selected endpoints on the path should be processed
05213                 pSpread points at the spread containing the path
05214                 AllSmooth is only needed if Index is -1.  If it is then AllSmooth is TRUE
05215                 if all the points should be made smooth, otherwise they are made cusps.
05216                 DontMoveOnCusp is TRUE if the control points shouldn't be moved when making a cusp
05217     Outputs:    -
05218     Returns:    -
05219     Purpose:    This operation will toggle the smoothness or otherwise of a point in a path.
05220                 If the point is smooth, this operation will reset the smoothing bits on any 
05221                 adjacent control points. It will also change their positions, making the point
05222                 visibly a corner. If the point is not smooth, this operation will set the
05223                 smoothing flags on the control points and perform a smoothing operation on the
05224                 points.  If the path consists of a single curve element and the endpoint
05225                 matches the start point, nothing is smoothed since we can't make any guesses
05226                 as to where the control points go.
05227     Errors:     -
05228     SeeAlso:    -
05229 ********************************************************************************************/
05230 void OpToggleSmooth::DoTogglePoint(NodePath* ThisNode, INT32 Index, Spread* pSpread, BOOL AllSmooth, BOOL DontMoveOnCusp)
05231 {
05232     if (!DoStartSelOp(TRUE,TRUE))
05233     {
05234         FailAndExecute();
05235         End();
05236         return;
05237     }
05238 
05239     // We need to make sure the node or nodes that will be effected will allow this op to happen
05240     ObjChangeFlags cFlags;
05241     cFlags.TransformNode = TRUE;
05242     ObjChangeParam ObjChange(OBJCHANGE_STARTING,cFlags,NULL,this);
05243     BOOL Failed = FALSE;
05244 
05245     if (ThisNode==NULL)
05246     {
05247         // If there's no node pointer then scan the selection!
05248         SelRange*   Selected = GetApplication()->FindSelection();
05249         Node*       pNode = Selected->FindFirst();
05250         while (pNode!=NULL && !Failed)
05251         {
05252             NodePath* pSelected = pNode->IsAnEditablePath();
05253             if ( (pSelected != NULL) && (pSelected->AllowOp(&ObjChange)) )
05254             {
05255                 if (!TogglePathPoints(pSelected, Index, AllSmooth, DontMoveOnCusp))
05256                 {
05257                     InformError();
05258                     Failed = TRUE;
05259                 }
05260             }
05261             pNode = Selected->FindNext(pNode);
05262         }
05263     }
05264     else
05265     {
05266         if (ThisNode->AllowOp(&ObjChange))
05267         {
05268             if (!TogglePathPoints(ThisNode, Index, AllSmooth, DontMoveOnCusp))
05269             {
05270                 InformError();
05271                 Failed = TRUE;
05272             }
05273         }
05274     }
05275 
05276     // Update all the parents of the effected paths.
05277     if (!Failed)
05278     {
05279         ObjChange.Define(OBJCHANGE_FINISHED,cFlags,NULL,this);
05280         Failed = !UpdateChangedNodes(&ObjChange);
05281     }
05282 
05283     if (Failed)
05284         FailAndExecute();
05285 
05286     End();
05287 }
05288 
05289 
05290 
05291 /********************************************************************************************
05292 >   BOOL OpToggleSmooth::TogglePathPoints(NodePath* CurrentNode, INT32 Index, BOOL AllSmooth = FALSE)
05293 
05294     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
05295     Created:    4/7/94
05296     Inputs:     ThisNode    is the nodepath containing the point we want to toggle.
05297                 Index       is the index into the path of the point clicked - if part of a
05298                             curve, it points at the endpoint, not the first control point.
05299                             If Index is -1 then all selected endpoints on the path should be
05300                             processed
05301                 AllSmooth   is only needed if Index is -1.  If it is then AllSmooth is TRUE
05302                             if all the points should be made smooth, otherwise they are made cusps.
05303     Outputs:    -
05304     Returns:    FALSE if an error occured
05305     Purpose:    This operation will toggle the smoothness or otherwise of a point in a path.
05306                 If the point is smooth, this operation will reset the smoothing bits on any 
05307                 adjacent control points. It will also change their positions, making the point
05308                 visibly a corner. If the point is not smooth, this operation will set the
05309                 smoothing flags on the control points and perform a smoothing operation on the
05310                 points.  If the path consists of a single curve element and the endpoint
05311                 matches the start point, nothing is smoothed since we can't make any guesses
05312                 as to where the control points go.
05313     Errors:     -
05314     SeeAlso:    -
05315 ********************************************************************************************/
05316 BOOL OpToggleSmooth::TogglePathPoints(NodePath* CurrentNode, INT32 Index, BOOL AllSmooth, BOOL DontMoveOnCusp)
05317 {
05318     ERROR2IF(CurrentNode==NULL, FALSE, "Path pointer was NULL");
05319 
05320     PathFlags* Flags = NULL;
05321     PathVerb* Verbs = NULL;
05322     DocCoord* Coords = NULL;
05323     CurrentNode->InkPath.GetPathArrays(&Verbs, &Coords, &Flags);
05324     ERROR2IF((Verbs==NULL) || (Coords==NULL) || (Flags==NULL), FALSE, "Path array pointer was NULL (panic!)");
05325 
05326     INT32 NumCoords = CurrentNode->InkPath.GetNumCoords();
05327     INT32 LowIndex  = (Index == -1) ? 0 : Index;
05328     INT32 HighIndex = (Index == -1) ? NumCoords-1 : Index;
05329 
05330     // Check to see if this is a single-segment closed curve, in which case we'll never
05331     // be able to smooth it. We can only be on the moveto...
05332     if ( (Verbs[0] == PT_MOVETO) && (Verbs[1] == PT_BEZIERTO) && (Verbs[3] & PT_CLOSEFIGURE) &&
05333           (Coords[0] == Coords[3]) && (!Flags[0].IsRotate) )
05334         return TRUE;
05335 
05336     BOOL Success = TRUE;
05337     BOOL ChangedThisPath = FALSE;
05338     for (INT32 CurrentIndex = LowIndex; (CurrentIndex <= HighIndex) && Success; CurrentIndex++)
05339     {
05340         BOOL Test = FALSE;      // TRUE if this endpoint should be toggled
05341         CurrentNode->InkPath.GetPathArrays(&Verbs, &Coords, &Flags);
05342 
05343         if (HighIndex != LowIndex)
05344         {
05345             if (Flags[CurrentIndex].IsEndPoint && Flags[CurrentIndex].IsSelected)
05346             {   // Force selected point to be either cusp or smooth
05347                 if (AllSmooth)
05348                     Test = !(Flags[CurrentIndex].IsRotate);
05349                 else
05350                     Test = Flags[CurrentIndex].IsRotate;
05351             }
05352             else
05353                 Test = FALSE;
05354         }
05355         else
05356         {   // Toggle single point
05357             Test = Flags[CurrentIndex].IsEndPoint;
05358         }
05359         
05360         // Toggle the smooth state of this endpoint
05361         if (Test)
05362         {
05363             ERROR3IF(!Flags[CurrentIndex].IsEndPoint, "Attempting to toggle a control point");
05364 
05365             // Force a re-draw of the place where the path used to be
05366             if (Success && !ChangedThisPath)
05367             {
05368                 Success = (RecalcBoundsAction::DoRecalc(this, &UndoActions, CurrentNode) != AC_FAIL);
05369 //              // Store the paths sub-selection state
05370 //              if (Success)
05371 //                  Success = (StorePathSubSelStateAction::DoRecord(this, &UndoActions, &CurrentNode->InkPath) != AC_FAIL);
05372             
05373                 ChangedThisPath = TRUE;
05374             }
05375 
05376             // Now let's adjust the smoothness of this point.
05377             if (Success)
05378             {
05379                 CurrentNode->InkPath.GetPathArrays(&Verbs, &Coords, &Flags);
05380                 if (Flags[CurrentIndex].IsRotate)
05381                     Success = MakePointCusp(CurrentNode, CurrentIndex, DontMoveOnCusp);
05382                 else
05383                     Success = MakePointSmooth(CurrentNode, CurrentIndex);
05384             }
05385         }
05386     }
05387 
05388     // Now create a RecordBoundsAction so that the action list has bounds recalculation
05389     // at both ends which ensures undo and redo both work properly
05390     if (Success && ChangedThisPath)
05391     {
05392         CurrentNode->InvalidateBoundingRect();
05393         GetApplication()->FindSelection()->UpdateBounds();
05394         Success = (RecordBoundsAction::DoRecord(this, &UndoActions, CurrentNode) != AC_FAIL);
05395     }
05396 
05397     return Success;
05398 }
05399 
05400 
05401 
05402 /********************************************************************************************
05403 >   BOOL OpToggleSmooth::MakePointCusp(NodePath* pPath, INT32 Index, BOOL DontMoveOnCusp)
05404 
05405     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com> (made a function from Jim code)
05406     Created:    10/11/95
05407     Inputs:     pPath points to the path to modify
05408                 Index - the index of the path to modify
05409                 DontMoveOnCusp - TRUE if the control points should not move, FALSE to position
05410                                  them 1/3 between prev/next endpoint
05411     Outputs:    -
05412     Returns:    TRUE/FALSE for success/failure
05413     Purpose:    Makes the specified endpoint in the path a cusp join.
05414     Errors:     Parameter checks.  Creates actions which may fail
05415     SeeAlso:    OpToggleSmooth::MakePointSmooth
05416 ********************************************************************************************/
05417 BOOL OpToggleSmooth::MakePointCusp(NodePath* pPath, INT32 Index, BOOL DontMoveOnCusp)
05418 {
05419     // Parameter checks
05420     ERROR2IF(pPath == NULL, FALSE, "Path pointer was NULL");
05421     ERROR2IF(Index < 0, FALSE, "Path index was negative");
05422     ERROR2IF(Index >= pPath->InkPath.GetNumCoords(), FALSE, "Path index off end of path");
05423     
05424     // Store the current state of the endpoint for undo then make it cusp
05425     ModifyElementAction* UnAction = NULL;
05426     PathFlags* Flags = NULL;
05427     PathVerb* Verbs = NULL;
05428     DocCoord* Coords = NULL;
05429     pPath->InkPath.GetPathArrays(&Verbs, &Coords, &Flags);
05430     ERROR2IF((Verbs==NULL) || (Coords==NULL) || (Flags==NULL), FALSE, "Path array pointer was NULL (panic!)");
05431     ActionCode Act = ModifyElementAction::Init(this, &UndoActions,  Verbs[Index], Flags[Index],
05432                                                                 Coords[Index], Index, pPath, (Action**)&UnAction);
05433     if (Act == AC_FAIL)
05434         return FALSE;
05435     Flags[Index].IsSmooth = FALSE;          
05436     Flags[Index].IsRotate = FALSE;
05437 
05438     // Modify the previous control point (if there is one)
05439     INT32 PrevControl = pPath->InkPath.FindPrevControlPoint(Index);
05440     if (PrevControl != -1)
05441     {
05442         Act = ModifyElementAction::Init(this, &UndoActions, Verbs[PrevControl], Flags[PrevControl],
05443                                                     Coords[PrevControl], PrevControl, pPath, (Action**)&UnAction);
05444         if (Act == AC_FAIL)
05445             return FALSE;
05446 
05447         // Move the control point if required and it is possible
05448         if (!DontMoveOnCusp && (!((Verbs[Index] == PT_MOVETO) && (Verbs[Index+1] == PT_BEZIERTO) &&
05449             (Verbs[Index+3] & PT_CLOSEFIGURE) && (Coords[Index] == Coords[Index+3]))))
05450         {
05451             Coords[PrevControl] = DocCoord::OneThird(Coords[PrevControl+1],Coords[PrevControl-2]);
05452         }
05453         Flags[PrevControl].IsSmooth = FALSE;
05454         Flags[PrevControl].IsRotate = FALSE;
05455 
05456         // Cope with the possibility that we have made the start/end of a closed
05457         // path a cusp - we can tell this if the endpoint associated with the 
05458         // previous control point is not the same as Index
05459         if (Index != PrevControl+1)
05460         {
05461             Act = ModifyElementAction::Init(this, &UndoActions, Verbs[PrevControl+1], Flags[PrevControl+1],
05462                                                 Coords[PrevControl+1], PrevControl+1, pPath, (Action**)&UnAction);
05463             if (Act == AC_FAIL)
05464                 return FALSE;
05465             Flags[PrevControl+1].IsSmooth = FALSE;
05466             Flags[PrevControl+1].IsRotate = FALSE;
05467         }
05468     }
05469 
05470     // Modify the next control point (if there is one)
05471     INT32 NextControl = pPath->InkPath.FindNextControlPoint(Index);
05472     if (NextControl != -1)
05473     {
05474         Act = ModifyElementAction::Init(this, &UndoActions, Verbs[NextControl], Flags[NextControl], 
05475                                                     Coords[NextControl], NextControl, pPath, (Action**)&UnAction);
05476         if (Act == AC_FAIL)
05477             return FALSE;
05478 
05479         // Move the control point if required and it is possible
05480         // If this is a single-segment closed curve, in which case we'll never
05481         // be able to calculate sensible points. Just clear the rotate bits
05482         if (!DontMoveOnCusp && !((Verbs[Index] == PT_MOVETO) && (Verbs[Index+1] == PT_BEZIERTO) &&
05483                                     (Verbs[Index+3] & PT_CLOSEFIGURE) && (Coords[Index] == Coords[Index+3])))
05484         {
05485             Coords[NextControl] = DocCoord::OneThird(Coords[NextControl-1], Coords[NextControl+2]);
05486         }
05487         Flags[NextControl].IsSmooth = FALSE;
05488         Flags[NextControl].IsRotate = FALSE;
05489     }
05490 
05491     return TRUE;
05492 }
05493 
05494 
05495 
05496 /********************************************************************************************
05497 >   BOOL OpToggleSmooth::MakePointCusp(NodePath* pPath, INT32 Index)
05498 
05499     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com> (made a function from Jim code)
05500     Created:    10/11/95
05501     Inputs:     pPath points to the path to modify
05502                 Index - the index of the path to modify
05503     Outputs:    -
05504     Returns:    TRUE/FALSE for success/failure
05505     Purpose:    Makes the specified endpoint in the path a smooth join.  Has to move the 
05506                 control points around the endpoint.
05507     Errors:     Parameter checks.  Creates actions which may fail
05508     SeeAlso:    OpToggleSmooth::MakePointCusp
05509 ********************************************************************************************/
05510 BOOL OpToggleSmooth::MakePointSmooth(NodePath* pPath, INT32 Index)
05511 {
05512     // Parameter checks
05513     ERROR2IF(pPath == NULL, FALSE, "Path pointer was NULL");
05514     ERROR2IF(Index < 0, FALSE, "Path index was negative");
05515     ERROR2IF(Index >= pPath->InkPath.GetNumCoords(), FALSE, "Path index off end of path");
05516     
05517     // Store the current state of the endpoint for undo then make it smooth
05518     ModifyElementAction* UnAction = NULL;
05519     PathFlags* Flags = NULL;
05520     PathVerb* Verbs = NULL;
05521     DocCoord* Coords = NULL;
05522     pPath->InkPath.GetPathArrays(&Verbs, &Coords, &Flags);
05523     ERROR2IF((Verbs==NULL) || (Coords==NULL) || (Flags==NULL), FALSE, "Path array pointer was NULL (panic!)");
05524     ActionCode Act = ModifyElementAction::Init(this, &UndoActions, Verbs[Index], Flags[Index],
05525                                                                 Coords[Index], Index, pPath, (Action**)&UnAction);
05526     if (Act == AC_FAIL)
05527         return FALSE;
05528     Flags[Index].IsSmooth = TRUE;           
05529     Flags[Index].IsRotate = TRUE;
05530 
05531     // Modify the previous control point (if there is one)
05532     INT32 PrevControl = pPath->InkPath.FindPrevControlPoint(Index);
05533     if (PrevControl != -1)
05534     {
05535         Act = ModifyElementAction::Init(this, &UndoActions, Verbs[PrevControl], Flags[PrevControl],
05536                                                     Coords[PrevControl], PrevControl, pPath, (Action**)&UnAction);
05537         if (Act == AC_FAIL)
05538             return FALSE;
05539         Flags[PrevControl].IsSmooth = TRUE;
05540         Flags[PrevControl].IsRotate = TRUE;
05541         Coords[PrevControl] = pPath->InkPath.SmoothControlPoint(PrevControl);
05542 
05543         // if PrevControl+1 != Index we must be smoothing the joined ends
05544         // of a closed subpath, so we'll have to set the smoothing bits on this point too
05545         if (Index != PrevControl+1)
05546         {
05547             Act = ModifyElementAction::Init(this, &UndoActions, Verbs[PrevControl+1], Flags[PrevControl+1],
05548                                                 Coords[PrevControl+1], PrevControl+1, pPath, (Action**)&UnAction);
05549             if (Act == AC_FAIL)
05550                 return FALSE;
05551             Flags[PrevControl+1].IsSmooth = TRUE;
05552             Flags[PrevControl+1].IsRotate = TRUE;
05553         }
05554     }
05555 
05556     // Modify the next control point (if there is one)
05557     INT32 NextControl = pPath->InkPath.FindNextControlPoint(Index);
05558     if (NextControl != -1)
05559     {
05560         Act = ModifyElementAction::Init(this, &UndoActions, Verbs[NextControl], Flags[NextControl],
05561                                                     Coords[NextControl], NextControl, pPath, (Action**)&UnAction);
05562         if (Act == AC_FAIL)
05563             return FALSE;
05564         Flags[NextControl].IsSmooth = TRUE;
05565         Flags[NextControl].IsRotate = TRUE;
05566         Coords[NextControl] = pPath->InkPath.SmoothControlPoint(NextControl);
05567     }
05568 
05569     return TRUE;
05570 }
05571 
05572 
05573 
05574 /********************************************************************************************
05575 
05576 >   BOOL OpNewPath::Init()
05577 
05578     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
05579     Created:    17/6/94
05580     Returns:    TRUE if all went OK, False otherwise
05581     Purpose:    Adds the operation to the list of all known operations
05582 
05583 ********************************************************************************************/
05584 
05585 BOOL OpNewPath::Init()
05586 {
05587     return (RegisterOpDescriptor(   0, 
05588                                     _R(IDS_NODEPATH_NEWPATH),
05589                                     CC_RUNTIME_CLASS(OpNewPath), 
05590                                     OPTOKEN_NODEPATH,
05591                                     OpNewPath::GetState,
05592                                     0,                              // help ID
05593                                     _R(IDBBL_NODEPATHOP),
05594                                     0 ) );                          // bitmap ID
05595 }
05596 
05597 
05598 /********************************************************************************************
05599 
05600 >   OpState OpNewPath::GetState(String_256* Description, OpDescriptor*)
05601 
05602     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
05603     Created:    17/6/94
05604     Outputs:    Description - GetState fills this string with an approriate description
05605                 of the current state of the selector tool
05606     Returns:    The state of the operation, so that menu items (ticks and greying can be
05607                 done properly
05608     Purpose:    Find out the state of the operation at the specific time
05609 
05610 ********************************************************************************************/
05611 
05612 OpState OpNewPath::GetState(String_256* Description, OpDescriptor*)
05613 {
05614     OpState Blobby;
05615     
05616     return Blobby;
05617 }
05618 
05619 
05620 
05621 /********************************************************************************************
05622 
05623 >   void OpNewPath::DoStartDragEdit(DocCoord FirstPoint,
05624                                     DocCoord LastPoint, 
05625                                     Spread *pSpread,
05626                                     ClickModifiers Mods, 
05627                                     BOOL MakeCurve,
05628                                     BOOL MakeSmooth)
05629 
05630     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com> (via Rik)
05631     Created:    18/7/94
05632     Inputs:     FirstPoint is the initial point of the path
05633                 LastPoint is the final point in the path
05634                 pSpread points at the spread the path is on
05635                 Mods - the click modifiers
05636                 MakeCurve is TRUE if the new path should be a curve, FALSE for a line
05637                 MakeSmooth is TRUE if the nee path should have rotate flags set, FALSE if not
05638     Purpose:    This is called when a Drag operation has been started when the user wants
05639                 to add a new path. FirstPoint comes from the floating endpoint, and LastPoint
05640                 comes from the new click position.
05641 
05642 ********************************************************************************************/
05643 
05644 void OpNewPath::DoStartDragEdit(DocCoord FirstPoint, DocCoord LastPoint, Spread* pSpread, 
05645                                         ClickModifiers Mods, BOOL MakeCurve, BOOL MakeSmooth)
05646 {
05647     ConstrainPoint = FirstPoint;
05648     ConstrainPrevPoint = FirstPoint;
05649     ConstrainNextPoint = FirstPoint;
05650     // Constrain the anchor and snap it to the grid
05651     ERROR3IF(ConstrainPoint == DocCoord(-1,-1),"DragConstrainPoint wasn't set");
05652     if (Mods.Constrain)
05653         DocView::ConstrainToAngle(ConstrainPoint, &LastPoint);
05654     DocView::SnapCurrent(pSpread, &LastPoint);
05655 
05656     // We had better take a note of the starting point of the drag
05657     StartMousePos = FirstPoint;
05658     LastMousePos = LastPoint;
05659     StartSpread  = pSpread;
05660     AddCurveFlag = MakeCurve;
05661     AddSmoothFlag = MakeSmooth;
05662 
05663     BOOL ok = CreateCursors();
05664     
05665     // We also need to make a version of the path that we can change
05666     if (ok)
05667         ok = BuildEditPath(FirstPoint, LastPoint);
05668 
05669     // Set UpdatePoint so the position of the dragged point is shown in the infobar
05670     if (AddCurveFlag)
05671         UpdatePoint = 3;
05672     else
05673         UpdatePoint = 1;
05674 
05675     // Start the drag
05676     if (ok)
05677     {
05678         // Render the bits of the path that are different
05679         DocRect EditPathBBox = EditPath.GetBoundingRect();
05680 //      RenderPathEditBlobs(EditPathBBox, pSpread);
05681 
05682         // And tell the Dragging system that we need drags to happen
05683         ok = StartDrag(DRAGTYPE_AUTOSCROLL, &EditPathBBox, &LastMousePos);
05684     }
05685 
05686     if (!ok)
05687     {
05688         InformError();
05689         FailAndExecute();
05690         End();
05691     }
05692 }
05693 
05694 /********************************************************************************************
05695 
05696 >   BOOL OpNewPath::BuildEditPath(DocCoord FirstPoint, DocCoord LastPoint)
05697 
05698     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
05699     Created:    20/6/94
05700     Input:      FirstPoint is the start of the path
05701                 Lastpoint is the end of the path
05702     Returns:    TRUE if it managed to build the path, FALSE if it failed
05703     Purpose:    Builds a new path consisting of one curve or line element.
05704     Errors:     If it runs out of memory then it will return FALSE
05705 
05706 ********************************************************************************************/
05707 
05708 BOOL OpNewPath::BuildEditPath(DocCoord FirstPoint, DocCoord LastPoint)
05709 {
05710     // We only need 4 elements in the path to begin with
05711     if (!EditPath.Initialise(4, 4))
05712         return FALSE;
05713 
05714     // Create the path.
05715     EditPath.InsertMoveTo(FirstPoint);
05716     if (AddCurveFlag)
05717         EditPath.InsertCurveTo(LastPoint, LastPoint, LastPoint);
05718     else
05719         EditPath.InsertLineTo(LastPoint);
05720 
05721     // Set the smoothing bits on all points
05722     PathFlags* Flags = EditPath.GetFlagArray();
05723     INT32 NumCoords = EditPath.GetNumCoords();
05724     for (INT32 i=0;i<NumCoords;i++)
05725     {
05726         Flags[i].IsSmooth = TRUE;
05727         Flags[i].IsRotate = AddSmoothFlag;
05728     }
05729     
05730     // Now select the endpoint and the previous control point
05731     if (AddCurveFlag)
05732     {
05733         Flags[2].IsSelected = Flags[3].IsSelected = TRUE;
05734         Flags[0].NeedToRender = Flags[3].NeedToRender = TRUE;
05735     }
05736     else
05737     {
05738         Flags[1].IsSelected = TRUE;
05739         Flags[0].NeedToRender = Flags[1].NeedToRender = TRUE;
05740     }
05741     
05742     EditPath.SmoothCurve();
05743 
05744     // It worked
05745     return TRUE;
05746 }
05747 
05748 /********************************************************************************************
05749 
05750 >   void OpNewPath::DragFinished(   DocCoord Pos, 
05751                                             ClickModifiers Mods, 
05752                                             Spread* pSpread, 
05753                                             BOOL Success,
05754                                             BOOL bSolidDrag)
05755 
05756     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
05757     Created:    22/6/94
05758     Inputs:     Pos = mouse position at end of drag
05759                 Mods = click modifiers (Adjust, etc.)
05760                 pSpread points at the spread the drag finished in
05761                 Success indicates whether the drag was aborted with Escape
05762     Outputs:    -
05763     Returns:    -
05764     Purpose:    This routine handles the end of the drag operation when adding a new
05765                 path to the tree.
05766     Errors:     -
05767     SeeAlso:    -
05768 
05769 ********************************************************************************************/
05770 
05771 void OpNewPath::DragFinished(DocCoord Pos, ClickModifiers Mods, Spread* pSpread, BOOL Success, BOOL bSolidDrag)
05772 {
05773     // Stop the drag
05774     RenderPathEditBlobs( EditPath.GetBoundingRect(), StartSpread );
05775     EndDrag();
05776     DestroyCursors();
05777     BeginSlowJob();
05778 
05779     BOOL ok = TRUE;
05780     NodePath* NewPath = NULL;
05781     Document* pDoc = GetWorkingDoc();
05782     ERROR3IF(pDoc == NULL, "Null Document while setting attributes for new NodePath");
05783     
05784     if (Success && (pDoc != NULL))
05785     {
05786         ok = DoStartSelOp(FALSE);
05787         
05788         // Create a path to hold the data
05789         NodePath* NewPath = new NodePath;
05790         ok = (NewPath != NULL);
05791 
05792         // Initialise the path
05793         if (ok)
05794             ok = NewPath->SetUpPath(24, 12);
05795         
05796         // Copy the data from the edit path to the new path
05797         if (ok)
05798             ok = NewPath->InkPath.CopyPathDataFrom(&EditPath);
05799         
05800         // Apply attributes to the new node
05801         if (ok)
05802             ok = pDoc->GetAttributeMgr().ApplyCurrentAttribsToNode((NodeRenderableInk*)NewPath);
05803 
05804         // Insert the new node into the tree
05805         if (ok)
05806             ok = DoInsertNewNode(NewPath, pSpread, TRUE);
05807 
05808         // Here we have to clear the floating endpoint condition that prevails in the line tool.
05809         // To avoid grief in tool DLL builds lets just broadcast a message saying an new
05810         // path has been created.  The line tool picks up this message and clears its floating endpoint 
05811         if (ok)
05812             BROADCAST_TO_ALL(NewPathCreatedMsg(NewPath, this, &UndoActions));
05813     }
05814     else
05815     {
05816         // Set up the flags that say it all went wrong
05817         FailAndExecute();
05818     }
05819 
05820     // Clean up after failure
05821     if (!ok)
05822     {
05823         if (NewPath != NULL)
05824         {
05825             NewPath->CascadeDelete();
05826             delete NewPath;
05827         }
05828         FailAndExecute();
05829     }
05830 
05831     End();      
05832 }
05833 
05834 
05835 /***********************************************************************************************
05836 
05837 >   class JoinShapesBecomeA: public BecomeA
05838 
05839     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
05840     Created:    10/1/96
05841     Purpose:    This is the class that is passed to other nodes when the combine op gets them to 
05842                 to turn into other types via their DoBecomeA() method
05843 
05844 ***********************************************************************************************/
05845 
05846 class JoinShapesBecomeA : public BecomeA
05847 {
05848 CC_DECLARE_MEMDUMP(JoinShapesBecomeA);
05849 public:
05850     JoinShapesBecomeA(NodePath* pThisJoinedPath) :
05851                 BecomeA(BECOMEA_PASSBACK,CC_RUNTIME_CLASS(NodePath),NULL),
05852                 pJoinedPath(pThisJoinedPath), pLastCreatedByNode(NULL), pLastAttrMap(NULL), pCreatedByAttrMap(NULL) {};
05853 
05854     ~JoinShapesBecomeA();
05855 
05856     // This function should be called when Reason == BECOMEA_PASSBACK 
05857     virtual BOOL PassBack(NodeRenderableInk* pNewNode,NodeRenderableInk* pCreatedByNode,CCAttrMap* pAttrMap);
05858 
05859     CCAttrMap* GetLastAttrMap();
05860 
05861 private:
05862     NodePath*           pJoinedPath;
05863 
05864     NodeRenderableInk*  pLastCreatedByNode;
05865     CCAttrMap*          pLastAttrMap;
05866     CCAttrMap*          pCreatedByAttrMap;
05867 };
05868 
05869 CC_IMPLEMENT_MEMDUMP(JoinShapesBecomeA,BecomeA);
05870 
05871 /********************************************************************************************
05872 
05873 >   JoinShapesBecomeA::~JoinShapesBecomeA()
05874 
05875     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
05876     Created:    15/1/96
05877     Inputs:     
05878     Outputs:    -
05879     Returns:    -
05880     Purpose:    The default destructor.
05881 
05882                 This will delete the attr map and the attrs within if it still has one
05883 
05884     Errors:     -
05885     SeeAlso:    -
05886 
05887 ********************************************************************************************/
05888 
05889 JoinShapesBecomeA::~JoinShapesBecomeA()
05890 {
05891     if (pLastAttrMap != NULL)
05892     {
05893         pLastAttrMap->DeleteAttributes();
05894         delete pLastAttrMap;
05895     }
05896 
05897     if (pCreatedByAttrMap != NULL)
05898         delete pCreatedByAttrMap;
05899 }
05900 
05901 /********************************************************************************************
05902 
05903 >   CCAttrMap* JoinShapesBecomeA::GetLastAttrMap()
05904 
05905     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
05906     Created:    15/1/96
05907     Inputs:     
05908     Outputs:    -
05909     Returns:    ptr to an attr map, or NULL if it fails
05910     Purpose:    This returns a ptr to an attr map that contains ptrs to the attrs of the last selected object.
05911 
05912                 This attr map is the one that's applied to the new joined path, using NodeRenderableInk::ApplyAttributes()
05913 
05914                 Do not delete this map or any of the attrs within it.  
05915 
05916     Errors:     -
05917     SeeAlso:    NodeRenderableInk::ApplyAttributes()
05918 
05919 ********************************************************************************************/
05920 
05921 CCAttrMap* JoinShapesBecomeA::GetLastAttrMap()
05922 {
05923     CCAttrMap* pAttrMap = pLastAttrMap;
05924 
05925     if (pAttrMap == NULL)
05926     {
05927         if (pCreatedByAttrMap == NULL && pLastCreatedByNode != NULL)
05928         {
05929             pCreatedByAttrMap = new CCAttrMap(30);
05930             if (pCreatedByAttrMap != NULL)
05931             {
05932                 if (!pLastCreatedByNode->FindAppliedAttributes(pCreatedByAttrMap))
05933                 {
05934                     delete pCreatedByAttrMap;
05935                     pCreatedByAttrMap = NULL;
05936                 }
05937             }
05938         }
05939 
05940         pAttrMap = pCreatedByAttrMap;
05941     }
05942 
05943     return pAttrMap;
05944 }
05945 
05946 /********************************************************************************************
05947 
05948 >   BOOL JoinShapesBecomeA::PassBack(NodeRenderableInk* pNewNode,NodeRenderableInk* pCreatedByNode,CCAttrMap* pAttrMap)
05949 
05950     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
05951     Created:    10/1/96
05952     Inputs:     pNewNode = the node created by the object being made into shapes
05953                 pCreatedByNode = ptr to the node that created the new node
05954                 pAttrMap = ptr to attrs (can be NULL)
05955     Outputs:    -
05956     Returns:    TRUE if the new path was used successfully, FALSE if op should be aborted
05957     Purpose:    This receives the paths from the selected objects and joins them to pJoinedPath.
05958     Errors:     -
05959     SeeAlso:    -
05960 
05961 ********************************************************************************************/
05962 
05963 BOOL JoinShapesBecomeA::PassBack(NodeRenderableInk* pNewNode,NodeRenderableInk* pCreatedByNode,CCAttrMap* pAttrMap)
05964 {
05965     ERROR3IF(pJoinedPath == NULL,"NULL joined path");
05966     ERROR3IF(pNewNode == NULL,"NULL new path");
05967     if (pJoinedPath == NULL || pNewNode == NULL)
05968         return TRUE;
05969 
05970     if (!pNewNode->IsNodePath())
05971     {
05972         ERROR3("Received a node that's not a NodePath");
05973         return TRUE;
05974     }
05975 
05976     // If we have an attr map from the last call, delete it and all the attributes it contains
05977     if (pLastAttrMap != NULL)
05978     {
05979         pLastAttrMap->DeleteAttributes();
05980         delete pLastAttrMap;
05981     }
05982 
05983     // Update the 'last' vars
05984     pLastCreatedByNode  = pCreatedByNode;
05985     pLastAttrMap        = pAttrMap;
05986 
05987     // Merge the new NodePath into our JoinedPath
05988     BOOL ok = pJoinedPath->InkPath.MergeTwoPaths(((NodePath*)pNewNode)->InkPath);
05989     
05990     // Don't need the supplied NodePath anymore
05991     pNewNode->CascadeDelete();
05992     delete pNewNode;
05993     pNewNode = NULL;
05994 
05995     return ok;
05996 }
05997 
05998 
05999 /********************************************************************************************
06000 
06001 >   OpJoinShapes::OpJoinShapes() 
06002 
06003     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
06004     Created:    20/7/94
06005     Inputs:     -
06006     Outputs:    -
06007     Returns:    -
06008     Purpose:    OpJoinShapes constructor
06009     Errors:     -
06010     SeeAlso:    -
06011 
06012 ********************************************************************************************/
06013             
06014             
06015 OpJoinShapes::OpJoinShapes(): SelOperation()                                
06016 {                              
06017 }
06018 
06019  /********************************************************************************************
06020 
06021 >   BOOL OpJoinShapes::Init()
06022 
06023     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
06024     Created:    20/7/94
06025     Inputs:     -
06026     Outputs:    -
06027     Returns:    TRUE if the operation could be successfully initialised 
06028                 FALSE if no more memory could be allocated 
06029                 
06030     Purpose:    OpJoinShapes initialiser method
06031     Errors:     ERROR will be called if there was insufficient memory to allocate the 
06032                 operation.
06033     SeeAlso:    -
06034 
06035 ********************************************************************************************/
06036 
06037 BOOL OpJoinShapes::Init()
06038 {
06039 
06040     BTNOP( JOINSHAPEOP, OpJoinShapes, ARRANGE)
06041     return TRUE;
06042 //  return (RegisterOpDescriptor(0,
06043  //                         _R(IDS_JOINSHAPEOP),
06044 //                          CC_RUNTIME_CLASS(OpJoinShapes),
06045  //                         OPTOKEN_NODEPATH,
06046  //                         OpJoinShapes::GetState,
06047  //                         0,  /* help ID */
06048  //                         _R(IDBBL_NODEPATHOP),
06049  //                         0   /* bitmap ID */)); 
06050 
06051 }               
06052     
06053 /********************************************************************************************
06054 
06055 >   OpState OpJoinShapes::GetState(String_256*, OpDescriptor*)
06056 
06057     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
06058     Created:    20/7/94
06059     Inputs:     -
06060     Outputs:    -
06061     Returns:    The state of the OpJoinShapes
06062     Purpose:    For finding the OpJoinShapes's state. 
06063     Errors:     -
06064     SeeAlso:    -
06065 
06066 ********************************************************************************************/
06067 
06068 OpState OpJoinShapes::GetState(String_256* UIDescription, OpDescriptor*)
06069 {
06070     OpState OpSt;
06071     String_256 DisableReason; 
06072 
06073     // Ensure that a document exists
06074     if (Document::GetSelected() == NULL)
06075     {
06076         // There is no selected document
06077         OpSt.Greyed = TRUE;
06078 
06079         // Load reason why operation is disabled
06080         DisableReason = String_256(_R(IDS_NO_DOC));
06081         *UIDescription = DisableReason;      
06082         return OpSt;                                 
06083     }
06084 
06085     OpSt.Greyed = FALSE;
06086 
06087     // Look at the current selection
06088     SelRange* Selected = GetApplication()->FindSelection();
06089     Node* pNode = Selected->FindFirst();
06090     BOOL SelectedInside = FALSE;
06091     BecomeA MyBecomeA(BECOMEA_TEST, CC_RUNTIME_CLASS(NodePath));
06092     MyBecomeA.ResetCount();
06093     
06094     while (pNode != NULL)
06095     {
06096         if (pNode->CanBecomeA(&MyBecomeA))
06097         {
06098             if (pNode->FindParentOfSelected() != NULL)
06099                 SelectedInside = TRUE;
06100         }
06101         pNode = Selected->FindNext(pNode);
06102     }
06103     
06104     // The operation is disabled if there are less than one path to join
06105     if (MyBecomeA.GetCount() < 2)
06106     {
06107         OpSt.Greyed = TRUE;
06108         DisableReason = String_256(_R(IDS_JOIN_NEEDS_TWO_PATHS));
06109         *UIDescription = DisableReason;
06110     }
06111     else
06112     {
06113         // Greyed out if paths are selected inside.
06114         if (SelectedInside)
06115         {
06116             OpSt.Greyed = TRUE;
06117             DisableReason = String_256(_R(IDS_GREY_WHEN_SELECT_INSIDE));
06118             *UIDescription = DisableReason;
06119         }
06120     }
06121     
06122     return(OpSt);   
06123 }
06124 
06125 /********************************************************************************************
06126 
06127 >   void OpJoinShapes::Do(OpDescriptor*)
06128 
06129     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com> (rewritten by Markn 12/1/96)
06130     Created:    20/7/94
06131     Inputs:     OpDescriptor (unused)
06132     Outputs:    -
06133     Returns:    -
06134     Purpose:    Performs the Join shapes operation. 
06135                 
06136     Errors:     -
06137     SeeAlso:    -
06138 
06139 ********************************************************************************************/
06140 
06141 void OpJoinShapes::Do(OpDescriptor*)
06142 {   
06143     BOOL ok = TRUE;
06144     
06145     // remember the selection before the operation
06146     if (ok)
06147         ok = DoStartSelOp(FALSE,FALSE);
06148 
06149     // Get the current selection
06150     SelRange* Selected = GetApplication()->FindSelection();
06151 
06152     // Create a new NodePath and insert it into the tree
06153     Node* pNode = Selected->FindFirst();
06154     NodePath* pJoinedPath = NULL;
06155     if (ok) (pJoinedPath = new NodePath);
06156     if (ok) ok = (pJoinedPath != NULL);
06157     if (ok) ok = pJoinedPath->SetUpPath();
06158     if (ok) ok = DoInsertNewNode(pJoinedPath,pNode,PREV,FALSE,FALSE,FALSE,FALSE);
06159 
06160     // JoinBecomeA will receive all the paths created by the selection and merge them into pJoinedPath
06161     JoinShapesBecomeA JoinBecomeA(pJoinedPath);
06162 
06163     // Now go through the selection, copying path data from selected paths into the new one
06164     pNode = Selected->FindFirst();
06165     while (ok && (pNode != NULL))
06166     {
06167         // Find the next node in the selection now, because this one might get hidden
06168         Node* pNextNode = Selected->FindNext(pNode);
06169 
06170         // Ask each node that can create a NodePath to do so via DoBecomeA.
06171         if (pNode != NULL && pNode->IsAnObject() && pNode->CanBecomeA(&JoinBecomeA))
06172             ok = ((NodeRenderableInk*)pNode)->DoBecomeA(&JoinBecomeA);
06173 
06174         // Next node
06175         pNode = pNextNode;
06176     }
06177 
06178     // Apply the attributes of the last node to generate a NodePath
06179     if (ok)
06180     {
06181         CCAttrMap* pAttrMap = JoinBecomeA.GetLastAttrMap();
06182         if (pAttrMap != NULL)
06183             pJoinedPath->ApplyAttributes(pAttrMap,TRUE);
06184     }
06185 
06186     // Hide all the selected nodes
06187     pNode = Selected->FindFirst();
06188     while (ok && pNode != NULL)
06189     {
06190         Node* pNextNode = Selected->FindNext(pNode);
06191 
06192         if (pNode != NULL)
06193             ok = DoHideNode(pNode,TRUE);
06194 
06195         // Next node
06196         pNode = pNextNode;
06197     }
06198 
06199     // Select the node, ensure that it's bounds are recalculated, and redraw it
06200     if (ok) pJoinedPath->Select(FALSE);
06201     if (ok) pJoinedPath->InvalidateBoundingRect();
06202     if (ok) ok = DoInvalidateNodeRegion(pJoinedPath,TRUE);
06203 
06204     // Inform any error that may occur
06205     if (!ok)
06206     {
06207         InformError();
06208         FailAndExecute();
06209     }
06210 
06211     // Tell the application to refresh the selection
06212     GetApplication()->UpdateSelection();
06213         
06214     End();
06215 }           
06216 
06217 /*  
06218 void OpJoinShapes::Do(OpDescriptor*)
06219 {   
06220     BOOL ok = TRUE;
06221     
06222     // remember the selection before the operation
06223     if (ok)
06224         ok = DoStartSelOp(FALSE,FALSE);
06225 
06226     // Now make shapes of the current selection
06227     SelRange* Selected = GetApplication()->FindSelection();
06228     if (ok)
06229         ok = DoMakeShapes(*Selected);
06230     
06231     // First find the *last* pathnode selected,  Selected inside paths are ignored
06232     NodePath* LastSelected = NULL;
06233     Node* pNode = Selected->FindFirst();
06234     INT32 count = 0;
06235     if (ok)
06236     {
06237         while (pNode != NULL)
06238         {
06239             if (IS_A(pNode,NodePath) && (pNode->FindParentOfSelected() == NULL))
06240             {
06241                 LastSelected = (NodePath*) pNode;
06242                 count++;
06243             }
06244             pNode = Selected->FindNext(pNode);
06245         }
06246     
06247         if ((LastSelected == NULL) || (count < 2))
06248         {
06249             FailAndExecute();
06250             End();
06251             return;
06252         }
06253     }
06254 
06255     // In order to maintain the attributes of at least one of the objects being joined
06256     // make a 'deep' copy of the last selected node which will contain any child
06257     // attributes. Then add all the other nodes to it.
06258     Node* pnp;              // Temp, for pointer to path (because NodeCopy takes a Node*)
06259     LastSelected->NodeCopy(&pnp);   // Make a deep copy of the last selected path
06260     NodePath* NewPath = (NodePath*)pnp;
06261     if (ok)
06262         ok = NewPath != NULL;
06263 
06264     // Now go through the selection, copying path data from selected paths into the new one
06265     // and hiding the nodes. 
06266     pNode = Selected->FindFirst();
06267     while (ok && (pNode != NULL))
06268     {
06269         // Find the next node in the selection, 'cos this one might get hidden
06270         Node* NextNode = Selected->FindNext(pNode);
06271         // For each node except LastSelected, merge the nodes
06272         if (IS_A(pNode,NodePath) && (pNode->FindParentOfSelected() == NULL) && (pNode != LastSelected))
06273         {
06274             if (!(NewPath->InkPath.MergeTwoPaths(((NodePath*)pNode)->InkPath)))
06275             {
06276                 NewPath->CascadeDelete();
06277                 delete NewPath;
06278                 ok = FALSE;
06279             }
06280 
06281             // It's safe to hide the node if this isn't the LastSelected node
06282             if (ok && (pNode != (Node*)LastSelected))
06283             {
06284                 if (!DoHideNode(pNode, TRUE))
06285                 {
06286                     NewPath->CascadeDelete();
06287                     delete NewPath;
06288                     ok = FALSE;
06289                 }
06290             }
06291 
06292         }
06293         pNode = NextNode;
06294     }
06295 
06296     // Tell the application to refresh the selection
06297     GetApplication()->UpdateSelection();
06298         
06299     // Now NewPath contains all the selected paths, so we have to insert this node in the tree
06300     if (ok && !DoInsertNewNode(NewPath, LastSelected, NEXT, TRUE))
06301     {
06302         NewPath->CascadeDelete();
06303         delete NewPath;
06304         ok = FALSE;
06305     }
06306 
06307     // And hide the last selected node
06308     if (ok)
06309         ok = DoHideNode(LastSelected, TRUE);
06310 
06311     if (!ok)
06312     {
06313         InformError();
06314         FailAndExecute();
06315     }
06316 
06317     End();
06318 }           
06319 */
06320     
06321 /********************************************************************************************
06322 
06323 >   OpBreakShapes::OpBreakShapes() 
06324 
06325     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
06326     Created:    22/7/94
06327     Inputs:     -
06328     Outputs:    -
06329     Returns:    -
06330     Purpose:    OpBreakShapes constructor
06331     Errors:     -
06332     SeeAlso:    -
06333 
06334 ********************************************************************************************/
06335             
06336             
06337 OpBreakShapes::OpBreakShapes(): SelOperation()                              
06338 {                              
06339 }
06340 
06341  /********************************************************************************************
06342 
06343 >   BOOL OpBreakShapes::Init()
06344 
06345     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
06346     Created:    22/7/94
06347     Inputs:     -
06348     Outputs:    -
06349     Returns:    TRUE if the operation could be successfully initialised 
06350                 FALSE if no more memory could be allocated 
06351                 
06352     Purpose:    OpBreakShapes initialiser method
06353     Errors:     ERROR will be called if there was insufficient memory to allocate the 
06354                 operation.
06355     SeeAlso:    -
06356 
06357 ********************************************************************************************/
06358 
06359 BOOL OpBreakShapes::Init()
06360 {
06361 
06362     BTNOP( BREAKSHAPEOP, OpBreakShapes, ARRANGE)
06363     return TRUE;
06364 //  return (RegisterOpDescriptor(0,
06365  //                         _R(IDS_JOINSHAPEOP),
06366 //                          CC_RUNTIME_CLASS(OpJoinShapes),
06367  //                         OPTOKEN_NODEPATH,
06368  //                         OpJoinShapes::GetState,
06369  //                         0,  /* help ID */
06370  //                         _R(IDBBL_NODEPATHOP),
06371  //                         0   /* bitmap ID */)); 
06372 
06373 }               
06374     
06375 /********************************************************************************************
06376 
06377 >   OpState OpBreakShapes::GetState(String_256*, OpDescriptor*)
06378 
06379     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
06380     Created:    20/7/94
06381     Inputs:     -
06382     Outputs:    -
06383     Returns:    The state of the OpBreakShapes
06384     Purpose:    For finding the OpBreakShapes's state. 
06385     Errors:     -
06386     SeeAlso:    -
06387 
06388 ********************************************************************************************/
06389 
06390 OpState OpBreakShapes::GetState(String_256* UIDescription, OpDescriptor*)
06391 {
06392     OpState OpSt;
06393     String_256 DisableReason; 
06394 
06395     // Ensure that a document exists
06396     if (Document::GetSelected() == NULL)
06397     {
06398         // There is no slected document
06399         OpSt.Greyed = TRUE;
06400         // Load reason why operation is disabled
06401         DisableReason = String_256(_R(IDS_NO_DOC));
06402         *UIDescription = DisableReason;      
06403         return OpSt;                                 
06404     }
06405 
06406 
06407     OpSt.Greyed = FALSE;
06408 
06409     // Look at the current selection
06410     SelRange* Selected = GetApplication()->FindSelection();
06411     Node* pNode = Selected->FindFirst();
06412 
06413     // Go through the selection until we find a complex path that's not selected inside
06414     BOOL FoundComplex = FALSE;
06415     BOOL SelectedInside = FALSE;    // TRUE only if the last selected complex path is selected inside
06416     while (pNode)
06417     {
06418         if (pNode->GetRuntimeClass() == CC_RUNTIME_CLASS(NodePath))
06419         {
06420             // Have we found a complex path?
06421             FoundComplex    = (((NodePath*)pNode)->InkPath.IsComplexPath());
06422 
06423             // Set SelectedInside to TRUE if we find a complex path AND it's selected inside.  FALSE otherwise
06424             // This is important, otherwise the greying out code below won't work correctly.
06425             SelectedInside  = (FoundComplex && (pNode->FindParentOfSelected() != NULL));
06426 
06427             // If we find a complex path that's not selected inside another node, so let op be doable
06428             if (FoundComplex && !SelectedInside)
06429                 break;
06430         }
06431         pNode = Selected->FindNext(pNode);
06432     }
06433     
06434     // The operation is disabled if there are no complex paths selected
06435 
06436     if (!FoundComplex)
06437     {
06438         OpSt.Greyed = TRUE;
06439         DisableReason = String_256(_R(IDS_BREAK_NEEDS_COMPLEX));
06440         *UIDescription = DisableReason;
06441     }
06442     else
06443     {
06444         // Greyed out if paths are selected inside.
06445         if (SelectedInside)
06446         {
06447             OpSt.Greyed = TRUE;
06448             DisableReason = String_256(_R(IDS_GREY_WHEN_SELECT_INSIDE));
06449             *UIDescription = DisableReason;
06450         }
06451     }
06452     
06453     return(OpSt);   
06454 }
06455 
06456 /********************************************************************************************
06457 
06458 >   void OpBreakShapes::Do(OpDescriptor*)
06459 
06460     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
06461     Created:    22/7/94
06462     Inputs:     OpDescriptor (unused)
06463     Outputs:    -
06464     Returns:    -
06465     Purpose:    Performs the Break shapes operation. 
06466                 
06467     Errors:     -
06468     SeeAlso:    -
06469 
06470 ********************************************************************************************/
06471     
06472 void OpBreakShapes::Do(OpDescriptor*)
06473 {   
06474     // Obtain the current selections 
06475     SelRange* Selected = GetApplication()->FindSelection();
06476 
06477     NodePath* NewPath;
06478     // Now, because we're going to be doing mad things to the selection, we have to make a list
06479     // of all the selected nodes, so that adding nodes into the tree won't confuse us
06480 
06481     List* NodeList = Selected->MakeListOfNodes();
06482 
06483     NodeListItem* CurItem = (NodeListItem*)(NodeList->GetHead());
06484 
06485     if (!CurItem)
06486         goto FailAndDeleteList;
06487 
06488     while(CurItem)
06489     {
06490         if ((CurItem->pNode->GetRuntimeClass() == CC_RUNTIME_CLASS(NodePath))   &&  // It's a NodePath  AND
06491             (((NodePath*)(CurItem->pNode))->InkPath.IsComplexPath())            &&  // It's complex     AND
06492             (CurItem->pNode->FindParentOfSelected() == NULL))                       // It's not selected inside
06493         {
06494             // get a pointer to the NodePath
06495             NodePath* ThisPath = (NodePath*)(CurItem->pNode);
06496             
06497             // Start at the very beginning (a very good place to start)
06498             INT32 SubPathIndex = 0;
06499             INT32 NumCoords = ThisPath->InkPath.GetNumCoords();
06500 
06501             while (SubPathIndex < NumCoords)
06502             {
06503                 // Create a new path, copy this subpath into it, and link it into the tree
06504                 // Create the path
06505                 NewPath = new NodePath;
06506                 if (!NewPath)
06507                 {
06508                     goto FailAndDeleteList;
06509                 }
06510                 
06511                 // Initialise the path
06512                 if (!NewPath->SetUpPath(24,12))
06513                 {
06514                     InformError(_R(IDS_OUT_OF_MEMORY), _R(IDS_OK));
06515                     goto FailAndDeleteListAndPath;
06516                 }
06517 
06518                 // Copy all attributes from the original shape to the new path
06519                 Node* pAttr = ThisPath->FindFirstChild();
06520                 while (pAttr != NULL)
06521                 {
06522                     if (pAttr->IsAnAttribute())
06523                     {
06524                         BOOL ok;
06525                         Node* pAttrCopy;
06526                         CALL_WITH_FAIL(pAttr->NodeCopy(&pAttrCopy), this, ok);
06527                         if (!ok) 
06528                         {
06529                             goto FailAndDeleteListAndPath;
06530                         }
06531                         pAttrCopy->AttachNode(NewPath, FIRSTCHILD);
06532                     }
06533                     pAttr = pAttr->FindNext();
06534                 }
06535 
06536                 // Set the filled bit if the original path has the filled bit set
06537                 if (ThisPath->InkPath.IsFilled)
06538                     NewPath->InkPath.IsFilled = TRUE;
06539                 
06540                 // Now copy the current subpath into this object
06541                 // Find the last element in the subpath
06542                 INT32 EndOfSubPath = SubPathIndex;
06543                 ThisPath->InkPath.FindEndOfSubPath(&EndOfSubPath);
06544                 // Skip to the next element (this will give us the number of elements)
06545                 ThisPath->InkPath.FindNext(&EndOfSubPath);
06546 
06547                 // Now EndOfSubPath either points at the start of the next subpath, or 
06548                 // the top of the path (i.e. the first free space).
06549 
06550                 // Now copy it
06551                 if (!(NewPath->InkPath.CopySectionFrom(ThisPath->InkPath, SubPathIndex, EndOfSubPath-SubPathIndex)))
06552                 {
06553                     InformError(_R(IDS_OUT_OF_MEMORY), _R(IDS_OK));
06554                     goto FailAndDeleteListAndPath;
06555                 }
06556                 
06557                 // Set the index to the start of the next subpath
06558                 SubPathIndex = EndOfSubPath;
06559 
06560                 // Now stick it in the tree
06561                 if (!DoInsertNewNode(NewPath, ThisPath, NEXT, TRUE))
06562                 {
06563                     goto FailAndDeleteListAndPath;
06564                 }
06565 
06566                 // And that's it - no need to do anything more, just loop to the next subpath
06567             }
06568 
06569             // Now we've broken up this object, let's hide it
06570             if (!DoHideNode(ThisPath, TRUE))
06571                 goto FailAndDeleteList;
06572 
06573         }
06574         CurItem = (NodeListItem*)(NodeList->GetNext(CurItem));
06575     }
06576     
06577     End();
06578 
06579     // delete the nodelist (and all the list items)
06580     while (!NodeList->IsEmpty())
06581         delete (NodeListItem*)(NodeList->RemoveHead());
06582     delete NodeList;
06583 
06584     return;
06585 
06586 FailAndDeleteListAndPath:
06587 
06588     NewPath->CascadeDelete();
06589     delete NewPath;
06590 
06591 FailAndDeleteList:
06592     while (!NodeList->IsEmpty())
06593         delete (NodeListItem*)(NodeList->RemoveHead());
06594     delete NodeList;
06595     FailAndExecute();
06596     End();
06597     return;
06598 }
06599 
06600 
06601 
06603 // OpDeletePoints
06604             
06605 
06606 /********************************************************************************************
06607 >   OpDeletePoints::OpDeletePoints() 
06608 
06609     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
06610     Created:    22/7/94
06611     Purpose:    OpDeletePoints constructor
06612 ********************************************************************************************/
06613 OpDeletePoints::OpDeletePoints(): SelOperation()                                
06614 {                              
06615 }
06616 
06617 
06618 /********************************************************************************************
06619 >   BOOL OpDeletePoints::Init()
06620 
06621     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
06622     Created:    26/7/94
06623     Inputs:     -
06624     Outputs:    -
06625     Returns:    TRUE if the operation could be successfully initialised 
06626                 FALSE if no more memory could be allocated 
06627     Purpose:    OpDeletePoints initialiser method
06628     Errors:     ERROR will be called if there was insufficient memory to allocate the 
06629                 operation.
06630     SeeAlso:    -
06631 ********************************************************************************************/
06632 BOOL OpDeletePoints::Init()
06633 {
06634     BTNOP( DELETEPOINTSOP, OpDeletePoints, EDIT)
06635     return TRUE;
06636 }               
06637 
06638 
06639     
06640 /********************************************************************************************
06641 
06642 >   OpState OpDeletePoints::GetState(String_256* UIDescription, OpDescriptor* pOpDesc)
06643 
06644     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com> - latterly Peter
06645     Created:    27/7/94
06646     Inputs:     -
06647     Outputs:    -
06648     Returns:    The state of the OpDeletePoints
06649     Purpose:    For finding the OpDeletePoints's state. 
06650     Errors:     -
06651     SeeAlso:    -
06652 ********************************************************************************************/
06653 
06654 OpState OpDeletePoints::GetState(String_256* UIDescription, OpDescriptor* pOpDesc)
06655 {
06656     // Look at the current selection
06657     SelRange* Selected = GetApplication()->FindSelection();
06658     Node* pNode = Selected->FindFirst();
06659 
06660     // We need to ask the effected nodes if they (and their parents) can handle this node being deleted
06661     ObjChangeParam ObjDelete(OBJCHANGE_STARTING, ObjChangeFlags(TRUE), NULL, NULL);
06662     ObjChangeParam ObjChange(OBJCHANGE_STARTING, ObjChangeFlags(), NULL, NULL);
06663 
06664     // Go through the selection until we find a selected point
06665     BOOL FoundSelected = FALSE;
06666     while (pNode != NULL)
06667     {
06668         if (IS_A(pNode, NodePath) || IS_A(pNode,NodeBlendPath))
06669         {
06670             if (((NodePath*)pNode)->InkPath.IsSubSelection())
06671             {
06672                 // See if this will delete the node or not
06673                 BOOL WillHide = WillDeleteEntirePath((NodePath*)pNode);
06674 
06675                 // Check that we can do this op of the node
06676                 BOOL Result = FALSE;
06677                 if (WillHide)
06678                     Result = pNode->AllowOp(&ObjDelete, FALSE);
06679                 else
06680                     Result = pNode->AllowOp(&ObjChange, FALSE);
06681                 
06682                 if (Result)
06683                 {
06684                     // We've found at least one node we can act upon
06685                     FoundSelected = TRUE;   
06686                     break;
06687                 }
06688             }
06689 
06690         }
06691         pNode = Selected->FindNext(pNode);
06692     }
06693     
06694     OpState OpSt;
06695     // The operation is disabled if there are no complex paths selected
06696     if (!FoundSelected)
06697     {
06698         OpSt.Greyed = TRUE;
06699         *UIDescription = String_256(_R(IDS_NEEDS_SELECTED_POINT));
06700     }
06701     
06702     return(OpSt);   
06703 }
06704 
06705 
06706 
06707 /********************************************************************************************
06708 >   void OpDeletePoints::Do(OpDescriptor*)
06709 
06710     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com> - latterly Peter
06711     Created:    22/7/94
06712     Inputs:     OpDescriptor (unused)
06713     Outputs:    -
06714     Returns:    -
06715     Purpose:    Performs the Delete point(s) operation. 
06716     Errors:     -
06717     SeeAlso:    -
06718 ********************************************************************************************/
06719     
06720 void OpDeletePoints::Do(OpDescriptor*)
06721 {   
06722     // Obtain the current selections 
06723     SelRange* Selected = GetApplication()->FindSelection();
06724     Node* pNode = Selected->FindFirst();
06725 
06726     BOOL ok = DoStartSelOp(TRUE,TRUE);
06727 
06728     // We need to ask the effected nodes if they (and their parents) can handle this node being deleted
06729     ObjChangeFlags cFlags/*(TRUE)*/;
06730     cFlags.TransformNode = TRUE;
06731     ObjChangeParam ObjDelete(OBJCHANGE_STARTING,ObjChangeFlags(TRUE),NULL,this);
06732     ObjChangeParam ObjChange(OBJCHANGE_STARTING,cFlags,NULL,this);
06733 
06734     // loop through the selection
06735     while (pNode != NULL)
06736     {
06737         // First see where the next node is, in case this one gets deleted
06738         Node* NextNode = Selected->FindNext(pNode);
06739 
06740         // Is the node of interest?
06741         // we're only interested in NodePaths which have selected points, and that will allow this op to happen
06742         BOOL DoThisPath = (IS_A(pNode, NodePath) || IS_A(pNode,NodeBlendPath)) && ((NodePath*)pNode)->InkPath.IsSubSelection();
06743 
06744         // Check with the path and it's parents
06745         BOOL WillDelete = FALSE;
06746         if (DoThisPath)
06747         {
06748             WillDelete = WillDeleteEntirePath((NodePath*)pNode);
06749             if (WillDelete)
06750                 DoThisPath = pNode->AllowOp(&ObjDelete);
06751             else
06752                 DoThisPath = pNode->AllowOp(&ObjChange);
06753         }
06754 
06755         // Do the delete if required
06756         if (DoThisPath && ok)
06757         {
06758             NodePath* ThisPath = (NodePath*)pNode;
06759             INT32 NumCoords = ThisPath->InkPath.GetNumCoords();
06760             PathVerb* Verbs = NULL;
06761             PathFlags* Flags = NULL;
06762             DocCoord* Coords = NULL;
06763             ThisPath->InkPath.GetPathArrays(&Verbs, &Coords, &Flags);
06764             BOOL PathStillExists = TRUE;
06765 
06766             // Record the bounds and do a redraw
06767             ok = RecalcBoundsAction::DoRecalc(this, &UndoActions, ThisPath) != AC_FAIL ;
06768 
06769 //          // Store the sub-selection state of the path
06770 //          if (ok)
06771 //              ok = (StorePathSubSelStateAction::DoRecord(this, &UndoActions, &ThisPath->InkPath) != AC_FAIL);
06772 
06773             // Simply hide all the path if all of it is to be deleted
06774             if (WillDelete)
06775             {
06776                 if (ok)
06777                     ok = DoHideNode(pNode, TRUE);
06778                 PathStillExists = FALSE;
06779             }
06780                     
06781             // We have to delete every selected endpoint in the path (if it still exists)
06782             while (ok && PathStillExists && ThisPath->InkPath.IsSubSelection())
06783             {
06784                 // First refresh the pointers to the arrays
06785                 ThisPath->InkPath.GetPathArrays(&Verbs, &Coords, &Flags);
06786                 NumCoords = ThisPath->InkPath.GetNumCoords();
06787 
06788                 // Scan for a selected endpoint
06789                 INT32   i;
06790                 for ( i =0;i<NumCoords && (!Flags[i].IsSelected || !Flags[i].IsEndPoint);i++)
06791                     ;   // ; deliberate
06792 
06793                 // Now, either i == NumCoords or it points at a selected endpoint
06794                 // if i == NumCoords there weren't any selected endpoints, so clear
06795                 // the selected bits on random control points
06796                 if (i == NumCoords)
06797                 {
06798                     for (i=0;i<NumCoords;i++)
06799                     {
06800                         ok = ok && DoChangeSelection(ThisPath, i,FALSE);
06801                     }
06802                 }
06803                 else
06804                 {
06805                     // i indexes into a selected endpoint, which is the one we have to delete.
06806                     // Are we looking at a MoveTo, CurveTo or a LineTo?
06807                     switch (Verbs[i] & ~PT_CLOSEFIGURE)
06808                     {
06809                         case PT_LINETO:
06810                             ok = DeleteLineTo(ThisPath, i, &PathStillExists);
06811                             break;
06812                         case PT_BEZIERTO:
06813                             ok = DeleteBezierTo(ThisPath, i, &PathStillExists);
06814                             break;
06815                         case PT_MOVETO:
06816                             ok = DeleteMoveTo(ThisPath, i, &PathStillExists);
06817                             break;
06818                     }
06819                 }
06820             }
06821 
06822             // now that we have deleted every selected point on the path we need to check to see if we are 
06823             // left with a closed path consisting of a moveto followed by a single line or a curve.
06824             // if we are then we should delete the node.
06825             if (ok && PathStillExists && !ThisPath->IsPathAllowable())
06826             {
06827                 if (ok)
06828                     ok = DoInvalidateNodeRegion(ThisPath,TRUE,TRUE);
06829                 if (ok)
06830                     ok = DoHideNode(ThisPath, TRUE);
06831                 PathStillExists = FALSE;
06832             }
06833 
06834             // Having finished deleting, record the bounds of the new path
06835             // This is the partner to the RecalcBoundsAction at the start of this Op
06836             if (PathStillExists && ok)
06837             {
06838                 ThisPath->InvalidateBoundingRect();
06839                 ok = RecordBoundsAction::DoRecord(this, &UndoActions, ThisPath) != AC_FAIL; 
06840             }
06841         }
06842         pNode = NextNode;
06843     }
06844 
06845     // Update all the parents of this node
06846     if (ok)
06847     {
06848         ObjChange.Define(OBJCHANGE_FINISHED,ObjChangeFlags(),NULL,this);
06849         ok = UpdateChangedNodes(&ObjChange);
06850     }
06851 
06852     if (!ok)
06853     {
06854         InformError();
06855         FailAndExecute();
06856     }
06857 
06858     GetApplication()->UpdateSelection();
06859 
06860     End();
06861 }
06862 
06863 
06864 
06865 /********************************************************************************************
06866 
06867 >   BOOL OpDeletePoints::DeleteLineTo(NodePath* pPath, INT32 Index, BOOL* PathExists)
06868 
06869     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com> - from Jim code
06870     Created:    27/10/94
06871     Inputs:     pPath - Pointer to the NodePath to delete the endpoint from.
06872                 Index - The number of the endpoint to delete
06873                 PathExists - pointer to a BOOL for flaging that the whole path has been deleted.
06874     Outputs:    PathExists - If the entire path has been deleted, *DeletedPath is set to TRUE
06875     Returns:    FALSE if an error occured during deletion, otherwise TRUE for sucess.
06876     Purpose:    This is called by OpDeletePoints::Do in order to delete a PT_LINETO endpoint
06877                 from a path.
06878     SeeAlso:    OpDeletePoints::Do. OpDeletePoints::DeleteMoveTo. OpDeletePoints::DeleteBezierTo
06879                 
06880 
06881 ********************************************************************************************/
06882 BOOL OpDeletePoints::DeleteLineTo(NodePath* pPath, INT32 Index, BOOL* PathExists)
06883 {
06884     PathVerb* Verbs = pPath->InkPath.GetVerbArray();
06885     PathFlags* Flags = pPath->InkPath.GetFlagArray();
06886     DocCoord* Coords = pPath->InkPath.GetCoordArray();
06887     INT32 NumCoords = pPath->InkPath.GetNumCoords();
06888     PathVerb    TempVerb;
06889     PathFlags   TempFlags;
06890     DocCoord    EndPoint;
06891 
06892     // work differently depending on if this is the last point in the path
06893     if (Index+1 != NumCoords)
06894     {
06895         // See what the next element is
06896         switch (Verbs[Index+1] & ~PT_CLOSEFIGURE)
06897         {
06898         case PT_LINETO:
06899             return DoDeletePathSection(pPath, Index, 1) ;
06900             break;
06901         case PT_BEZIERTO:
06902             // for a line next to a bezier, delete the bezier segment
06903             // and change the line coordinate to go to the end of the bezier
06904             EndPoint = Coords[Index+3]; // Get coords of bez endpt
06905             TempVerb = Verbs[Index];
06906             TempFlags = Flags[Index];
06907             TempFlags.IsSelected = Flags[Index+3].IsSelected;
06908 
06909             // Change the LineTo element to go to the old end pos of the bezier
06910             // setting the selection status of the new point
06911             // Delete whole bezier section
06912             return (DoAlterPathElement(pPath, Index, EndPoint, TempFlags, TempVerb)
06913                                              && DoDeletePathSection(pPath, Index+1, 3));
06914             break;
06915         case PT_MOVETO:
06916             // Next point is a MoveTo so this is the last point in a subpath
06917             // See if the path is closed
06918             if (Verbs[Index] & PT_CLOSEFIGURE)
06919             {
06920                 // The subpath is closed, so do something clever (see below)
06921                 // As a temporary measure to prevent an infinite loop we'll
06922                 // deselect this point 
06923                 return DoChangeSelection(pPath, Index, FALSE) ;
06924             }
06925             else
06926             {
06927                 // This subpath is open, so we either just delete the element
06928                 // or if the previous element is a MoveTo, we delete that as well
06929                 if (Verbs[Index-1] == PT_MOVETO)
06930                     return DoDeletePathSection(pPath, Index-1, 2);
06931                 else
06932                     return DoDeletePathSection(pPath, Index, 1);
06933             }
06934             break;
06935         default :
06936             ERROR2(FALSE, "Corrupted path found in DeleteLineTo");
06937         }
06938     }
06939     else
06940     {
06941         // Here, the point being deleted is the last point in the path
06942         // See if the path is being closed
06943         if (Verbs[Index] & PT_CLOSEFIGURE)
06944         {
06945             // We're deleting the last point of a closed path. HELP!
06946             // This entails deleting the corresponding MoveTo as well
06947             // Which is quite complicated. I'll do that in a minute.
06948             // For now, I'll just clear selection
06949             return DoChangeSelection(pPath, Index, FALSE);
06950         }
06951         else
06952         {
06953             // This is an open path, so the only worry is if this is the only
06954             // line element in the path
06955             if (Verbs[Index-1] == PT_MOVETO)
06956             {
06957                 // Two possiblilities here, either this is the only path, or 
06958                 // it's part of a subpath.
06959 
06960                 if (NumCoords == 2)
06961                 {
06962                     // This is the only path, so delete the whole node
06963                     *PathExists = FALSE;
06964                     return (DoInvalidateNodeRegion(pPath,TRUE,TRUE) && DoHideNode(pPath, TRUE)) ;
06965                 }
06966                 else
06967                 {
06968                     // It's not the only subpath, so just delete these two elements
06969                     return DoDeletePathSection(pPath, Index-1, 2);
06970                 }
06971             }
06972             else
06973             {
06974                 // It's not the only element in the subpath, so just delete it
06975                 return DoDeletePathSection(pPath, Index, 1);
06976             }
06977         }
06978     }
06979 }
06980 
06981 
06982 
06983 /********************************************************************************************
06984 
06985 >   BOOL OpDeletePoints::DeleteMoveTo(NodePath* pPath, INT32 Index, BOOL* PathExists)
06986 
06987     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com> - from Jim code
06988     Created:    27/10/94
06989     Inputs:     pPath - Pointer to the NodePath to delete the endpoint from.
06990                 Index - The number of the endpoint to delete
06991                 PathExists - pointer to a BOOL for flaging that the whole path has been deleted.
06992     Outputs:    PathExists - If the entire path has been deleted, *DeletedPath is set to TRUE
06993     Returns:    FALSE if an error occured during deletion, otherwise TRUE for sucess.
06994     Purpose:    This is called by OpDeletePoints::Do in order to delete a PT_MOVETO endpoint
06995                 from a path.
06996     SeeAlso:    OpDeletePoints::Do. OpDeletePoints::DeleteLineTo. OpDeletePoints::DeleteBezierTo
06997                 
06998 
06999 ********************************************************************************************/
07000 BOOL OpDeletePoints::DeleteMoveTo(NodePath* pPath, INT32 Index, BOOL* PathExists)
07001 {
07002     PathVerb* Verbs = pPath->InkPath.GetVerbArray();
07003     PathFlags* Flags = pPath->InkPath.GetFlagArray();
07004     DocCoord* Coords = pPath->InkPath.GetCoordArray();
07005     INT32 NumCoords = pPath->InkPath.GetNumCoords();
07006     PathFlags   TempFlags;
07007     DocCoord    EndPoint;
07008 
07009     // I think this is slightly simpler than all the others (except for closed)
07010     // First see if this subpath is closed
07011     if (pPath->InkPath.IsSubPathClosed(Index))
07012     {
07013         // Great, the path is closed, so I'll have to do all sorts of difficult things
07014         // (yet again, see above)
07015         // or just deselect it, whichever's easier
07016         if (!DoChangeSelection(pPath, Index, FALSE))
07017             return FALSE;
07018 
07019         // Right, time to bite the bullet and do this.
07020         // This is actually the only occasion when this type of point can be
07021         // deleted - when a path is closed, the endpoint cannot be selected 
07022         // without the start point being selected, so we only have to cope with
07023         // this situation and ensure that we clear the selection bits
07024         // on the corresponding endpoints
07025 
07026         INT32 EndIndex = Index;
07027         pPath->InkPath.FindEndOfSubPath(&EndIndex);
07028 
07029         // There are situations where we have got a two element path, a MoveTo, then either
07030         // a LineTo or a BezierTo.  We must deal with the case of both endpoints being
07031         // selected and remove the entire sub-path.
07032         INT32 SubPathElements = EndIndex - Index;
07033         if (Verbs[EndIndex] == PT_BEZIERTO)
07034             SubPathElements += 2;
07035 
07036         if (((SubPathElements == 1) && (Flags[Index+1].IsSelected) ) ||
07037             ((SubPathElements == 3) && (Flags[Index+3].IsSelected) && ((Verbs[Index+3] & ~PT_CLOSEFIGURE) == PT_BEZIERTO) ) )
07038         {
07039             if (SubPathElements == 1)
07040             {
07041                 return (DoChangeSelection(pPath, Index+1, FALSE) &&
07042                         DoDeletePathSection(pPath, Index, 2) );
07043             }
07044             else
07045             {
07046                 return (DoChangeSelection(pPath, Index+3, FALSE) &&
07047                         DoDeletePathSection(pPath, Index, 4) );
07048             }
07049         }
07050 
07051         // What we have to do here is find the last element, and the first element,
07052         // delete one of them (probably the first one but it might depend on what 
07053         // type of point they are)
07054 
07055         // Now EndIndex tells us where the end element is
07056         // Let's see what the two elements are
07057         PathVerb StartVerb = Verbs[Index+1] & ~PT_CLOSEFIGURE;
07058         PathVerb EndVerb = Verbs[EndIndex] & ~PT_CLOSEFIGURE;
07059 
07060         if  ( StartVerb == PT_BEZIERTO && EndVerb == PT_BEZIERTO )
07061         {
07062             // Both beziers. What we do here is remember the second
07063             // control point of the first bezier, as well as the endpoint,
07064             // then change the second control point and the endpoint
07065             // of the last bezier accordingly
07066             if ( ! (DoAlterPathElement(pPath, EndIndex+1, Coords[Index+2], Flags[Index+2], Verbs[Index+2]) &&
07067                     // Alter the endpoint, keeping the same verb but changing the other elements
07068                     DoAlterPathElement(pPath, EndIndex+2, Coords[Index+3], Flags[Index+3], Verbs[EndIndex+2]) &&
07069                     // Now alter the MoveTo coordinates
07070                     DoAlterPathElement(pPath, Index, Coords[Index+3], Flags[Index], Verbs[Index]) &&
07071                     // And now delete the first curve element altogether
07072                     DoDeletePathSection(pPath, Index+1, 3)
07073                     ) )
07074             {
07075                 return FALSE;
07076             }
07077 
07078         }
07079         else if (StartVerb == PT_LINETO && EndVerb == PT_BEZIERTO)
07080         {
07081             // When one element is a line, we always keep the line
07082             // In this case, we have to change the coords of the 
07083             // moveto to be the same as the start of the final section,
07084             // then delete the final section
07085             if ( ! (DoAlterPathElement(pPath, Index, Coords[EndIndex-1], Flags[Index], Verbs[Index]) &&
07086                     // Alter the element previous to the curve to have the close bit set
07087                     DoAlterPathElement(pPath, EndIndex-1, Coords[EndIndex-1], Flags[EndIndex-1], Verbs[EndIndex-1] | PT_CLOSEFIGURE) &&
07088                     // And delete the curve section
07089                     DoDeletePathSection(pPath, EndIndex, 3)
07090                     ) )
07091             {
07092                 return FALSE;
07093             }
07094         }
07095         else if (StartVerb == PT_BEZIERTO && EndVerb == PT_LINETO)
07096         {
07097             // Similar to the previous case, except the lineto is at the end
07098             // What we do here is set the lineto coord and the moveto coord
07099             // to the the endpoint of the first (curve) element, making sure the
07100             // lineto selection bit is clear.
07101             if ( ! (DoChangeSelection(pPath, EndIndex, Flags[Index+3].IsSelected) &&
07102                     DoAlterPathElement(pPath, EndIndex, Coords[Index+3], Flags[EndIndex], Verbs[EndIndex]) &&
07103                     DoAlterPathElement(pPath, Index, Coords[Index+3], Flags[Index], Verbs[Index]) &&
07104                     // Now delete the curve element
07105                     DoDeletePathSection(pPath, Index+1, 3)
07106                     ) )
07107             {
07108                 return FALSE;
07109             }
07110         }
07111         else
07112         {
07113             // start and end must be linetos
07114             // Alter the final lineto and the moveto to have the coords of
07115             // the end of the first lineto
07116             if ( ! (DoAlterPathElement(pPath, Index, Coords[Index+1], Flags[Index], Verbs[Index]) &&
07117                     DoAlterPathElement(pPath, EndIndex, Coords[Index+1], Flags[EndIndex], Verbs[EndIndex]) &&
07118                     // make sure the endpoint isn't selected
07119                     DoChangeSelection(pPath, EndIndex, Flags[Index+1].IsSelected) &&
07120                     // delete the first lineto
07121                     DoDeletePathSection(pPath, Index+1, 1)
07122                     ) )
07123             {
07124                 return FALSE;
07125             }
07126         }           
07127     
07128         // Now recache the pointers
07129         Coords = pPath->InkPath.GetCoordArray();
07130         Verbs = pPath->InkPath.GetVerbArray();
07131         Flags = pPath->InkPath.GetFlagArray();
07132 
07133         // The last thing I have to do is check the selected status of the new endpoint
07134         // and select the MoveTo if it is selected, and deselect the moveto otherwise
07135         // Index is still valid for the start of the path
07136         EndIndex = Index;
07137         pPath->InkPath.FindEndOfSubPath(&EndIndex);
07138         BOOL IsSelected;
07139         if ((Verbs[EndIndex] & ~PT_CLOSEFIGURE) == PT_BEZIERTO)
07140             IsSelected = Flags[EndIndex+2].IsSelected;
07141         else
07142             IsSelected = Flags[EndIndex].IsSelected;
07143 
07144         // Change the selection of the MoveTo to match the selection
07145         // of the endpoint in this closed path
07146         return DoChangeSelection(pPath, Index, IsSelected);
07147     }
07148     else
07149     {
07150         // The path isn't closed. 
07151         // Are we looking at the start of a curve or a line?
07152         if ((Verbs[Index+1] & ~ PT_CLOSEFIGURE) == PT_BEZIERTO)
07153         {
07154             // We're deleting this bezier segment. We have to check if this is a lone element
07155             if (Index+4 == NumCoords || Verbs[Index+4] == PT_MOVETO)
07156             {
07157                 // It's a lone element, so either delete the subpath or delete the whole node
07158                 if (NumCoords == 4)
07159                 {
07160                     // It's all there is in the path, so delete the whole thing
07161                     *PathExists = FALSE;
07162                     return (DoInvalidateNodeRegion(pPath,TRUE,TRUE) && DoHideNode(pPath, TRUE)) ;
07163                 }
07164                 else
07165                 {
07166                     return DoDeletePathSection(pPath, Index, 4);
07167                 }
07168             }
07169             else
07170             {
07171                 // It's not a lone element, so delete the curveto, and change the MoveTo
07172                 // so that it now moves to the endpoint of the curve
07173                 EndPoint = Coords[Index+3];
07174 
07175                 // Look at the selected bit of the end of the curve, and copy that
07176                 // to the moveto, in case that point wanted deleting as well
07177                 BOOL WasSelected = Flags[Index+3].IsSelected;
07178 
07179                 // Delete the section containing the curve
07180                 PathFlags TempFlags = Flags[Index];
07181                 TempFlags.IsSelected = WasSelected;
07182                 PathVerb TempVerb = Verbs[Index];
07183                 if (!DoDeletePathSection(pPath, Index+1, 3))
07184                     return FALSE;
07185 
07186                 // Alter the MoveTo element so coords and flags are correct
07187                 return DoAlterPathElement(pPath, Index, EndPoint, TempFlags, TempVerb);
07188             }
07189         }
07190         else
07191         {
07192             // Since we can't have a moveto following a moveto, the next element
07193             // has to be a path (let's check that with an ENSURE)
07194             ERROR2IF(((Verbs[Index+1] & ~PT_CLOSEFIGURE) != PT_LINETO), FALSE, "Badly formed path in DeletePoint");
07195 
07196             // Check if it's a single element
07197             if (Index+2 == NumCoords || Verbs[Index+2] == PT_MOVETO)
07198             {
07199                 // It's a lone element so either delete the subpath or the whole path
07200                 // See if this is the only subpath
07201                 if (NumCoords == 2)
07202                 {
07203                     // This is the only element in the path, so hide the whole node
07204                     *PathExists = FALSE;
07205                     return (DoInvalidateNodeRegion(pPath,TRUE,TRUE) && DoHideNode(pPath, TRUE)) ;
07206                 }
07207                 else
07208                 {
07209                     // Just delete this subpath
07210                     return DoDeletePathSection(pPath, Index, 2);
07211                 }
07212             }
07213             else
07214             {
07215                 // There is another element after this, so delete the line element
07216                 // and alter the Moveto element so that it goes to where the endpoint
07217                 // used to go
07218                 EndPoint = Coords[Index+1];
07219 
07220                 // Look at the selected bit of the line element
07221                 BOOL WasSelected = Flags[Index+1].IsSelected;
07222 
07223                 // Delete the section containing the line
07224                 PathFlags TempFlags = Flags[Index];
07225                 TempFlags.IsSelected = WasSelected;
07226                 PathVerb TempVerb = Verbs[Index];
07227                 if (!DoDeletePathSection(pPath, Index+1, 1))
07228                     return FALSE;
07229 
07230                 // Alter the MoveTo so that it goes to the position of the lineto, and it's selected
07231                 // if necessary
07232                 return DoAlterPathElement(pPath, Index, EndPoint, TempFlags, TempVerb);
07233             }
07234         }
07235     }
07236 }
07237 
07238 
07239 
07240 
07241 /********************************************************************************************
07242 >   BOOL OpDeletePoints::DeleteBezierTo(NodePath* pPath, INT32 Index, BOOL* PathExists)
07243 
07244     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com> - from Jim code
07245     Created:    27/10/94
07246     Inputs:     pPath - Pointer to the NodePath to delete the endpoint from.
07247                 Index - The number of the endpoint to delete
07248                 PathExists - pointer to a BOOL for flaging that the whole path has been deleted.
07249     Outputs:    PathExists - If the entire path has been deleted, *DeletedPath is set to TRUE
07250     Returns:    FALSE if an error occured during deletion, otherwise TRUE for sucess.
07251     Purpose:    This is called by OpDeletePoints::Do in order to delete a PT_BEZIERTO endpoint
07252                 from a path.
07253     SeeAlso:    OpDeletePoints::Do. OpDeletePoints::DeleteMoveTo. OpDeletePoints::DeleteLineTo
07254 ********************************************************************************************/
07255 BOOL OpDeletePoints::DeleteBezierTo(NodePath* pPath, INT32 Index, BOOL* PathExists)
07256 {
07257     PathVerb* Verbs = pPath->InkPath.GetVerbArray();
07258 //  PathFlags* Flags = pPath->InkPath.GetFlagArray();
07259 //  DocCoord* Coords = pPath->InkPath.GetCoordArray();
07260     INT32 NumCoords = pPath->InkPath.GetNumCoords();
07261     PathFlags   TempFlags;
07262     DocCoord    EndPoint;
07263 
07264     // See if this is the end of the path
07265     if (Index+1 != NumCoords)
07266     {
07267         // See what the next element is
07268         switch (Verbs[Index+1] & ~ PT_CLOSEFIGURE)
07269         {
07270         case PT_LINETO:
07271             // A curve followed by a line. Just delete the curve.
07272             return DoDeletePathSection(pPath, Index-2, 3);
07273             break;
07274         case PT_BEZIERTO:
07275             // A curve followed by a curve. First delete the second control point,
07276             // the endpoint, and the first control point of the next (curve) element
07277             // Then we should smooth it somehow (later)
07278             return DoDeletePathSection(pPath, Index-1, 3);
07279             break;
07280         case PT_MOVETO:
07281             // Tricky again, the next point is a MoveTo, so we have to check all the special
07282             // cases like closed paths, paths with single elements, etc.
07283             if (Verbs[Index] & PT_CLOSEFIGURE)
07284             {
07285                 // The path is closed, it's fiddle time again (see above)
07286                 // For now, deselect it
07287                 return DoChangeSelection(pPath, Index, FALSE);
07288             }
07289             else
07290             {
07291                 // At least the path isn't closed. We might safely be able to delete this
07292                 // section, as long as the previous element isn't a MoveTo, in which case
07293                 // we have to delete the whole subpath
07294                 if (Verbs[Index-3] == PT_MOVETO)
07295                 {
07296                     // Delete the whole subpath
07297                     return DoDeletePathSection(pPath, Index-3, 4);
07298                 }
07299                 else
07300                 {
07301                     // Only delete the curve section
07302                     return DoDeletePathSection(pPath, Index-2, 3);
07303                 }
07304             }
07305             break;
07306         default:
07307             ERROR2(FALSE,"Corrupt path found in DeleteBezierTo");
07308         }
07309     }
07310     else
07311     {
07312         // This is the case when we're at the end of the path
07313         // Is the path a closed one
07314         if (Verbs[Index] & PT_CLOSEFIGURE)
07315         {
07316             // the path is closed. This is still tricky (see above and above)
07317             // so trick that I'll just deselect it
07318             return DoChangeSelection(pPath, Index, FALSE);
07319         }
07320         else
07321         {
07322             // The path isn't closed, so check for this being the only element in
07323             // the path
07324             if (Verbs[Index-3] == PT_MOVETO)
07325             {
07326                 // Either this could be one of many subpaths, or the only subpath
07327                 if (NumCoords == 4)
07328                 {
07329                     // This is the only subpath, it only has one element, so delete
07330                     // the whole path
07331                     *PathExists = FALSE;
07332                     return (DoInvalidateNodeRegion(pPath,TRUE,TRUE) && DoHideNode(pPath, TRUE));
07333                 }
07334                 else
07335                 {
07336                     // There are other subpaths, so just delete this one
07337                     return DoDeletePathSection(pPath, Index-3, 4);
07338                 }
07339             }
07340             else
07341             {
07342                 // It's not the only element in the path so delete it
07343                 return DoDeletePathSection(pPath, Index-2, 3);
07344             }
07345         }
07346     }
07347 }
07348 
07349 
07350 
07351 /********************************************************************************************
07352 >   static BOOL OpDeletePoints::WillDeleteEntirePath(NodePath* pTreePath)
07353 
07354     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
07355     Created:    29/04/95
07356     Inputs:     Points to a NodePath in the tree
07357     Outputs:    -
07358     Returns:    TRUE if a call to DeletePoints will result in the entire path being deleted
07359                 FALSE if some of the path will remain in the document
07360     Purpose:    Sees if some of the path will remain after a delete points operation
07361     Errors:     -
07362     SeeAlso:    OpDeletePoints::GetState
07363 ********************************************************************************************/
07364 BOOL OpDeletePoints::WillDeleteEntirePath(NodePath* pTreePath)
07365 {   
07366     // If the path has no selected endpoints then it won't be deleted
07367     if (!pTreePath->InkPath.IsSubSelection())
07368         return FALSE;
07369 
07370     // Get the path pointers;
07371     INT32 NumCoords = pTreePath->InkPath.GetNumCoords();
07372     PathVerb* Verbs = NULL;
07373     DocCoord* Coords = NULL;
07374     PathFlags* Flags = NULL;
07375     pTreePath->InkPath.GetPathArrays(&Verbs, &Coords, &Flags);
07376     BOOL PathStillExists = TRUE;
07377 
07378     // We need to scan all the endpoints.  If there are ALL selected then we can 
07379     // just delete the whole path.
07380     INT32 loop = 0;
07381     BOOL Selected = TRUE;
07382     while (Selected && (loop < NumCoords))
07383     {
07384         if (Flags[loop].IsEndPoint && !Flags[loop].IsSelected)
07385             Selected = FALSE;
07386         loop++;
07387     }
07388     if (Selected)
07389         return TRUE;
07390 
07391     // Make a copy of the path before testing the delete
07392     NodePath* pPath = (NodePath*)pTreePath->SimpleCopy();
07393     if (pPath == NULL)
07394         return FALSE;
07395 
07396     // We have to delete every selected endpoint in the path
07397     while (PathStillExists)
07398     {
07399         // First refresh the pointers to the arrays
07400         NumCoords = pPath->InkPath.GetNumCoords();
07401         pPath->InkPath.GetPathArrays(&Verbs, &Coords, &Flags);
07402 
07403         // Scan for a selected endpoint
07404         INT32           i;
07405         for ( i =0;i<NumCoords && (!Flags[i].IsSelected || !Flags[i].IsEndPoint);i++)
07406             ;   // ; deliberate
07407 
07408         // break out if we didn't find an endpoint
07409         if (i == NumCoords)
07410             break;
07411 
07412         // i indexes into a selected endpoint which we have to delete.
07413         switch (Verbs[i] & ~PT_CLOSEFIGURE)
07414         {
07415             case PT_LINETO:
07416                 TryDeleteLineTo(pPath, i, &PathStillExists);
07417                 break;
07418             case PT_BEZIERTO:
07419                 TryDeleteBezierTo(pPath, i, &PathStillExists);
07420                 break;
07421             case PT_MOVETO:
07422                 TryDeleteMoveTo(pPath, i, &PathStillExists);
07423                 break;
07424             default:
07425                 ERROR3("What was that path verb?");
07426         }
07427     }
07428 
07429     // now that we have deleted every selected point on the path we need to check 
07430     // the path to see if is still valid
07431     if (PathStillExists)
07432         PathStillExists = pPath->IsPathAllowable();
07433 
07434     delete pPath;
07435 
07436     return !PathStillExists;
07437 }
07438 
07439          
07440 
07441 /********************************************************************************************
07442 >   static BOOL OpDeletePoints::TryDeleteLineTo(NodePath* pPath, INT32 Index, BOOL* PathExists)
07443 
07444     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com> - from Jim code
07445     Created:    29/04/95
07446     Inputs:     pPath - Pointer to the NodePath to delete the endpoint from.
07447                 Index - The number of the endpoint to delete
07448                 PathExists - pointer to a BOOL for flaging that the whole path has been deleted.
07449     Outputs:    PathExists - If the entire path has been deleted, *DeletedPath is set to TRUE
07450     Returns:    FALSE if an error occured during deletion, otherwise TRUE for sucess.
07451     Purpose:    This is called by OpDeletePoints::WillDeletePath in order to delete a
07452                 PT_LINETO endpoint from a path.
07453     SeeAlso:    OpDeletePoints::WillDeletePath. OpDeletePoints::DeleteMoveTo.
07454                 OpDeletePoints::DeleteBezierTo
07455 ********************************************************************************************/
07456 BOOL OpDeletePoints::TryDeleteLineTo(NodePath* pPath, INT32 Index, BOOL* PathExists)
07457 {
07458     // Get the path pointers
07459     INT32 NumCoords = pPath->InkPath.GetNumCoords();
07460     PathVerb* Verbs = NULL;
07461     DocCoord* Coords = NULL;
07462     PathFlags* Flags = NULL;
07463     pPath->InkPath.GetPathArrays(&Verbs, &Coords, &Flags);
07464     
07465     // work differently depending on if this is the last point in the path
07466     if (Index+1 != NumCoords)
07467     {
07468         // See what the next element is
07469         switch (Verbs[Index+1] & ~PT_CLOSEFIGURE)
07470         {
07471             case PT_LINETO:
07472                 // Delete the current line, createing a line from Index-1 to Index +1
07473                 return (pPath->InkPath.DeleteSection(Index, 1));
07474                 break;
07475             case PT_BEZIERTO:
07476                 // Delete the folloing curve, creating a line from Index-1 to Index+3
07477                 Flags[Index].IsSelected = Flags[Index+3].IsSelected;
07478                 Coords[Index] = Coords[Index+3];
07479                 return pPath->InkPath.DeleteSection(Index+1, 3);
07480                 break;
07481             case PT_MOVETO:
07482                 // Next point is a MoveTo so this is the last point in a subpath
07483                 if (Verbs[Index] & PT_CLOSEFIGURE)
07484                 {
07485                     // Subpath is closed, leave it it to the next moveto
07486                     Flags[Index].IsSelected = FALSE;
07487                     return TRUE;
07488                 }
07489                 else
07490                 {
07491                     // This subpath is open, so we either just delete the element
07492                     // or if the previous element is a MoveTo, we delete that as well
07493                     if (Verbs[Index-1] == PT_MOVETO)
07494                         return (pPath->InkPath.DeleteSection(Index-1, 2));
07495                     else
07496                         return (pPath->InkPath.DeleteSection(Index, 1));
07497                 }
07498                 break;
07499             default :
07500                 ERROR2(FALSE, "Corrupted path found in DeleteLineTo");
07501         }
07502     }
07503     else
07504     {
07505         // Here, the point being deleted is the last point in the path
07506         // See if the path is closed
07507         if (Verbs[Index] & PT_CLOSEFIGURE)
07508         {
07509             // Just deselect the last point
07510             Flags[Index].IsSelected = FALSE;
07511             return TRUE;
07512         }
07513         else
07514         {
07515             if (Verbs[Index-1] == PT_MOVETO)
07516             {
07517                 if (NumCoords == 2)
07518                 {
07519                     // This is the only path, so delete the whole node
07520                     *PathExists = FALSE;
07521                     return TRUE;
07522                 }
07523                 else
07524                 {
07525                     // It's not the only subpath, so just delete these two elements
07526                     return (pPath->InkPath.DeleteSection(Index-1, 2));
07527                 }
07528             }
07529             else
07530             {
07531                 // It's not the only element in the subpath, so just delete it
07532                 return (pPath->InkPath.DeleteSection(Index, 1));
07533             }
07534         }
07535     }
07536 }
07537 
07538 
07539 
07540 /********************************************************************************************
07541 >   BOOL OpDeletePoints::TryDeleteMoveTo(NodePath* pPath, INT32 Index, BOOL* PathExists)
07542 
07543     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com> - from Jim code
07544     Created:    27/10/94
07545     Inputs:     pPath - Pointer to the NodePath to delete the endpoint from.
07546                 Index - The number of the endpoint to delete
07547                 PathExists - pointer to a BOOL for flaging that the whole path has been deleted.
07548     Outputs:    PathExists - If the entire path has been deleted, *DeletedPath is set to TRUE
07549     Returns:    FALSE if an error occured during deletion, otherwise TRUE for sucess.
07550     Purpose:    This is called by OpDeletePoints::WillDeletePath in order to delete a PT_MOVETO endpoint
07551                 from a path.
07552     SeeAlso:    OpDeletePoints::WillDeletePath. OpDeletePoints::DeleteLineTo. OpDeletePoints::DeleteBezierTo
07553 ********************************************************************************************/
07554 BOOL OpDeletePoints::TryDeleteMoveTo(NodePath* pPath, INT32 Index, BOOL* PathExists)
07555 {
07556     // Get the path pointers
07557     INT32 NumCoords = pPath->InkPath.GetNumCoords();
07558     PathVerb* Verbs = NULL;
07559     DocCoord* Coords = NULL;
07560     PathFlags* Flags = NULL;
07561     pPath->InkPath.GetPathArrays(&Verbs, &Coords, &Flags);
07562 
07563     // See if this subpath is closed
07564     if (pPath->InkPath.IsSubPathClosed(Index))
07565     {
07566         // Deselect it
07567         Flags[Index].IsSelected = FALSE;
07568 
07569         // Get the index of the other end of the subpath
07570         INT32 EndIndex = Index;
07571         pPath->InkPath.FindEndOfSubPath(&EndIndex);
07572 
07573         // There are situations where we have got a two element path, a MoveTo, then either
07574         // a LineTo or a BezierTo.  We must deal with the case of both endpoints being
07575         // selected and remove the entire sub-path.
07576         INT32 SubPathElements = EndIndex - Index;
07577         if (Verbs[EndIndex] == PT_BEZIERTO)
07578             SubPathElements += 2;
07579 
07580         if (((SubPathElements == 1) && (Flags[Index+1].IsSelected) ) ||
07581             ((SubPathElements == 3) && (Flags[Index+3].IsSelected) && ((Verbs[Index+3] & ~PT_CLOSEFIGURE) == PT_BEZIERTO) ) )
07582         {
07583             if (SubPathElements == 1)
07584             {
07585                 Flags[Index+1].IsSelected = FALSE;
07586                 return (pPath->InkPath.DeleteSection(Index, 2));
07587             }
07588             else
07589             {
07590                 Flags[Index+3].IsSelected = FALSE;
07591                 return (pPath->InkPath.DeleteSection(Index, 4));
07592             }
07593         }
07594 
07595         // Now EndIndex tells us where the end element is
07596         PathVerb StartVerb = Verbs[Index+1] & ~PT_CLOSEFIGURE;
07597         PathVerb EndVerb = Verbs[EndIndex] & ~PT_CLOSEFIGURE;
07598 
07599         if  ( StartVerb == PT_BEZIERTO && EndVerb == PT_BEZIERTO )
07600         {
07601             // Both beziers. What we do here is remember the second
07602             // control point of the first bezier, as well as the endpoint,
07603             // then change the second control point and the endpoint
07604             // of the last bezier accordingly
07605             Coords[EndIndex+1] = Coords[Index+2];
07606             Flags[EndIndex+1] = Flags[Index+2];
07607             Verbs[EndIndex+1] = Verbs[Index+2];
07608 
07609             Coords[EndIndex+2] = Coords[Index+3];
07610             Flags[EndIndex+2] = Flags[Index+3];
07611 
07612             Coords[Index] = Coords[Index+3];
07613 
07614             return pPath->InkPath.DeleteSection(Index+1, 3);
07615         }
07616         else if (StartVerb == PT_LINETO && EndVerb == PT_BEZIERTO)
07617         {
07618             // When one element is a line, we always keep the line
07619             // In this case, we have to change the coords of the 
07620             // moveto to be the same as the start of the final section,
07621             // then delete the final section
07622             Coords[Index] = Coords[EndIndex-1];
07623 
07624             Verbs[EndIndex-1] = Verbs[EndIndex-1] | PT_CLOSEFIGURE;
07625 
07626             Coords[Index] = Coords[Index+3];
07627 
07628             return pPath->InkPath.DeleteSection(EndIndex, 3);
07629         }
07630         else if (StartVerb == PT_BEZIERTO && EndVerb == PT_LINETO)
07631         {
07632             // Similar to the previous case, except the lineto is at the end
07633             // What we do here is set the lineto coord and the moveto coord
07634             // to the the endpoint of the first (curve) element, making sure the
07635             // lineto selection bit is clear.
07636             Flags[EndIndex].IsSelected = Flags[Index+3].IsSelected;
07637 
07638             Coords[EndIndex] = Coords[Index+3];
07639 
07640             Coords[Index] = Coords[Index+3];
07641 
07642             return pPath->InkPath.DeleteSection(Index+1, 3);
07643         }
07644         else
07645         {
07646             // start and end must be linetos
07647             // Alter the final lineto and the moveto to have the coords of
07648             // the end of the first lineto
07649             Coords[Index] = Coords[Index+1];
07650 
07651             Coords[EndIndex] = Coords[Index+1];
07652 
07653             Flags[EndIndex].IsSelected = Flags[Index+1].IsSelected;
07654 
07655             return pPath->InkPath.DeleteSection(Index+1, 1);
07656         }           
07657     
07658         // Recache the pointers
07659         pPath->InkPath.GetPathArrays(&Verbs, &Coords, &Flags);
07660 
07661         // The last thing I have to do is check the selected status of the new endpoint
07662         // and select the MoveTo if it is selected, and deselect the moveto otherwise
07663         // Index is still valid for the start of the path
07664         EndIndex = Index;
07665         pPath->InkPath.FindEndOfSubPath(&EndIndex);
07666         BOOL IsSelected;
07667         if ((Verbs[EndIndex] & ~PT_CLOSEFIGURE) == PT_BEZIERTO)
07668             IsSelected = Flags[EndIndex+2].IsSelected;
07669         else
07670             IsSelected = Flags[EndIndex].IsSelected;
07671 
07672         // Change the selection of the MoveTo to match the selection
07673         // of the endpoint in this closed path
07674         Flags[Index].IsSelected = IsSelected;
07675     }
07676     else
07677     {
07678         // The path isn't closed. 
07679         // Are we looking at the start of a curve or a line?
07680         if ((Verbs[Index+1] & ~ PT_CLOSEFIGURE) == PT_BEZIERTO)
07681         {
07682             // We're deleting this bezier segment. We have to check if this is a lone element
07683             if (Index+4 == NumCoords || Verbs[Index+4] == PT_MOVETO)
07684             {
07685                 // It's a lone element, so either delete the subpath or delete the whole node
07686                 if (NumCoords == 4)
07687                 {
07688                     *PathExists = FALSE;
07689                     return TRUE;
07690                 }
07691                 else
07692                     return pPath->InkPath.DeleteSection(Index, 4); 
07693             }
07694             else
07695             {
07696                 Coords[Index] = Coords[Index+3];
07697                 Flags[Index].IsSelected = Flags[Index+3].IsSelected;
07698                 
07699                 return pPath->InkPath.DeleteSection(Index+1, 3);
07700             }
07701         }
07702         else
07703         {
07704             // Since we can't have a moveto following a moveto, the next element
07705             // has to be a path (let's check that with an ENSURE)
07706             ERROR2IF(((Verbs[Index+1] & ~PT_CLOSEFIGURE) != PT_LINETO), FALSE, "Badly formed path in DeletePoint");
07707 
07708             // Check if it's a single element
07709             if (Index+2 == NumCoords || Verbs[Index+2] == PT_MOVETO)
07710             {
07711                 // It's a lone element so either delete the subpath or the whole path
07712                 // See if this is the only subpath
07713                 if (NumCoords == 2)
07714                 {
07715                     *PathExists = FALSE;
07716                     return TRUE;
07717                 }
07718                 else
07719                     return pPath->InkPath.DeleteSection(Index, 2);
07720             }
07721             else
07722             {
07723                 // There is another element after this, so delete the line element
07724                 // and alter the Moveto element so that it goes to where the endpoint
07725                 // used to go
07726                 Coords[Index] = Coords[Index+1];
07727                 Flags[Index].IsSelected = Flags[Index+1].IsSelected;
07728 
07729                 return pPath->InkPath.DeleteSection(Index+1, 1);
07730             }
07731         }
07732     }
07733 }
07734 
07735 
07736 
07737 /********************************************************************************************
07738 >   BOOL OpDeletePoints::TryDeleteBezierTo(NodePath* pPath, INT32 Index, BOOL* PathExists)
07739 
07740     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com> - from Jim code
07741     Created:    27/10/94
07742     Inputs:     pPath - Pointer to the NodePath to delete the endpoint from.
07743                 Index - The number of the endpoint to delete
07744                 PathExists - pointer to a BOOL for flaging that the whole path has been deleted.
07745     Outputs:    PathExists - If the entire path has been deleted, *DeletedPath is set to TRUE
07746     Returns:    FALSE if an error occured during deletion, otherwise TRUE for sucess.
07747     Purpose:    This is called by OpDeletePoints::WillDeletePath in order to delete a
07748                 PT_BEZIERTO endpoint from a path.
07749     SeeAlso:    OpDeletePoints::WillDeletePath. OpDeletePoints::DeleteMoveTo.
07750                 OpDeletePoints::DeleteLineTo
07751 ********************************************************************************************/
07752 BOOL OpDeletePoints::TryDeleteBezierTo(NodePath* pPath, INT32 Index, BOOL* PathExists)
07753 {
07754     PathVerb* Verbs = pPath->InkPath.GetVerbArray();
07755     PathFlags* Flags = pPath->InkPath.GetFlagArray();
07756 //  DocCoord* Coords = pPath->InkPath.GetCoordArray();
07757     INT32 NumCoords = pPath->InkPath.GetNumCoords();
07758     PathFlags   TempFlags;
07759     DocCoord    EndPoint;
07760 
07761     // See if this is the end of the path
07762     if (Index+1 != NumCoords)
07763     {
07764         // See what the next element is
07765         switch (Verbs[Index+1] & ~PT_CLOSEFIGURE)
07766         {
07767             case PT_LINETO:
07768                 // A curve followed by a line. Just delete the curve.
07769                 return pPath->InkPath.DeleteSection(Index-2, 3);
07770                 break;
07771             case PT_BEZIERTO:
07772                 // A curve followed by a curve. First delete the second control point,
07773                 // the endpoint, and the first control point of the next (curve) element
07774                 return pPath->InkPath.DeleteSection(Index-1, 3);
07775                 break;
07776             case PT_MOVETO:
07777                 // Tricky again, the next point is a MoveTo, so we have to check all the special
07778                 // cases like closed paths, paths with single elements, etc.
07779                 if (Verbs[Index] & PT_CLOSEFIGURE)
07780                 {
07781                     // The path is closed, For now, deselect it
07782                     Flags[Index].IsSelected = FALSE;
07783                     return TRUE;
07784                 }
07785                 else
07786                 {
07787                     // We might safely be able to delete this
07788                     // section, as long as the previous element isn't a MoveTo, in which case
07789                     // we have to delete the whole subpath
07790                     if (Verbs[Index-3] == PT_MOVETO)
07791                     {
07792                         // Delete the whole subpath
07793                         return pPath->InkPath.DeleteSection(Index-3, 4);
07794                     }
07795                     else
07796                     {
07797                         // Only delete the curve section
07798                         return pPath->InkPath.DeleteSection(Index-2, 3);
07799                     }
07800                 }
07801                 break;
07802             default:
07803                 ERROR2(FALSE,"Corrupt path found in DeleteBezierTo");
07804         }
07805     }
07806     else
07807     {
07808         // This is the case when we're at the end of the path
07809         if (Verbs[Index] & PT_CLOSEFIGURE)
07810         {
07811             // the path is closed. This is still tricky (see above and above)
07812             Flags[Index].IsSelected = FALSE;
07813             return TRUE;
07814         }
07815         else
07816         {
07817             // The path isn't closed, so check for this being the only element in the path
07818             if (Verbs[Index-3] == PT_MOVETO)
07819             {
07820                 // Either this could be one of many subpaths, or the only subpath
07821                 if (NumCoords == 4)
07822                 {
07823                     *PathExists = FALSE;
07824                     return TRUE;
07825                 }
07826                 else
07827                 {
07828                     // There are other subpaths, so just delete this one
07829                     return pPath->InkPath.DeleteSection(Index-3, 4);
07830                 }
07831             }
07832             else
07833             {
07834                 // It's not the only element in the path so delete it
07835                 return pPath->InkPath.DeleteSection(Index-2, 3);
07836             }
07837         }
07838     }
07839 }
07840 
07841 
07842 
07844 // OpReshapeOrAddPoint
07845 
07846 
07847 /********************************************************************************************
07848 
07849 >   void OpReshapeOrAddPoint::DoStartDragEdit(NodePath* OrigPath, DocCoord Anchor,
07850                                                     Spread *pSpread, INT32 ControlIndex,
07851                                                     double pdist)
07852 
07853     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
07854     Created:    5/7/93
07855     Inputs:     OrigPath - The original Node path object
07856                 Anchor - The position of the mouse at the start of the Drag
07857                 pSpread - the spread that the drag started on
07858                 ControlIndex - The Coord number of the control point that we are dragging
07859                                this is the index of the first element of the segment plus 1
07860                 pdist - parametric distance along the curve we're dragging from (0 to 1)
07861     Purpose:    This is called when a Drag operation has been started on the middle of a path.
07862                 It will try and reshape the curve as it's dragging according to the maths in the 
07863                 function RecalculatePath
07864     SeeAlso:    OpReshapeOrAddPoint::RecalculatePath
07865                 
07866 
07867 ********************************************************************************************/
07868 
07869 void OpReshapeOrAddPoint::DoStartDragEdit(NodePath* OrigPath, DocCoord Anchor,
07870                                                 Spread *pSpread, INT32 SegmentIndex, double pdist)
07871 {
07872     // We had better take a note of the starting point of the drag
07873     LastMousePos = Anchor;
07874     StartMousePos = Anchor;
07875     FurthestPoint = Anchor;
07876     StartSpread  = pSpread;
07877     OriginalPath = OrigPath;
07878     Index = SegmentIndex;
07879     paramdist = pdist;
07880 
07881     CreateCursors();
07882 
07883     // We also need to make a version of the path that we can change
07884     if (!BuildEditPath())
07885     {
07886         // Failed to get the memory that we needed to copy the path, so moan
07887         TRACEUSER( "Jim", _T("Failed to get the mem to copy the path data for editing\n") );
07888         
07889         // Inform the person doing the clicking that life is not looking so good
07890         InformError( _R(IDS_OUT_OF_MEMORY), _R(IDS_OK) );
07891         End();
07892         return;
07893     }
07894 
07895     // Render the bits of the path that are different
07896     DocRect EditPathBBox = EditPath.GetBoundingRect();
07897     RenderPathEditBlobs(EditPathBBox, pSpread);
07898 
07899     // Record the original positions of the two control points
07900     DocCoord* Coords = EditPath.GetCoordArray();
07901     PathVerb* Verbs = EditPath.GetVerbArray();
07902     if (Verbs[Index] == PT_BEZIERTO)
07903     {
07904         OrigControl1 = Coords[Index];
07905         OrigControl2 = Coords[Index+1];
07906     }
07907     else
07908     {
07909         ERROR3("Control Point wasn't a control point!");
07910     }
07911 
07912     // And tell the Dragging system that we need drags to happen
07913     StartDrag(DRAGTYPE_AUTOSCROLL, &EditPathBBox, &LastMousePos);
07914 
07915     SetStatusLineHelp();
07916 }
07917 
07918 
07919 
07920 /********************************************************************************************
07921 
07922 >   void OpReshapeOrAddPoint::DragPointerMove( DocCoord PointerPos, 
07923                                                 ClickModifiers ClickMods, Spread *pSpread, BOOL bSolidDrag)
07924 
07925     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
07926     Created:    5/7/93
07927     Inputs:     PointerPos - The current position of the mouse in Doc Coords
07928                 ClickMods - Which key modifiers are being pressed
07929                 pSpread - the spread that the cursor is over now
07930     Purpose:    This is called every time the mouse moves, during a drag.
07931     SeeAlso:    ClickModifiers
07932 
07933 ********************************************************************************************/
07934 
07935 void OpReshapeOrAddPoint::DragPointerMove( DocCoord PointerPos, ClickModifiers ClickMods, Spread *pSpread, BOOL bSolidDrag)
07936 {
07937     EndSnapped = FALSE;     // Always false in this operation
07938     
07939     // If drag has moved onto a different spread, convert the coord to be relative to the
07940     // original spread.
07941     if (pSpread != StartSpread)
07942         PointerPos = MakeRelativeToSpread(StartSpread, pSpread, PointerPos);
07943 
07944     // Now snap it to the current grid
07945     DocView::SnapCurrent(StartSpread,&PointerPos);
07946 
07947     // Rub out the old EORed version of the path
07948     RenderPathEditBlobs( EditPath.GetBoundingRect(), StartSpread );
07949 
07950     // This is the bit where we go off and re-calculate the paths position,
07951     // based on how much the mouse has moved
07952     DocCoord Offset;
07953     Offset.x = PointerPos.x - StartMousePos.x;
07954     Offset.y = PointerPos.y - StartMousePos.y;
07955     RecalculatePath( Offset );
07956 
07957     // Compare this point with the furthest point so far
07958     if (PointerPos.Distance(StartMousePos) > FurthestPoint.Distance(StartMousePos))
07959     {
07960         FurthestPoint.x = PointerPos.x;
07961         FurthestPoint.y = PointerPos.y;
07962     }
07963 
07964     // Draw in the new version of the path and update the Last Mouse Position
07965     LastMousePos = PointerPos;
07966     RenderPathEditBlobs( EditPath.GetBoundingRect(), StartSpread );
07967 
07968     SetStatusLineHelp();
07969 }
07970 
07971 
07972 /********************************************************************************************
07973 
07974 >   void OpReshapeOrAddPoint::DragFinished( DocCoord PointerPos, ClickModifiers ClickMods, BOOL Success, BOOL bSolidDrag)
07975 
07976     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
07977     Created:    30/8/94
07978     Inputs:     PointerPos - The position of the mouse at the end of the drag
07979                 ClickMods - the key modifiers being pressed
07980                 Success - TRUE if the drag was terminated properly, FALSE if it
07981                 was ended with the escape key being pressed
07982     Purpose:    This is called when a drag operation finishes.
07983     SeeAlso:    ClickModifiers
07984 
07985 ********************************************************************************************/
07986 
07987 void OpReshapeOrAddPoint::DragFinished( DocCoord PointerPos, ClickModifiers ClickMods, 
07988                                        Spread* pSpread, BOOL Success, BOOL bSolidDrag)
07989 {
07990     RenderPathEditBlobs( EditPath.GetBoundingRect(), StartSpread );
07991     EndDrag();
07992     DestroyCursors();
07993     BeginSlowJob();
07994     
07995     BOOL Worked = TRUE;
07996 
07997     if ( Success )
07998     {
07999         ObjChangeFlags cFlags;
08000         cFlags.TransformNode = TRUE;
08001         ObjChangeParam ObjChange(OBJCHANGE_STARTING,cFlags,OriginalPath,this);
08002 
08003         Worked = DoStartSelOp(TRUE,TRUE) ;
08004 
08005         // Create and send a change message about this path edit
08006         if (Worked)
08007         {
08008             Worked = OriginalPath->AllowOp(&ObjChange) ;
08009         }
08010 
08011         // If the furthest mouse position is close to the start, add a point instead
08012         if (Worked)
08013         {
08014 //          // Store the paths sub-selection state
08015 //          if (Worked)
08016 //              Worked = (StorePathSubSelStateAction::DoRecord(this, &UndoActions, &OriginalPath->InkPath) != AC_FAIL);
08017 
08018             // Due to popular request, I've changed this test so that moving by even a pixel
08019             // will reshape the line, so a single click has to be a single click to add a 
08020             // point.
08021             if (StartMousePos == FurthestPoint)
08022             {
08023                 Worked = DragFinishedAddPoint(); 
08024             }
08025             else
08026             {
08027                 BOOL Optimise = CopyNeedToRenderFlags() && !(EndSnapped || SnapToAnother);
08028 
08029                 // Check we are dealing with a node path. If not, we shouldn't optimise the redraw.
08030                 // Really we should ask the node whether it can cope with optimised redraw. This means
08031                 // whether its correct simply to redraw the changed rectangular section around the moved
08032                 // coordinate. For envelope and perspective moulds whose shapes are govened by derived
08033                 // path objects its not correct to redraw optimally as this can result in sections of the
08034                 // object being left undrawn when the whole surface / contents have changed.
08035 
08036                 if (!IS_A(OriginalPath,NodePath))
08037                     Optimise = FALSE;
08038 
08039                 // Force a re-draw of the place where the path used to be
08040                 if (RecalcBoundsAction::DoRecalc(this, &UndoActions, OriginalPath, Optimise) == AC_FAIL)
08041                     Worked = FALSE;
08042 
08043                 if (Worked && HaveMadeCurve)
08044                 {
08045                     Worked = DragFinishedReshapeLine();
08046                 }
08047 
08048                 if (Worked)
08049                 {
08050                     // The drag was a real drag (man) so replace the edited path
08051                     // Go and copy the edited path back over the original path
08052                     if (!CopyEditedPathBack())
08053                     {
08054 //                      if (IsUserName("Jim")) TRACE( _T("Failed to copy the edited path back to the original") );
08055                         Error::SetError( _R(IDS_OUT_OF_MEMORY) );
08056                         Worked = FALSE;
08057                     }
08058                 }
08059         
08060                 // Recalculate the path's bounding box
08061                 OriginalPath->InvalidateBoundingRect();
08062         
08063                 // tell the world that something in the selection has changed 
08064                 // so that selection bounds are updated
08065                 GetApplication()->UpdateSelection();
08066 
08067                 // Force a redraw of the place where the path is now.
08068                 if (Worked && (RecordBoundsAction::DoRecord(this, &UndoActions, OriginalPath, Optimise) == AC_FAIL))
08069                 {
08070                     Worked = FALSE;
08071                 }
08072             }
08073         }
08074 
08075         // Inform all the parents of this node that it has been changed.
08076         if (Worked)
08077         {
08078             ObjChange.Define(OBJCHANGE_FINISHED,cFlags,OriginalPath,this);
08079             Worked = UpdateChangedNodes(&ObjChange);
08080         }
08081     }
08082     else
08083         FailAndExecute();
08084 
08085     if (!Worked)
08086     {
08087 //      InformError();
08088         FailAndExecute();
08089     }
08090     End();
08091 }
08092 
08093 
08094 
08095 /********************************************************************************************
08096 
08097 >   BOOL OpReshapeOrAddPoint::DragFinishedAddPoint( )
08098 
08099     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
08100     Created:    23/3/95
08101     Inputs:     -
08102     Outputs:    Changes OriginalPath, adding a point when the drag started, creating two new
08103                 path segments, one either side.  Also builds undo information into the operation
08104                 history.
08105     Returns:    TRUE/FALSE for success/failure.
08106     Purpose:    This is called when a drag on a path segment finishes and we want to add a new
08107                 point on the segment.
08108     SeeAlso:    OpReshapeOrAddPoint::DragFinished
08109 
08110 ********************************************************************************************/
08111 BOOL OpReshapeOrAddPoint::DragFinishedAddPoint( )
08112 {
08113     // First, find the closest point, the element it's at, and go from there
08114     INT32 SplitElement = 0;
08115     PathVerb    NewVerbs[6];
08116     DocCoord    NewCoords[6];
08117     PathFlags   NewFlags[6];
08118     for (INT32 j=0;j<6;j++)
08119     {
08120         NewCoords[j].x = 0;
08121         NewCoords[j].y = 0;
08122     }
08123     UINT32 NumElements = 0;
08124     if (OriginalPath->InkPath.SplitAtPoint(StartMousePos, &SplitElement, &NumElements, NewVerbs, NewCoords))
08125     {
08126         // The path was split, so we know where, and how, so let's party on the path
08127         PathVerb* Verbs = NULL;
08128         PathFlags* Flags = NULL;
08129         DocCoord* Coords = NULL;
08130         OriginalPath->InkPath.GetPathArrays(&Verbs, &Coords, &Flags);
08131         ERROR2IF((Verbs == NULL) || (Flags == NULL) || (Coords == NULL), FALSE, "Path array pointer was NULL");
08132 //      UINT32 NumCoords = OriginalPath->InkPath.GetNumCoords();
08133 
08134         // Before we go any further we want to deselect all current points
08135 //      for (UINT32 loop = 0; loop < NumCoords; loop++)
08136 //      {
08137 //          if (!DoChangeSelection(OriginalPath, loop, FALSE))
08138 //              return FALSE;
08139 //      }
08140         OriginalPath->InkPath.ClearSubSelection();
08141 
08142         INT32 NumToChange = 0;
08143         // We're adding something, either a line or curve - check which
08144         if ((Verbs[SplitElement] & ~PT_CLOSEFIGURE) == PT_BEZIERTO)
08145         {
08146             // Adding a curve - number to change = 6
08147             NumToChange = 6;
08148             // Initialise the flags appropriately
08149             INT32 i;
08150             for (i=0;i<3;i++)
08151             {
08152                 NewFlags[i] = Flags[SplitElement+i];
08153                 NewFlags[i+3] = Flags[SplitElement+i];
08154             }
08155             for (i = 1; i <=3; i++)
08156             {
08157                 NewFlags[i].IsSelected = TRUE;      
08158                 NewFlags[i].IsSmooth = FALSE;
08159                 NewFlags[i].IsRotate = TRUE;
08160             }
08161             NewFlags[0].IsSmooth = Flags[SplitElement].IsSmooth;
08162             NewFlags[0].IsRotate = Flags[SplitElement].IsRotate;
08163             NewFlags[4].IsSmooth = Flags[SplitElement+1].IsSmooth;
08164             NewFlags[4].IsRotate = Flags[SplitElement+1].IsRotate;
08165             NewFlags[5].IsSmooth = Flags[SplitElement+2].IsSmooth;
08166             NewFlags[5].IsRotate = Flags[SplitElement+2].IsRotate;
08167 
08168             if (Verbs[SplitElement+2] & PT_CLOSEFIGURE)
08169                 NewVerbs[5] |= PT_CLOSEFIGURE;
08170         }
08171         else
08172         {
08173             NumToChange = 2;
08174             NewFlags[0] = NewFlags[1] = Flags[SplitElement];
08175             NewFlags[0].IsSmooth = NewFlags[0].IsRotate = FALSE;
08176             NewFlags[0].IsSelected = TRUE;
08177             if (Verbs[SplitElement] & PT_CLOSEFIGURE)
08178                 NewVerbs[1] |= PT_CLOSEFIGURE;
08179         }
08180 
08181         // NumToChange is the number of elements that want changing
08182 
08183         Action* UnAction;
08184         ActionCode Act = RemovePathElementAction::Init(this, &UndoActions, NumToChange / 2, SplitElement, (Action**)(&UnAction));
08185         if (Act == AC_FAIL)
08186         {
08187             return FALSE;
08188         }
08189         ((RemovePathElementAction*)UnAction)->RecordPath(OriginalPath);
08190         PathFlags tempflags;
08191         tempflags.IsSmooth = TRUE;
08192         tempflags.IsRotate = TRUE;
08193         tempflags.IsSelected = TRUE;
08194 
08195         if (NumToChange == 6)
08196         {
08197             OriginalPath->InkPath.SetPathPosition(SplitElement);
08198             if (!(OriginalPath->InkPath.InsertCurveTo(NewCoords[0], NewCoords[1], NewCoords[2], &tempflags)))
08199             {
08200                 return FALSE;
08201             }
08202         }
08203         else
08204         {
08205             if (!(OriginalPath->InkPath.InsertLineTo(NewCoords[0], &tempflags)))
08206             {
08207                 return FALSE;
08208             }
08209         }
08210         
08211         // Refresh the path pointers in case things have moved
08212         OriginalPath->InkPath.GetPathArrays(&Verbs, &Coords, &Flags);
08213 
08214         // Build an action to record the changes we're about to make to the path
08215         ModifyPathAction* ModAction;
08216         Act = ModifyPathAction::Init(this, &UndoActions, NumToChange, (Action**)(&ModAction));
08217         if (Act == AC_FAIL)
08218         {
08219             return FALSE;
08220         }
08221         
08222         if ((Act!=AC_NORECORD) && (ModAction!=NULL))
08223         {
08224             PathVerb* ChangedVerbs;
08225             PathFlags* ChangedFlags;
08226             DocCoord* ChangedCoords;
08227             INT32* ChangedIndices;
08228 
08229             ALLOC_WITH_FAIL(ChangedVerbs, (PathVerb*) CCMalloc(NumToChange * sizeof(PathVerb)), this);
08230             ALLOC_WITH_FAIL(ChangedFlags, (PathFlags*) CCMalloc(NumToChange* sizeof(PathFlags)), this);
08231             ALLOC_WITH_FAIL(ChangedCoords, (DocCoord*) CCMalloc(NumToChange* sizeof(DocCoord)), this);
08232             ALLOC_WITH_FAIL(ChangedIndices, (INT32*) CCMalloc(NumToChange* sizeof(INT32)), this);
08233 
08234             if (!ChangedVerbs || !ChangedFlags || !ChangedCoords || !ChangedIndices)
08235             {
08236                 if (ChangedVerbs) CCFree(ChangedVerbs);
08237                 if (ChangedFlags) CCFree(ChangedFlags);
08238                 if (ChangedCoords) CCFree(ChangedCoords);
08239                 if (ChangedIndices) CCFree(ChangedIndices);
08240                 return FALSE;
08241             }
08242 
08243             // Now record the arrays...
08244             for (INT32 i=0; i<NumToChange; i++)
08245             {
08246                 ChangedIndices[i]   = SplitElement+i;
08247                 ChangedVerbs[i]     = Verbs[SplitElement+i];
08248                 ChangedFlags[i]     = Flags[SplitElement+i];
08249                 ChangedCoords[i]    = Coords[SplitElement+i];
08250             }
08251             
08252             ModAction->StoreArrays(ChangedVerbs, ChangedFlags, ChangedCoords, ChangedIndices, OriginalPath);
08253         }
08254 
08255         // Reread the pointers, in case they've changed
08256         OriginalPath->InkPath.GetPathArrays(&Verbs, &Coords, &Flags);
08257 
08258         // Now that the undo rigmarole has been done, let's change the path
08259         for (INT32 i=0; i<NumToChange; i++)
08260         {
08261             Verbs[SplitElement+i] = NewVerbs[i];
08262             Flags[SplitElement+i] = NewFlags[i];
08263             Coords[SplitElement+i] = NewCoords[i];
08264         }
08265 
08266         GetApplication()->FindSelection()->UpdateBounds();
08267     }
08268     else
08269     {
08270         return FALSE;
08271     }
08272 
08273     return TRUE;
08274 }
08275 
08276 
08277 
08278 /********************************************************************************************
08279 
08280 >   BOOL OpReshapeOrAddPoint::DragFinishedReshapeLine( )
08281 
08282     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
08283     Created:    23/3/95
08284     Inputs:     -
08285     Outputs:    Changes OriginalPath, converting the dragged line segment into a curve, then
08286                 reshaping it as required by the drag.
08287     Returns:    TRUE/FALSE for success/failure.
08288     Purpose:    This is called when a drag on a path line segment finishes and we want to
08289                 reshape it
08290     SeeAlso:    OpReshapeOrAddPoint::DragFinished, OpMakeSegmentsCurves::CarryOut
08291 
08292 ********************************************************************************************/
08293 BOOL OpReshapeOrAddPoint::DragFinishedReshapeLine( )
08294 {
08295     return OpMakeSegmentsCurves::CarryOut(Index, Index-1, OriginalPath, this, &UndoActions);
08296 }
08297 
08298 
08299 
08300 /********************************************************************************************
08301 >   BOOL OpReshapeOrAddPoint::BuildEditPath()
08302 
08303     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com> - later attacked by Peter
08304     Created:    17/02/94
08305     Returns:    TRUE if it managed to build the path, FALSE if it failed
08306     Purpose:    Builds a copy of the path that we can edit, without destroying the original
08307                 data.  If the dragged segment is a line then it is converted to a curve segment
08308                 Also sets the NeedToRenderFlags for the EOR rendering during the drag.
08309                 The parts of the path need to be rendered during the drag is the segment
08310                 being dragged and the next and previous segmetns if the rotate flags are on
08311                 the endpoints at each end of the dragged segment
08312     Errors:     If it runs out of memory then it will return FALSE
08313 ********************************************************************************************/
08314 BOOL OpReshapeOrAddPoint::BuildEditPath()
08315 {
08316     // Make a copy of the path
08317     UINT32 NumCoords = OriginalPath->InkPath.GetNumCoords();
08318     if (!EditPath.Initialise(NumCoords, 24))
08319         return FALSE;
08320     if (!EditPath.CopyPathDataFrom(&(OriginalPath->InkPath)))
08321         return FALSE;
08322 
08323     // If the drag segment is a line then convert it to a curve
08324     PathFlags* Flags;
08325     PathVerb* Verbs;
08326     DocCoord* Coords;
08327     EditPath.GetPathArrays(&Verbs, &Coords, &Flags);
08328     if ((Verbs[Index] & ~PT_CLOSEFIGURE) == PT_LINETO)
08329     {
08330         const DocCoord Start = Coords[Index-1];
08331         const DocCoord End = Coords[Index];
08332         BOOL IsClosed = OriginalPath->InkPath.GetVerbArray()[Index] & PT_CLOSEFIGURE;
08333         BOOL IsSelected = OriginalPath->InkPath.GetFlagArray()[Index].IsSelected;
08334 
08335         // Insert a curve segment
08336         DocCoord OneThird((Start.x+(End.x-Start.x)/3), (Start.y+(End.y-Start.y)/3) );
08337         DocCoord TwoThird((End.x+(Start.x-End.x)/3), (End.y+(Start.y-End.y)/3) );
08338         EditPath.SetPathPosition(Index);
08339         if (!EditPath.InsertCurveTo(OneThird, TwoThird, End))
08340             return FALSE;
08341 
08342         // Remove the line segment
08343         if (!EditPath.DeleteSection(Index+3, 1))
08344             return FALSE;
08345 
08346         // Copy the selection and closure state of the LineTo
08347         EditPath.GetPathArrays(&Verbs, &Coords, &Flags);
08348         Flags[Index+2].IsSelected = IsSelected;
08349         if (IsClosed)
08350             Verbs[Index+2] = Verbs[Index+2] | PT_CLOSEFIGURE; 
08351 
08352         // We need to recalc paramdist now
08353         HaveMadeCurve = TRUE;
08354         INT32 tempel;
08355         EditPath.SqrDistanceToPoint(StartMousePos, &tempel, &paramdist);
08356     }
08357 
08358     // Clear the NeedToRender flags
08359     EditPath.ClearNeedToRender();
08360 
08361     // Set the render flag on this segment
08362     EditPath.GetPathArrays(&Verbs, &Coords, &Flags);
08363     if ((Verbs[Index] & ~PT_CLOSEFIGURE) == PT_BEZIERTO)
08364         Flags[Index+2].NeedToRender = TRUE;
08365     else
08366         Flags[Index].NeedToRender = TRUE;
08367 
08368     // Clear the smooth bit on the endpoints of this curve
08369     Flags[Index-1].IsSmooth = FALSE;
08370     Flags[Index+2].IsSmooth = FALSE;
08371 
08372     // Check before setting the NeedToRender on the adjacent segments as well
08373     if ((Verbs[Index] & ~PT_CLOSEFIGURE) == PT_BEZIERTO)
08374     {
08375         INT32 Opposite = EditPath.FindOppositeControlPoint(Index);
08376         if ( (Opposite>=0) && (Flags[Opposite+1].IsRotate || Flags[Index-1].IsRotate) )
08377             Flags[Opposite+1].NeedToRender = TRUE;
08378 
08379         Opposite = EditPath.FindOppositeControlPoint(Index+1);
08380         if ( (Opposite>=0) && (Flags[Opposite-1].IsRotate || Flags[Index+2].IsRotate) )
08381             Flags[Opposite+2].NeedToRender = TRUE;
08382     }
08383 
08384     // It worked
08385     return TRUE;
08386 }
08387 
08388 
08389 
08390 /********************************************************************************************
08391 
08392 >   void OpReshapeOrAddPoint::RecalculatePath( DocCoord Offset )
08393 
08394     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
08395     Created:    1/9/94
08396     Inputs:     Offset - The Amount the pointer has moved from the *original* position (i.e. not
08397                         the last position).
08398     Scope:      Private
08399     Purpose:    Will reshape a curve segment using a bit of voodoo maths  
08400 
08401                 Assuming the mouse was originally clicked on the path a certain distance along it
08402                 (say 1/4 of the whole curve). The parametric distance would thus be 0.25 - this is the 
08403                 distance that controls the first control point. 1-0.25 controls the second control point.
08404 
08405                 Given:  (px,py) = current pointer position
08406                         (ox,oy) = original pointer position
08407                         (x0,y0) = first control point
08408                         (x1,y1) = second control point
08409                         (x2,y2) = endpoint of curve
08410                         pdist = parametric distance
08411 
08412                 then:   newx0 = x0+(px-ox)*3/pdist
08413                         newy0 = y0+(py-oy)*3/pdist
08414                         newx1 = x1+(px-ox)*3/(1-pdist)
08415                         newy1 = y1+(py-oy)*3/(1-pdist)
08416 
08417                 Disclaimer: This maths was plucked out of the blue, and so might not be the best way 
08418                 of doing it.
08419 
08420 ********************************************************************************************/
08421 
08422 void OpReshapeOrAddPoint::RecalculatePath( DocCoord Offset )
08423 {
08424     // Don't recalculate if the segment is a straight line...
08425     const double factor = 0.656875;
08426     PathVerb* Verbs = EditPath.GetVerbArray();
08427     DocCoord* Coords = EditPath.GetCoordArray();
08428     PathFlags* Flags = EditPath.GetFlagArray();
08429     if (Verbs[Index] == PT_BEZIERTO)        // first ctrl pt won't have closefigure set
08430     {
08431         // We have to calculate by how much to move the two control points on the curve,
08432         // and if the endpoints are set to rotate, we have to rotate the opposite control
08433         // points as well
08434 
08435         // Since the routine CalcRotate will change the *current* coords of the control point,
08436         // and we calculate the new coords from the *original* control point (to avoid accumulated
08437         // inaccuracies) we have to generate an offset that CalcRotate can use.
08438         
08439         DocCoord ControlOffset;
08440         INT32 OppositeIndex = EditPath.FindOppositeControlPoint(Index);
08441         ControlOffset.x = (INT32)(OrigControl1.x + (Offset.x) * factor / paramdist);
08442         ControlOffset.y = (INT32)(OrigControl1.y + (Offset.y) * factor / paramdist);
08443 
08444         ControlOffset.x -= Coords[Index].x;
08445         ControlOffset.y -= Coords[Index].y;
08446 
08447         if (Flags[Index-1].IsRotate && OppositeIndex >= 0)
08448         {
08449             EditPath.CalcRotate(Coords[Index-1], &Coords[Index], &Coords[OppositeIndex], ControlOffset);
08450         }
08451         else
08452         {
08453             Coords[Index].x += ControlOffset.x;
08454             Coords[Index].y += ControlOffset.y;
08455         }
08456 
08457         // We need to clear the smooth flags off the endpoints and control points around where
08458         // the path is being dragged.  We know Index-1 to Index+2 are Beziers.
08459         if ((Index > 1) && ((Verbs[Index-1] & ~PT_CLOSEFIGURE) == PT_BEZIERTO) )
08460             Flags[Index-2].IsSmooth = FALSE;
08461         Flags[Index-1].IsSmooth = FALSE;
08462         Flags[Index].IsSmooth = FALSE;
08463         Flags[Index+1].IsSmooth = FALSE;
08464         Flags[Index+2].IsSmooth = FALSE;
08465         if ((Index < EditPath.GetNumCoords()-3) && ((Verbs[Index+3] & ~PT_CLOSEFIGURE) == PT_BEZIERTO) )
08466             Flags[Index+3].IsSmooth = FALSE;
08467 
08468         // If the index was at the very start or end of the path then we also need to turn off the
08469         // rotate flags so future path extension is correct
08470         if (Index == 1)
08471 
08472         {
08473             Flags[Index].IsRotate = FALSE;
08474             Flags[Index-1].IsRotate = FALSE;
08475         }                                                 
08476         if (Index == EditPath.GetNumCoords()-3)
08477         {
08478             Flags[Index+1].IsRotate = FALSE;
08479             Flags[Index+2].IsRotate = FALSE;
08480         }
08481     
08482         ControlOffset.x = (INT32)(OrigControl2.x + (Offset.x) * factor / (1.0-paramdist));
08483         ControlOffset.y = (INT32)(OrigControl2.y + (Offset.y) * factor / (1.0-paramdist));
08484 
08485         ControlOffset.x -= Coords[Index+1].x;
08486         ControlOffset.y -= Coords[Index+1].y;
08487         OppositeIndex = EditPath.FindOppositeControlPoint(Index+1);
08488 
08489         if (Flags[Index+2].IsRotate && OppositeIndex >= 0)
08490         {
08491             EditPath.CalcRotate(Coords[Index+2], &Coords[Index+1], &Coords[OppositeIndex], ControlOffset);
08492         }
08493         else
08494         {
08495             Coords[Index+1].x += ControlOffset.x;
08496             Coords[Index+1].y += ControlOffset.y;
08497         }
08498     }
08499 }
08500 
08501 
08502 /********************************************************************************************
08503 
08504 >   BOOL OpReshapeOrAddPoint::Init()
08505 
08506     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
08507     Created:    5/7/93
08508     Returns:    TRUE if all went OK, False otherwise
08509     Purpose:    Adds the operation to the list of all known operations
08510 
08511 ********************************************************************************************/
08512 
08513 BOOL OpReshapeOrAddPoint::Init()
08514 {
08515     return (RegisterOpDescriptor(
08516                                     0, 
08517                                     _R(IDS_NODEPATH_EDIT),
08518                                     CC_RUNTIME_CLASS(OpReshapeOrAddPoint), 
08519                                     OPTOKEN_NODEPATH,
08520                                     OpReshapeOrAddPoint::GetState,
08521                                     0,                                      // help ID
08522                                     _R(IDBBL_NODEPATHOP),
08523                                     0 ) );                                  // bitmap ID
08524 }
08525 
08526 
08527 
08528 
08529 /********************************************************************************************
08530 
08531 >   OpReshapeOrAddPoint::OpReshapeOrAddPoint()
08532 
08533     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
08534     Created:    18/03/95
08535     Purpose:    Constructor to initialise the members variables
08536 
08537 ********************************************************************************************/
08538 
08539 OpReshapeOrAddPoint::OpReshapeOrAddPoint() : OpNodePathEditBlob()
08540 {
08541     Index = -1;             
08542     paramdist = -1;     
08543     HaveMadeCurve = FALSE;      
08544 };
08545 
08546 
08547 
08548 /********************************************************************************************
08549 
08550 >   OpState OpReshapeOrAddPoint::GetState(String_256* Description, OpDescriptor*)
08551 
08552     Author:     Rik_Heywood (Xara Group Ltd) <camelotdev@xara.com>
08553     Created:    5/7/93
08554     Outputs:    Description - GetState fills this string with an approriate description
08555                 of the current state of the selector tool
08556     Returns:    The state of the operation, so that menu items (ticks and greying can be
08557                 done properly
08558     Purpose:    Find out the state of the operation at the specific time
08559 
08560 ********************************************************************************************/
08561 
08562 OpState OpReshapeOrAddPoint::GetState(String_256* Description, OpDescriptor*)
08563 {
08564     OpState Blobby;
08565     
08566     return Blobby;
08567 }
08568 
08569 
08570 /********************************************************************************************
08571 >   void OpReshapeOrAddPoint::SetStatusLineHelp()
08572 
08573     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
08574     Created:    4/1/96
08575     Purpose:    For getting a message for the status line during drag operations
08576 ********************************************************************************************/
08577 void OpReshapeOrAddPoint::SetStatusLineHelp()
08578 {
08579     String_256 HelpString;
08580     if (StartMousePos == FurthestPoint)
08581         HelpString = String_256(_R(IDS_PATHDRAGADDREFORM));
08582     else
08583         HelpString = String_256(_R(IDS_PATHDRAGFINISHREFORM));
08584 
08585     GetApplication()->UpdateStatusBarText(&HelpString, FALSE);
08586 }
08587 
08588 
08589 
08590 
08591 
08593 // The ModifyPathAction class                                                                //
08595 
08596 /********************************************************************************************
08597 
08598 >   ModifyPathAction::ModifyPathAction()
08599 
08600     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
08601     Created:    6/5/94
08602     Inputs:     -
08603     Outputs:    -
08604     Returns:    -
08605     Purpose:    Constructor for the action to undo path modification
08606     Errors:     -
08607     SeeAlso:    -
08608 
08609 ********************************************************************************************/
08610 
08611 ModifyPathAction::ModifyPathAction()
08612 {
08613     ChangedVerbs = NULL;
08614     ChangedFlags = NULL;
08615     ChangedCoords = NULL;
08616     ChangedIndices = NULL;
08617 }
08618 
08619 
08620 /********************************************************************************************
08621 
08622 >   ActionCode ModifyPathAction::Init(  Operation* pOp,
08623                                         ActionList* pActionList,
08624                                         INT32 NumChangedElements,
08625                                         Action** NewAction)
08626 
08627     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
08628     Created:    6/5/94
08629     Inputs:     pOp is the pointer to the operation to which this action belongs
08630                 pActionList is the action list to which this action should be added
08631                 NumChangedElements is the number of elements in the path that are going to
08632                 change. This tells the action how much memory the total action is going to take
08633     Outputs:    NewAction is a pointer to a pointer to an action, allowing the function to return
08634                 a pointer to the created action
08635     Returns:    ActionCode, one of AC_OK, AC_NO_RECORD or AC_FAIL
08636     Purpose:    This is the function which creates an instance of this action. If there is no room 
08637                 in the undo buffer (which is determined by the base class Init function called within)
08638                 the function will either return AC_NO_RECORD which means the operation can continue, 
08639                 but no undo information needs to be stored, or AC_OK which means the operation should
08640                 continue AND record undo information. If the function returns AC_FAIL, there was not 
08641                 enough memory to record the undo information, and the user has decided not to continue
08642                 with the operation.
08643     Errors:     -
08644     SeeAlso:    Action::Init()
08645 
08646 ********************************************************************************************/
08647 
08648 
08649 
08650 ActionCode ModifyPathAction::Init(  Operation* pOp,
08651                                     ActionList* pActionList,
08652                                     INT32 NumChangedElements,
08653                                     Action** NewAction)
08654 {
08655     UINT32 ActSize = sizeof(ModifyPathAction) +
08656                     NumChangedElements * (sizeof(PathVerb)+sizeof(PathFlags)+sizeof(DocCoord)+sizeof(INT32));
08657 
08658     ActionCode Ac = Action::Init( pOp, pActionList, ActSize, CC_RUNTIME_CLASS(ModifyPathAction), NewAction);
08659     if ((Ac==AC_OK) && (*NewAction != NULL))
08660         ((ModifyPathAction*)*NewAction)->NumElements = NumChangedElements;
08661 
08662 //  if (IsUserName("Jim")) TRACE( _T("Creating ModifyPathAction"));
08663 
08664     return Ac;
08665 }
08666 
08667 /********************************************************************************************
08668 
08669 >   void ModifyPathAction::StoreArrays(PathVerb* Verbs, PathFlags* Flags, DocCoord* Coords, 
08670                                         INT32* Indices, NodePath* WhichPath)
08671 
08672     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
08673     Created:    6/5/94
08674     Inputs:     Verbs is a pointer to an array of path verbs
08675                 Flags is a pointer to an array of path flags
08676                 Coords is a pointer to an array of DocCoords
08677                 Indices is a pointer to an array of indices into the path
08678     Outputs:    -
08679     Returns:    -
08680     Purpose:    This function initialises the array pointers in this action. Note that the 
08681                 variable NumElements is initialised in the Init function
08682     Errors:     -
08683     SeeAlso:    -
08684 
08685 ********************************************************************************************/
08686 
08687 void ModifyPathAction::StoreArrays(PathVerb* Verbs, PathFlags* Flags, DocCoord* Coords, 
08688                                     INT32* Indices, NodePath* WhichPath)
08689 {
08690     ChangedPath = WhichPath;
08691     ChangedVerbs = Verbs;
08692     ChangedFlags = Flags;
08693     ChangedCoords = Coords;
08694     ChangedIndices = Indices;
08695 }
08696 
08697 
08698 
08699 /********************************************************************************************
08700 
08701 >   ActionCode ModifyPathAction::Execute()
08702 
08703     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
08704     Created:    11/7/94
08705     Inputs:     -
08706     Outputs:    -
08707     Returns:    Action code, one of AC_OK, AC_NORECORD or AC_FAIL.
08708     Purpose:    This is the virtual function that is called when the action is executed
08709                 by the Undo/Redo system. This is the function that actually undoes the modifypath
08710                 action by replacing the indicated sections of the path with data from itself, and
08711                 records redo information from the current state of the path.
08712     Errors:     -
08713     SeeAlso:    -
08714 
08715 ********************************************************************************************/
08716 
08717 ActionCode ModifyPathAction::Execute()
08718 {
08719     // Here we're undoing the modify, so we have to write the list of changed array entries
08720     // back into the path. We also have to build undo information for the redo. Usefully,
08721     // We can use the existing arrays, swapping the data in the undo action for the data 
08722     // in the path, then passing the arrays on to the redo record. We have to make sure 
08723     // we don't try and de-allocate the arrays after we've done this, so we'll put a check in
08724     // the destructor for null pointers
08725     
08726     ModifyPathAction* ModAction;
08727     
08728     ActionCode Act;
08729     Act = ModifyPathAction::Init(pOperation, pOppositeActLst, NumElements, (Action**)(&ModAction));
08730     if (Act == AC_FAIL)
08731         return AC_FAIL;
08732 
08733     // Force a re-draw of the place where the path used to be
08734     Document* pDoc = GetWorkingDoc();
08735     ERROR2IF( pDoc == NULL, AC_FAIL, "There was no current document when undoing modifypath" );
08736     Spread* pSpread = ChangedPath->FindParentSpread();
08737     DocRect Invalid = ChangedPath->GetUnionBlobBoundingRect();
08738     pDoc->ForceRedraw( pSpread, Invalid, FALSE, ChangedPath );
08739 
08740     // Get pointers to all the arrays of the path
08741 
08742     PathVerb* Verbs = ChangedPath->InkPath.GetVerbArray();
08743     DocCoord* Coords = ChangedPath->InkPath.GetCoordArray();
08744     PathFlags* Flags = ChangedPath->InkPath.GetFlagArray();
08745 
08746     // Now modify the data, swapping data from the two sets of arrays
08747 
08748     PathVerb TempVerb;
08749     DocCoord TempCoord;
08750     PathFlags TempFlag;
08751     
08752     for (INT32 i=0;i<NumElements;i++)
08753     {
08754         TempVerb = Verbs[ChangedIndices[i]];
08755         TempCoord = Coords[ChangedIndices[i]];
08756         TempFlag = Flags[ChangedIndices[i]];
08757         Verbs[ChangedIndices[i]] = ChangedVerbs[i];
08758         Coords[ChangedIndices[i]] = ChangedCoords[i];
08759         Flags[ChangedIndices[i]] = ChangedFlags[i];
08760         ChangedVerbs[i] = TempVerb;
08761         ChangedCoords[i] = TempCoord;
08762         ChangedFlags[i] = TempFlag;
08763 
08764     }
08765 
08766     // Now, the path in the document has been changed, the arrays now contain what was in the path
08767     // so we tell the action where the arrays are.
08768 
08769     if (Act==AC_OK && ModAction!=NULL)
08770         ModAction->StoreArrays(ChangedVerbs, ChangedFlags, ChangedCoords, ChangedIndices, ChangedPath);
08771 
08772     // And set the array pointers to NULL so the destructor doesn't try and deallocate them
08773 
08774     ChangedVerbs = NULL;
08775     ChangedCoords = NULL;
08776     ChangedFlags = NULL;
08777     ChangedIndices = NULL;
08778 
08779     ChangedPath->InvalidateBoundingRect();
08780 
08781     // Now cause a redraw of the new paths position
08782     Invalid = ChangedPath->GetUnionBlobBoundingRect();
08783     pDoc->ForceRedraw( pSpread, Invalid, FALSE, ChangedPath );
08784 
08785     return Act;
08786 }
08787 
08788 
08789 
08790 
08791 ModifyPathAction::~ModifyPathAction()
08792 {
08793     if (ChangedVerbs)
08794         CCFree(ChangedVerbs);
08795     if (ChangedCoords)
08796         CCFree(ChangedCoords);
08797     if (ChangedFlags)
08798         CCFree(ChangedFlags);
08799     if (ChangedIndices)
08800         CCFree(ChangedIndices);
08801 }
08802 
08804 // The RemovePathElementAction class                                                         //
08806 
08807 /********************************************************************************************
08808 
08809 >   RemovePathElementAction::RemovePathElementAction()
08810 
08811     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
08812     Created:    22/6/94
08813     Inputs:     -
08814     Outputs:    -
08815     Returns:    -
08816     Purpose:    Constructor for the action to undo path element insertion
08817     Errors:     -
08818     SeeAlso:    -
08819 
08820 ********************************************************************************************/
08821 
08822 RemovePathElementAction::RemovePathElementAction()
08823 {
08824 }
08825 
08826 
08827 /********************************************************************************************
08828 
08829 >   ActionCode RemovePathElementAction::Init(  Operation* pOp,
08830                                         ActionList* pActionList,
08831                                         INT32 NumChangedElements,
08832                                         INT32 ChangedIndex,
08833                                         Action** NewAction)
08834 
08835     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
08836     Created:    22/6/94
08837     Inputs:     pOp is the pointer to the operation to which this action belongs
08838                 pActionList is the action list to which this action should be added
08839                 NumChangedElements is the number of elements in the path that are going to
08840                 be removed.
08841                 ChangedIndex is the index into the first element to be deleted
08842     Outputs:    NewAction is a pointer to a pointer to an action, allowing the function to return
08843                 a pointer to the created action
08844     Returns:    ActionCode, one of AC_OK, AC_NO_RECORD or AC_FAIL
08845     Purpose:    This is the function which creates an instance of the action to remove elements from a 
08846                 path. If there is no room in the undo buffer (which is determined by the base class Init 
08847                 function called within) the function will either return AC_NO_RECORD which means the 
08848                 operation can continue, but no undo information needs to be stored, or AC_OK which means 
08849                 the operation should continue AND record undo information. If the function returns AC_FAIL, 
08850                 there was not enough memory to record the undo information, and the user has decided not 
08851                 to continue with the operation.
08852     Errors:     -
08853     SeeAlso:    Action::Init()
08854 
08855 ********************************************************************************************/
08856 
08857 
08858 
08859 ActionCode RemovePathElementAction::Init(  Operation* pOp,
08860                                     ActionList* pActionList,
08861                                     INT32 NumChangedElements,
08862                                     INT32 ChangedIndex,
08863                                     Action** NewAction)
08864 {
08865     UINT32 ActSize = sizeof(RemovePathElementAction) +
08866                     NumChangedElements * (sizeof(PathVerb)+sizeof(PathFlags)+sizeof(DocCoord));
08867 
08868     ActionCode Ac = Action::Init( pOp, pActionList, ActSize, CC_RUNTIME_CLASS(RemovePathElementAction), NewAction);
08869 
08870     if ((Ac == AC_OK) && (*NewAction != NULL))
08871     {
08872         ((RemovePathElementAction*)*NewAction)->NumElements = NumChangedElements;
08873         ((RemovePathElementAction*)*NewAction)->FirstChangedIndex = ChangedIndex;
08874     }
08875 
08876     return Ac;
08877 }
08878 
08879 /********************************************************************************************
08880 
08881 >   void RemovePathElementAction::RecordPath(NodePath* WhichPath)
08882 
08883     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
08884     Created:    22/6/94
08885     Inputs:     WhichPath is the path that's being edited
08886     Outputs:    -
08887     Returns:    -
08888     Purpose:    This function records a pointer to the path. That's all.
08889     Errors:     -
08890     SeeAlso:    -
08891 
08892 ********************************************************************************************/
08893 
08894 
08895 
08896 void RemovePathElementAction::RecordPath(NodePath* WhichPath)
08897 {
08898     ChangedPath = WhichPath;
08899 }
08900 
08901 ActionCode RemovePathElementAction::Execute()
08902 {
08903     // Here we're undoing the insert, so we have to remove the elements that were inserted.
08904     // At the same time, we have to create a redo operation which will end up being an
08905     // InsertPathElementAction.
08906     
08907     InsertPathElementAction* ModAction;
08908     
08909     ActionCode Act;
08910     Act = InsertPathElementAction::Init(pOperation, pOppositeActLst, NumElements, FirstChangedIndex,
08911                                             (Action**)(&ModAction));
08912     if (Act == AC_FAIL)
08913         return AC_FAIL;
08914 
08915     // Force a re-draw of the place where the path used to be
08916     Document* pDoc = GetWorkingDoc();
08917     ERROR2IF( pDoc == NULL, AC_FAIL, "There was no current document when undoing modifypath" );
08918     Spread* pSpread = ChangedPath->FindParentSpread();
08919     DocRect Invalid = ChangedPath->GetUnionBlobBoundingRect();
08920     pDoc->ForceRedraw( pSpread, Invalid, FALSE, ChangedPath );
08921 
08922     if ((Act!=AC_NORECORD) && (ModAction!=NULL))
08923     {
08924         // I have to claim some memory to store the elements I'm deleting...
08925 
08926         PathVerb* ChangedVerbs;
08927         DocCoord* ChangedCoords;
08928         PathFlags* ChangedFlags;
08929 
08930         ALLOC_WITH_FAIL(ChangedVerbs,(PathVerb*) CCMalloc(NumElements * sizeof(PathVerb)),pOperation);
08931         ALLOC_WITH_FAIL(ChangedCoords,(DocCoord*) CCMalloc(NumElements * sizeof(DocCoord)),pOperation);
08932         ALLOC_WITH_FAIL(ChangedFlags,(PathFlags*) CCMalloc(NumElements * sizeof(PathFlags)),pOperation);
08933 
08934         if (!ChangedVerbs || !ChangedCoords || !ChangedFlags)
08935         {
08936             if (ChangedVerbs) CCFree(ChangedVerbs);
08937             if (ChangedCoords) CCFree(ChangedCoords);
08938             if (ChangedFlags) CCFree(ChangedFlags);
08939             return AC_FAIL;
08940         }
08941 
08942         // Get pointers to all the arrays of the path
08943         PathVerb* Verbs = ChangedPath->InkPath.GetVerbArray();
08944         DocCoord* Coords = ChangedPath->InkPath.GetCoordArray();
08945         PathFlags* Flags = ChangedPath->InkPath.GetFlagArray();
08946 
08947         // Now copy the data from the path into the arrays
08948         for (INT32 i=0;i<NumElements;i++)
08949         {
08950             ChangedVerbs[i] = Verbs[FirstChangedIndex+i];
08951             ChangedCoords[i] = Coords[FirstChangedIndex+i];
08952             ChangedFlags[i] = Flags[FirstChangedIndex+i];
08953         }
08954 
08955         // Now pass these arrays to the Insert action
08956         ModAction->RecordPath(ChangedVerbs, ChangedFlags,ChangedCoords, ChangedPath);
08957     }
08958 
08959     // Now we've recorded the data we're about to delete, let's delete it
08960     ChangedPath->InkPath.DeleteSection(FirstChangedIndex, NumElements);
08961 
08962     ChangedPath->InvalidateBoundingRect();
08963 
08964     // Now cause a redraw of the new path
08965     Invalid = ChangedPath->GetUnionBlobBoundingRect();
08966     pDoc->ForceRedraw( pSpread, Invalid, FALSE, ChangedPath );
08967 
08968     return Act;
08969 }
08970 
08971 
08972 
08973 RemovePathElementAction::~RemovePathElementAction()
08974 {
08975 //  if (ChangedVerbs)
08976 //      CCFree(ChangedVerbs);
08977 //  if (ChangedCoords)
08978 //      CCFree(ChangedCoords);
08979 //  if (ChangedFlags)
08980 //      CCFree(ChangedFlags);
08981 //  if (ChangedIndices)
08982 //      CCFree(ChangedIndices);
08983 }
08984 
08986 // The InsertPathElementAction class                                                         //
08988 
08989 /********************************************************************************************
08990 
08991 >   InsertPathElementAction::InsertPathElementAction()
08992 
08993     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
08994     Created:    6/5/94
08995     Inputs:     -
08996     Outputs:    -
08997     Returns:    -
08998     Purpose:    Constructor for the action to undo path modification
08999     Errors:     -
09000     SeeAlso:    -
09001 
09002 ********************************************************************************************/
09003 
09004 InsertPathElementAction::InsertPathElementAction()
09005 {
09006     ChangedVerbs = NULL;
09007     ChangedFlags = NULL;
09008     ChangedCoords = NULL;
09009 }
09010 
09011 
09012 /********************************************************************************************
09013 
09014 >   ActionCode InsertPathElementAction::Init(  Operation* pOp,
09015                                         ActionList* pActionList,
09016                                         INT32 NumChangedElements,
09017                                         INT32 ChangedIndex,
09018                                         Action** NewAction)
09019 
09020     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
09021     Created:    6/5/94
09022     Inputs:     pOp is the pointer to the operation to which this action belongs
09023                 pActionList is the action list to which this action should be added
09024                 NumChangedElements is the number of elements in the path that are going to
09025                 change. This tells the action how much memory the total action is going to take
09026     Outputs:    NewAction is a pointer to a pointer to an action, allowing the function to return
09027                 a pointer to the created action
09028     Returns:    ActionCode, one of AC_OK, AC_NO_RECORD or AC_FAIL
09029     Purpose:    This is the function which creates an instance of this action. If there is no room 
09030                 in the undo buffer (which is determined by the base class Init function called within)
09031                 the function will either return AC_NO_RECORD which means the operation can continue, 
09032                 but no undo information needs to be stored, or AC_OK which means the operation should
09033                 continue AND record undo information. If the function returns AC_FAIL, there was not 
09034                 enough memory to record the undo information, and the user has decided not to continue
09035                 with the operation.
09036     Errors:     -
09037     SeeAlso:    Action::Init()
09038 
09039 ********************************************************************************************/
09040 
09041 
09042 
09043 ActionCode InsertPathElementAction::Init(  Operation* pOp,
09044                                     ActionList* pActionList,
09045                                     INT32 NumChangedElements,
09046                                     INT32 ChangedIndex,
09047                                     Action** NewAction)
09048 {
09049     UINT32 ActSize = sizeof(InsertPathElementAction) +
09050                     NumChangedElements * (sizeof(PathVerb)+sizeof(PathFlags)+sizeof(DocCoord));
09051 
09052     ActionCode Ac = Action::Init( pOp, pActionList, ActSize, CC_RUNTIME_CLASS(InsertPathElementAction), NewAction);
09053     if ((Ac == AC_OK) && (*NewAction != NULL))
09054     {
09055         ((InsertPathElementAction*)*NewAction)->NumElements = NumChangedElements;
09056         ((InsertPathElementAction*)*NewAction)->InsertionPoint = ChangedIndex;
09057     }
09058 
09059     return Ac;
09060 }
09061 
09062 /********************************************************************************************
09063 
09064 >   void InsertPathElementAction::RecordPath(PathVerb* Verbs, PathFlags* Flags, DocCoord* Coords, 
09065                                          NodePath* WhichPath)
09066 
09067     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
09068     Created:    22/6/94
09069     Inputs:     Verbs is a pointer to an array of path verbs
09070                 Flags is a pointer to an array of path flags
09071                 Coords is a pointer to an array of DocCoords
09072                 WhichPath is the path that's being operated on
09073     Outputs:    -
09074     Returns:    -
09075     Purpose:    This function initialises the array pointers in this action. Note that the 
09076                 variables NumElements and InsertionPoint are initialised in the Init function
09077     Errors:     -
09078     SeeAlso:    -
09079 
09080 ********************************************************************************************/
09081 
09082 
09083 
09084 void InsertPathElementAction::RecordPath(PathVerb* Verbs, PathFlags* Flags, DocCoord* Coords, 
09085                                         NodePath* WhichPath)
09086 {
09087     ChangedPath = WhichPath;
09088     ChangedVerbs = Verbs;
09089     ChangedFlags = Flags;
09090     ChangedCoords = Coords;
09091 }
09092 
09093 ActionCode InsertPathElementAction::Execute()
09094 {
09095     // Here we're undoing the modify, so we have to write the list of changed array entries
09096     // back into the path. We also have to build undo information for the redo. Usefully,
09097     // We can use the existing arrays, swapping the data in the undo action for the data 
09098     // in the path, then passing the arrays on to the redo record. We have to make sure 
09099     // we don't try and de-allocate the arrays after we've done this, so we'll put a check in
09100     // the destructor for null pointers
09101     
09102     RemovePathElementAction* ModAction;
09103     
09104     ActionCode Act;
09105     Act = RemovePathElementAction::Init(pOperation, pOppositeActLst, NumElements, InsertionPoint, (Action**)(&ModAction));
09106     if (Act == AC_FAIL)
09107         return AC_FAIL;
09108 
09109     // Tell the operation where the path is
09110     if (ModAction!=NULL)
09111         ModAction->RecordPath(ChangedPath);
09112 
09113     // Force a re-draw of the place where the path used to be
09114     Document* pDoc = GetWorkingDoc();
09115     ERROR2IF( pDoc == NULL, AC_FAIL, "There was no current document when undoing modifypath" );
09116     Spread* pSpread = ChangedPath->FindParentSpread();
09117     DocRect Invalid = ChangedPath->GetUnionBlobBoundingRect();
09118     pDoc->ForceRedraw( pSpread, Invalid, FALSE, ChangedPath );
09119 
09120     // See if we can open the path up
09121     if (!ChangedPath->InkPath.InsertSection(InsertionPoint,
09122                                             NumElements))   // WARNING! This routine will claim memory
09123         return AC_FAIL;                                     // in a way the undo system can't track, so it
09124                                                             // won't be able to warn the user that memory is
09125                                                             // tight and would they prefer not to do this operation
09126 
09127     // Get pointers to all the arrays of the path
09128     PathVerb* Verbs = ChangedPath->InkPath.GetVerbArray();
09129     DocCoord* Coords = ChangedPath->InkPath.GetCoordArray();
09130     PathFlags* Flags = ChangedPath->InkPath.GetFlagArray();
09131 
09132     // Now insert the data from the arrays in the action record
09133 
09134     for (INT32 i=0;i<NumElements;i++)
09135     {
09136         Verbs[i+InsertionPoint] = ChangedVerbs[i];
09137         Coords[i+InsertionPoint] = ChangedCoords[i];
09138         Flags[i+InsertionPoint] = ChangedFlags[i];
09139     }
09140 
09141     ChangedPath->InvalidateBoundingRect();
09142 
09143     Invalid = ChangedPath->GetUnionBlobBoundingRect();
09144     pDoc->ForceRedraw( pSpread, Invalid, FALSE, ChangedPath );
09145 
09146     return Act;
09147 }
09148 
09149 InsertPathElementAction::~InsertPathElementAction()
09150 {
09151     if (ChangedVerbs)
09152         CCFree(ChangedVerbs);
09153     if (ChangedCoords)
09154         CCFree(ChangedCoords);
09155     if (ChangedFlags)
09156         CCFree(ChangedFlags);
09157 }
09158 
09160 // The ModifyElementAction class                                                                 //
09162 
09163 /********************************************************************************************
09164 
09165 >   ModifyElementAction::ModifyElementAction()
09166 
09167     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
09168     Created:    23/6/94
09169     Inputs:     -
09170     Outputs:    -
09171     Returns:    -
09172     Purpose:    Constructor for the action to undo path element modification
09173     Errors:     -
09174     SeeAlso:    -
09175 
09176 ********************************************************************************************/
09177 
09178 ModifyElementAction::ModifyElementAction()
09179 {
09180     ChangedPath = NULL;
09181 }
09182 
09183 
09184 /********************************************************************************************
09185 
09186 >   ActionCode ModifyElementAction::Init(  Operation* pOp,
09187                                         ActionList* pActionList,
09188                                         PathVerb Verb, 
09189                                         PathFlags Flags, 
09190                                         DocCoord Coord, 
09191                                         INT32 Index, 
09192                                         NodePath* WhichPath,
09193                                         Action** NewAction)
09194 
09195     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
09196     Created:    23/6/94
09197     Inputs:     pOp is the pointer to the operation to which this action belongs
09198                 pActionList is the action list to which this action should be added
09199                 Verb is the verb of the path that we're changing
09200                 Flags are the flags of the changed element
09201                 DocCoord is the coords of the element
09202                 Index is the index into the path of the element
09203     Outputs:    NewAction is a pointer to a pointer to an action, allowing the function to return
09204                 a pointer to the created action
09205     Returns:    ActionCode, one of AC_OK, AC_NO_RECORD or AC_FAIL
09206     Purpose:    This is the function which creates an instance of this action. If there is no room 
09207                 in the undo buffer (which is determined by the base class Init function called within)
09208                 the function will either return AC_NO_RECORD which means the operation can continue, 
09209                 but no undo information needs to be stored, or AC_OK which means the operation should
09210                 continue AND record undo information. If the function returns AC_FAIL, there was not 
09211                 enough memory to record the undo information, and the user has decided not to continue
09212                 with the operation.
09213     Errors:     -
09214     SeeAlso:    Action::Init()
09215 
09216 ********************************************************************************************/
09217 
09218 
09219 
09220 ActionCode ModifyElementAction::Init(  Operation* pOp,
09221                                     ActionList* pActionList,
09222                                     PathVerb Verb, 
09223                                     PathFlags Flags, 
09224                                     DocCoord Coord, 
09225                                     INT32 Index, 
09226                                     NodePath* WhichPath,
09227                                     Action** NewAction)
09228 {
09229     UINT32 ActSize = sizeof(ModifyElementAction);
09230 
09231     ActionCode Ac = Action::Init( pOp, pActionList, ActSize, CC_RUNTIME_CLASS(ModifyElementAction), NewAction);
09232 
09233     if ((Ac == AC_OK) && (*NewAction != NULL))
09234     {
09235         ((ModifyElementAction*)*NewAction)->ChangedVerb = Verb;
09236         ((ModifyElementAction*)*NewAction)->ChangedFlags = Flags;
09237         ((ModifyElementAction*)*NewAction)->ChangedCoord = Coord;
09238         ((ModifyElementAction*)*NewAction)->ChangedIndex = Index;
09239         ((ModifyElementAction*)*NewAction)->ChangedPath = WhichPath;
09240     }
09241 
09242     return Ac;
09243 }
09244 
09245 ActionCode ModifyElementAction::Execute()
09246 {
09247     // Here we're undoing the modify, so we have to change the element in the path,
09248     // recording redo information at the same time.
09249     
09250     ModifyElementAction* ModAction;
09251     
09252     // Get pointers to all the arrays of the path
09253 
09254     PathVerb* Verbs = ChangedPath->InkPath.GetVerbArray();
09255     DocCoord* Coords = ChangedPath->InkPath.GetCoordArray();
09256     PathFlags* Flags = ChangedPath->InkPath.GetFlagArray();
09257 
09258     // Create a redo action for this action, which is also a ModifyElementAction
09259 
09260     ActionCode Act;
09261     Act = ModifyElementAction::Init(pOperation, 
09262                                     pOppositeActLst, 
09263                                     Verbs[ChangedIndex],
09264                                     Flags[ChangedIndex],
09265                                     Coords[ChangedIndex],
09266                                     ChangedIndex,
09267                                     ChangedPath,
09268                                     (Action**)(&ModAction));
09269     if (Act == AC_FAIL)
09270         return AC_FAIL;
09271 
09272     // Force a re-draw of the place where the path used to be
09273     Document* pDoc = GetWorkingDoc();
09274     ERROR2IF( pDoc == NULL, AC_FAIL, "There was no current document when undoing modifypath" );
09275     Spread* pSpread = ChangedPath->FindParentSpread();
09276     DocRect Invalid = ChangedPath->GetUnionBlobBoundingRect();
09277     pDoc->ForceRedraw( pSpread, Invalid, FALSE, ChangedPath );
09278 
09279     Verbs[ChangedIndex] = ChangedVerb;
09280     Flags[ChangedIndex] = ChangedFlags;
09281     Coords[ChangedIndex] = ChangedCoord;
09282 
09283     ChangedPath->InvalidateBoundingRect();
09284     Invalid = ChangedPath->GetUnionBlobBoundingRect();
09285     pDoc->ForceRedraw( pSpread, Invalid, FALSE, ChangedPath );
09286 
09287     return Act;
09288 }
09289 
09290 ModifyElementAction::~ModifyElementAction()
09291 {
09292 }
09293 
09295 // The ModifyFlagsAction class                                                               //
09297 
09298 /********************************************************************************************
09299 
09300 >   ModifyFlagsAction::ModifyFlagsAction()
09301 
09302     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
09303     Created:    23/6/94
09304     Inputs:     -
09305     Outputs:    -
09306     Returns:    -
09307     Purpose:    Constructor for the action to undo path flags modification
09308     Errors:     -
09309     SeeAlso:    -
09310 
09311 ********************************************************************************************/
09312 
09313 ModifyFlagsAction::ModifyFlagsAction()
09314 {
09315     ChangedPath = NULL;
09316 }
09317 
09318 
09319 /********************************************************************************************
09320 
09321 >   ActionCode ModifyFlagsAction::Init(  Operation* pOp,
09322                                         ActionList* pActionList,
09323                                         PathFlags Flags, 
09324                                         INT32 Index, 
09325                                         NodePath* WhichPath,
09326                                         Action** NewAction)
09327 
09328     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
09329     Created:    19/7/94
09330     Inputs:     pOp is the pointer to the operation to which this action belongs
09331                 pActionList is the action list to which this action should be added
09332                 Flags are the flags of the changed element
09333                 Index is the index into the path of the element
09334     Outputs:    NewAction is a pointer to a pointer to an action, allowing the function to return
09335                 a pointer to the created action
09336     Returns:    ActionCode, one of AC_OK, AC_NO_RECORD or AC_FAIL
09337     Purpose:    This is the function which creates an instance of this action. If there is no room 
09338                 in the undo buffer (which is determined by the base class Init function called within)
09339                 the function will either return AC_NO_RECORD which means the operation can continue, 
09340                 but no undo information needs to be stored, or AC_OK which means the operation should
09341                 continue AND record undo information. If the function returns AC_FAIL, there was not 
09342                 enough memory to record the undo information, and the user has decided not to continue
09343                 with the operation.
09344     Errors:     -
09345     SeeAlso:    Action::Init()
09346 
09347 ********************************************************************************************/
09348 
09349 
09350 
09351 ActionCode ModifyFlagsAction::Init(  Operation* pOp,
09352                                     ActionList* pActionList,
09353                                     PathFlags Flags, 
09354                                     INT32 Index, 
09355                                     NodePath* WhichPath,
09356                                     Action** NewAction)
09357 {
09358     UINT32 ActSize = sizeof(ModifyFlagsAction);
09359 
09360     ActionCode Ac = Action::Init( pOp, pActionList, ActSize, CC_RUNTIME_CLASS(ModifyFlagsAction), NewAction);
09361     if ((Ac==AC_OK) && (*NewAction != NULL))
09362     {
09363         ((ModifyFlagsAction*)*NewAction)->ChangedFlags = Flags;
09364         ((ModifyFlagsAction*)*NewAction)->ChangedIndex = Index;
09365         ((ModifyFlagsAction*)*NewAction)->ChangedPath = WhichPath;
09366     }
09367 //  if (IsUserName("Jim")) TRACE( _T("Creating ModifyElementAction"));
09368 
09369     return Ac;
09370 }
09371 
09372 ActionCode ModifyFlagsAction::Execute()
09373 {
09374     // Here we're undoing the modify, so we have to change the element in the path,
09375     // recording redo information at the same time.
09376     
09377     ModifyFlagsAction* ModAction;
09378     
09379     // Get pointers to all the arrays of the path
09380 
09381     PathFlags* Flags = ChangedPath->InkPath.GetFlagArray();
09382 
09383     // Create a redo action for this action, which is also a ModifyElementAction
09384 
09385     ActionCode Act;
09386     Act = ModifyFlagsAction::Init(pOperation, 
09387                                     pOppositeActLst, 
09388                                     Flags[ChangedIndex],
09389                                     ChangedIndex,
09390                                     ChangedPath,
09391                                     (Action**)(&ModAction));
09392     if (Act == AC_FAIL)
09393         return AC_FAIL;
09394 
09395     // Force a re-draw of the place where the path used to be
09396     Document* pDoc = GetWorkingDoc();
09397     ERROR2IF( pDoc == NULL, AC_FAIL, "There was no current document when undoing modifypath" );
09398     Spread* pSpread = ChangedPath->FindParentSpread();
09399     DocRect Invalid = ChangedPath->GetUnionBlobBoundingRect();
09400     pDoc->ForceRedraw( pSpread, Invalid, FALSE, ChangedPath );
09401 
09402     Flags[ChangedIndex] = ChangedFlags;
09403 
09404     Invalid = ChangedPath->GetUnionBlobBoundingRect();
09405     pDoc->ForceRedraw( pSpread, Invalid, FALSE, ChangedPath );
09406 
09407     return Act;
09408 }
09409 
09410 ModifyFlagsAction::~ModifyFlagsAction()
09411 {
09412 }
09413 
09415 // The ModifyFilledAction class                                                              //
09417 
09418 /********************************************************************************************
09419 
09420 >   ModifyFilledAction::ModifyFilledAction()
09421 
09422     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
09423     Created:    21/7/94
09424     Inputs:     -
09425     Outputs:    -
09426     Returns:    -
09427     Purpose:    Constructor for the action to undo path Filled flag
09428     Errors:     -
09429     SeeAlso:    -
09430 
09431 ********************************************************************************************/
09432 
09433 ModifyFilledAction::ModifyFilledAction()
09434 {
09435     UndoFilled = TRUE;
09436     OldFilledState = FALSE;
09437 }
09438 
09439 
09440 /********************************************************************************************
09441 
09442 >   ActionCode ModifyFilledAction::Init(  Operation* pOp,
09443                                         ActionList* pActionList,
09444                                         BOOL UndoFillOrStroke,
09445                                         BOOL FilledFlag,
09446                                         NodePath* WhichPath,
09447                                         Action** NewAction)
09448 
09449     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
09450     Created:    21/7/94
09451     Inputs:     pOp is the pointer to the operation to which this action belongs
09452                 pActionList is the action list to which this action should be added
09453                 UndoFillOrStroke is TRUE if this action should undo a fill, otherwise a stroke
09454                 FilledFlag is the state of the flag that must be replaced when undoing
09455                 WhichPath is the path whose flag we're modifying
09456     Outputs:    NewAction is a pointer to a pointer to an action, allowing the function to return
09457                 a pointer to the created action
09458     Returns:    ActionCode, one of AC_OK, AC_NO_RECORD or AC_FAIL
09459     Purpose:    This is the function which creates an instance of this action. If there is no room 
09460                 in the undo buffer (which is determined by the base class Init function called within)
09461                 the function will either return AC_NO_RECORD which means the operation can continue, 
09462                 but no undo information needs to be stored, or AC_OK which means the operation should
09463                 continue AND record undo information. If the function returns AC_FAIL, there was not 
09464                 enough memory to record the undo information, and the user has decided not to continue
09465                 with the operation.
09466     Errors:     -
09467     SeeAlso:    Action::Init()
09468 
09469 ********************************************************************************************/
09470 
09471 
09472 
09473 ActionCode ModifyFilledAction::Init(Operation* pOp,
09474                                     ActionList* pActionList,
09475                                     BOOL UndoFillOrStroke,
09476                                     BOOL FilledFlag,
09477                                     NodePath* WhichPath,
09478                                     Action** NewAction)
09479 {
09480     UINT32 ActSize = sizeof(ModifyFilledAction);
09481 
09482     ActionCode Ac = Action::Init( pOp, pActionList, ActSize, CC_RUNTIME_CLASS(ModifyFilledAction), NewAction);
09483     if ((Ac==AC_OK) && (*NewAction != NULL))
09484     {
09485         ((ModifyFilledAction*)*NewAction)->UndoFilled = UndoFillOrStroke;
09486         ((ModifyFilledAction*)*NewAction)->OldFilledState = FilledFlag;
09487         ((ModifyFilledAction*)*NewAction)->ChangedPath = WhichPath;
09488     }
09489 //  if (IsUserName("Jim")) TRACE( _T("Creating ModifyElementAction"));
09490 
09491     return Ac;
09492 }
09493 
09494 ActionCode ModifyFilledAction::Execute()
09495 {
09496     // Here we're undoing the modify, so we have to change the element in the path,
09497     // recording redo information at the same time.
09498     
09499     ModifyFilledAction* ModAction;
09500     
09501     // Create a redo action for this action, which is also a ModifyElementAction
09502 
09503     BOOL CurState;
09504     if (UndoFilled)
09505         CurState = ChangedPath->InkPath.IsFilled;
09506     else
09507         CurState = ChangedPath->InkPath.IsStroked;
09508     
09509     ActionCode Act;
09510     Act = ModifyFilledAction::Init(pOperation, 
09511                                     pOppositeActLst, 
09512                                     UndoFilled,
09513                                     CurState,
09514                                     ChangedPath,
09515                                     (Action**)(&ModAction));
09516     if (Act == AC_FAIL)
09517         return AC_FAIL;
09518 
09519     // Force a re-draw of the place where the path used to be
09520     Document* pDoc = GetWorkingDoc();
09521     ERROR2IF( pDoc == NULL, AC_FAIL, "There was no current document when undoing modifypath" );
09522     Spread* pSpread = ChangedPath->FindParentSpread();
09523     DocRect Invalid = ChangedPath->GetUnionBlobBoundingRect();
09524     pDoc->ForceRedraw( pSpread, Invalid, FALSE, ChangedPath );
09525 
09526     if (UndoFilled)
09527         ChangedPath->InkPath.IsFilled = OldFilledState;
09528     else
09529         ChangedPath->InkPath.IsStroked = OldFilledState;
09530 
09531     Invalid = ChangedPath->GetUnionBlobBoundingRect();
09532     pDoc->ForceRedraw( pSpread, Invalid, FALSE, ChangedPath );
09533 
09534     return Act;
09535 }
09536 
09537 ModifyFilledAction::~ModifyFilledAction()
09538 {
09539 }
09540 
09542 // The RecalcBoundsAction class                                                              //
09544 
09545 /********************************************************************************************
09546 
09547 >   RecalcBoundsAction::RecalcBoundsAction()
09548 
09549     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
09550     Created:    11/7/94
09551     Inputs:     -
09552     Outputs:    -
09553     Returns:    -
09554     Purpose:    Constructor for the action to recalculate bounds of a node
09555     Errors:     -
09556     SeeAlso:    -
09557 
09558 ********************************************************************************************/
09559 RecalcBoundsAction::RecalcBoundsAction()
09560 {
09561 }
09562 
09563 
09564 
09565 /********************************************************************************************
09566 
09567 >   ActionCode RecalcBoundsAction::Init(  Operation* pOp,
09568                                         ActionList* pActionList,
09569                                         NodeRenderableBounded* WhichNode,
09570                                         Action** NewAction)
09571 
09572     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
09573     Created:    11/7/94
09574     Inputs:     pOp is the pointer to the operation to which this action belongs
09575                 pActionList is the action list to which this action should be added
09576                 WhichNode is the object whose bounds we want to recalculate
09577     Outputs:    NewAction is a pointer to a pointer to an action, allowing the function to return
09578                 a pointer to the created action
09579     Returns:    ActionCode, one of AC_OK, AC_NO_RECORD or AC_FAIL
09580     Purpose:    This is the function which creates an instance of this action. If there is no room 
09581                 in the undo buffer (which is determined by the base class Init function called within)
09582                 the function will either return AC_NO_RECORD which means the operation can continue, 
09583                 but no undo information needs to be stored, or AC_OK which means the operation should
09584                 continue AND record undo information. If the function returns AC_FAIL, there was not 
09585                 enough memory to record the undo information, and the user has decided not to continue
09586                 with the operation.
09587     Errors:     -
09588     SeeAlso:    Action::Init()
09589 
09590 ********************************************************************************************/
09591 ActionCode RecalcBoundsAction::Init(  Operation* pOp,
09592                                     ActionList* pActionList,
09593                                     NodeRenderableBounded* WhichNode,
09594                                     Action** NewAction)
09595 {
09596     UINT32 ActSize = sizeof(RecalcBoundsAction);
09597 
09598     ActionCode Ac = Action::Init( pOp, pActionList, ActSize, CC_RUNTIME_CLASS(RecalcBoundsAction), NewAction);
09599     if ((Ac == AC_OK) && (*NewAction != NULL))
09600     {
09601         ((RecalcBoundsAction*)*NewAction)->ChangedNode = WhichNode;
09602         ((RecalcBoundsAction*)*NewAction)->OldBounds = WhichNode->GetBoundingRect();
09603     }
09604 
09605     return Ac;
09606 }
09607 
09608 
09609 
09610 /********************************************************************************************
09611 
09612 >   ActionCode RecalcBoundsAction::DoRecord(Operation* pOp,
09613                                             ActionList* pActionList,
09614                                             NodeRenderableBounded* WhichNode,
09615                                             BOOL OptimiseRedraw = FALSE)
09616 
09617     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
09618     Created:    12/7/94
09619     Inputs:     pOp is the currently running operation
09620                 pActionList is a pointer ot the action list to which the action should be appended
09621                 WhichNode is the node we're dealing with here
09622                 OptimiseRedraw : If this is TRUE and WhichNode is a NodePath then the NeedToRender
09623                                  flags are used to determine the areas of the screen to redraw
09624     Outputs:    -
09625     Returns:    Action code which indicates success or failure to create the action
09626     Purpose:    This static function makes it a little easier to use this action. It creates an instance
09627                 of this action and appends it to the action list. Then it invalidates the current
09628                 view according to the blob rectangle of the object.
09629                 This function should be called before you do anything to change the bounding box of an
09630                 object. It will invalidate the view (including blobs) allowing you to manipulate the object
09631                 to your heart's content. In order to keep undo and redo running, make sure that after you 
09632                 have finished manipulating the object, you call RecordBoundsAction::DoRecord which will not
09633                 only invalidate the new bounds rectangle of the object (including blobs) but will also
09634                 make sure that there are the correct actions in the undo list.
09635     Errors:     -
09636     SeeAlso:    RecordBoundsAction::DoRecalc
09637 
09638 ********************************************************************************************/
09639 ActionCode RecalcBoundsAction::DoRecalc(Operation* pOp,
09640                                         ActionList* pActionList,
09641                                         NodeRenderableBounded* WhichNode,
09642                                         BOOL OptimiseRedraw)
09643 {
09644     ERROR3IF(OptimiseRedraw && !WhichNode->IsNodePath(),"Can't optimise redraw for a non-path object");
09645 
09646     RecalcBoundsAction* RecAction;
09647     ActionCode Act = RecalcBoundsAction::Init(pOp, pActionList, WhichNode, (Action**)&RecAction);
09648     if (Act != AC_FAIL)
09649     {
09650         // Force a re-draw of the place where the path used to be
09651         Document* pDoc = pOp->GetWorkingDoc();
09652         ERROR2IF( pDoc == NULL, AC_FAIL, "There was no current document in RecordBoundsAction::DoRecord" );
09653         Spread* pSpread = WhichNode->FindParentSpread();
09654         DocRect Invalid;
09655 
09656         ((NodePath*)WhichNode)->ReleaseCached();
09657         
09658         // Do an optimised redraw if possible
09659         if (WhichNode->IsNodePath() && OptimiseRedraw)
09660             OptimiseRedraw = CauseOptimisedRedraw((NodePath*)WhichNode, pDoc, pSpread);
09661         else
09662             OptimiseRedraw = FALSE;
09663 
09664         if (!OptimiseRedraw)
09665             pDoc->ForceRedraw( pSpread, WhichNode->GetUnionBlobBoundingRect());
09666     }
09667     return Act;
09668 }
09669 
09670 
09671 
09672 /********************************************************************************************
09673 
09674 >   ActionCode RecalcBoundsAction::Execute()
09675 
09676     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
09677     Created:    11/7/94
09678     Inputs:     -
09679     Outputs:    -
09680     Returns:    ActionCode, either AC_OK, AC_NORECORD or AC_FAIL
09681     Purpose:    Will replace the bounding box of an object, recording the existing bounds
09682     Errors:     -
09683     SeeAlso:    -
09684 
09685 ********************************************************************************************/
09686 ActionCode RecalcBoundsAction::Execute()
09687 {
09688     // This is undoing a bounds recalculation
09689     
09690     RecordBoundsAction* ReAction;
09691     
09692     // Create a redo action for this action, which is also a RecalcBoundsAction
09693 
09694     ActionCode Act;
09695     Act = RecordBoundsAction::Init(pOperation, 
09696                                     pOppositeActLst, 
09697                                     ChangedNode,
09698                                     (Action**)(&ReAction));
09699     if (Act == AC_FAIL)
09700         return AC_FAIL;
09701 
09702     // Force a re-draw of the place where the path used to be
09703     Document* pDoc = GetWorkingDoc();
09704     ERROR2IF( pDoc == NULL, AC_FAIL, "There was no current document when undoing RecalcBounds" );
09705     Spread* pSpread = ChangedNode->FindParentSpread();
09706     
09707     if (pSpread != NULL)
09708     {
09709         DocRect Invalid = ChangedNode->GetUnionBlobBoundingRect();
09710         pDoc->ForceRedraw( pSpread, Invalid, FALSE, ChangedNode );
09711 
09712         // Mark the bounding rect as invalid
09713         ChangedNode->InvalidateBoundingRect();
09714     }
09715     
09716     return Act;
09717 }
09718 
09719 RecalcBoundsAction::~RecalcBoundsAction()
09720 {
09721 }
09722 
09723 
09724 
09725 /********************************************************************************************
09726 
09727 >   static BOOL RecalcBoundsAction::CauseOptimisedRedraw(NodePath* pChangedPath, Document* pDoc, Spread* pSpread)
09728 
09729     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
09730     Created:    28/04/95
09731     Inputs:     pChangedPath - points to the path to cause a redraw of
09732                 pDoc - points to the document containing the path
09733                 pSpread - points to the spread containing the path
09734     Outputs:    -
09735     Returns:    TRUE if the optimised redraw succeded,
09736                 FALSE if the entire shape should be redrawn (an error occured)
09737     Purpose:    Called by DoRecord and DoRecalc to only invalidate the areas of the screen 
09738                 covered by the parts of the path that have changed.  These will have the
09739                 NeedToRender flag set.
09740     Errors:     -
09741     SeeAlso:    RecordBoundsAction::DoRecord, RecalcBoundsAction::DoRecalc
09742 
09743 ********************************************************************************************/
09744 BOOL RecalcBoundsAction::CauseOptimisedRedraw(NodePath* pChangedPath, Document* pDoc, Spread* pSpread)
09745 {
09746     // If the shape is filled then we can't optimise
09747     if (pChangedPath->InkPath.IsFilled)
09748         return FALSE;
09749 
09750     // Build an attribute map
09751     CCAttrMap AttribMap(30);
09752     if (!pChangedPath->FindAppliedAttributes(&AttribMap)) 
09753         return FALSE;
09754 
09755     // Get the current linewidth applied to the NodePath
09756     MILLIPOINT LineWidth = 0;
09757     void* pLineWidth = NULL;
09758     AttribMap.Lookup( CC_RUNTIME_CLASS(AttrLineWidth), pLineWidth );
09759     ERROR3IF(pLineWidth == NULL, "Did not find line width attribute");
09760     if (pLineWidth != NULL) 
09761         LineWidth = ((AttrLineWidth*)pLineWidth)->Value.LineWidth;
09762     else
09763         return FALSE;
09764 
09765     // Get the current dash pattern applied to the NodePath
09766     // Can't optimise if it's a dashed line - the pattern shifts around
09767     void* pDashPat = NULL;
09768     AttribMap.Lookup( CC_RUNTIME_CLASS(AttrDashPattern), pDashPat );
09769     ERROR3IF(pDashPat == NULL, "Did not find dash pattern attribute");
09770     if ((pDashPat == NULL) || (((AttrDashPattern*)pDashPat)->Value.DashPattern.Elements != 0) )
09771         return FALSE;
09772 
09773     // Get the current mitre limit applied to the NodePath
09774     MILLIPOINT MitreLimit = 0;
09775     void* pMitreAttr = NULL;
09776     AttribMap.Lookup( CC_RUNTIME_CLASS(AttrMitreLimit),pMitreAttr);
09777     ERROR3IF(pMitreAttr == NULL, "Did not find mitre attribute");
09778     if (pMitreAttr != NULL) 
09779         MitreLimit = ((AttrMitreLimit*)pLineWidth)->Value.MitreLimit;
09780     else
09781         return FALSE;
09782 
09783 
09784     // See if theres a brush attribute applied to the nodepath
09785     AttrBrushType* pAttrBrush = (AttrBrushType*) pChangedPath->FindAppliedAttribute(CC_RUNTIME_CLASS(AttrBrushType));
09786     if (pAttrBrush != NULL && pAttrBrush->GetBrushHandle() != BrushHandle_NoBrush)
09787     {
09788         pAttrBrush->FlushCache();
09789         DocRect Redraw = pAttrBrush->GetAttrBoundingRect(pChangedPath);
09790         pDoc->ForceRedraw(pSpread, Redraw, FALSE, pChangedPath);
09791     }
09792 
09793     // Get the arrowheads applied to the NodePath
09794     AttrEndArrow* pEndArrow = (AttrEndArrow*) pChangedPath->FindAppliedAttribute(CC_RUNTIME_CLASS(AttrEndArrow));
09795     AttrStartArrow* pStartArrow = (AttrStartArrow*) pChangedPath->FindAppliedAttribute(CC_RUNTIME_CLASS(AttrStartArrow));
09796 
09797     // redraw the areas covered by the arrowheads
09798     // PABODGE - only redraw if the arrohead has moved
09799     if (pEndArrow != NULL)
09800     {
09801         DocRect Redraw = pEndArrow->GetAttrBoundingRect(pChangedPath, &AttribMap);
09802         pDoc->ForceRedraw( pSpread, Redraw, FALSE, pChangedPath );
09803     }
09804     if (pStartArrow != NULL)
09805     {
09806         DocRect Redraw = pStartArrow->GetAttrBoundingRect(pChangedPath, &AttribMap);
09807         pDoc->ForceRedraw( pSpread, Redraw, FALSE, pChangedPath );
09808     }
09809 
09810     // PABODGE - should only inflate by mitre limit if mitre join applied!
09811     MILLIPOINT InflationSize = LineWidth + MitreLimit*4;
09812     const INT32 NumPoints = pChangedPath->InkPath.GetNumCoords();
09813     INT32 loop = 0;
09814     INT32 Previous = 0;
09815     PathVerb* Verbs = NULL;
09816     DocCoord* Coords = NULL;
09817     PathFlags* Flags = NULL;
09818     pChangedPath->InkPath.GetPathArrays(&Verbs, &Coords, &Flags);
09819 
09820     if (pChangedPath->InkPath.FindNextEndPoint(&loop))
09821     {
09822         while (loop < NumPoints)
09823         {
09824             // If the need to render flag is set then cause a redraw of this segment
09825             if (Flags[loop].NeedToRender)
09826             {
09827                 // Get the size of the redraw rect
09828                 DocRect Invalid(Coords[Previous], Coords[Previous]);
09829                 while (Previous < loop)
09830                 {
09831                     Previous++;
09832                     Invalid.IncludePoint(Coords[Previous]);
09833                 }
09834     
09835                 // Do the redraw
09836                 Invalid.Inflate(InflationSize);
09837                 pDoc->ForceRedraw( pSpread, Invalid, FALSE, pChangedPath );
09838             }
09839 
09840             // Point to the next endpoint
09841             Previous = loop;
09842             if (!pChangedPath->InkPath.FindNextEndPoint(&loop))
09843                 break;
09844         }
09845     }
09846     else
09847         return FALSE;
09848 
09849     return TRUE;
09850 }
09851 
09852 
09853 
09855 // The RecordBoundsAction class                                                              //
09857 
09858 /********************************************************************************************
09859 
09860 >   RecordBoundsAction::RecordBoundsAction()
09861 
09862     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
09863     Created:    11/7/94
09864     Inputs:     -
09865     Outputs:    -
09866     Returns:    -
09867     Purpose:    Constructor for the action to recalculate bounds of a node
09868     Errors:     -
09869     SeeAlso:    -
09870 
09871 ********************************************************************************************/
09872 RecordBoundsAction::RecordBoundsAction()
09873 {
09874 }
09875 
09876 
09877 
09878 /********************************************************************************************
09879 
09880 >   ActionCode RecordBoundsAction::Init(  Operation* pOp,
09881                                         ActionList* pActionList,
09882                                         NodeRenderableBounded* WhichNode,
09883                                         Action** NewAction)
09884 
09885     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
09886     Created:    11/7/94
09887     Inputs:     pOp is the pointer to the operation to which this action belongs
09888                 pActionList is the action list to which this action should be added
09889                 WhichNode is the object whose bounds we want to recalculate
09890     Outputs:    NewAction is a pointer to a pointer to an action, allowing the function to return
09891                 a pointer to the created action
09892     Returns:    ActionCode, one of AC_OK, AC_NO_RECORD or AC_FAIL
09893     Purpose:    This is the function which creates an instance of this action. If there is no room 
09894                 in the undo buffer (which is determined by the base class Init function called within)
09895                 the function will either return AC_NO_RECORD which means the operation can continue, 
09896                 but no undo information needs to be stored, or AC_OK which means the operation should
09897                 continue AND record undo information. If the function returns AC_FAIL, there was not 
09898                 enough memory to record the undo information, and the user has decided not to continue
09899                 with the operation.
09900     Errors:     -
09901     SeeAlso:    Action::Init()
09902 
09903 ********************************************************************************************/
09904 ActionCode RecordBoundsAction::Init(  Operation* pOp,
09905                                     ActionList* pActionList,
09906                                     NodeRenderableBounded* WhichNode,
09907                                     Action** NewAction)
09908 {
09909     UINT32 ActSize = sizeof(RecordBoundsAction);
09910 
09911     ActionCode Ac = Action::Init( pOp, pActionList, ActSize, CC_RUNTIME_CLASS(RecordBoundsAction), NewAction);
09912     if ((Ac == AC_OK) && (*NewAction != NULL))
09913         ((RecordBoundsAction*)*NewAction)->ChangedNode = WhichNode;
09914 
09915     return Ac;
09916 }
09917 
09918 
09919 
09920 /********************************************************************************************
09921 
09922 >   ActionCode RecordBoundsAction::DoRecord(Operation* pOp,
09923                                             ActionList* pActionList,
09924                                             NodeRenderableBounded* WhichNode,
09925                                             BOOL OptimiseRedraw = FALSE)
09926 
09927     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
09928     Created:    12/7/94
09929     Inputs:     pOp is the currently running operation
09930                 pActionList is a pointer ot the action list to which the action should be appended
09931                 WhichNode is the node we're dealing with here
09932                 OptimiseRedraw : If this is TRUE and WhichNode is a NodePath then the NeedToRender
09933                                  flags are used to determine the areas of the screen to redraw
09934     Outputs:    -
09935     Returns:    Action code which indicates success or failure to create the action
09936     Purpose:    This static function makes it a little easier to use this action. It creates an instance
09937                 of this action and appends it to the action list. Then it invalidates the current
09938                 view according to the blob rectangle of the object.
09939     Errors:     ERROR2 if the operation dosen't have a document
09940                 ERROR3 if you attempt to optimise for a non path object
09941     SeeAlso:    RecalcBoundsAction::DoRecalc
09942 
09943 ********************************************************************************************/
09944 ActionCode RecordBoundsAction::DoRecord(Operation* pOp,
09945                                         ActionList* pActionList,
09946                                         NodeRenderableBounded* WhichNode,
09947                                         BOOL OptimiseRedraw)
09948 {
09949     ERROR3IF(OptimiseRedraw && !WhichNode->IsNodePath(),"Can't optimise redraw for a non-path object");
09950 
09951     RecordBoundsAction* RecAction;
09952     ActionCode Act = RecordBoundsAction::Init(pOp, pActionList, WhichNode, (Action**)&RecAction);
09953     if (Act != AC_FAIL)
09954     {
09955         // Force a re-draw of the place where the path used to be
09956         Document* pDoc = pOp->GetWorkingDoc();
09957         ERROR2IF( pDoc == NULL, AC_FAIL, "There was no current document in RecordBoundsAction::DoRecord" );
09958 
09959         Spread* pSpread = WhichNode->FindParentSpread();
09960 
09961         // Do an optimised redraw if possible
09962         if (WhichNode->IsNodePath() && OptimiseRedraw)
09963             OptimiseRedraw = RecalcBoundsAction::CauseOptimisedRedraw((NodePath*)WhichNode, pDoc, pSpread);
09964         else
09965             OptimiseRedraw = FALSE;
09966 
09967         if (!OptimiseRedraw)
09968             pDoc->ForceRedraw( pSpread, WhichNode->GetUnionBlobBoundingRect(), FALSE, WhichNode);
09969     }
09970     return Act;
09971 }
09972 
09973 
09974 
09975 /********************************************************************************************
09976 
09977 >   ActionCode RecordBoundsAction::Execute()
09978 
09979     Author:     Jim_Lynn (Xara Group Ltd) <camelotdev@xara.com>
09980     Created:    11/7/94
09981     Inputs:     -
09982     Outputs:    -
09983     Returns:    ActionCode, either AC_OK, AC_NORECORD or AC_FAIL
09984     Purpose:    Will replace the bounding box of an object, recording the existing bounds
09985     Errors:     -
09986     SeeAlso:    -
09987 
09988 ********************************************************************************************/
09989 ActionCode RecordBoundsAction::Execute()
09990 {
09991     // This is undoing a bounds recalculation
09992     
09993     RecalcBoundsAction* ReAction;
09994     
09995     // Create a redo action for this action, which is also a RecalcBoundsAction
09996 
09997     ActionCode Act;
09998     Act = RecalcBoundsAction::Init(pOperation, 
09999                                     pOppositeActLst, 
10000                                     ChangedNode,
10001                                     (Action**)(&ReAction));
10002     if (Act == AC_FAIL)
10003         return AC_FAIL;
10004 
10005     // Force a re-draw of the place where the path used to be
10006     Document* pDoc = GetWorkingDoc();
10007     ERROR2IF( pDoc == NULL, AC_FAIL, "There was no current document when undoing RecalcBounds" );
10008     Spread* pSpread = ChangedNode->FindParentSpread();
10009     
10010     if (pSpread != NULL)
10011     {
10012         DocRect Invalid = ChangedNode->GetUnionBlobBoundingRect();
10013         pDoc->ForceRedraw( pSpread, Invalid, FALSE, ChangedNode );
10014     }
10015 
10016     return Act;
10017 }
10018 
10019 
10020 
10021 RecordBoundsAction::~RecordBoundsAction()
10022 {
10023 }
10024 
10025 
10026 
10027 
10028 
10029 /********************************************************************************************
10030 
10031 >   SavePathArraysAction::SavePathArraysAction()
10032 
10033     Author:     Mike_Kenny (Xara Group Ltd) <camelotdev@xara.com>
10034     Created:    27/01/95
10035     Purpose:    Constructor for the action to store a path on the undo
10036 
10037 ********************************************************************************************/
10038 
10039 SavePathArraysAction::SavePathArraysAction()
10040 {
10041     ChangedPath   = NULL;
10042     ChangedVerbs  = NULL;
10043     ChangedFlags  = NULL;
10044     ChangedCoords = NULL;
10045 }
10046 
10047 
10048 /********************************************************************************************
10049 
10050 >   SavePathArraysAction::~SavePathArraysAction()
10051 
10052     Author:     Mike_Kenny (Xara Group Ltd) <camelotdev@xara.com>
10053     Created:    27/01/95
10054     Purpose:    destructor for the action to store a path on the undo
10055 
10056 ********************************************************************************************/
10057 
10058 SavePathArraysAction::~SavePathArraysAction()
10059 {
10060     if (ChangedVerbs)
10061         CCFree(ChangedVerbs);
10062     if (ChangedCoords)
10063         CCFree(ChangedCoords);
10064     if (ChangedFlags)
10065         CCFree(ChangedFlags);
10066 }
10067 
10068 
10069 /********************************************************************************************
10070 
10071 >   ActionCode SavePathArraysAction::DoRecord(Operation* pOp,
10072                                               ActionList* pActionList,
10073                                               Path* pPath)
10074 
10075     Author:     Mike_Kenny (Xara Group Ltd) <camelotdev@xara.com>
10076     Created:    27/01/95
10077     Inputs:     pOp is the currently running operation
10078                 pActionList = a pointer ot the action list to which the action should be appended
10079                 pPath       = a pointer to the path whose data will be saved.
10080     Outputs:    NewAction   = a pointer to a pointer to an action, allowing the function 
10081                               to return a pointer to the created action
10082     Returns:    ActionCode, one of AC_OK, AC_NO_RECORD or AC_FAIL
10083     Purpose:    Use this function to get the SavePathArraysAction going. It is far simpler
10084                 and less error prone than calling SavePathArraysAction::Init directly.
10085     Errors:     -
10086     SeeAlso:    For a full explanation of this action see SavePathArraysAction::Init()
10087 
10088 ********************************************************************************************/
10089 
10090 ActionCode SavePathArraysAction::DoRecord(Operation* pOp, ActionList* pActionList, Path* pPath)
10091 {
10092     SavePathArraysAction* SaveAction;
10093     ActionCode Act = SavePathArraysAction::Init(pOp, pActionList, pPath, (Action**)&SaveAction, TRUE);
10094     return Act;
10095 }
10096 
10097 
10098 
10099 /********************************************************************************************
10100 
10101 >   ActionCode SavePathArraysAction::Init(Operation* pOp,
10102                                           ActionList* pActionList,
10103                                           Path* pPath,
10104                                           Action** NewAction,
10105                                           BOOL CreateArrays = TRUE)
10106 
10107     Author:     Mike_Kenny (Xara Group Ltd) <camelotdev@xara.com>
10108     Created:    27/01/95
10109     Inputs:     pOp         = a pointer to the operation to which this action belongs
10110                 pActionList = the action list to which this action should be added
10111                 pPath       = a pointer to the path whose data will be saved.
10112                 CreateArrays= a boolean which should always be TRUE except for when this
10113                               action is being executed by the undo/redo system.
10114     Outputs:    NewAction   = a pointer to a pointer to an action, allowing the function 
10115                               to return a pointer to the created action
10116     Returns:    ActionCode, one of AC_OK, AC_NO_RECORD or AC_FAIL
10117     Purpose:    The actions static init function.
10118                 The purpose of this function is to create instances of SavePathsArrayAction.
10119                 Hence it static nature. It seems strange that you can call a function inside
10120                 a class to create another instance of that class, but thats what really happens
10121                 here. We pass back a pointer to the action if we've succeeded in creating
10122                 it.
10123                 This particular action init function also creates some external arrays to
10124                 save a paths data in. You need to call the function with CreateArrays set
10125                 to TRUE, to get it to save the paths arrays. It needs this parameter because
10126                 the very same function is called within the execution of a previously created
10127                 savepath action which incidently sets CreateArrays to FALSE.
10128                 (You can get a serious headache thinking about this one).
10129                 The idea is really to create the save arrays once, when this init function
10130                 is called and swap them between undo and redo actions, killing them off only
10131                 when the last thread is destructed.
10132     SeeAlso:    SavePathArraysAction::DoRecord()
10133 
10134 ********************************************************************************************/
10135 
10136 ActionCode SavePathArraysAction::Init(Operation* pOp,
10137                                       ActionList* pActionList,
10138                                       Path* pPath,
10139                                       Action** NewAction,
10140                                       BOOL CreateArrays)
10141 {
10142     ERROR1IF(pPath==NULL,AC_FAIL,"SavePathArraysAction::Init() passed a NULL path pointer");
10143 
10144     UINT32 NumElements = pPath->GetNumCoords();
10145     UINT32 ActSize = sizeof(SavePathArraysAction) + NumElements*(sizeof(PathVerb) + 
10146                                                                 sizeof(PathFlags) +
10147                                                                 sizeof(DocCoord) );
10148 
10149     ActionCode Ac = Action::Init( pOp, pActionList, ActSize, CC_RUNTIME_CLASS(SavePathArraysAction), NewAction);
10150 
10151     SavePathArraysAction* CreatedAction = (SavePathArraysAction*)(*NewAction);
10152     if (CreatedAction!=NULL)
10153     {
10154         CreatedAction->ChangedPath = pPath;
10155         if (CreateArrays)
10156             CreatedAction->SetUpArrays(pPath,pOp);
10157     }
10158     return Ac;
10159 }
10160 
10161 
10162 
10163 /********************************************************************************************
10164 
10165 >   ActionCode SavePathArraysAction::SetUpArrays(Path* pPath, Operation* pOp)
10166 
10167     Author:     Mike_Kenny (Xara Group Ltd) <camelotdev@xara.com>
10168     Created:    27/01/95
10169     Inputs:     pPath   = pointer to the path to save
10170                 pOp     = pointer to the current operation
10171     Returns:    one of AC_OK, AC_NORECORD, AC_FAIL
10172     Purpose:    
10173 
10174 ********************************************************************************************/
10175 
10176 ActionCode SavePathArraysAction::SetUpArrays(Path* pPath, Operation* pOp)
10177 {
10178     if (ChangedFlags!=NULL || ChangedVerbs!=NULL || ChangedCoords!=NULL)
10179     {
10180         ENSURE(TRUE,"SavePathArraysAction::SetUpArrays() failed");
10181         return AC_FAIL;
10182     }
10183 
10184     if (!SavePathArrays(pPath, pOp, &ChangedFlags, &ChangedVerbs, &ChangedCoords))
10185         return AC_FAIL;
10186 
10187     return AC_OK;
10188 }
10189 
10190 
10191 
10192 /********************************************************************************************
10193 
10194 >   ActionCode SavePathArraysAction::Execute()
10195 
10196     Author:     Mike_Kenny (Xara Group Ltd) <camelotdev@xara.com>
10197     Created:    27/01/95
10198     Inputs:     -
10199     Returns:    one of AC_OK, AC_NORECORD, AC_FAIL
10200     Purpose:    This function executes the SavePath action which will create an opposite
10201                 action record dependent on whether we're undoing or redoing. It will swap
10202                 the saved information about the path between the executing action record
10203                 and the arrays of the path.
10204 
10205 ********************************************************************************************/
10206 
10207 ActionCode SavePathArraysAction::Execute()
10208 {
10209     // first try to create an opposite action
10210     SavePathArraysAction* SaveAction;
10211     
10212     ActionCode Act;
10213     Act = SavePathArraysAction::Init(pOperation, pOppositeActLst, ChangedPath, (Action**)(&SaveAction), FALSE);
10214     if (Act == AC_FAIL)
10215         return AC_FAIL;
10216 
10217     // swap over the path data between this action record and the path.
10218     SwapPathArrays();
10219 
10220     // and update the new actions array pointers
10221     if (SaveAction!=NULL)
10222     {
10223         SaveAction->ChangedFlags = ChangedFlags;
10224         SaveAction->ChangedVerbs = ChangedVerbs;
10225         SaveAction->ChangedCoords = ChangedCoords;
10226 
10227         // make sure we clear these pointer vals before the destructor does
10228         // as we're sharing the arrays.
10229         ChangedVerbs  = NULL;
10230         ChangedCoords = NULL;
10231         ChangedFlags  = NULL;
10232     }
10233 
10234     return Act;
10235 }
10236 
10237 
10238 /********************************************************************************************
10239 
10240 >   BOOL SavePathArraysAction::SavePathArrays(  Path* pInputPath,
10241                                                 Operation* pOp,
10242                                                 PathFlags** pFlags,
10243                                                 PathVerb** pVerbs,
10244                                                 DocCoord** pCoords)
10245 
10246     Author:     Mike_Kenny (Xara Group Ltd) <camelotdev@xara.com>
10247     Created:    27/01/95
10248     Inputs:     pPath   = a pointer to the path to save
10249                 pOp     = the current operation pointer
10250     Outputs     pFlags points to a copied flags array, set to NULL if failed
10251                 pVerbs points to a copied verb array, set to NULL if failed
10252                 pCoords points to a copied coord array, set to NULL if failed
10253     Returns:    TRUE if the paths arrays have been saved
10254                 FALSE if not
10255     Purpose:    Creates three arrays and copies the input paths verbs, coords and flags
10256                 into these. If successfull it will return pointers to these arrays in
10257                 pFlags, pVerbs and pCoords.
10258                                                           
10259 ********************************************************************************************/
10260 
10261 BOOL SavePathArraysAction::SavePathArrays(  Path* pPath, 
10262                                             Operation* pOp,
10263                                             PathFlags** pFlags,
10264                                             PathVerb** pVerbs,
10265                                             DocCoord** pCoords)
10266 {   
10267     UINT32 NumElements = pPath->GetNumCoords();
10268     PathFlags* DFlags;
10269     PathVerb* DVerbs;
10270     DocCoord* DCoords;
10271 
10272     ALLOC_WITH_FAIL(DVerbs,(PathVerb*) CCMalloc(NumElements * sizeof(PathVerb)),pOp);
10273     ALLOC_WITH_FAIL(DCoords,(DocCoord*) CCMalloc(NumElements * sizeof(DocCoord)),pOp);
10274     ALLOC_WITH_FAIL(DFlags,(PathFlags*) CCMalloc(NumElements * sizeof(PathFlags)),pOp);
10275 
10276     if (!DVerbs || !DCoords || !DFlags)
10277     {
10278         if (DVerbs)  CCFree(DVerbs);
10279         if (DCoords) CCFree(DCoords);
10280         if (DFlags)  CCFree(DFlags);
10281 
10282         *pFlags=NULL;
10283         *pVerbs=NULL;
10284         *pCoords=NULL;
10285 
10286         return FALSE;
10287     }
10288 
10289     // Now copy the data from the path into the arrays
10290     DocCoord*  SCoords = pPath->GetCoordArray();
10291     PathFlags* SFlags  = pPath->GetFlagArray();
10292     PathVerb*  SVerbs  = pPath->GetVerbArray();
10293 
10294     // Copy all the data
10295     memmove((void*)(DCoords), (void*)(SCoords), NumElements*sizeof(DocCoord));
10296     memmove((void*)(DVerbs),  (void*)(SVerbs),  NumElements*sizeof(PathVerb));
10297     memmove((void*)(DFlags),  (void*)(SFlags),  NumElements*sizeof(PathFlags));
10298 
10299     *pFlags = DFlags;
10300     *pVerbs = DVerbs;
10301     *pCoords = DCoords;
10302 
10303     return TRUE;
10304 }
10305 
10306 
10307 /********************************************************************************************
10308 
10309 >   void SavePathArraysAction::SwapPathArrays()
10310 
10311     Author:     Mike_Kenny (Xara Group Ltd) <camelotdev@xara.com>
10312     Created:    27/01/95
10313     Inputs:     -
10314     Returns:    -
10315     Purpose:    Swaps the path array data with that saved in the action record.
10316 
10317 ********************************************************************************************/
10318 
10319 void SavePathArraysAction::SwapPathArrays()
10320 {
10321     INT32 NumElements = ChangedPath->GetNumCoords();
10322     PathVerb*  Verbs  = ChangedPath->GetVerbArray();
10323     DocCoord*  Coords = ChangedPath->GetCoordArray();
10324     PathFlags* Flags  = ChangedPath->GetFlagArray();
10325 
10326     PathVerb TempVerb;
10327     DocCoord TempCoord;
10328     PathFlags TempFlag;
10329     
10330     for (INT32 i=0; i<NumElements; i++)
10331     {
10332         TempVerb=Verbs[i];   Verbs[i]=ChangedVerbs[i];   ChangedVerbs[i]=TempVerb;
10333         TempCoord=Coords[i]; Coords[i]=ChangedCoords[i]; ChangedCoords[i]=TempCoord;
10334         TempFlag=Flags[i];   Flags[i]=ChangedFlags[i];   ChangedFlags[i]=TempFlag;
10335     }
10336 }
10337 
10338 
10339 
10340 /********************************************** StorePathSubSelStateAction ******************************************/
10341 
10342 
10343 
10345 //> StorePathSubSelStateAction::StorePathSubSelStateAction()
10346 //
10347 //  Author:     Peter
10348 //  Created:    9/11/95
10349 //  Purpose:    Constructor.  Initalises member variables
10350 //********************************************************************************************/
10351 //StorePathSubSelStateAction::StorePathSubSelStateAction()
10352 //{
10353 //  pStatePath = NULL;
10354 //  pIndexArray = NULL;
10355 //  NumStoredIndexes = 0;
10356 //  RecordingSelected = TRUE;
10357 //}
10358 //
10359 //
10360 //
10362 //> StorePathSubSelStateAction::~StorePathSubSelStateAction()
10363 //
10364 //  Author:     Peter
10365 //  Created:    9/11/95
10366 //  Purpose:    Destructor.  Frees claimed memory
10367 //********************************************************************************************/
10368 //StorePathSubSelStateAction::~StorePathSubSelStateAction()
10369 //{
10370 //  if (pIndexArray != NULL)
10371 //      CCFree(pIndexArray);
10372 //}
10373 //
10374 //
10375 //
10377 //> static ActionCode StorePathSubSelStateAction::DoRecord(Operation* pOp,
10378 //                                                          ActionList* pActionList,
10379 //                                                          Path* pPath)
10380 //  Author:     Peter
10381 //  Created:    9/11/95
10382 //  Inputs:     pOp         = a pointer to the current operation
10383 //              pActionList = a pointer to the action list to which the action should be appended
10384 //              pPath       = a pointer to the path whose data will be saved.
10385 //  Outputs:    Sets up action, recording the sub-selection state.
10386 //  Returns:    ActionCode, one of AC_OK, AC_NO_RECORD or AC_FAIL
10387 //  Purpose:    Use this function to the sub-selection state of a path in your operation.
10388 //              It's a wrapper around StorePathSubSelStateAction::Init.
10389 //  Errors:     -
10390 //  SeeAlso:    StorePathSubSelStateAction::Init()
10391 //********************************************************************************************/
10392 //ActionCode StorePathSubSelStateAction::DoRecord(Operation* pOp, ActionList* pActionList, Path* pPath)
10393 //{
10394 //  StorePathSubSelStateAction* SaveAction = NULL;
10395 //  return StorePathSubSelStateAction::Init(pOp, pActionList, pPath, (Action**)&SaveAction);
10396 //}
10397 //
10398 //
10399 //
10401 //> static ActionCode StorePathSubSelStateAction::Init(Operation* pOp,
10402 //                                                    ActionList* pActionList,
10403 //                                                    Path* pPath,
10404 //                                                    Action** NewAction)
10405 //  Author:     Peter
10406 //  Created:    9/11/95
10407 //  Inputs:     pOp         = a pointer to the operation to which this action belongs
10408 //              pActionList = the action list to which this action should be added
10409 //              pPath       = a pointer to the path whose data will be saved.
10410 //  Outputs:    The action is created, the path state is stored and the action is inserted
10411 //              into the actionlist
10412 //  Returns:    ActionCode, one of AC_OK, AC_NO_RECORD or AC_FAIL
10413 //  Purpose:    The purpose of this function is to create instances of StorePathSubSelStateAction.
10414 //              The action stores the sub-selection state of the given path.  It either stores
10415 //              the indexes of the selected endpoints, or the unselected ones, depending on
10416 //              which will use less memory.
10417 //              NOTE: Don't call this function directly - call DoRecord() 
10418 //  SeeAlso:    StorePathSubSelStateAction::DoRecord()
10419 //********************************************************************************************/
10420 //ActionCode StorePathSubSelStateAction::Init(Operation* pOp, ActionList* pActionList, Path* pPath, Action** NewAction)
10421 //{
10422 //  ERROR2IF((pPath==NULL) || (pOp==NULL) || (pActionList==NULL), AC_FAIL, "StorePathSubSelStateAction::Init() passed a NULL pointer");
10423 //
10424 //  // Work out how much memory the action array will require
10425 //  const UINT32 NumElements = pPath->GetNumCoords();       // Number of elements in the path
10426 //  PathFlags* pFlags = pPath->GetFlagArray();          // Pointer to the element flags
10427 //  UINT32 SelectedPoints = 0;                          // Count of selected endpoints
10428 //  UINT32 UnselectedPoints = 0;                            // Count of unselected endpoints.
10429 //  for (UINT32 loop = 0; loop<NumElements; loop++)
10430 //  {
10431 //      if (pFlags[loop].IsEndPoint)
10432 //      {
10433 //          if (pFlags[loop].IsSelected)
10434 //              SelectedPoints++;
10435 //          else
10436 //              UnselectedPoints++;
10437 //      }
10438 //  }
10439 //  const UINT32 ArraySize = sizeof(UINT32) * __min(SelectedPoints,UnselectedPoints);
10440 //
10441 //  // Create the action object
10442 //  UINT32 ActSize = sizeof(SavePathArraysAction) + ArraySize;
10443 //  ActionCode Ac = Action::Init( pOp, pActionList, ActSize, CC_RUNTIME_CLASS(StorePathSubSelStateAction), NewAction);
10444 //
10445 //  // Complete the setup of the action
10446 //  if ((Ac == AC_OK) && (*NewAction != NULL))
10447 //  {
10448 //      StorePathSubSelStateAction* CreatedAction = (StorePathSubSelStateAction*)(*NewAction);
10449 //      CreatedAction->pStatePath = pPath;
10450 //      CreatedAction->RecordingSelected = (SelectedPoints < UnselectedPoints);
10451 //      CreatedAction->NumStoredIndexes = __min(SelectedPoints,UnselectedPoints);
10452 //
10453 //      // Store the indexes of the selected or unselected points
10454 //      if (ArraySize > 0)
10455 //      {
10456 //          // Claim memory for the array (theoritically this wont fail as Init ensured there was enough)
10457 //          ALLOC_WITH_FAIL(CreatedAction->pIndexArray, ((UINT32*)CCMalloc(ArraySize)), pOp);
10458 //          if (CreatedAction->pIndexArray != NULL)
10459 //          {
10460 //              pFlags = pPath->GetFlagArray();         // Recache flag array pointer (it may have moved)
10461 //              UINT32 ArrayIndex = 0;
10462 //
10463 //              // Store the index of the endpoints whose selection state matches what we are storing
10464 //              for (UINT32 loop = 0; loop<NumElements; loop++)
10465 //              {
10466 //                  if (pFlags[loop].IsEndPoint && (pFlags[loop].IsSelected == CreatedAction->RecordingSelected) )
10467 //                      CreatedAction->pIndexArray[ArrayIndex++] = loop;
10468 //              }
10469 //          }
10470 //          else
10471 //              CreatedAction->RecordingSelected = FALSE;   // Deselect the lot
10472 //      }
10473 //  }
10474 //
10475 //  return Ac;
10476 //}
10477 //
10478 //
10479 //
10481 //> ActionCode StorePathSubSelStateAction::Execute()
10482 //
10483 //  Author:     Peter
10484 //  Created:    9/11/95
10485 //  Inputs:     -
10486 //  Returns:    one of AC_OK, AC_NORECORD, AC_FAIL
10487 //  Purpose:    This function creates a new action to store the path sub-selection state
10488 //              the sets the sub-selection state to the one in the stored arrays
10489 //********************************************************************************************/
10490 //ActionCode StorePathSubSelStateAction::Execute()
10491 //{
10492 //  ERROR2IF(pStatePath == NULL, AC_FAIL, "Path pointer was NULL");
10493 //
10494 //  // first try to create an opposite action
10495 //  StorePathSubSelStateAction* SaveAction;
10496 //  ActionCode Act = StorePathSubSelStateAction::Init(pOperation, pOppositeActLst, pStatePath, (Action**)(&SaveAction));
10497 //  if (Act == AC_FAIL)
10498 //      return AC_FAIL;
10499 //
10500 //  // now set the path sub-selection state to what we have recorded.
10501 //  const UINT32 NumElements = pStatePath->GetNumCoords();  // Number of elements in the path
10502 //  PathFlags* pFlags = pStatePath->GetFlagArray();         // Pointer to the element flags
10503 //  if (pIndexArray == NULL)
10504 //  {
10505 //      // Set all the endpoints to the stored selection state
10506 //      if (RecordingSelected)
10507 //          pStatePath->ClearSubSelection();
10508 //      else
10509 //          pStatePath->SetAllSubSelection();
10510 //  }
10511 //  else
10512 //  {
10513 //      // Set indexed endpoints to the stored selection state, and the others to the opposite
10514 //      UINT32 ArrayIndex = 0;
10515 //      for (UINT32 loop = 0; loop<NumElements; loop++)
10516 //      {
10517 //          if (pFlags[loop].IsEndPoint)
10518 //          {
10519 //              if ((ArrayIndex < NumStoredIndexes) && (pIndexArray[ArrayIndex] == loop))
10520 //              {
10521 //                  pFlags[loop].IsSelected = RecordingSelected;
10522 //                  ArrayIndex ++;
10523 //              }
10524 //              else
10525 //                  pFlags[loop].IsSelected = !RecordingSelected;
10526 //          }
10527 //      }
10528 //  }
10529 //
10530 //  // Clean up the control points selection state
10531 //  pStatePath->EnsureSelection(TRUE);
10532 //
10533 //  return Act;
10534 //}
10535 
10536 
10537 
10538 
10539 
10540 
10541 /********************************************************************************************
10542 >   BOOL OpCloseNodePaths::Init()
10543 
10544     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
10545     Created:    19/12/95
10546     Returns:    TRUE if all went OK, FALSE if initalisation failed
10547     Purpose:    Adds the operation to the list of all known operations
10548 ********************************************************************************************/
10549 BOOL OpCloseNodePaths::Init()
10550 {
10551     const INT32 HID_AUTOCLOSEPATHS = 0;
10552     BTNOP( AUTOCLOSEPATHS, OpCloseNodePaths, ARRANGE)
10553     return TRUE;                                             
10554 }
10555 
10556 
10557 
10558 /********************************************************************************************
10559 >   OpState OpCloseNodePaths::GetState(String_256* Description, OpDescriptor*)
10560 
10561     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
10562     Created:    19/12/95
10563     Outputs:    -
10564     Returns:    The state of the operation, so that menu items (ticks and greying can be
10565                 done properly
10566     Purpose:    Find out the state of the operation at the specific time.  Greyed unless there
10567                 is an open path with the start or end selected
10568 ********************************************************************************************/
10569 OpState OpCloseNodePaths::GetState(String_256* Description, OpDescriptor*)
10570 {
10571     OpState Blobby(FALSE, TRUE);        // unticked and greyed
10572 
10573     // Get the selection range and the first selected object
10574     SelRange* pSelection = GetApplication()->FindSelection();
10575     ERROR2IF(pSelection == NULL, Blobby, "No SelRange!");
10576     Node* pSelNode = pSelection->FindFirst();
10577 
10578     // Test all selected objects
10579     while (pSelNode != NULL)
10580     {
10581         if (pSelNode->IsNodePath())
10582         {
10583             if (IsThisPathCloseable((NodePath*)pSelNode))
10584             {
10585                 Blobby.Greyed = FALSE;
10586                 break;
10587             }
10588         }
10589 
10590         pSelNode = pSelection->FindNext(pSelNode);
10591     }
10592 
10593     return Blobby;
10594 }
10595 
10596 
10597 
10598 /********************************************************************************************
10599 >   virtual void OpCloseNodePaths::DoWithParam(OpDescriptor* pOpDesc, OpParam* pOpParam)
10600 
10601     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
10602     Created:    19/12/95
10603     Inputs:     pOpParam->Param1 = TRUE = close with curve, FALSE = close with line
10604                 pOpParam->Param2 = TRUE = close with smooth, FALSE = close with cusp
10605     Outputs:    -
10606     Returns:    -
10607     Purpose:    Closes all the selected open paths whose ends are selected
10608 ********************************************************************************************/
10609 void OpCloseNodePaths::DoWithParam(OpDescriptor* pOpDesc, OpParam* pOpParam)
10610 {
10611     BeginSlowJob();
10612 
10613     BOOL ok = TRUE;
10614     AddCurveFlag = pOpParam->Param1;
10615     AddSmoothFlag = pOpParam->Param2;
10616     IsPathClosing = TRUE;
10617     
10618     // Start the sel operation
10619     if (ok)
10620         ok = DoStartSelOp(TRUE, TRUE);
10621 
10622     // Get the selection range and the first selected object
10623     SelRange* pSelection = GetApplication()->FindSelection();
10624     Node* pSelNode = pSelection->FindFirst();
10625 
10626     // Test all selected objects
10627     while (ok && (pSelNode != NULL))
10628     {
10629         if (pSelNode->IsNodePath())
10630         {
10631             if (IsThisPathCloseable((NodePath*)pSelNode))
10632             {
10633                 NodePath *pPath = (NodePath*)pSelNode;
10634 //              BOOL skip = FALSE;
10635 
10636                 // Create and send a change message about this path edit
10637                 ObjChangeParam ObjChange(OBJCHANGE_STARTING,ObjChangeFlags(), pPath, this);
10638                 if (pPath->AllowOp(&ObjChange))
10639                     ok = CloseThisPath(pPath);
10640             }
10641         }
10642 
10643         pSelNode = pSelection->FindNext(pSelNode);
10644     }
10645 
10646     // Inform all the parents of this node that it has been changed.
10647     if (ok)
10648     {
10649         ObjChangeParam ObjFinished(OBJCHANGE_FINISHED, ObjChangeFlags(), NULL, this);
10650         ok = UpdateChangedNodes(&ObjFinished);
10651     }
10652 
10653     if (!ok)
10654     {
10655         FailAndExecute();
10656         InformError();
10657     }
10658 
10659     End();
10660 }
10661 
10662 
10663 
10664 /********************************************************************************************
10665 >   BOOL OpCloseNodePaths::CloseThisPath(NodePath* pPath)
10666 
10667     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
10668     Created:    19/12/95
10669     Inputs:     pPath - the path to close
10670     Outputs:    -
10671     Returns:    TRUE/FALSE for success/failure
10672     Purpose:    Sets up the member variables and then calls OpNodePathAddEndpoint::CompleteThisPath
10673                 to do all the work in closing the path
10674 ********************************************************************************************/
10675 BOOL OpCloseNodePaths::CloseThisPath(NodePath* pPath)
10676 {
10677     PathFlags* Flags = NULL;
10678     PathVerb* Verbs = NULL;
10679     DocCoord* Coords = NULL;
10680     pPath->InkPath.GetPathArrays(&Verbs, &Coords, &Flags);
10681     ERROR2IF( (Flags==NULL) || (Verbs==NULL) || (Coords==NULL), FALSE, "Path array pointer was NULL");
10682     INT32 NumCoords = pPath->InkPath.GetNumCoords();
10683 
10684     // Setup all member variables relating to this path
10685     InsertPosition = NumCoords-1;
10686     OriginalPath = pPath;
10687     StartMousePos = Coords[NumCoords-1];
10688     LastMousePos = Coords[NumCoords-1];
10689     StartSpread = pPath->FindParentSpread();
10690     UpdatePoint = -1;
10691     ERROR2IF( StartSpread==NULL, FALSE, "Path was not on a spread");
10692     
10693     // We also need to make a version of the path that we can change
10694     BOOL ok = BuildEditPath(LastMousePos);
10695 
10696     if (ok)
10697         ok = CompleteThisPath(StartMousePos);
10698 
10699     EditPath.ClearPath();
10700     
10701     return ok;
10702 }
10703 
10704 
10705 
10706 /********************************************************************************************
10707 >   BOOL OpCloseNodePaths::IsThisPathCloseable(NodePath* pPath)
10708 
10709     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
10710     Created:    19/12/95
10711     Inputs:     pPath - a pointer to a path
10712     Outputs:    -
10713     Returns:    TRUE if the path can be autoclosed, FALSE if not
10714     Purpose:    Finds out wether a path can be autoclosed.  It can if it is not closed and
10715                 the first or last endpoints in the last sub-path are selected.
10716 ********************************************************************************************/
10717 BOOL OpCloseNodePaths::IsThisPathCloseable(NodePath* pPath)
10718 {
10719     BOOL Closeable = FALSE;
10720     PathFlags* Flags = NULL;
10721     PathVerb* Verbs = NULL;
10722     pPath->InkPath.GetPathArrays(&Verbs, NULL, &Flags);
10723     if ((Flags==NULL) || (Verbs==NULL))
10724     {
10725         ERROR3("Path array pointer was NULL");
10726         return FALSE;
10727     }
10728     INT32 NumCoords = pPath->InkPath.GetNumCoords();
10729 
10730     // See if the end of the (last sub-path of the) path is open
10731     if (!(Verbs[NumCoords-1] & PT_CLOSEFIGURE))
10732     {
10733         // Get the coord of the start of the sub-path
10734         INT32 SubPathStart = NumCoords-1;
10735         pPath->InkPath.FindStartOfSubPath(&SubPathStart);
10736 
10737         // Is the path longer than one line and is the first or last point selected?
10738         if (SubPathStart < (NumCoords-2))
10739             Closeable = (Flags[NumCoords-1].IsSelected || Flags[SubPathStart].IsSelected);
10740     }
10741 
10742     return Closeable;
10743 }
10744 
10745 

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