combshps.cpp

Go to the documentation of this file.
00001 // $Id: combshps.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 // Implements the combine shape ops, like add, subtract, intersect and slice
00099 
00100 /*
00101 */
00102 
00103 #include "camtypes.h"
00104 #include "combshps.h"
00105 //#include "markn.h"
00106 //#include "resource.h"
00107 //#include "app.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00108 #include "nodepath.h"
00109 #include "gclip.h"
00110 #include "gclips.h"
00111 //#include "nodeattr.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00112 #include "progress.h"
00113 #include "grndrgn.h"
00114 //#include "combbuts.h"
00115 #include "gdraw.h"
00116 //#include "docview.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00117 //#include "document.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00118 //#include "group.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00119 #include "attrmap.h"
00120 #include "opbevel.h"
00121 //#include "nodecomp.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00122 #include "nodetxts.h"
00123 #include "brshattr.h"
00124 #include "strkattr.h"
00125 #include "lineattr.h"
00126 #include "ppstroke.h"
00127 #include "ophist.h"
00128 //#include "opliveeffects.h"
00129 
00130 DECLARE_SOURCE("$Revision: 1282 $");
00131 
00132 
00133 CC_IMPLEMENT_DYNCREATE(OpCombineShapes,     SelOperation)
00134 CC_IMPLEMENT_DYNCREATE(OpAddShapes,         OpCombineShapes)
00135 CC_IMPLEMENT_DYNCREATE(OpSubtractShapes,    OpCombineShapes)
00136 CC_IMPLEMENT_DYNCREATE(OpIntersectShapes,   OpCombineShapes)
00137 CC_IMPLEMENT_DYNCREATE(OpSliceShapes,       OpCombineShapes)
00138 CC_IMPLEMENT_DYNCREATE(SelObjPathList,      ListItem)
00139 CC_IMPLEMENT_DYNCREATE(SelObjPathListItem,  ListItem)
00140 
00141 CC_IMPLEMENT_MEMDUMP(CombineBecomeA,        BecomeA);
00142 
00143 #define new CAM_DEBUG_NEW
00144 
00145 static INT32    DefaultTolerance        = 1;
00146 static INT32    DefaultFlatness         = 100;
00147 static INT32    DefaultStrokeWidth      = 100;
00148 static INT32    AddPathGranularity      = 1024;
00149 static BOOL CombineEffectBitmaps    = FALSE;
00150 
00151 
00152 /********************************************************************************************
00153 
00154 >   OpCombineShapes::OpCombineShapes()
00155 
00156     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
00157     Created:    12/1/95
00158     Inputs:     -
00159     Outputs:    -
00160     Returns:    -
00161     Purpose:    Default constructor
00162     Errors:     -
00163     SeeAlso:    -
00164 
00165 ********************************************************************************************/
00166 
00167 OpCombineShapes::OpCombineShapes()
00168 {
00169     Reason      = COMBINE_NONE;
00170     pSelRange   = NULL;
00171     NumPaths    = 0;
00172     JobCount    = 0;
00173 
00174     pContextNode = NULL;
00175     pListOfResults = NULL;
00176 }
00177 
00178 /********************************************************************************************
00179 
00180 >   OpCombineShapes::~OpCombineShapes()
00181 
00182     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
00183     Created:    12/1/95
00184     Inputs:     -
00185     Outputs:    -
00186     Returns:    -
00187     Purpose:    Default destructor - does nothing
00188     Errors:     -
00189     SeeAlso:    -
00190 
00191 ********************************************************************************************/
00192 
00193 OpCombineShapes::~OpCombineShapes()
00194 {
00195     ListOfSelObjPathLists.DeleteAll();
00196     if (pListOfResults)
00197     {
00198         delete pListOfResults;
00199         pListOfResults = NULL;
00200     };
00201 }
00202 
00203 /********************************************************************************************
00204 
00205 >   static BOOL OpCombineShapes::Init()
00206 
00207     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
00208     Created:    12/1/95
00209     Inputs:     -
00210     Outputs:    -
00211     Returns:    -
00212     Purpose:    Initialises all the ops that implement the combine shape ops:
00213                     OpAddShapes
00214                     OpSubtractShapes
00215                     OpIntersectShapes
00216                     OpSliceShapes
00217     Errors:     -
00218     SeeAlso:    -
00219 
00220 ********************************************************************************************/
00221 
00222 // This macro gives a short-hand way of registering all the combine opdescriptors
00223 // It may have to be updated if we every want to put combine ops on buttons
00224 
00225 #define REG_COMBINE_OP(NAME,Name)\
00226     {\
00227         BOOL Blobby = RegisterOpDescriptor(\
00228                         0,\
00229                         _R(IDS_COMBINE_ ## NAME ), /*NORESOURCEFIX*/ \
00230                         CC_RUNTIME_CLASS(Op ## Name ## Shapes),\
00231                         OPTOKEN_ ## NAME ## SHAPES,\
00232                         OpCombineShapes::GetState,\
00233                         0,\
00234                         _R(IDBBL_COMBINE_ ## NAME), /*NORESOURCEFIX*/ \
00235                         _R(IDD_COMBINEBUTTONS),\
00236                         _R(IDC_BTN_COMBINE ## NAME),  /*NORESOURCEFIX*/ \
00237                         SYSTEMBAR_ARRANGE,\
00238                         TRUE,\
00239                         FALSE,\
00240                         FALSE,\
00241                         0,\
00242                         GREY_WHEN_NO_SELECTION | DONT_GREY_WHEN_SELECT_INSIDE\
00243                       );\
00244         ERROR1IF(!Blobby, FALSE, _R(IDS_OUT_OF_MEMORY));\
00245     }\
00246 
00247 BOOL OpCombineShapes::Init()
00248 {
00249     REG_COMBINE_OP(ADD,         Add);
00250     REG_COMBINE_OP(SUBTRACT,    Subtract);
00251     REG_COMBINE_OP(INTERSECT,   Intersect);
00252     REG_COMBINE_OP(SLICE,       Slice);
00253 
00254     if (Camelot.DeclareSection(_T("CombineShapes"),5))
00255     {
00256         Camelot.DeclarePref(_T("CombineShapes"), _T("DefaultTolerance"),    &DefaultTolerance,  1,1000);
00257         Camelot.DeclarePref(_T("CombineShapes"), _T("DefaultFlatness"),     &DefaultFlatness,   10,2000);
00258         Camelot.DeclarePref(_T("CombineShapes"), _T("DefaultStrokeWidth"),  &DefaultStrokeWidth,10,72000);
00259         Camelot.DeclarePref(_T("CombineShapes"), _T("AddPathGranularity"),  &AddPathGranularity,1,1024*1024);
00260         Camelot.DeclarePref(_T("CombineShapes"), _T("CombineEffectsBitmaps"), &CombineEffectBitmaps, FALSE, TRUE);
00261     }
00262 
00263     return TRUE;
00264 }
00265 
00266 /********************************************************************************************
00267 
00268 >   static OpState OpCombineShapes::GetState(String_256* pStr, OpDescriptor*)
00269 
00270     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
00271     Created:    12/1/95
00272     Inputs:     pStr = string to place messages in
00273     Outputs:    -
00274     Returns:    The op state, i.e. the ticked and greyed state of the op
00275     Purpose:    Gives the op an oppertunity to say whether the op is executable or not.
00276 
00277                 A combine op is only available if there are more than one selected nodes that are
00278                 or can become paths.
00279     Errors:     -
00280     SeeAlso:    -
00281 
00282 ********************************************************************************************/
00283 
00284 OpState OpCombineShapes::GetState(String_256* pStr, OpDescriptor* pOpDesc)
00285 {
00286     OpState OpSt;
00287     UINT32 Count = 0;
00288     UINT32 MinCount = 2;
00289 
00290     String Str(OPTOKEN_ADDSHAPES);
00291     if (pOpDesc->Token == Str)
00292         MinCount = 1;
00293 
00294     // Get the selected range
00295     SelRange* pSelRange = GetApplication()->FindSelection();
00296     BOOL bRestorePromote = pSelRange->SetPromoteToParent(TRUE);
00297 
00298     BecomeA BecomePath(BECOMEA_REPLACE, CC_RUNTIME_CLASS(NodePath));
00299     BecomePath.SetInsertComplexBlendStepsAsPaths (TRUE);        // compound nodes MUST be inserted as paths!
00300 
00301     // Count the number of selected nodes that are or can become NodePaths
00302     if (pSelRange != NULL)
00303     {
00304         Node* pNode = pSelRange->FindFirst();
00305 
00306         while (pNode != NULL)
00307         {
00308             if (pNode->CanBecomeA(&BecomePath))
00309                 Count++;
00310 
00311             pNode = pSelRange->FindNext(pNode);
00312         }
00313     }
00314 
00315     pSelRange->SetPromoteToParent(bRestorePromote);
00316 
00317     // if at least 'MinCount' objects of the selection can be combined, ungrey the op
00318     if (Count >= MinCount)
00319         OpSt.Greyed = FALSE;
00320     else
00321     {
00322         // The op can't be done, so put an explanation in the string provided
00323         OpSt.Greyed = TRUE;
00324 
00325         String_256 Str(_R(IDS_COMBINE_SEL_ERROR));
00326         *pStr = Str;
00327     }
00328 
00329     return OpSt;
00330 }
00331 
00332 /********************************************************************************************
00333 
00334 >   virtual void OpCombineShapes::GetOpName(String_256* OpName) 
00335 
00336     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
00337     Created:    16/1/95
00338     Inputs:     OpName = ptr to str to place op name in
00339     Outputs:    The undo string for the operation
00340     Returns:    
00341     Purpose:    The GetOpName fn is overridden so that we return back a description 
00342                 appropriate to the type of attribute that the operation applies. 
00343     Errors:     -
00344     SeeAlso:    -
00345 
00346 ********************************************************************************************/
00347 
00348 void OpCombineShapes::GetOpName(String_256* OpName) 
00349 { 
00350     UINT32 IDS = 0;
00351     switch (Reason)
00352     {
00353         case COMBINE_ADD:       IDS = _R(IDBBL_COMBINE_ADD);        break;
00354         case COMBINE_SUBTRACT:  IDS = _R(IDBBL_COMBINE_SUBTRACT);   break;
00355         case COMBINE_INTERSECT: IDS = _R(IDBBL_COMBINE_INTERSECT);  break;
00356         case COMBINE_SLICE:     IDS = _R(IDBBL_COMBINE_SLICE);      break;
00357 
00358         default:
00359             ERROR3_PF(("Unknown combine reason : %d",Reason));
00360             break;
00361     }
00362 
00363     if (IDS > 0)
00364         *OpName = String_256(IDS);
00365 }  
00366 
00367 /********************************************************************************************
00368 
00369 >   BOOL OpCombineShapes::BeginSlowJob()
00370 
00371     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
00372     Created:    25/1/95
00373     Inputs:     -
00374     Outputs:    -
00375     Returns:    TRUE if OK
00376     Purpose:    Wrapper around ::BeginSlowJob() that sets up the hour glass counter correctly
00377     Errors:     -
00378     SeeAlso:    -
00379 
00380 ********************************************************************************************/
00381 
00382 BOOL OpCombineShapes::BeginSlowJob()
00383 {
00384     NumPaths = 0;
00385     JobCount = 0;
00386     SelObjPathList* pList = GetFirstList();
00387     while (pList != NULL)
00388     {
00389         NumPaths += pList->GetCount();
00390         pList = GetNextList(pList);
00391     }   
00392 
00393     INT32 SlowJobCount = NumPaths;
00394     if (Reason == COMBINE_SLICE)
00395         SlowJobCount *= 2;
00396     String_64 Str(_R(IDS_COMBINE_SLOWJOB));
00397     return (::BeginSlowJob(SlowJobCount,FALSE,&Str));
00398 }
00399     
00400 
00401 /********************************************************************************************
00402 
00403 >   virtual void OpCombineShapes::Do(OpDescriptor*)
00404 
00405     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
00406     Created:    12/1/95
00407     Inputs:     -
00408     Outputs:    -
00409     Returns:    -
00410     Purpose:    This is it! The routine that gets all the hard work underway.
00411 
00412                 It acts upon the value in member var 'Reason', performing the shape-combining op
00413                 specified by that value
00414     Errors:     -
00415     SeeAlso:    -
00416 
00417 ********************************************************************************************/
00418 
00419 void OpCombineShapes::Do(OpDescriptor*)
00420 {
00421     BeginSlowJob();
00422     DoStartSelOp(TRUE, FALSE);
00423     
00424     BOOL ok = TRUE;
00425     NodeInsertCount = 0;
00426     pListOfResults = new ListRange();
00427     BOOL bCombineEffectBitmaps = CombineEffectBitmaps;
00428 
00429     // If shift key is pressed toggle the effect handling method
00430     ClickModifiers ClickMods = ClickModifiers::GetClickModifiers();
00431     if (ClickMods.Adjust)
00432         bCombineEffectBitmaps = !bCombineEffectBitmaps;
00433 
00434     // ---------------------------------------------------------
00435     // Phil's new version: Scan the selection and call DoBecomeA once only!
00436     //
00437     // Convert all shapes in the selection in-place into paths.
00438     // NOTE! this allows all compound nodes to become groups in place
00439     // (which doesn't happen with the passback method). This is important
00440     // for retaining the tree structure that the paths are found in so that
00441     // paths remain in place inside groups and effects subtrees.
00442     //
00443     pSelRange = GetApplication()->FindSelection();
00444     Range OldRange(*pSelRange);
00445     RangeControl rg = OldRange.GetRangeControlFlags();
00446     rg.PromoteToParent = TRUE;
00447     OldRange.SetRangeControl(rg);
00448 
00449 	::BeginSlowJob();
00450 
00451     switch (Reason)
00452     {
00453         // act upon the reason
00454         case COMBINE_ADD:
00455             ok = ConvertToShapes(&OldRange, bCombineEffectBitmaps);
00456             if (ok) ok = DoAddShapes();
00457             if (ok && NodeInsertCount==0) {ok=FALSE; InformWarning(_R(IDS_COMBINE_NORESULT));}
00458             if (ok) ok = DoHideListedNodes(bCombineEffectBitmaps);
00459 //          if (ok) ok = DoSelectResultNodes(bCombineEffectBitmaps);
00460             break;
00461 
00462         case COMBINE_SUBTRACT:
00463             ok = ConvertToShapes(&OldRange, bCombineEffectBitmaps);
00464             if (ok) ok = DoCombineShapes(CLIP_STYLE_SUBTRACT, bCombineEffectBitmaps);
00465             if (ok && NodeInsertCount==0) {ok=FALSE; InformWarning(_R(IDS_COMBINE_NORESULT));}
00466             if (ok) ok = DoHideListedNodes(bCombineEffectBitmaps);
00467 //          if (ok) ok = DoSelectResultNodes(bCombineEffectBitmaps);
00468             break;
00469 
00470         case COMBINE_INTERSECT:
00471             ok = ConvertToShapes(&OldRange, bCombineEffectBitmaps);
00472             if (ok) ok = DoCombineShapes(CLIP_STYLE_INTERSECT, bCombineEffectBitmaps);
00473             if (ok && NodeInsertCount==0) {ok=FALSE; InformWarning(_R(IDS_COMBINE_NORESULT));}
00474             if (ok) ok = DoHideListedNodes(bCombineEffectBitmaps);
00475 //          if (ok) ok = DoSelectResultNodes(bCombineEffectBitmaps);
00476             break;
00477 
00478         case COMBINE_SLICE:
00479             {
00480                 // Slice = Subtract + Intersect
00481                 // Make a copy of the selection before we do anything else
00482                 Range* pNewRange = CopyRange(&OldRange);
00483                 OriginalBoundingRect = pSelRange->GetBoundingRect(TRUE);        // Use PromoteToParent to get TRUE bounds!
00484 
00485                 // Subtract on the original selection
00486                 ok = ConvertToShapes(&OldRange, bCombineEffectBitmaps);
00487                 if (ok) ok = DoCombineShapes(CLIP_STYLE_SUBTRACT, bCombineEffectBitmaps);
00488                 if (ok) ok = DoHideListedNodes(bCombineEffectBitmaps);
00489 
00490                 // Have to delete lists because they are global! Argh!
00491                 ListOfSelObjPathLists.DeleteAll();
00492                 pListOfResults->Clear();
00493 
00494                 // Intersect on the copy
00495                 if (ok) ok = ConvertToShapes(pNewRange, bCombineEffectBitmaps);
00496                 if (ok) ok = DoCombineShapes(CLIP_STYLE_INTERSECT, bCombineEffectBitmaps);
00497                 if (ok) ok = DoHideListedNodes(bCombineEffectBitmaps);
00498 
00499                 if (ok && NodeInsertCount==0) {ok=FALSE; InformWarning(_R(IDS_COMBINE_SLICE_NORESULT));}
00500 
00501 //              if (ok) ok = DoSelectResultNodes(bCombineEffectBitmaps);
00502 
00503                 delete pNewRange;
00504                 pNewRange = NULL;
00505             }
00506             break;
00507 
00508         default:
00509             ERROR3_PF(("Unknown combine reason : %d",Reason));
00510             ok = FALSE;
00511             break;
00512     }
00513 
00514 	::EndSlowJob();
00515 
00516     if (!ok)
00517     {
00518         FailAndExecute();
00519 
00520         // Get ptr to selected doc
00521         Document* pDoc = Document::GetSelected();
00522         Spread*  pSpread  = Document::GetSelectedSpread();
00523 
00524         // If there's a selected docview, redraw the selection
00525         if (pDoc != NULL && pSelRange != NULL && pSpread != NULL)
00526             pDoc->ForceRedraw(pSpread, pSelRange->GetBlobBoundingRect());
00527     }
00528     else
00529         pSelRange->Update(TRUE);
00530 
00531     // Destroy our lists of selected object paths
00532     ListOfSelObjPathLists.DeleteAll();
00533     if (pListOfResults)
00534     {
00535         delete pListOfResults;
00536         pListOfResults = NULL;
00537     };
00538 
00539     GetApplication()->UpdateSelection();
00540 
00541     // ::EndSlowJob();
00542 
00543     End();
00544 }
00545 
00546 //---------------------------------------------------
00547 
00548 /********************************************************************************************
00549 
00550 > BOOL OpCombineShapes::DoAddShapes()
00551 
00552     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
00553     Created:    12/1/95
00554     Inputs:     -
00555     Outputs:    -
00556     Returns:    TRUE if OK, FALSE if failed
00557     Purpose:    This adds all the paths together to form a combined silhouette path of the selection.
00558 
00559                 This can be thought of as a logical OR of the paths
00560     Errors:     -
00561     SeeAlso:    OpCombineShapes::Do()
00562 
00563 ********************************************************************************************/
00564 
00565 BOOL OpCombineShapes::DoAddShapes()
00566 {
00567     // We'll need a new NodePath to put in the tree
00568     NodePath* pNodePath = new NodePath;
00569 
00570     // If we have trouble setting up the NodePath, return FALSE
00571     if (pNodePath == NULL)       return FALSE;
00572     if (!pNodePath->SetUpPath()) return FALSE;
00573 
00574 //  if (!SubtractAllShapesFromAllLines()) return FALSE;
00575 
00576     // Get the list of paths produced by the first selected object
00577     SelObjPathList* pSelObjPathList = GetFirstList();
00578 
00579     // This algorithm was very inefficient for large numbers of nodes 
00580     // as it adds each node to the output path in turn.  As the output path 
00581     // grows it takes longer and longer to add each successive object.
00582     // To avoid this we add each path to a temporary path and when 
00583     // it grows too large we add the temporary path into the output path 
00584     // and clear the temporary one.
00585 
00586     NodePath* pTempPath = new NodePath;
00587     if (pTempPath == NULL)       return FALSE;
00588     if (!pTempPath->SetUpPath()) return FALSE;
00589 
00590     while (pSelObjPathList != NULL)
00591     {
00592         // Add all the paths in this list to the path inside pNodePath
00593         if (!AddPathsFromList(&(pTempPath->InkPath),pSelObjPathList))
00594             return FALSE;
00595 
00596         if (pTempPath->InkPath.GetNumCoords() > AddPathGranularity)
00597         {
00598             // Combine the TempPath with the output path
00599             if (!AddOrMergePaths(&(pNodePath->InkPath), &(pTempPath->InkPath)))
00600                 return(FALSE);
00601 
00602             // And clear out the TempPath
00603             if (!pTempPath->InkPath.ClearPath(FALSE))
00604                 return(FALSE);
00605         }
00606 
00607         // Get the next list of paths generated by the next selected object
00608         pSelObjPathList = GetNextList(pSelObjPathList);
00609     }
00610 
00611     // If the TempPath has anything in
00612     if (pTempPath->InkPath.GetNumCoords() > 0)
00613     {
00614         // Combine the TempPath with the output path
00615         if (!AddOrMergePaths(&(pNodePath->InkPath), &(pTempPath->InkPath)))
00616             return(FALSE);
00617     }
00618 
00619     delete pTempPath;
00620 
00621     // We have successfully added all the paths together to form the union inside pNodePath
00622 
00623     // Find the last path item in the last list (i.e. the top-most path in the selection)
00624     pSelObjPathList     = GetLastList();
00625     SelObjPathListItem* pSelObjPathListItem = pSelObjPathList->GetLastItem();
00626     NodeRenderableInk*  pCreatedByNode  = pSelObjPathListItem->GetCreatedByNode();
00627 
00628     // Get ptr to last selected object because that is where we're going to put the new node path
00629 //  pContextNode = pSelRange->FindLast();
00630     pContextNode = pSelObjPathListItem->GetNodePath();
00631     if (pContextNode==NULL)
00632         pContextNode = pCreatedByNode;
00633     AttachDir = NEXT;
00634     if (!DoInsertNewNode(pNodePath))
00635         return FALSE;
00636 
00637     // Set up a set of default path flags to enable the bezier tool to edit this node (Naughty gavin!)
00638     pNodePath->InkPath.InitialiseFlags();
00639 
00640     // Select the new node path (and don't redraw - this will be done due to DoInsertNewNode())
00641     pNodePath->Select(FALSE);
00642 
00643     // Apply the attrs of the node that created the last path to the new NodePath
00644     CCAttrMap*          pAttrMap        = pSelObjPathListItem->GetAttrMap();
00645     if (!ApplyAttributes(pCreatedByNode,pNodePath,pAttrMap))
00646         return FALSE;
00647 
00648     return TRUE;
00649 }
00650 
00651 //---------------------------------------------------
00652 
00653 /********************************************************************************************
00654 
00655 > BOOL OpCombineShapes::AddPathsFromList(Path* pPath,SelObjPathList* pSelObjPathList)
00656 
00657     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
00658     Created:    12/1/95
00659     Inputs:     pPath           = ptr to path to place the result
00660                 pSelObjPathList = ptr to list of paths
00661     Outputs:    pPath contains the union of all the paths in pSelObjPathList
00662     Returns:    TRUE if OK, FALSE if failed
00663     Purpose:    This adds all the paths in the list together to form a combined silhouette path of the selection.
00664 
00665                 This can be thought of as a logical OR of the paths
00666     Errors:     -
00667     SeeAlso:    OpCombineShapes::Do()
00668 
00669 ********************************************************************************************/
00670 
00671 BOOL OpCombineShapes::AddPathsFromList(Path* pPath,SelObjPathList* pSelObjPathList)
00672 {
00673     // Check our input params
00674     ERROR3IF(pPath == NULL,"pPath == NULL");
00675     ERROR3IF(pSelObjPathList == NULL,"pSelObjPathList== NULL");
00676     if (pPath == NULL || pSelObjPathList == NULL) return FALSE;
00677 
00678     NodePath* pTempPath = new NodePath;
00679     if (pTempPath == NULL)       return FALSE;
00680     if (!pTempPath->SetUpPath()) return FALSE;
00681 
00682     // Get the first path in this list
00683     SelObjPathListItem* pSelObjPathListItem = pSelObjPathList->GetFirstItem();
00684 
00685     BOOL ok = TRUE;
00686 
00687     while (ok && pSelObjPathListItem != NULL)
00688     {
00689         // Get a pointer to the actual node path in this list item
00690         Path* pSrcPath = pSelObjPathListItem->GetPath();
00691 
00692         if (pSrcPath != NULL)
00693         {
00694             // Add this path into the TempPath
00695             ok = AddOrMergePaths(&(pTempPath->InkPath), pSrcPath);
00696 
00697             // If the TempPath is now too large
00698             if (ok && pTempPath->InkPath.GetNumCoords() > AddPathGranularity)
00699             {
00700                 // Combine the TempPath with the output path
00701                 ok = AddOrMergePaths(pPath, &(pTempPath->InkPath));
00702                 // And clear the TempPath
00703                 if (ok) ok = pTempPath->InkPath.ClearPath(FALSE);
00704             }
00705             if (ok) pSelObjPathList->SetProducedPaths(TRUE);
00706         }
00707         else
00708         {
00709             // What? No Src path? Surely not!
00710             ERROR3("pSrcPath == NULL");
00711             ok = FALSE;
00712         }
00713 
00714         if (ok)
00715             ok = ::ContinueSlowJob(++JobCount);
00716 
00717         // Get the next item in the selected object path list.
00718         pSelObjPathListItem = pSelObjPathList->GetNextItem(pSelObjPathListItem);
00719     }
00720 
00721     // If the TempPath has anything in
00722     if (ok && pTempPath->InkPath.GetNumCoords() > 0)
00723     {
00724         // Combine the TempPath with the output path
00725         ok = AddOrMergePaths(pPath, &(pTempPath->InkPath));
00726     }
00727 
00728     delete pTempPath;
00729 
00730     return (ok);
00731 }
00732 
00733 
00734 
00735 /****************************************************************************
00736 
00737 >   BOOL OpCombineShapes::AddOrMergePaths(Path* pPath1, Path* pPath2)
00738 
00739     Author:     Gerry_Iles (Xara Group Ltd) <camelotdev@xara.com>
00740     Created:    08/02/2005
00741 
00742     Inputs:     pPath1      - pointer to a Path
00743                 pPath2      - pointer to a Path
00744     Returns:    TRUE if ok, FALSE if bother
00745     Purpose:    Helper function for DoAddShapes and AddPathsFromList
00746 
00747 ****************************************************************************/
00748 
00749 BOOL OpCombineShapes::AddOrMergePaths(Path* pPath1, Path* pPath2)
00750 {
00751     BOOL ok = TRUE;
00752     // if num coords == 0, the path is effectively empty, so just copy this node path's path
00753     if (pPath1->GetNumCoords() == 0)
00754         ok = pPath1->MergeTwoPaths(*pPath2);
00755     else
00756     {
00757         // Otherwise actually 'add' the paths together, using ClipPathToPath
00758         ok = AddPaths(pPath1,pPath2);
00759 
00760         // If this fails, we can just merge the two paths together as this achieves the desired
00761         // silhouette effect
00762         if (!ok) ok = pPath1->MergeTwoPaths(*pPath2);
00763 
00764     }
00765     return(ok);
00766 }
00767 
00768 
00769 /********************************************************************************************
00770 
00771 > BOOL OpCombineShapes::AddPaths(Path* pPath1,Path* pPath2)
00772 
00773     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
00774     Created:    12/1/95
00775     Inputs:     pPath1 = ptr to first path, and also the place to store resultant path
00776                 pPath2 = ptr to second path, i.e. the path to add to pPath1
00777     Outputs:    *pPath1 contains the result of adding the two paths (if TRUE returned).
00778     Returns:    TRUE if OK, FALSE if failed
00779     Purpose:    This adds the two paths together using ClipPathToPath, storing the result in pPath1
00780     Errors:     -
00781     SeeAlso:    OpCombineShapes::AddPathsFromList()
00782 
00783 ********************************************************************************************/
00784 
00785 BOOL OpCombineShapes::AddPaths(Path* pPath1,Path* pPath2)
00786 {
00787     ERROR2IF(pPath1 == NULL,FALSE,"pPath1 == NULL");
00788     ERROR2IF(pPath2 == NULL,FALSE,"pPath2 == NULL");
00789 
00790     // check for a zero-bounding box path
00791     DocRect dr = pPath2->GetBoundingRect();
00792 
00793     if (dr.Width() <= 1 && dr.Height() <= 1)
00794     {
00795         ERROR3("We have a zero bounding rect path");
00796         return FALSE;
00797     }
00798 
00799     Path StrokedPath2;
00800     if (!StrokedPath2.Initialise()) return FALSE;
00801 
00802     // is path 2 a line?
00803     if (pPath2->GetPathType() == PATHTYPE_LINE)
00804     {
00805         if (!StrokePathToPath(pPath2,&StrokedPath2))
00806             return FALSE;
00807     }
00808 
00809     INT32 len = CombinePaths(CLIP_STYLE_ADD,pPath2,&StrokedPath2,pPath1,pPath1);
00810 
00811     return (len > 0);
00812 }
00813 
00814 //-----------------------------------------------
00815 
00816 /********************************************************************************************
00817 
00818 > BOOL OpCombineShapes::DoCombineShapes(ClipStyle Style, BOOL bCombineEffectBitmaps)
00819 
00820     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
00821     Created:    16/1/95
00822     Inputs:     Style = High level style of clipping
00823     Outputs:    -
00824     Returns:    TRUE if OK, FALSE if failed
00825     Purpose:    Firstly, this adds all the paths together in the last list to form the clip path.
00826                 Then all the other paths in the other lists are combined with the clip path, using
00827                 the method specified in 'Flags'
00828     Errors:     -
00829     SeeAlso:    OpCombineShapes::Do()
00830 
00831 ********************************************************************************************/
00832 
00833 BOOL OpCombineShapes::DoCombineShapes(ClipStyle Style, BOOL bCombineEffectBitmaps)
00834 {
00835     Path ClipPath;
00836     Path StrokedClipPath;
00837     if (!ClipPath.Initialise())         return FALSE;
00838     if (!StrokedClipPath.Initialise())  return FALSE;
00839 
00840     // Get the list of paths produced by the top selected object
00841     SelObjPathList* pLastSelObjPathList = GetLastList();
00842 
00843     // Add all the paths in the last list to form the clip path to combine with
00844     if (!AddPathsFromList(&ClipPath,pLastSelObjPathList))
00845         return FALSE;
00846 
00847     // is the clip path is a line?
00848     if (ClipPath.GetPathType() == PATHTYPE_LINE)
00849     {
00850         if (!StrokePathToPath(&ClipPath,&StrokedClipPath))
00851             return FALSE;
00852     }
00853 
00854     // Get the list of paths produced by the first selected object
00855     SelObjPathList* pSelObjPathList = GetFirstList();
00856 
00857     ERROR2IF(pSelObjPathList == NULL,FALSE,"ptr to First list is NULL");
00858     ERROR2IF(pSelObjPathList->GetSelNode() == NULL,FALSE,"First list has no sel node");
00859 
00860     if (bCombineEffectBitmaps)
00861     {
00862         //-----------------------------------------------
00863         // Effects stacks will not be retained - the topmost effect will turn itself into
00864         // a bitmap
00865         //
00866         // All paths the op will produce will be placed into a group for convenience.
00867         // We don't know how many paths will be produced until after the op has done its stuff, so we
00868         // stick everything in a group, and if only one path is produced, we take it out and hide the group.
00869 
00870         // So, firstly create a group to put the paths in.
00871         pContextNode = new NodeGroup;
00872         if (pContextNode == NULL)
00873             return FALSE;
00874 
00875         // Insert the group into the tree
00876         if (!UndoableOperation::DoInsertNewNode((NodeGroup*)pContextNode,pSelObjPathList->GetSelNode(),NEXT,FALSE,FALSE,FALSE,FALSE))
00877             return FALSE;
00878 
00879         // We want all new paths to be inserted as children of the group
00880         AttachDir = LASTCHILD;
00881 
00882         // Make a note of how many paths have been inserted into the tree so far, so we can tell after the main
00883         // loop how many paths have been produced
00884         INT32 PrevInsertCount = NodeInsertCount;
00885 
00886         // This will point to a node to select, if indeed we want to select the node (see later)
00887         NodeRenderable* pSelectNode = NULL;
00888 
00889         // Main loop.  This produces all the paths and places them into the group
00890         while (pSelObjPathList != NULL && pSelObjPathList != pLastSelObjPathList)
00891         {
00892             // Combine all the paths in this list with the ClipPath
00893             // pContextNode is static! Argh!
00894             if (!CombinePathsFromList(Style,&ClipPath,&StrokedClipPath,pSelObjPathList))
00895                 return FALSE;
00896 
00897             // Get the next list of paths generated by the next selected object
00898             pSelObjPathList = GetNextList(pSelObjPathList);
00899         }
00900 
00901         // We now need to decide what to do based on the number of paths produced.
00902         if (NodeInsertCount == (PrevInsertCount+1))
00903         {
00904             // Only one path was produced, so localise the attrs (just in case), move the path out of the
00905             // group, and hide the unneeded group
00906             if (!DoLocaliseCommonAttributes((NodeGroup*)pContextNode))
00907                 return FALSE;
00908 
00909             Node* pNode = pContextNode->FindFirstChild();
00910             while (pNode != NULL && !pNode->IsAnObject())
00911                 pNode = pNode->FindNext();
00912 
00913             if (pNode != NULL)
00914             {
00915                 if (!DoMoveNode(pNode,pContextNode,NEXT))
00916                     return FALSE;
00917             }
00918 
00919             if (!DoHideNode(pContextNode,TRUE))
00920                 return FALSE;
00921 
00922             pSelectNode = (NodeRenderable*)pNode;
00923             pListOfResults->AddNode(pSelectNode);
00924         }
00925         else if (NodeInsertCount > (PrevInsertCount+1))
00926         {
00927             // More than one path was produced, so keep the group and factor out any common attributes.
00928             if (!DoFactorOutCommonChildAttributes((NodeGroup*)pContextNode))
00929                 return FALSE;
00930 
00931             pSelectNode = (NodeGroup*)pContextNode;
00932             pListOfResults->AddNode(pSelectNode);
00933         }
00934         else
00935         {
00936             // The op produced not paths so just hide the empty group
00937             if (!DoHideNode(pContextNode,TRUE))
00938                 return FALSE;
00939         }
00940 
00941         // Select the new node (and don't redraw - this will be done due to DoInsertNewNode())
00942         //
00943         // When slicing, only select the objects produced on the intersection stage
00944 //      if (pSelectNode != NULL && (Reason != COMBINE_SLICE || Style != CLIP_STYLE_SUBTRACT))
00945 //          pSelectNode->Select(FALSE);
00946     }
00947     else
00948     {
00949         //-----------------------------------------------
00950         // Effects stacks will be retained
00951         // So all shape combination must be done within the effects stack structure
00952         //
00953         // Make a note of how many paths have been inserted into the tree so far, so we can tell after the main
00954         // loop how many paths have been produced
00955 //      INT32 PrevInsertCount = NodeInsertCount;
00956 
00957         // Main loop.  This produces all the paths
00958         while (pSelObjPathList != NULL && pSelObjPathList != pLastSelObjPathList)
00959         {
00960             // Combine all the paths in this list with the ClipPath
00961             // pContextNode is static! Argh!
00962             if (!CombinePathsFromList(Style, &ClipPath, &StrokedClipPath, pSelObjPathList, bCombineEffectBitmaps))
00963                 return FALSE;
00964 
00965             // Factor out any common attributes.
00966             Node* pSelNode = pSelObjPathList->GetSelNode();
00967             if (pSelNode && pSelNode->IsCompoundClass())
00968                 if (!DoFactorOutCommonChildAttributes((NodeCompound*)pSelNode, TRUE))
00969                     return FALSE;
00970 
00971             // Get the next list of paths generated by the next selected object
00972             pSelObjPathList = GetNextList(pSelObjPathList);
00973         }
00974 
00975     }
00976 
00977     // We have successfully combined all the paths
00978     return TRUE;
00979 }
00980 
00981 
00982 
00983 
00984 /********************************************************************************************
00985 
00986 > BOOL OpCombineShapes::CombinePathsFromList(   ClipStyle Style,
00987                                                 Path* pClipPath,
00988                                                 Path* pStrokedClipPath,
00989                                                 SelObjPathList* pSelObjPathList,
00990                                                 BOOL bCombineEffectBitmaps = TRUE)
00991 
00992     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
00993     Created:    16/1/95
00994     Inputs:     Style           = High level style of clipping
00995                 pClipPath       = ptr to path to combine with the paths in pSelObjPathList
00996                 pStrokedClipPath= ptr to stroked version of pClipPath ONLY if pClipPath is a line
00997                 pSelObjPathList = ptr to list of paths
00998     Outputs:    -
00999     Returns:    TRUE if OK, FALSE if failed
01000     Purpose:    This combines pClipPath with all the paths in pSelObjPathList.
01001 
01002                 For each path it tries to combine it with pClipPath.  A new NodePath is generated and
01003                 placed in the tree at the place the selected object lives.
01004     Errors:     -
01005     SeeAlso:    OpCombineShapes::DoCombineShapes()
01006 
01007 ********************************************************************************************/
01008 
01009 BOOL OpCombineShapes::CombinePathsFromList( ClipStyle Style,
01010                                             Path* pClipPath,
01011                                             Path* pStrokedClipPath,
01012                                             SelObjPathList* pSelObjPathList,
01013                                             BOOL bCombineEffectBitmaps)
01014 {
01015     // Check our input params
01016     ERROR3IF(pClipPath == NULL,"pClipPath == NULL");
01017     ERROR3IF(pSelObjPathList == NULL,"pSelObjPathList == NULL");
01018     if (pClipPath == NULL || pSelObjPathList == NULL) return FALSE;
01019 
01020     // Get the first path in this list
01021     SelObjPathListItem* pFirstItem = pSelObjPathList->GetFirstItem();
01022     SelObjPathListItem* pSelObjPathListItem = pFirstItem;
01023     BOOL ok = TRUE;
01024 
01025     while (ok && pSelObjPathListItem != NULL)
01026     {
01027         // Get a pointer to the actual node path in this list item
01028         NodePath* pNodePath = pSelObjPathListItem->GetNodePath();
01029         NodePath* pNewNodePath = new NodePath;
01030         NodeRenderableInk* pCreatedByNode = pSelObjPathListItem->GetCreatedByNode();
01031 
01032         if (pNodePath != NULL && pNewNodePath != NULL && pNewNodePath->SetUpPath())
01033         {
01034             //pNewNodePath->InkPath.Initialise(pNodePath->InkPath.GetNumCoords(), 64);
01035             //if    (pNewNodePath->InkPath.CopyPathDataFrom(&(pNodePath->InkPath)))
01036             if (CombinePaths(Style, pClipPath, pStrokedClipPath, &(pNodePath->InkPath), &(pNewNodePath->InkPath)))
01037             {
01038                 if (bCombineEffectBitmaps)
01039                     ok = DoInsertNewNode(pNewNodePath);
01040                 else
01041                 {
01042                     if (pSelObjPathListItem==pFirstItem)
01043                     {
01044                         // The first path produced can live where it's originating node lived
01045                         // inside whatever Effect stack was in place
01046                         ok = UndoableOperation::DoInsertNewNode(pNewNodePath, pNodePath, NEXT, TRUE, FALSE, FALSE, TRUE);
01047                     }
01048                     else
01049                     {
01050                         // If more than one path is produced under an Effect node
01051                         // Then we must make a new Effect Stack for it
01052                         //
01053                         // BUT NOTE! I don't think any multiple set of nodes are produced outside
01054                         // a group at the moment - so the effect applciation code found here
01055                         // has never yet been tested!
01056                         Node* pInsertNode = pNodePath;
01057                         ListRange* pStack = EffectsStack::GetEffectsStackFromNode(pNodePath, FALSE, TRUE, TRUE);    // Include locked effects
01058                         if (pStack)
01059                             pInsertNode = pStack->FindLast();
01060                         ok = UndoableOperation::DoInsertNewNode(pNewNodePath, pInsertNode, NEXT, TRUE, FALSE, FALSE, TRUE);
01061                         pInsertNode = pNewNodePath;
01062                         delete pStack;
01063 
01064                         // Now copy the effects stack for this new child...
01065                         if (ok) ok = ApplyEffects(pSelObjPathListItem, pNewNodePath, NULL);
01066 
01067                     }
01068 
01069                     // Select the resulting path now
01070                     if (ok && pCreatedByNode->IsSelected())
01071                         pNewNodePath->Select(FALSE);
01072 
01073                     if (ok)
01074                     {
01075                         pListOfResults->AddNode(pNewNodePath);
01076                         NodeInsertCount++;
01077                     }
01078                 }
01079 
01080                 // Find out who created the path, and apply its attrs to the new NodePath
01081                 CCAttrMap*          pAttrMap        = pSelObjPathListItem->GetAttrMap();
01082                 
01083                 if (ok) ok = ApplyAttributes(pCreatedByNode, pNewNodePath, pAttrMap, pSelObjPathListItem);
01084 
01085                 // Set up a set of default path flags to enable the bezier tool to edit this node
01086                 if (ok) pNewNodePath->InkPath.InitialiseFlags();
01087 
01088                 if (ok) pSelObjPathList->SetProducedPaths(TRUE);
01089             }
01090             else
01091             {
01092                 if (pNewNodePath!=NULL) { delete pNewNodePath; pNewNodePath = NULL; }
01093 
01094                 // Don't worry if the actual combine fails.  We'll just not put anything in the tree
01095                 // because, hmmm, I wonder....
01096             }
01097         }
01098         else
01099         {
01100             // What? Error? Surely not!
01101             ERROR3("pNodePath == NULL || pNewNodePath == NULL || !pNewNodePath->SetUpPath())");
01102             ok = FALSE;
01103 
01104             if (pNewNodePath!=NULL) { delete pNewNodePath; pNewNodePath = NULL; }
01105         }
01106 
01107         if (ok)
01108             ok = ::ContinueSlowJob(++JobCount);
01109 
01110         // Get the next item in the selected object path list.
01111         pSelObjPathListItem = pSelObjPathList->GetNextItem(pSelObjPathListItem);
01112     }
01113 
01114     return (ok);
01115 }
01116 
01117 /********************************************************************************************
01118 
01119 > BOOL OpCombineShapes::CombinePaths(ClipStyle Style,Path* pClipPath,Path* pSrcPath,Path* pDestPath)
01120 
01121     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
01122     Created:    16/1/95
01123     Inputs:     Style = High level style of clipping
01124                 pClipPath           = ptr to the path to combine with pSrcPath
01125                 pStrokedClipPath    = ptr to stroked version of pClipPath ONLY if pClipPath is a line
01126                 pSrcPath            = ptr to path that will be combined with pClipPath
01127                 pDestPath           = ptr to path to place the result in
01128     Outputs:    *pDestPath contains the result of combining pCliPath with pSrcPath(if TRUE returned).
01129     Returns:    TRUE if OK, FALSE if failed
01130     Purpose:    This combines pClipPath from pSrcPath using ClipPathToPath, storing the result in pDestPath
01131     Errors:     -
01132     SeeAlso:    OpCombineShapes::CombinePathsFromList()
01133 
01134 ********************************************************************************************/
01135 
01136 BOOL OpCombineShapes::CombinePaths(ClipStyle Style,Path* pClipPath,Path* pStrokedClipPath,Path* pSrcPath,Path* pDestPath)
01137 {
01138     // Check those entry params
01139     ERROR2IF(pClipPath          == NULL,FALSE,"pClipPath == NULL");
01140     ERROR2IF(pStrokedClipPath   == NULL,FALSE,"pStrokedClipPath == NULL");
01141     ERROR2IF(pSrcPath           == NULL,FALSE,"pSrcPath == NULL");
01142     ERROR2IF(pDestPath          == NULL,FALSE,"pDestPath == NULL");
01143 
01144     UINT32 Flags = 0;   // Flags passed to ClipPathToPath()
01145     BOOL Clip = TRUE;   // If this is TRUE, ClipPathToPath() is called at the end of this func
01146     INT32 len = -1;     // This is the length of the path produced by ClipPathToPath
01147 
01148     BOOL SrcIsLine  = (pSrcPath->GetPathType()  == PATHTYPE_LINE);  // TRUE if src path is a line
01149     BOOL ClipIsLine = (pClipPath->GetPathType() == PATHTYPE_LINE);  // TRUE if clip path is a line
01150 
01151     Path DestPath;      // Used when using a line to slice the source path up
01152 
01153     // If the clip path is a line and the op is slice, try and make a closed path using pClipPath
01154     // and the bounding rect of pSrcPath
01155     if (ClipIsLine && Reason == COMBINE_SLICE)
01156     {
01157         if (DestPath.Initialise() && ConvertClipLineToShape(pClipPath,pSrcPath,&DestPath))
01158         {
01159             // If successfully created a closed path, then pretend func was entered with the new path
01160             // by pointing pClipPath to it. Also the clip path is no longer a line
01161             pClipPath = &DestPath;
01162             ClipIsLine = FALSE;
01163         }
01164     }
01165     
01166     switch (Style)
01167     {
01168         case CLIP_STYLE_SUBTRACT:
01169             Flags = 1;  // 1 is Source AND NOT Clip
01170             if (SrcIsLine)  Flags |= CLIPPING_IS_STROKED;   // If source is a line, set the stroked flag
01171 
01172             if (ClipIsLine)
01173             {
01174                 // If slicing with a line, don't bother doing the subtraction stage 
01175                 if (Reason == COMBINE_SLICE)
01176                     Clip = FALSE;
01177                 else
01178                 // Otherwise subtract with a stroked version of the line.
01179                 // If the user's mad enough to try it, then we'd better try our best
01180                 { pClipPath = pStrokedClipPath; Flags |= CLIPPING_CLIP_WINDING; }
01181             }
01182             break;
01183 
01184         case CLIP_STYLE_INTERSECT:
01185             // If src is a line, Flags = 0 | stroked flag, else Flags = Source AND Clip
01186             if (SrcIsLine) Flags = CLIPPING_IS_STROKED; else Flags = 2; 
01187             if (ClipIsLine)
01188             {
01189                 // If slicing with a line, don't bother doing the intersection stage 
01190                 if (Reason == COMBINE_SLICE)
01191                     Clip = FALSE;
01192                 else
01193                 // Otherwise intersect with a stroked version of the line.
01194                 // If the user's mad enough to try it, then we'd better try our best
01195                 { pClipPath = pStrokedClipPath; Flags |= CLIPPING_CLIP_WINDING; }
01196             }
01197             break;
01198 
01199         case CLIP_STYLE_ADD:
01200             Flags = 7;  // 7 is Source OR Clip
01201             // convert lines into paths by stroking them
01202             // Also we must use a Non-zero winding rule to avoid cross segment lines taking effect
01203             if (SrcIsLine)  { StrokePathToPath(pSrcPath,pSrcPath);   Flags |= CLIPPING_SOURCE_WINDING; }
01204             if (ClipIsLine) { pClipPath = pStrokedClipPath; Flags |= CLIPPING_CLIP_WINDING; }
01205             break;
01206     }
01207 
01208     if (Clip)
01209     {
01210         // Clip those paths together
01211         // Mark Howitt 31/10/00
01212         // I`ve removed the ClipPathToPathWithAutoFlatness function and replaced it with
01213         // the function that returns you a flatness value to use with the ClipPath function
01214         double ClippingFlatness = pClipPath->CalculateFlatnessValueFromPath(750.0, 2.0, 375.0);
01215         double SourceFlatness = pSrcPath->CalculateFlatnessValueFromPath(750.0, 2.0, 375.0);
01216 
01217         len  = pClipPath->ClipPathToPath(*pSrcPath, pDestPath, Flags, DefaultTolerance, ClippingFlatness, SourceFlatness);
01218 /*
01219         // show debugging info about the path we have clipped
01220         INT32 NumCoords = pDestPath->GetNumCoords();
01221         PathVerb * pVerbs = pDestPath->GetVerbArray();
01222         DocCoord * pCoord = pDestPath->GetCoordArray();
01223 
01224         for (INT32 i = 0; i < NumCoords; i++)
01225         {
01226             if (pVerbs[i] == PT_MOVETO)
01227                 TRACE( _T("MOVE TO "));
01228             else
01229             if (pVerbs[i] == PT_BEZIERTO)
01230                 TRACE( _T("BEZIER TO "));
01231             else
01232             if (pVerbs[i] == PT_LINETO)
01233                 TRACE( _T("LINE TO "));
01234 
01235             TRACE( _T("%d, %d\n"), pCoord[i].x, pCoord[i].y);
01236         }
01237         TRACE( _T("end line\n"));
01238 */
01239     }
01240 
01241     return (len > 0);
01242 }
01243 
01244 
01245 /********************************************************************************************
01246 
01247 > BOOL OpCombineShapes::ConvertClipLineToShape(Path* pClipPath,Path* pSrcPath,Path* pDestPath)
01248 
01249     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
01250     Created:    16/1/95
01251     Inputs:     pClipPath   = ptr to the path to convert
01252                 pSrcPath    = ptr to path that will be combined with pClipPath
01253                 pDestPath   = ptr to path to place the result in
01254     Outputs:    *pDestPath contains the result of converting pCliPath with regards to pSrcPath (if TRUE returned).
01255     Returns:    TRUE if OK, FALSE if failed
01256 
01257     Purpose:    Only call this if pClipPath is a line.
01258     
01259                 This creates a shape out of pClipPath such that the path from the start point of pClipPath to
01260                 the end point of pClipPath does NOT intersect with pSrcPath.
01261                 It can only do this if the start and end point of pClipPath are outside the coord bounds of pSrcPath.
01262                 If either pClipPath ends are inside pSrcPath's bounds, FALSE is returned.
01263 
01264                 The shape is created by tacking on parts of the bounding rectangle to pClipPath, then closing
01265                 the resultant path.
01266                 Note: pDestPath must not point to pClipPath OR pSrcPath (i.e. you'll need three paths)
01267     Errors:     -
01268     SeeAlso:    OpCombineShapes::CombinePaths()
01269 
01270 ********************************************************************************************/
01271 
01272 BOOL OpCombineShapes::ConvertClipLineToShape(Path* pClipPath,Path* pSrcPath,Path* pDestPath)
01273 {
01274     // Check for stupid entry parama
01275     ERROR2IF(pClipPath == NULL,FALSE,"pClipPath == NULL");
01276     ERROR2IF(pSrcPath  == NULL,FALSE,"pSrcPath == NULL");
01277     ERROR2IF(pDestPath == NULL,FALSE,"pClipPath == NULL");
01278 
01279     // Get the coord bounds of the source path.  Inflate it slightly so that we don't run into tolerance
01280     // problems when calling ClipPathToPath()
01281     //DocRect Rect = GetBoundingRect(pSrcPath);
01282 
01283     // we must be consistant as to the rect we use to get a consistant shape
01284     // otherwise some items will all get cut along the line but it is luck
01285     // which side of the cut the shapes appear in (sjk 15/11/00)
01286 //  DocRect Rect = GetApplication()->FindSelection()->GetBoundingRect(); // use a consistant rect for all the shapes
01287     DocRect Rect = OriginalBoundingRect; // use a consistant rect for all the shapes
01288 
01289     Rect.Inflate((DefaultTolerance*2)+10);
01290     
01291     INT32       NumClipCoords = pClipPath->GetNumCoords();
01292     DocCoord*   pClipCoords   = pClipPath->GetCoordArray();
01293     DocCoord    ClipStart     = pClipCoords[0];                 // Start point of pClipPath
01294     DocCoord    ClipEnd       = pClipCoords[NumClipCoords-1];   // End   point of pClipPath
01295     DocCoord    ExtraClipStart= pClipCoords[0];                 // Start point of pClipPath
01296     DocCoord    ExtraClipEnd  = pClipCoords[NumClipCoords-1];   // End   point of pClipPath
01297     BOOL        NeedExtraClipStart  = FALSE;
01298     BOOL        NeedExtraClipEnd    = FALSE;
01299 
01300     Path        FlatPath;
01301 //  BOOL        SetFlatPath = FALSE;
01302 //  double      FLATTNESS =  1024;  // arbitry figure of how flattened the curve will be
01303                                     // only used for test for extra cuts
01304 
01305     // We have to choose a corner point of the bounding rect for both the start and end points
01306     // of pClipPath.
01307 
01308     DocCoord ClipCornerStart,ClipCornerEnd;
01309 
01310     DocCoord mid = Rect.Centre();
01311 
01312     if (ClipStart.x < mid.x)
01313         ClipCornerStart.x = Rect.lo.x;
01314     else
01315         ClipCornerStart.x = Rect.hi.x;
01316 
01317     if (ClipStart.y < mid.y)
01318         ClipCornerStart.y = Rect.lo.y;
01319     else
01320         ClipCornerStart.y = Rect.hi.y;
01321 
01322     if (ClipEnd.x < mid.x)
01323         ClipCornerEnd.x = Rect.lo.x;
01324     else
01325         ClipCornerEnd.x = Rect.hi.x;
01326 
01327     if (ClipEnd.y < mid.y)
01328         ClipCornerEnd.y = Rect.lo.y;
01329     else
01330         ClipCornerEnd.y = Rect.hi.y;
01331 
01332     // start clip point in the rect?
01333     // move it to the nearest edge
01334     if (Rect.ContainsCoord(ClipStart))
01335     {
01336         NeedExtraClipStart = TRUE;
01337         DocCoord v = ClipStart - pClipCoords[1];
01338         INT32 giveup = 250; // number of times to try
01339 
01340         // extrapperlate the last part of the line until it breaks out of the bounding rect
01341         // give up if it fails to break out after an arbitry number of goes
01342         while (Rect.ContainsCoord(ExtraClipStart) && giveup > 0)
01343         {
01344             ExtraClipStart.x += v.x * 10;
01345             ExtraClipStart.y += v.y * 10;
01346             giveup--;
01347         }
01348 
01349         // failled to leave the shape
01350         if (giveup == 0) 
01351             return FALSE;
01352 
01353         // moving the start may change which is the nearest corner to go for
01354         DocCoord mid = Rect.Centre();
01355 
01356         if (ExtraClipStart.x < mid.x)
01357             ClipCornerStart.x = Rect.lo.x;
01358         else
01359             ClipCornerStart.x = Rect.hi.x;
01360 
01361         if (ExtraClipStart.y < mid.y)
01362             ClipCornerStart.y = Rect.lo.y;
01363         else
01364             ClipCornerStart.y = Rect.hi.y;
01365     }
01366 
01367     // perform all the same calcualtions for the other end of the clip line
01368     if (Rect.ContainsCoord(ClipEnd))
01369     {
01370         NeedExtraClipEnd = TRUE;
01371         DocCoord v = ClipEnd - pClipCoords[NumClipCoords-2];
01372         INT32 giveup = 250; // number of times to try
01373 
01374         // extrapperlate the last part of the line until it breaks out of the bounding rect
01375         // give up if it fails to break out after an arbitry number of goes
01376         while (Rect.ContainsCoord(ExtraClipEnd) && giveup > 0)
01377         {
01378             ExtraClipEnd.x += v.x * 10;
01379             ExtraClipEnd.y += v.y * 10;
01380             giveup--;
01381         }
01382 
01383         // failled to leave the shape
01384         if (giveup == 0) 
01385             return FALSE;
01386 
01387         // moving the end may change which is the nearest corner to go for
01388         DocCoord mid = Rect.Centre();
01389 
01390         if (ExtraClipEnd.x < mid.x)
01391             ClipCornerEnd.x = Rect.lo.x;
01392         else
01393             ClipCornerEnd.x = Rect.hi.x;
01394 
01395         if (ExtraClipEnd.y < mid.y)
01396             ClipCornerEnd.y = Rect.lo.y;
01397         else
01398             ClipCornerEnd.y = Rect.hi.y;            
01399     }
01400 
01401     // We now have two sensible corners of Rect to connect pClipPath's end points to
01402     // and if required also optimal points on this Rect boundry to that are
01403     // in direct line with the ends of the cutting line
01404 
01405     // Firstly, copy pClipPath to pDestPath
01406     pDestPath->ClearPath(FALSE);
01407     if (!pDestPath->MergeTwoPaths(*pClipPath)) return FALSE;
01408 
01409     // add on an extra end part to make this end of the clip path outside the bounds of the selection
01410     if (NeedExtraClipEnd)
01411     {
01412         if (!pDestPath->AddLineTo(ExtraClipEnd)) return FALSE;
01413     }
01414 
01415     // Connect the end of the line to the end corner point
01416     if (!pDestPath->AddLineTo(ClipCornerEnd)) return FALSE;
01417 
01418     // add in the corner nearest the end
01419     // If end corner x != start corner x, we need to connect these up
01420     if (ClipCornerEnd.x != ClipCornerStart.x)
01421     {
01422         ClipCornerEnd.x = ClipCornerStart.x;
01423         if (!pDestPath->AddLineTo(ClipCornerEnd)) return FALSE;
01424     }
01425 
01426     // if the start and end corners are opposing we need to use an intermediate corner to avoid the selection
01427     // If end corner y != start corner y, we need to connect these up
01428     if (ClipCornerEnd.y != ClipCornerStart.y)
01429     {
01430         ClipCornerEnd.y = ClipCornerStart.y;
01431         if (!pDestPath->AddLineTo(ClipCornerEnd)) return FALSE;
01432     }
01433 
01434     // add in the corner nearest the start
01435     // add on an extra end part to make this end of the clip path outside the bounds of the selection
01436     if (NeedExtraClipStart)
01437     {
01438         if (!pDestPath->AddLineTo(ExtraClipStart)) return FALSE;
01439     }
01440     
01441     // Finally, close the path up by connecting the start corner point to the start of the line
01442     // and setting PT_CLOSEFIGURE in the last verb
01443     if (!pDestPath->AddLineTo(ClipStart)) return FALSE;
01444 
01445     INT32 n = pDestPath->GetNumCoords();
01446     PathVerb* pVerbs = pDestPath->GetVerbArray();
01447     pVerbs[n-1] |= PT_CLOSEFIGURE;
01448 
01449     return TRUE;
01450 }
01451 
01452 /********************************************************************************************
01453 
01454 > DocRect OpCombineShapes::GetBoundingRect(Path* pPath)
01455 
01456     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
01457     Created:    17/2/95
01458     Inputs:     pPath = ptr to path
01459     Outputs:    -
01460     Returns:    The stroked bounding rect of this path, disregarding attributes
01461     Purpose:    This gets the exact pure path bounds, using GDraw_CalcStrokeBBox.
01462     Errors:     -
01463     SeeAlso:    OpCombineShapes::DoCombineShapes()
01464 
01465 ********************************************************************************************/
01466 
01467 
01468 DocRect OpCombineShapes::GetBoundingRect(Path* pPath)
01469 {
01470     DocRect Rect;
01471 
01472     if (pPath != NULL)
01473     {
01474         #if REAL_GDRAW
01475         // Get GDraw to get the stroked bounds for this path
01476         GDrawContext *GD = GRenderRegion::GetStaticDrawContext();
01477         
01478         if (GD != NULL)
01479         {
01480             GD->CalcStrokeBBox((POINT*)pPath->GetCoordArray(),pPath->GetVerbArray(),pPath->GetNumCoords(),
01481                                 (tagRECT*)(&Rect),pPath->IsFilled, 0, CAPS_ROUND, JOIN_ROUND, NULL);
01482         }
01483         #else
01484         Rect = pPath->GetBoundingRect();
01485         #endif
01486     }
01487 
01488     if (!Rect.IsValid())
01489         Rect.MakeEmpty();
01490 
01491     return Rect;
01492 }
01493 
01494 //---------------------------------------------
01495 
01496 /********************************************************************************************
01497 
01498 > BOOL OpCombineShapes::SubtractAllShapesFromAllLines()
01499 
01500     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
01501     Created:    26/1/95
01502     Inputs:     -
01503     Outputs:    -
01504     Returns:    TRUE if OK, FALSE if failed
01505     Purpose:    This subtracts all the shapes in all the lists from all the lines in all the lists
01506     Errors:     -
01507     SeeAlso:    OpCombineShapes::DoCombineShapes()
01508 
01509 ********************************************************************************************/
01510 
01511 /*BOOL OpCombineShapes::SubtractAllShapesFromAllLines()
01512 {
01513     // Get the list of paths produced by the first selected object
01514     SelObjPathList* pSelObjPathList = GetFirstList();
01515 
01516     while (pSelObjPathList != NULL)
01517     {
01518         SelObjPathListItem* pSelObjPathListItem = pSelObjPathList->GetFirstItem();
01519 
01520         while (pSelObjPathListItem != NULL)
01521         {
01522             Path* pLine = pSelObjPathListItem->GetPath();
01523             ERROR3IF(pLine == NULL,"SelObjPathListItem pLine == NULL");
01524 
01525             if (pLine != NULL && pLine->GetPathType() == PATHTYPE_LINE)
01526             {
01527                 if (!SubtractAllShapesFromLine(pLine))
01528                     return FALSE;
01529             }
01530                 
01531             pSelObjPathListItem = pSelObjPathList->GetNextItem(pSelObjPathListItem);
01532         }
01533 
01534         // Get the next list of paths generated by the next selected object
01535         pSelObjPathList = GetNextList(pSelObjPathList);
01536     }
01537 
01538     return TRUE;
01539 }
01540 */
01541 /********************************************************************************************
01542 
01543 > BOOL OpCombineShapes::SubtractAllShapesFromLine(Path* pLine)
01544 
01545     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
01546     Created:    26/1/95
01547     Inputs:     pLine = ptr to a line
01548     Outputs:    pLine is changed
01549     Returns:    TRUE if OK, FALSE if failed
01550     Purpose:    This subtracts all the shapes in all the lists from pLine
01551     Errors:     -
01552     SeeAlso:    OpCombineShapes::DoCombineShapes()
01553 
01554 ********************************************************************************************/
01555 
01556 /*BOOL OpCombineShapes::SubtractAllShapesFromLine(Path* pLine)
01557 {
01558     // Get the list of paths produced by the first selected object
01559     SelObjPathList* pSelObjPathList = GetFirstList();
01560 
01561     while (pSelObjPathList != NULL)
01562     {
01563         // Combine all the paths in this list with the ClipPath
01564         if (!SubtractShapesFromLine(pLine,pSelObjPathList))
01565             return FALSE;
01566 
01567         // Get the next list of paths generated by the next selected object
01568         pSelObjPathList = GetNextList(pSelObjPathList);
01569     }
01570 
01571     return TRUE;
01572 }
01573 */
01574 /********************************************************************************************
01575 
01576 > BOOL OpCombineShapes::SubtractShapesFromLine(Path* pLine,SelObjPathList* pSelObjPathList)
01577 
01578     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
01579     Created:    26/1/95
01580     Inputs:     pLine           = ptr to a line
01581                 pSelObjPathList = ptr to list of paths
01582     Outputs:    pLine is changed
01583     Returns:    TRUE if OK, FALSE if failed
01584     Purpose:    This subtracts all the shapes in pSelObjPathList from pLine
01585     Errors:     -
01586     SeeAlso:    OpCombineShapes::DoCombineShapes()
01587 
01588 ********************************************************************************************/
01589 /*
01590 BOOL OpCombineShapes::SubtractShapesFromLine(Path* pLine,SelObjPathList* pSelObjPathList)
01591 {
01592     // Get the first path in this list
01593     SelObjPathListItem* pSelObjPathListItem = pSelObjPathList->GetFirstItem();
01594 
01595     while (pSelObjPathListItem != NULL)
01596     {
01597         Path* pClipPath = pSelObjPathListItem->GetPath();
01598         ERROR3IF(pClipPath == NULL,"SelObjPathListItem pClipPath == NULL");
01599 
01600         if (pClipPath != NULL && pClipPath != pLine && pClipPath->GetPathType() == PATHTYPE_SHAPE)
01601         {
01602             if (!CombinePaths(CLIP_STYLE_SUBTRACT,pClipPath,pLine,pLine))
01603                 return FALSE;
01604         }
01605 
01606         pSelObjPathListItem = pSelObjPathList->GetNextItem(pSelObjPathListItem);
01607     }
01608 
01609     return TRUE;
01610 }
01611 */
01612 //---------------------------------------------------
01613 
01614 /********************************************************************************************
01615 
01616 > BOOL OpCombineShapes::ConvertToShapes(Range* pRange, BOOL bCombineEffectBitmaps)
01617 
01618     Author:     Phil_Martin (Xara Group Ltd) <camelotdev@xara.com>
01619     Created:    06/04/2005
01620     Inputs:     -
01621     Outputs:    -
01622     Returns:    TRUE if OK, FALSE if failed
01623     Purpose:    This generates the data structure that holds all the paths generated by the selected objects.
01624                 NOTE! Pointers to paths in the tree are stored in the lists (unlike CreateSelObjPathLists)
01625 
01626                 This consists of a top level list than contains a series of secondary lists of paths.
01627                 There is one instance of a secondary list per selected object.
01628 
01629                 List Of Selected Object Path lists
01630                     List of paths made by first selected object
01631                         SelObjPathListItem
01632                         SelObjPathListItem
01633                         SelObjPathListItem
01634                         ...
01635                     List of paths made by second selected object
01636                         ...
01637     Errors:     -
01638     SeeAlso:    CreateSelObjPathList()
01639 
01640 ********************************************************************************************/
01641 
01642 BOOL OpCombineShapes::ConvertToShapes(Range* pRange, BOOL bCombineEffectBitmaps)
01643 {
01644     CombineBecomeA BecomePath(BECOMEA_REPLACE, this, NULL, NULL, FALSE);
01645     BecomePath.SetInsertComplexBlendStepsAsPaths (TRUE);        // compound nodes MUST be inserted as paths!
01646     BecomePath.SetResultsStayInPlace(!bCombineEffectBitmaps);   // Only stay in place if notcaptruing effect bitmaps
01647 
01648     Node* pNode = pRange->FindFirst(FALSE);
01649     Node* pNextNode = NULL;
01650     BOOL ok = TRUE;
01651 
01652     while (pNode!=NULL && ok)
01653     {
01654         pNextNode = pRange->FindNext(pNode, FALSE);
01655 
01656         // Make a path list for this selected object if it can become a NodePath
01657         SelObjPathList* pSelObjPathList = new SelObjPathList(pNode);
01658         ok = (pSelObjPathList!=NULL);
01659         if (ok)
01660         {
01661             ListOfSelObjPathLists.AddTail(pSelObjPathList);
01662 
01663             // --- This code ripped from CreateSelObjPathList - looks suspect! ---
01664             // Check to see if this is the Top most visible object. This is used by the
01665             // Cached Compound Nodes to help determine whether or not to use the Cached nodes or BMP!
01666             BOOL IsTopObject = (pNextNode == NULL);
01667             // --- ! ---
01668 
01669             if (pNode->CanBecomeA(&BecomePath))
01670                 ok = CreateSelObjPathList(pNode, pSelObjPathList, IsTopObject, bCombineEffectBitmaps, BECOMEA_REPLACE);
01671 
01672             // Tidy up if things have gone wrong
01673             if (!ok)
01674             {
01675                 ListOfSelObjPathLists.RemoveTail();
01676                 delete pSelObjPathList;
01677             }
01678 
01679             if (pNode->IsAnObject()) ((NodeRenderableInk*)pNode)->ReleaseCached();
01680         }
01681 
01682         pNode = pNextNode;
01683 
01684         if (ok)
01685             ok = ::ContinueSlowJob(++JobCount);
01686     }
01687 
01688     return ok;
01689 }
01690 
01691 /********************************************************************************************
01692 
01693 > BOOL OpCombineShapes::CreateSelObjPathLists(BOOL bCombineEffectBitmaps);
01694 
01695     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
01696     Created:    12/1/95
01697     Inputs:     -
01698     Outputs:    -
01699     Returns:    TRUE if OK, FALSE if failed
01700     Purpose:    This generates the data structure that holds all the paths generated by the selected objects.
01701 
01702                 This consists of a top level list than contains a series of secondary lists of paths.
01703                 There is one instance of a secondary list per selected object.
01704 
01705                 List Of Selected Object Path lists
01706                     List of paths made by first selected object
01707                         SelObjPathListItem
01708                         SelObjPathListItem
01709                         SelObjPathListItem
01710                         ...
01711                     List of paths made by second selected object
01712                         ...
01713     Errors:     -
01714     SeeAlso:    CreateSelObjPathList()
01715 
01716 ********************************************************************************************/
01717 
01718 BOOL OpCombineShapes::CreateSelObjPathLists(BOOL bCombineEffectBitmaps)
01719 {
01720     ERROR2IF(pSelRange == NULL,FALSE,"pSelRange == NULL");
01721 
01722     Node* pSelNode = pSelRange->FindFirst();
01723     UINT32 Count = 0;   // Used for a check made at end of routine
01724     BOOL ok = TRUE;
01725     BOOL IsTopObject = TRUE;
01726 
01727 	::BeginSlowJob();
01728 
01729     BecomeA BecomePath(BECOMEA_REPLACE, CC_RUNTIME_CLASS(NodePath));
01730     BecomePath.SetResultsStayInPlace(!bCombineEffectBitmaps);
01731 
01732     while (ok && pSelNode != NULL)
01733     {
01734         if (pSelNode->CanBecomeA(&BecomePath))
01735         {
01736             Count++;    // Count num objs that can become a NodePath
01737 
01738             // Make a path list for this selected object if it can become a NodePath
01739             SelObjPathList* pSelObjPathList = new SelObjPathList(pSelNode);
01740 
01741             if (pSelObjPathList != NULL)
01742             {
01743                 // Check to see if this is the Top most visable object. This is used by the
01744                 // Cached Compound Nodes to help determine wether or not to use the Cached nodes or BMP!
01745                 IsTopObject = (pSelRange->FindNext(pSelNode) == NULL);
01746 
01747                 // Get the selected object to create the actual NodePath objects
01748                 // and if successful, stuff the list on our list of sel obj path lists
01749                 if (CreateSelObjPathList(pSelNode, pSelObjPathList, IsTopObject, bCombineEffectBitmaps))
01750                 {
01751                     ListOfSelObjPathLists.AddTail(pSelObjPathList);
01752                 }
01753                 else
01754                 {
01755                     ok = FALSE;
01756                 }
01757             }
01758             else
01759                 ok = FALSE;
01760         }
01761 
01762         if (ok)
01763             ok = ::ContinueSlowJob(++JobCount);
01764 
01765         // Get the next selected object
01766         pSelNode = pSelRange->FindNext(pSelNode);
01767     }
01768 
01769 	::EndSlowJob();
01770 
01771     if (ok)
01772     {
01773         UINT32 MinCount = 2;
01774         if (Reason == COMBINE_ADD)
01775             MinCount = 1;
01776 
01777         if (Count < MinCount)
01778         {
01779             ERROR3_PF(("Less than %d of the selected objects could become paths",MinCount));
01780             return FALSE;
01781         }
01782     }
01783 
01784     return ok;
01785 }
01786 
01787 /********************************************************************************************
01788 
01789 > BOOL OpCombineShapes::CreateSelObjPathList(Node* pNode,SelObjPathList* pSelObjPathList,BOOL IsFirstNode, BOOL bCombineEffectBitmaps, BecomeAReason reason = BECOMEA_REPLACE)
01790 
01791     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
01792     Created:    12/1/95
01793     Inputs:     pNode           = ptr to the node that will generate one or more NodePaths
01794                 pSelObjPathList = the list to add the paths to using SelObjPathListItems
01795     Outputs:    -
01796     Returns:    TRUE if OK, FALSE if failed
01797     Purpose:    This generates the data structure that holds all the paths generated by a given selected object.
01798 
01799                 It uses a derived BecomeA class to receive the paths and generate SelObjPathListItems
01800                 to add to pSelObjPathList
01801     Errors:     -
01802     SeeAlso:    CreateSelObjPathLists()
01803 
01804 ********************************************************************************************/
01805 
01806 BOOL OpCombineShapes::CreateSelObjPathList(Node* pNode,SelObjPathList* pSelObjPathList,BOOL IsFirstNode, BOOL bCombineEffectBitmaps, BecomeAReason reason)
01807 {
01808     ERROR2IF(pNode == NULL || pSelObjPathList == NULL,FALSE,"One or more params are NULL");
01809 
01810     // Set up a BecomeA derived object, so that we can receive all the paths generated by pNode.
01811     CombineBecomeA ParamBecomeA(reason, this,pSelObjPathList,pNode,IsFirstNode);
01812     ParamBecomeA.SetInsertComplexBlendStepsAsPaths(TRUE);       // compound nodes MUST be inserted as paths!
01813     ParamBecomeA.SetResultsStayInPlace(!bCombineEffectBitmaps);
01814 
01815     // if we have a brush attribute we have to do it independently (ugh)
01816     AttrBrushType* pBrush = NULL;
01817     ((NodeRenderableInk*)pNode)->FindAppliedAttribute(CC_RUNTIME_CLASS(AttrBrushType), (NodeAttribute **)&pBrush);
01818     BOOL gotAValidBrush = FALSE;
01819     if (pBrush != NULL && pBrush->GetBrushHandle() != BrushHandle_NoBrush)
01820     {
01821         gotAValidBrush = TRUE;
01822 
01823         BOOL useBrush = FALSE;          // CGS:  decide whether to use the brush for DoBecomeA or pNode ....
01824         
01825         if (Reason == COMBINE_ADD)
01826         {
01827             useBrush = TRUE;
01828         }
01829         /*else                  // put this back in if you want the slicing of lines to be sliced accurately in two.
01830                                 // if you want two new lines with brushes applied, leave like this
01831         {
01832             if (IS_A (pNode, NodePath))
01833             {
01834                 NodePath* pPath = (NodePath*) pNode;
01835                 if (!pPath->InkPath.IsClosed ())
01836                 {
01837                     useBrush = TRUE;                // its a nodepath thats filled
01838                 }
01839             }
01840         }*/
01841 
01842         if (useBrush)
01843         {
01844             return (pBrush->DoBecomeA(&ParamBecomeA, (NodeRenderableInk*)pNode));
01845         }
01846     }
01847 
01848     // if we haven't got a brush then see if we have a stroke
01849     BOOL ok = TRUE;
01850     AttrStrokeType* pStroke = NULL;
01851     ((NodeRenderableInk*)pNode)->FindAppliedAttribute(CC_RUNTIME_CLASS(AttrStrokeType), (NodeAttribute**)&pStroke);
01852     AttrVariableWidth* pVarWidth = NULL;
01853     ((NodeRenderableInk*)pNode)->FindAppliedAttribute(CC_RUNTIME_CLASS(AttrVariableWidth), (NodeAttribute**)&pVarWidth);
01854     
01855     if (pStroke && pStroke->HasPathProcessor() && pVarWidth && pVarWidth->HasActiveValueFunction())
01856     {
01857         if (!gotAValidBrush)
01858         {
01859             // Convert stroke to paths first, allow it to place any nodes it creates
01860             // after the original but without deleting the original
01861             if (pStroke)
01862             {
01863                 ParamBecomeA.SetSecondary();                    // Tell the pathprocessor that it doesn't own the node
01864                 ok = pStroke->DoBecomeA(&ParamBecomeA, pNode);
01865                 ParamBecomeA.SetSecondary(FALSE);
01866             }
01867 
01868             // Now convert the node to paths, placing nodes after the original (before stroke paths)
01869             // and deleting the original
01870             if (ok)
01871                 ok = pNode->DoBecomeA(&ParamBecomeA);       // Attributes will be shuffled
01872 
01873             return ok;
01874         }
01875         else
01876         {
01877             // We've got a brush and a stroke on the same node!
01878             // The brush already knows about the stroke, so let that handle it ....
01879             //
01880             // Convert stroke to paths first, allow it to place any nodes it creates
01881             // after the original but without deleting the original
01882             if (pBrush)
01883             {
01884                 ParamBecomeA.SetSecondary();                    // Tell the pathprocessor that it doesn't own the node
01885                 ok = pBrush->DoBecomeA(&ParamBecomeA, (NodeRenderableInk*)pNode);
01886                 ParamBecomeA.SetSecondary(FALSE);
01887             }
01888 
01889             // Now convert the node to paths, placing nodes after the original (before stroke paths)
01890             // and deleting the original
01891             if (ok)
01892                 ok = pNode->DoBecomeA(&ParamBecomeA);       // Attributes will be shuffled
01893 
01894             return ok;
01895         }
01896     }
01897 
01898     // Get pNode to generate its paths for us
01899     return (pNode->DoBecomeA(&ParamBecomeA));
01900 }
01901 
01902 /********************************************************************************************
01903 
01904 > BOOL OpCombineShapes::DoHideListedNodes(BOOL bCombineEffectBitmaps)
01905 
01906     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
01907     Created:    16/1/95
01908     Inputs:     -
01909     Outputs:    -
01910     Returns:    TRUE if OK, FALSE if failed
01911     Purpose:    This hides all the nodes that were selected when the op was invoked.
01912     Errors:     -
01913     SeeAlso:    -
01914 
01915 ********************************************************************************************/
01916 
01917 BOOL OpCombineShapes::DoHideListedNodes(BOOL bCombineEffectBitmaps)
01918 {
01919     // Get the list of paths produced by the first selected object
01920     SelObjPathList* pSelObjPathList = GetFirstList();
01921     BOOL ok = TRUE;
01922 
01923     while (pSelObjPathList != NULL)
01924     {
01925         if (bCombineEffectBitmaps)
01926         {
01927             Node* pSelNode = pSelObjPathList->GetSelNode();
01928             ERROR3IF(pSelNode == NULL,"pSelNode == NULL");
01929             if (pSelNode == NULL) return FALSE;
01930 
01931             if (pSelNode->IsAnObject())
01932             {
01933                 // Invalidate the region
01934                 if (!DoInvalidateNodeRegion((NodeRenderableInk*)pSelNode,TRUE,FALSE))
01935                     return FALSE;
01936 
01937                 if (pSelObjPathList->HasProducedPaths())
01938                 {
01939                     // Hide the node that generated the list
01940 
01941                         if (!DoHideNode(pSelNode,TRUE)) return FALSE;
01942                 }
01943                 else
01944                     pSelNode->SetSelected(FALSE);       // If it didn't contribute, just deselect it
01945             }
01946         }
01947         else
01948         {
01949             // Hide the individual path-producing nodes, leaving the
01950             // tree structure around them intact
01951             //
01952             // Get the first path in this list
01953             SelObjPathListItem* pFirstItem = pSelObjPathList->GetFirstItem();
01954             SelObjPathListItem* pListItem = pFirstItem;
01955 
01956             while (ok && pListItem != NULL)
01957             {
01958                 // Always hide the creator node
01959                 NodeRenderableInk* pCreator = pListItem->GetCreatedByNode();
01960                 Node* pParent = pCreator->FindParent();
01961                 if (pParent)
01962                 {
01963                     if (ok) ok = DoInvalidateNodeRegion(pCreator, TRUE, FALSE);
01964                     if (ok) ok = DoHideNode(pCreator, TRUE);
01965                 }
01966 
01967                 // Hide the created path node if its in the tree
01968                 NodePath* pPathNode = pListItem->GetNodePath();
01969                 if (pPathNode->FindParent())
01970                 {
01971                     if (pParent==NULL)
01972                         pParent = pPathNode->FindParent();
01973                     if (ok) ok = DoInvalidateNodeRegion(pPathNode, TRUE, FALSE);
01974                     if (ok) ok = DoHideNode(pPathNode, TRUE);
01975                 }
01976 
01977                 // If this hide has resulted in our parent becoming empty
01978                 // Then hide that too...
01979                 while (pParent && pParent->IsCompoundClass() && !((NodeCompound*)pParent)->HasVisibleChildren())
01980                 {
01981                     Node* pNextParent = pParent->FindParent();
01982 
01983                     if (ok) ok = DoInvalidateNodeRegion((NodeCompound*)pParent, TRUE, FALSE);
01984                     if (ok) ok = DoHideNode((NodeCompound*)pParent, TRUE);
01985 
01986                     pParent = pNextParent;
01987                 }
01988 
01989                 // Get the next item in the selected object path list.
01990                 pListItem = pSelObjPathList->GetNextItem(pListItem);
01991             }
01992         }
01993 
01994         // Get the next list of paths generated by the next selected object
01995         pSelObjPathList = GetNextList(pSelObjPathList);
01996     }
01997 
01998     // it all went well
01999     return ok;
02000 }
02001 
02002 
02003 
02004 
02005 /********************************************************************************************
02006 
02007 > BOOL OpCombineShapes::DoSelectResultNodes(BOOL bCombineEffectBitmaps)
02008 
02009     Author:     Phil_Martin (Xara Group Ltd) <camelotdev@xara.com>
02010     Created:    07/04/2005
02011     Inputs:     -
02012     Outputs:    -
02013     Returns:    TRUE if OK, FALSE if failed
02014     Purpose:    This selects all the nodes in the results list.
02015     Errors:     -
02016     SeeAlso:    -
02017 
02018 ********************************************************************************************/
02019 
02020 BOOL OpCombineShapes::DoSelectResultNodes(BOOL bCombineEffectBitmaps)
02021 {
02022     // Get the list of paths produced by the first selected object
02023     BOOL ok = TRUE;
02024 
02025     NodeRenderableInk::DeselectAll(FALSE, FALSE);
02026 
02027     Node* pNode = pListOfResults->FindFirst();
02028     while (pNode)
02029     {
02030         if (pNode->IsNodeRenderableClass())
02031             ((NodeRenderable*)pNode)->Select(FALSE);
02032 
02033         pNode = pListOfResults->FindNext(pNode);
02034     }
02035 
02036     // it all went well
02037     return ok;
02038 }
02039 
02040 
02041 
02042 
02043 /********************************************************************************************
02044 
02045 > BOOL OpCombineShapes::ApplyAttributes(NodeRenderableInk* pSrcNode,NodeRenderableInk* pDestNode,CCAttrMap* pAttrMap, SelObjPathListItem* pListItem = NULL)
02046 
02047     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
02048     Created:    16/1/95
02049     Inputs:     pSrcNode    = ptr to node to get attrs from
02050                 pDestNode   = ptr to node to apply pSrcNode's attrs to
02051                 pAttrMap    = ptr to attr map (NULL means get attrs applied to pSrcNode)
02052                 pListItem   = so that the can control processing based upon the values of this
02053     Outputs:    -
02054     Returns:    TRUE if OK, FALSE if failed
02055     Purpose:    This applies attrs pDestNode.
02056                 If pAttrMap == NULL, the attrs applied to pSrcNode are applied to pDestNode, else
02057                 the attrs in pAttrMap are applied.
02058     Errors:     -
02059     SeeAlso:    -
02060 
02061 ********************************************************************************************/
02062 
02063 BOOL OpCombineShapes::ApplyAttributes(NodeRenderableInk* pSrcNode,NodeRenderableInk* pDestNode,CCAttrMap* pAttrMap, SelObjPathListItem* pListItem /*= NULL*/)
02064 {
02065     ERROR3IF(pSrcNode  == NULL,"pSrcNode == NULL");
02066     ERROR3IF(pDestNode == NULL,"pDestNode == NULL");
02067     if (pSrcNode == NULL || pDestNode == NULL) return FALSE;
02068 
02069     BOOL ok = TRUE;             // Set to TRUE if we successfully apply attrs to pDestNode
02070     BOOL DeleteAttrs = FALSE;   // TRUE if we need to delete attr map before returning
02071 
02072 
02073     if (pAttrMap == NULL)
02074     {
02075         // Find the currently applied attributes and make a local copy of the map (like most
02076         // passback functions do)
02077         CCAttrMap AppliedMap(30);
02078         ok = pSrcNode->FindAppliedAttributes(&AppliedMap);
02079         pAttrMap = AppliedMap.Copy();
02080         DeleteAttrs = TRUE;
02081     }
02082 
02083     // if we have a stroked object we need to do some attribute juggling
02084     if (AdjustAttributesForStroke(pAttrMap, pListItem) == 2)
02085         return FALSE;
02086 
02087     // If all's OK, apply attrs to pDestNode
02088     ok = pDestNode->ApplyAttributes(pAttrMap,TRUE);
02089 
02090     // Delete the copied attr map if we have to
02091     if (DeleteAttrs)
02092     {
02093         pAttrMap->DeleteAttributes();
02094         delete pAttrMap;
02095     }
02096 
02097     return ok;
02098 }
02099 
02100 
02101 /********************************************************************************************
02102 
02103 > UINT32 OpCombineShapes::AdjustAttributesForStroke(CCAttrMap* pAttrMap)
02104 
02105     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
02106     Created:    4/9/2000
02107     Inputs:     pAttrMap - the attribute map that we are about to apply to our combined node
02108     Outputs:    swaps a few things around in the map
02109     Returns:    0 - if we do not have an active stroke attribute (therefore do nothing)
02110                 1 - if we do have an active stroke and everything went well
02111                 2 - if we have an active stroke and something went wrong (e.g. out of memory)
02112     Purpose:    If we are doing combine shapes on a stroked shape then we need to make the following
02113                 changes:
02114                 1) Do not keep the AttrStrokeType and VariableWidth around, this is because their
02115                 effects have been achieved via the DoBecomeA so if we keep them then effectively
02116                 we are stroking our final output twice.
02117                 2) Set line width to zero.  Once again the line width effect has been extracted 
02118                 from the DoBecomeA
02119                 3) Use the current stroke colour for the new fill colour.
02120                 4) Ditch the stroke colour?
02121 
02122                 We must actually make new nodes for these attributes as changing the existing
02123                 ones will mess up our undo.  These attributes must be deleted after they are 
02124                 applied by calling DeleteExtraAttributesForStroke()
02125     Errors:     -
02126     SeeAlso:    -
02127     Notes:      Phil, 16/11/2005
02128                 We know that we own the attributes referred to by the Atrtibute Map so we
02129                 can party on them without worrying about affecting other data structures.
02130                 This allows us to remove attributes from the map completely, leaving it in
02131                 a consistent state.
02132 
02133 ********************************************************************************************/
02134 
02135 UINT32 OpCombineShapes::AdjustAttributesForStroke(CCAttrMap* pAttrMap, SelObjPathListItem* pListItem /*= NULL*/)
02136 {
02137     if (pAttrMap == NULL)
02138         return 0;
02139 
02140     // first see if we have an active stroke, if not we can just quit already
02141     AttrStrokeType* pStroke = NULL;
02142     pAttrMap->Lookup(CC_RUNTIME_CLASS(AttrStrokeType), (void*&)pStroke);
02143     
02144     if (pStroke == NULL || !pStroke->HasPathProcessor())
02145         return 0;
02146 
02147     AttrVariableWidth* pVarWidth = NULL;
02148     pAttrMap->Lookup(CC_RUNTIME_CLASS(AttrVariableWidth), (void*&)pVarWidth);
02149     
02150     // CGS:  we should only really continue IF we have an active value function!  Otherwise, we seem to run
02151     // into problems when we have give new objects most recent attributes turned on ....
02152     if ((pVarWidth == NULL) || !pVarWidth->HasActiveValueFunction ())
02153         return (0);
02154 
02155     // right so we want to set the line width to zero
02156     AttrLineWidth* pLineWidth = new AttrLineWidth;
02157     if (pLineWidth != NULL)
02158     {
02159         pAttrMap->RemoveAttribute(CC_RUNTIME_CLASS(AttrLineWidth));
02160         pLineWidth->Value.LineWidth = 0;
02161         pAttrMap->SetAt(CC_RUNTIME_CLASS(AttrLineWidth), (void*)pLineWidth);
02162     }
02163 
02164     // now get the stroke colour and use it to make a fill colour
02165     AttrStrokeColour * pColour = NULL;
02166 
02167     // look up the stroke colour attribute          
02168     pAttrMap->Lookup(CC_RUNTIME_CLASS(AttrStrokeColour),
02169                         (void *&)pColour);
02170     
02171 
02172     // make a new flat fill attribute and apply this to the node
02173     if (pColour)
02174     {
02175         if (!pListItem || pListItem->GetStrokeCreatedPassbackPath ())
02176         {
02177             StrokeColourAttribute * pAttrVal = (StrokeColourAttribute *)pColour->GetAttributeValue();
02178 
02179             if (pAttrVal)
02180             {
02181                 AttrFlatColourFill* pFill = new AttrFlatColourFill;
02182                 
02183                 if (pFill)
02184                 {
02185                     DocColour Col = pAttrVal->Colour;
02186                     pFill->SetStartColour(&Col);
02187                     
02188                     // set back in the map
02189                     pAttrMap->RemoveAttribute(CC_RUNTIME_CLASS(AttrFillGeometry));
02190                     pAttrMap->SetAt(CC_RUNTIME_CLASS(AttrFillGeometry), (void*)pFill);
02191                 }
02192                 else
02193                     return 2;
02194             }
02195         }
02196         // now make a new stroke colour and set it transparent      
02197         AttrStrokeColour* pNewStrokeCol = new AttrStrokeColour;
02198         if (pNewStrokeCol)
02199         {
02200             pNewStrokeCol->Value.Colour = DocColour(COLOUR_NONE);
02201             pAttrMap->RemoveAttribute(CC_RUNTIME_CLASS(AttrStrokeColour));
02202             pAttrMap->SetAt(CC_RUNTIME_CLASS(AttrStrokeColour), (void*)pNewStrokeCol);
02203         }
02204     }
02205 
02206     // now remove the stroke and var width attributes, and any brushes whilst we're here
02207     pAttrMap->RemoveAttribute(CC_RUNTIME_CLASS(AttrVariableWidth));
02208     pAttrMap->RemoveAttribute(CC_RUNTIME_CLASS(AttrStrokeType));
02209     pAttrMap->RemoveAttribute(CC_RUNTIME_CLASS(AttrBrushType));
02210 
02211     return 1;
02212 }
02213 
02214 
02215 /********************************************************************************************
02216 
02217 >   void OpCombineShapes::DeleteExtraStrokeAttributes(CCAttrMap* pAttrMap)
02218 
02219     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
02220     Created:    4/9/2000
02221     Inputs:     pAttrMap - the attrmap that we generated to apply to the new combined shapes
02222     Outputs:    some attributes are deleted from the map
02223     Returns:    -
02224     Purpose:    To delete the extra attributes we allocated in AdjustAttributesForStroke
02225     SeeAlso:    -
02226 
02227 ********************************************************************************************/
02228 
02229 /*void OpCombineShapes::DeleteExtraStrokeAttributes(CCAttrMap* pAttrMap, SelObjPathListItem* pListItem)
02230 {
02231     if (pAttrMap == NULL)
02232     {
02233         ERROR3("Attribute map is NULL in OpCombineShapes::DeleteExtraStrokeAttributes");
02234         return;
02235     }
02236 
02237     // Just look em up and wipe em out
02238     AttrLineWidth* pLineWidth = NULL;
02239     pAttrMap->Lookup((void*)CC_RUNTIME_CLASS(AttrLineWidth), (void*&)pLineWidth);
02240     if (pLineWidth != NULL)
02241     {
02242 TRACEUSER( "Phil", _T("DESA Removing line width %x\n"), pLineWidth);
02243         delete pLineWidth;
02244         pLineWidth = NULL;
02245 
02246         pAttrMap->RemoveKey((void*)CC_RUNTIME_CLASS(AttrLineWidth));
02247     }
02248 
02249     AttrStrokeColour* pStrokeCol = NULL;
02250     pAttrMap->Lookup((void*)CC_RUNTIME_CLASS(AttrStrokeColour), (void*&)pStrokeCol);
02251     if (pStrokeCol != NULL)
02252     {
02253 TRACEUSER( "Phil", _T("DESA Removing stroke colour %x\n"), pStrokeCol);
02254         delete pStrokeCol;
02255         pStrokeCol = NULL;
02256 
02257         pAttrMap->RemoveKey((void*)CC_RUNTIME_CLASS(AttrStrokeColour));
02258     }
02259 
02260     if (!pListItem || pListItem->GetStrokeCreatedPassbackPath ())
02261     {   
02262         AttrFillGeometry* pFill = NULL;
02263         pAttrMap->Lookup((void*)CC_RUNTIME_CLASS(AttrFillGeometry), (void*&)pFill);
02264         if (pFill)
02265         {
02266 TRACEUSER( "Phil", _T("DESA Removing fill %x\n"), pFill);
02267             delete pFill;
02268             pFill = NULL;
02269 
02270             pAttrMap->RemoveKey((void*)CC_RUNTIME_CLASS(AttrFillGeometry));
02271         }
02272     }
02273 
02274     return;
02275 }
02276 */
02277 
02278 
02279 /********************************************************************************************
02280 
02281 > BOOL OpCombineShapes::ApplyEffects(SelObjPathListItem* pListItem, NodeRenderableInk* pDestNode, NodeRenderableInk* pParentEffect)
02282 
02283     Author:     Phil_Martin (Xara Group Ltd) <camelotdev@xara.com>
02284     Created:    04/02/2005
02285     Inputs:     pListItem   = so that the can control processing based upon the values of this
02286                 pDestNode   = ptr to node to apply pSrcNode's attrs to
02287     Outputs:    -
02288     Returns:    TRUE if OK, FALSE if failed
02289     Purpose:    This applies attrs pDestNode.
02290                 If pAttrMap == NULL, the attrs applied to pSrcNode are applied to pDestNode, else
02291                 the attrs in pAttrMap are applied.
02292     Errors:     -
02293     SeeAlso:    -
02294 
02295 ********************************************************************************************/
02296 
02297 BOOL OpCombineShapes::ApplyEffects(SelObjPathListItem* pListItem, NodeRenderableInk* pDestNode, NodeRenderableInk* pParentEffect)
02298 {
02299     BOOL ok = TRUE;
02300     PORTNOTE("other", "Removed OpCombineShapes::ApplyEffects");
02301 #ifndef EXCLUDE_FROM_XARALX
02302 
02303     ERROR3IF(pListItem  == NULL, "pListItem == NULL");
02304     ERROR3IF(pDestNode == NULL, "pDestNode == NULL");
02305 
02306     NodeRenderableInk* pSrcNode = pListItem->GetCreatedByNode();
02307     ERROR3IF(pSrcNode  == NULL, "pSrcNode == NULL");
02308 
02309     // Get the effect stack for the object which produced this path (pSrcNode)
02310     // If the source node had effects applied to it
02311     // And that stack is different than the stack above the context node
02312     // Then we must copy the effect stack onto the destination node
02313     ListRange* pStack = pListItem->GetEffectStack();
02314     Node* pEffect = NULL;
02315     if (pStack)
02316         pEffect = pStack->FindFirst();
02317     if (pEffect)
02318     {
02319         if (pParentEffect==NULL || pParentEffect!=pEffect)
02320         {
02321             // We need to copy the effect stack onto this object
02322             OpLiveEffect::DoCopyEffectsStack(this, pDestNode, pStack, GetWorkingDoc(), GetWorkingDoc());
02323         }
02324     }
02325 #endif
02326     return ok;
02327 }
02328 
02329 
02330 /********************************************************************************************
02331 
02332 >   static BOOL OpCombineShapes::GetSubpath(Path& Src,Path* const pDest,INT32* pStartIndex)
02333 
02334     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
02335     Created:    18/1/95
02336     Inputs:     Src         = source path
02337                 pDest       = ptr to place to put result
02338                 pStartIndex = index to start looking for sub paths in Src (0 = first subpath)
02339     Outputs:    *pStartIndex is updated to be the start of the next subpath (-1 means there are no more)
02340     Returns:    TRUE if all went well, FALSE otherwise
02341     Purpose:    This picks out a subpath from Src and places it into pDest
02342     SeeAlso:    -
02343 
02344 ********************************************************************************************/
02345 /*
02346 BOOL OpCombineShapes::GetSubpath(Path& Src,Path* const pDest,INT32* pStartIndex)
02347 {
02348     ERROR2IF(pDest == NULL,FALSE,"pDest == NULL");
02349     ERROR2IF(pStartIndex == NULL,FALSE,"pDest == NULL");
02350 
02351     INT32 StartIndex = *pStartIndex;
02352     INT32 NumCoords = Src.GetNumCoords();
02353     if (StartIndex < 0 || StartIndex >= NumCoords)
02354         return FALSE;
02355 
02356     // Get the start and end indexes for the current subpath
02357     INT32 EndIndex=StartIndex;
02358     Src.FindEndElOfSubPath(&EndIndex);
02359     ERROR2IF(EndIndex >= NumCoords,FALSE,"EndIndex gone beyond num coords in path");
02360 
02361     if (!pDest->MakeSpaceInPath((EndIndex-StartIndex)+1))
02362         return FALSE;
02363 
02364     // Copy the subpath into destination path
02365     if (!Src.CopySectionTo(pDest,StartIndex,(EndIndex-StartIndex)+1))
02366         return FALSE;
02367 
02368     // Make sure the filled and stroked flags are correct
02369     pDest->IsFilled  = Src.IsFilled;
02370     pDest->IsStroked = Src.IsStroked;
02371 
02372     INT32 NextStartIndex = EndIndex+1;
02373     if (NextStartIndex >= NumCoords)
02374         NextStartIndex = -1;
02375 
02376     *pStartIndex = NextStartIndex;
02377 
02378     return (TRUE);
02379 }
02380 */
02381 
02382 /********************************************************************************************
02383 
02384 >   BOOL OpCombineShapes::StrokePathToPath(Path* pPath,Path* pDest)
02385 
02386     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
02387     Created:    26/1/95
02388     Inputs:     pPath = ptr to path
02389                 pDest = ptr to path to place result in
02390     Outputs:    -
02391     Returns:    TRUE if OK, FALSE if it goes bang
02392     Purpose:    Strokes pPath, and places result in pDest
02393     SeeAlso:    -
02394 
02395 ********************************************************************************************/
02396 
02397 BOOL OpCombineShapes::StrokePathToPath(Path* pPath,Path* pDest)
02398 {
02399     ERROR2IF(pPath == NULL,FALSE,"pPath == NULL");
02400     ERROR2IF(pDest == NULL,FALSE,"pDest == NULL");
02401 
02402     return pPath->StrokePathToPath(DefaultStrokeWidth,LineCapButt,MitreJoin,NULL,pDest,200);
02403 
02404  /*
02405     INT32 NumCoords = pPath->GetNumCoords();
02406     INT32 OutputSize = (NumCoords*40)+1000;
02407     Path OutputPath;
02408     if (!OutputPath.Initialise(OutputSize)) return FALSE;
02409 
02410     DocCoord*   IPoints =           pPath->GetCoordArray();
02411     BYTE*       ITypes  = (BYTE*)   pPath->GetVerbArray();
02412     DWORD       ILength = (DWORD)   NumCoords;
02413 
02414     DocCoord*   OPoints =           OutputPath.GetCoordArray();
02415     BYTE*       OTypes  = (BYTE*)   OutputPath.GetVerbArray();
02416     DWORD       OLength = (DWORD)   OutputSize;
02417 
02418     DashType Dash;
02419     Dash.Length = 0;
02420 
02421     INT32 len = GRenderRegion::StrokePathToPath(IPoints,ITypes,ILength,
02422                                     OPoints,OTypes,OLength,
02423                                     FALSE,(TOLERANCE*5)/2,200,LineCapButt,MitreJoin,&Dash);
02424 
02425     if (!OutputPath.MergeTwoPaths(OutputPath.GetCoordArray(),OutputPath.GetVerbArray(),OutputPath.GetFlagArray(),
02426                                 len,TRUE)) return FALSE;
02427 
02428     if (len > 0)
02429     {
02430         pDest->ClearPath(FALSE);
02431         if (!pDest->MakeSpaceInPath(len)) return FALSE;
02432         if (!pDest->MergeTwoPaths(  OutputPath.GetCoordArray(),OutputPath.GetVerbArray(),OutputPath.GetFlagArray(),
02433                                     len,TRUE)) return FALSE;
02434 
02435         pDest->InitialiseFlags();
02436         RemoveBogusCloseFigures(pDest);
02437         INT32 n = pDest->GetNumCoords();
02438         PathVerb* pVerbs = pDest->GetVerbArray();
02439         pVerbs[n-1] |= PT_CLOSEFIGURE;
02440     }
02441 
02442     return TRUE;
02443 */
02444 }
02445 
02446 /********************************************************************************************
02447 
02448 >   BOOL OpCombineShapes::DoInsertNewNode(NodeRenderableBounded* pNewNode)
02449 
02450     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
02451     Created:    30/1/95
02452     Inputs:     pNewNode = ptr to the node to insert
02453     Outputs:    -
02454     Returns:    TRUE if OK, FALSE if it goes bang
02455     Purpose:    High-level interface to UndoableOperation::DoInsertNode().
02456 
02457                 Every time this call is successful, it incs a counter so that the op can
02458                 tell how many new nodes have been placed in the tree as a result of the shape combining
02459                 operation.
02460 
02461                 Uses member vars pContextNode & AttachDir
02462 
02463                 If no new nodes are created, the op can then fail, rather than just giving the effect of deleting
02464                 the selection
02465     SeeAlso:    -
02466 
02467 ********************************************************************************************/
02468 
02469 BOOL OpCombineShapes::DoInsertNewNode(NodeRenderableBounded* pNewNode)
02470 {
02471     ERROR2IF(pNewNode == NULL,FALSE,"Given NULL node ptr");
02472     ERROR2IF(pContextNode == NULL,FALSE,"Context node is NULL");
02473 
02474     if (UndoableOperation::DoInsertNewNode(pNewNode,pContextNode,AttachDir,TRUE,FALSE,FALSE,TRUE))
02475     {
02476         NodeInsertCount++;
02477         return TRUE;
02478     }
02479     else
02480         return FALSE;
02481 }
02482 
02483 
02484 
02485 
02486 /********************************************************************************************
02487 
02488 >   ListRange* OpCombineShapes::CopyRange(Range* pRange)
02489 
02490     Author:     Phil_Martin (Xara Group Ltd) <camelotdev@xara.com>
02491     Created:    06/05/2005
02492     Inputs:     pRange = ptr to the range to copy
02493     Outputs:    -
02494     Returns:    ptr to ListRange if OK,
02495                 NULL otherwise
02496     Purpose:    Copy the specified range of nodes
02497     SeeAlso:    -
02498 
02499 ********************************************************************************************/
02500 
02501 ListRange* OpCombineShapes::CopyRange(Range* pRange)
02502 {
02503     BOOL ok = TRUE;
02504     Node* pNode = pRange->FindFirst();
02505     ListRange* pNewRange = new ListRange();
02506 
02507     while (pNode && ok && pNewRange)
02508     {
02509         Node* pNext = pRange->FindNext(pNode);
02510 
02511         // Make a copy of the current node and all its children
02512         Node* TheCopy;
02513         BOOL CopiedOK; 
02514         CALL_WITH_FAIL(pNode->NodeCopy(&TheCopy), this, CopiedOK);
02515         if (CopiedOK)
02516         {
02517             // make sure that it is bounded
02518             ERROR2IF(!TheCopy->IsBounded(), FALSE, "Object being copied is not a NodeRenderableBounded"); 
02519             NodeRenderableBounded* BoundCopy = (NodeRenderableBounded*)TheCopy;
02520 
02521             // Insert the copied node next to it's originator
02522             ok = UndoableOperation::DoInsertNewNode(BoundCopy, pNode, NEXT,
02523                                                      FALSE,     // Don't Invalidate region 
02524                                                      FALSE,     // Don't Clear the selections
02525                                                      FALSE,     // Don't select new node
02526                                                      FALSE      // Don't normalise attributes
02527                                                      );
02528             if (!ok)
02529             {
02530                 // Failed, so tidy up before returning
02531                 TheCopy->CascadeDelete(); 
02532                 delete TheCopy; 
02533             } 
02534             else
02535             {
02536                 // Record the copied node in the ListRange
02537                 pNewRange->AddNode(TheCopy);
02538 
02539                 // Call PostDuplicate on the copied node and all it's children
02540                 Node* pCurrent = BoundCopy->FindFirstDepthFirst();
02541                 while (pCurrent!=NULL && ok)
02542                 {
02543                     ok = pCurrent->PostDuplicate(this);
02544                     pCurrent = pCurrent->FindNextDepthFirst(BoundCopy);
02545                 }
02546             }
02547         }
02548 
02549         pNode = pNext;
02550     }
02551 
02552     if (!ok)
02553     {
02554         delete pNewRange;
02555         pNewRange = NULL;
02556     }
02557 
02558     return pNewRange;
02559 }
02560 
02561 
02562 
02563 
02564 //----------------------------------------------------
02565 /********************************************************************************************
02566 
02567 > BOOL CombineBecomeA::PassBack(NodeRenderableInk* pNewNode,NodeRenderableInk* pCreatedByNode,CCAttrMap* pAttrMap)
02568 
02569     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
02570     Created:    12/1/95
02571     Inputs:     pNewNode        = ptr to new generated node (should be a NodePath)
02572                 pCreatedByNode  = ptr to the node that generated this NodePath
02573                 pAttrMap        = ptr attr map (NULL means get attrs applied to pCreatedByNode)
02574     Outputs:    -
02575     Returns:    -
02576     Purpose:    This is the func that receives the NodePaths generated by the selection.
02577                 The constructor ensures that we have a list to add SelObjPathListItems to.
02578     Errors:     -
02579     SeeAlso:    CreateSelObjPathList()
02580 
02581 ********************************************************************************************/
02582 
02583 BOOL CombineBecomeA::PassBack(NodeRenderableInk* pNewNode,NodeRenderableInk* pCreatedByNode,CCAttrMap* pAttrMap)
02584 {
02585     // Do a quick param check
02586     ERROR2IF(pNewNode == NULL,FALSE,"pNewNode == NULL");
02587     ERROR2IF(pSelObjPathList == NULL,FALSE,"pSelObjPathList is NULL");
02588     ERROR2IF(!pNewNode->IsNodePath(),FALSE,"pNewNode not a kind of NodePath");
02589 
02590     // We've received a NodePath from a selected object, so make a sel obj path list item
02591     // out of it and add it to the list
02592 
02593     SelObjPathListItem* pSelObjPathListItem = new SelObjPathListItem((NodePath*)pNewNode,pCreatedByNode,pAttrMap, strokeCreatedPassbackPath);
02594 
02595     if (pSelObjPathListItem != NULL)
02596     {
02597         pSelObjPathList->AddTailItem(pSelObjPathListItem);
02598         return TRUE;
02599     }
02600     else
02601         return FALSE;
02602 
02603 /*
02604     *** code for splitting the new NodePath into separate subpaths ***
02605 
02606     // Get ptr to src path of new node
02607     Path* pSrcPath = &(((NodePath*)pNewNode)->InkPath);
02608 
02609     // We've received a NodePath from a selected object, so make sel obj path list items
02610     // for each of the sub paths contained within it
02611 
02612     Path DestPath;
02613     BOOL ok = DestPath.Initialise();
02614 
02615     INT32 StartIndex = 0;
02616 
02617     if (ok)
02618     {
02619         do
02620         {
02621             ok = OpCombineShapes::GetSubpath(*pSrcPath,&DestPath,&StartIndex);
02622             if (ok)
02623             {
02624                 NodePath* pNodePath = new NodePath;
02625 
02626                 ok = (pNodePath != NULL);
02627 
02628                 if (ok) ok = pNodePath->SetUpPath(DestPath.GetNumCoords());
02629                 if (ok) ok = pNodePath->InkPath.CopyPathDataFrom(&DestPath);
02630 
02631                 if (ok)
02632                 {
02633                     SelObjPathListItem* pSelObjPathListItem = new SelObjPathListItem(pNodePath,pCreatedByNode);
02634                     ok = (pSelObjPathListItem != NULL);
02635                     if (ok) pSelObjPathList->AddTailItem(pSelObjPathListItem);
02636                 }
02637 
02638                 if (!ok && pNodePath!=NULL)
02639                     delete pNodePath;
02640             }
02641 
02642         } while (ok && StartIndex >=0);
02643     }
02644 
02645     if (pNewNode != NULL)
02646         delete pNewNode;
02647 
02648     return ok;
02649 */
02650 }
02651 
02652 //----------------------------------
02653 
02654 /********************************************************************************************
02655 
02656 > SelObjPathListItem::SelObjPathListItem(NodePath* pThisNodePath,NodeRenderableInk* pThisCreatedByNode,CCAttrMap* pThisAttrMap, BOOL createdByStroke)
02657 
02658     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
02659     Created:    12/1/95
02660     Inputs:     pThisNodePath       = the NodePath this item refers to
02661                 pThisCreatedByNode  = the node that created pThisNodePath
02662                 pThisAttrMap        = ptr to attr map (NULL means get applied attrs from pThisCreatedByNode)
02663                 createdByStroke     = was the item created by a stroke?
02664     Outputs:    -
02665     Returns:    -
02666     Purpose:    Main constructor.  Sets up the item correctly
02667     Errors:     -
02668     SeeAlso:    
02669 
02670 ********************************************************************************************/
02671 
02672 SelObjPathListItem::SelObjPathListItem(NodePath* pThisNodePath,NodeRenderableInk* pThisCreatedByNode,CCAttrMap* pThisAttrMap, BOOL createdByStroke /*= FALSE*/)
02673 {
02674     pNodePath       = pThisNodePath;
02675     pCreatedByNode  = pThisCreatedByNode;
02676     pAttrMap        = pThisAttrMap;
02677     strokeCreatedPassbackPath = createdByStroke;
02678     pEffectStack    = NULL;
02679 }
02680 
02681 /********************************************************************************************
02682 
02683 > SelObjPathListItem::~SelObjPathListItem()
02684 
02685     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
02686     Created:    12/1/95
02687     Inputs:     -
02688     Outputs:    -
02689     Returns:    -
02690     Purpose:    Destructor that deletes the member path pointer, and any attrs supplied
02691     Errors:     -
02692     SeeAlso:    
02693 
02694 ********************************************************************************************/
02695 
02696 SelObjPathListItem::~SelObjPathListItem()
02697 {
02698     // Must delete the NodePaths generated via the DoBecomeA() mechanism
02699     // Only delete if this node is not in the tree!
02700     if (pNodePath != NULL && pNodePath!=pCreatedByNode && pNodePath->FindParent()==NULL && pNodePath->GetHiddenCnt()==0)
02701     {
02702         pNodePath->CascadeDelete();
02703         delete pNodePath;
02704     }
02705 
02706     if (pAttrMap  != NULL)
02707     {
02708         // Delete all the attrs in the map
02709         CCRuntimeClass * pType;
02710         void* pVal;
02711 
02712         // iterating all (key, value) pairs
02713         for (CCAttrMap::iterator Pos = pAttrMap->GetStartPosition(); Pos != pAttrMap->GetEndPosition();)
02714         {
02715             // Get attr at position Pos
02716             pAttrMap->GetNextAssoc(Pos,pType,pVal);
02717 
02718             if (pVal != NULL)
02719             {
02720                 pAttrMap->RemoveKey(pType);
02721                 delete (NodeAttribute*)pVal;
02722             }
02723         }
02724 
02725         // delete the actual map
02726         delete pAttrMap;
02727     }
02728 
02729     if (pEffectStack)
02730     {
02731         delete pEffectStack;
02732         pEffectStack = NULL;
02733     }
02734 }
02735 
02736 /********************************************************************************************
02737 
02738 >   Path* SelObjPathListItem::GetPath()
02739 
02740     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
02741     Created:    26/1/95
02742     Inputs:     -
02743     Outputs:    -
02744     Returns:    Ptr to the path, or NULL
02745     Purpose:    Get that Path ptr boy!
02746     SeeAlso:    -
02747 
02748 ********************************************************************************************/
02749 
02750 Path* SelObjPathListItem::GetPath()
02751 {
02752     if (pNodePath != NULL) 
02753         return &(pNodePath->InkPath); 
02754     else 
02755         return NULL; 
02756 }
02757 

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