cutop.cpp

Go to the documentation of this file.
00001 // $Id: cutop.cpp 1467 2006-07-18 17:00:07Z gerry $
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 // Implementation of cut/copy and paste operations 
00099 
00100 /*
00101 //*/
00102 
00103 #include "camtypes.h" 
00104 #include "cutop.h"
00105 
00106 //#include "app.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00107 #include "clipint.h"
00108 //#include "docrect.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00109 //#include "document.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00110 //#include "docview.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00111 //#include "ink.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00112 #include "layer.h" 
00113 //#include "spread.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00114 //#include "trans2d.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00115 #include "wrkrect.h"
00116 #include "objchge.h"
00117 
00118 //#include "clikdrag.h"
00119 
00120 //#include "jason.h"
00121 //#include "peter.h"
00122 //#include "simon.h"
00123 //#include "tool.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00124 //#include "attrmgr.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00125 
00126 #include "textops.h"
00127 #include "nodetxts.h"       // For temporary disabling of text subselection copying/cutting
00128 //#include "barsdlgs.h"     // button controls
00129 
00130 //#include "fillattr.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00131 #include "ndoptmz.h"
00132 #include "progress.h"
00133 #include "transop.h"
00134 //#include "bubbleid.h"
00135 
00136 #include "camdoc.h"         // for CCamDoc::GetKernelPathName & CCamDoc::SetPathName
00137 
00138 #include "fillramp.h"
00139 #include "opgrad.h"
00140 #include "blobs.h"
00141 
00142 //#include "cxfdefs.h"      // for TemplateAttribute - in camtypes.h [AUTOMATICALLY REMOVED]
00143 #include "cxftags.h"
00144 //#include "cxfrec.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00145 //#include "cxfrech.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00146 //#include "attrval.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00147 #include "userattr.h"
00148 #include "tmpltatr.h"
00149 
00150 #include "ngsentry.h"       // for NodeSetProperty etc
00151 #include "slicehelper.h"    // for the MeshLayers fn
00152 
00153 //#include "will2.h"            // for _R(IDS_FILLTOOL_FILLINFOBARNAME)
00154 #include "filltool.h"       // for GradInfoBarOp
00155 #include "ngitem.h"         // for SGNameItem
00156 //#include "units.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00157 #include "effects_stack.h"
00158 #include "ophist.h"
00159 
00160 DECLARE_SOURCE("$Revision: 1467 $");
00161 
00162 CC_IMPLEMENT_DYNCREATE(OpCut, SelOperation)
00163 CC_IMPLEMENT_DYNCREATE(OpCopy, UndoableOperation)  
00164 CC_IMPLEMENT_DYNCREATE(OpPaste, SelOperation)  
00165 CC_IMPLEMENT_DYNCREATE(OpDelete, SelOperation)
00166 CC_IMPLEMENT_DYNCREATE(CarbonCopyOp, SelOperation)
00167 CC_IMPLEMENT_DYNCREATE(OpDuplicate, CarbonCopyOp)
00168 CC_IMPLEMENT_DYNCREATE(OpClone, CarbonCopyOp)
00169 CC_IMPLEMENT_DYNCREATE(OpCopyAndTransform, CarbonCopyOp)
00170 CC_IMPLEMENT_DYNCREATE(OpPasteAttributes, SelOperation)
00171 CC_IMPLEMENT_DYNAMIC(OpParamPasteAtPosition,OpParam)
00172 
00173 
00174 /********************************************************************************************
00175 
00176 >   static void SetCutOpText(String_256 *UIDescription, UINT32 PrefixStringID)
00177 
00178     Author:     Jason_Williams (Xara Group Ltd) <camelotdev@xara.com>
00179     Created:    22/5/95
00180     Inputs:     UIDescription - points to the UOIDescription for a GetState method
00181                 PrefixStringID - the ID of a prefix to use (e.g. "Cut ")
00182 
00183     Outputs:    UIDescription will either be left unchanged (no special description
00184                 available) or will be filled in (e.g. "Cut lines")
00185 
00186     Purpose:    Shared code used by almost all the GetState methods in kernel cutop.cpp
00187                 Given a prefix string ("Copy "), it scans the selection for a description
00188                 and generates an op description string ("Cut shape")
00189 
00190 ********************************************************************************************/
00191 
00192 static void SetCutOpText(String_256 *UIDescription, UINT32 PrefixStringID)
00193 {
00194     String_256 Description = GetApplication()->FindSelection()->Describe(MENU);
00195 
00196     if (!Description.IsEmpty())
00197     {
00198         *UIDescription = String_256(PrefixStringID);
00199         *UIDescription += Description;
00200     }
00201     // else Leave the UIDescription as it stands (probably a simple "Cut" etc)
00202 }
00203 
00204 
00205 
00206 // ------------------------------------------------------------------------------------------
00207 // OpCut methods
00208             
00209 /********************************************************************************************
00210 
00211 >   OpCut::OpCut() 
00212 
00213     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
00214     Created:    29/9/93
00215     Inputs:     -
00216     Outputs:    -
00217     Returns:    -
00218     Purpose:    OpCut constructor
00219     Errors:     -
00220     SeeAlso:    -
00221 
00222 ********************************************************************************************/
00223             
00224             
00225 OpCut::OpCut(): SelOperation()                              
00226 {                              
00227 }
00228 
00229  /********************************************************************************************
00230 
00231 >   BOOL OpCut::Init()
00232 
00233     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
00234     Created:    28/9/93
00235     Inputs:     -
00236     Outputs:    -
00237     Returns:    TRUE if the operation could be successfully initialised 
00238                 FALSE if no more memory could be allocated 
00239                 
00240     Purpose:    OpCut initialiser method
00241     Errors:     ERROR will be called if there was insufficient memory to allocate the 
00242                 operation.
00243     SeeAlso:    -
00244 
00245 ********************************************************************************************/
00246 
00247 BOOL OpCut::Init()
00248 {
00249     return (RegisterOpDescriptor(0,
00250                             _R(IDS_CUTOP),
00251                             CC_RUNTIME_CLASS(OpCut),
00252                             OPTOKEN_CUT,
00253                             OpCut::GetState,
00254                             0,                          // help ID 
00255                             _R(IDBBL_CUT),
00256                             0,
00257                             0,
00258                             SYSTEMBAR_ILLEGAL,          // For now !
00259                             TRUE,                       // Receive messages
00260                             FALSE,
00261                             FALSE,
00262                             0,
00263                             0 //(GREY_WHEN_NO_CURRENT_DOC | GREY_WHEN_NO_SELECTION)
00264     ));
00265 }               
00266     
00267 /********************************************************************************************
00268 
00269 >   OpState OpCut::GetState(String_256*, OpDescriptor*)
00270 
00271     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
00272     Created:    28/9/93
00273     Inputs:     -
00274     Outputs:    -
00275     Returns:    The state of the OpCut
00276     Purpose:    For finding OpCut's state. 
00277     Errors:     -
00278     SeeAlso:    -
00279 
00280 ********************************************************************************************/
00281 
00282 OpState OpCut::GetState(String_256* UIDescription, OpDescriptor*)
00283 {
00284     OpState OpSt;
00285 
00286     // BODGE Temporarily disable copying TextStory sub-selections
00287 /*
00288     Range Sel(*(GetApplication()->FindSelection()));
00289     Node* Current = Sel.FindFirst();
00290     while(Current != NULL)
00291     {
00292         if (Current->IS_KIND_OF(BaseTextClass))
00293         {
00294             if (!(IS_A(Current, TextStory)))
00295             {
00296                 OpSt.Greyed = TRUE;
00297                 return(OpSt);
00298             }
00299         }
00300         Current = Sel.FindNext(Current, TRUE); 
00301     }
00302 */
00303 
00304     SelRange* pSelRange = GetApplication()->FindSelection();    // get the selected range
00305 
00306     // Set up the ObjChangeParam so we can ask the selected nodes if they minds being cut
00307     ObjChangeFlags cFlags(TRUE);
00308     ObjChangeParam ObjChange(OBJCHANGE_STARTING,cFlags,NULL,NULL);
00309 
00310     // Will one or more selected nodes allow this op?
00311     if (!pSelRange->AllowOp(&ObjChange,FALSE))
00312     {
00313         // None of the nodes can be deleted
00314         OpSt.Greyed = TRUE;
00315 
00316         // Load reason why operation is disabled
00317         UINT32 IDS = ObjChange.GetReasonForDenial();
00318         if (IDS == 0) IDS = _R(IDS_NO_OBJECTS_SELECTED);    // if 0 (i.e. not been set), then assume there's no selection
00319         *UIDescription = String_256(IDS);               // Resolve the string ID
00320     }
00321     else
00322         SetCutOpText(UIDescription, _R(IDS_CLIPBOARD_PRECUT));
00323 
00324     return(OpSt);
00325 }
00326 
00327 /********************************************************************************************
00328 
00329 >   void OpCut::Do(OpDescriptor*)
00330 
00331     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
00332     Created:    16/8/93
00333     Inputs:     OpDescriptor (unused)
00334     Outputs:    -
00335     Returns:    -
00336     Purpose:    Performs the Cut operation. 
00337                 
00338     Errors:     -
00339     SeeAlso:    -
00340 
00341 ********************************************************************************************/
00342     
00343 void OpCut::Do(OpDescriptor*)
00344 {  
00345     ObjectSet CompoundSet;
00346 
00347     BeginSlowJob(-1, FALSE);        // Make sure we have a simple hourglass, without a delay
00348 
00349     // Check with the clipboard if it is OK to go ahead
00350     InternalClipboard *Clipboard = InternalClipboard::Instance();
00351     if (Clipboard == NULL || !Clipboard->PrepareForCopy())
00352     {
00353         FailAndExecute();
00354 
00355         EndSlowJob();
00356         End();
00357         return;
00358     }
00359 
00360     // Obtain the current selection
00361     Range Sel(*(GetApplication()->FindSelection()));
00362     
00363     // DMc change to range control
00364     RangeControl rg = Sel.GetRangeControlFlags();
00365     rg.PromoteToParent = TRUE;
00366     Sel.Range::SetRangeControl(rg);
00367 
00368 
00369     // Prepare an ObjChangeParam so we can mark which nodes will allow this op to happen to them
00370     ObjChangeFlags cFlags(TRUE);
00371     ObjChangeParam ObjChange(OBJCHANGE_STARTING,cFlags,NULL,this);
00372 
00373     if (!DoStartSelOp(TRUE,FALSE))  // Try to record the selection state
00374         goto EndOperation;  
00375     
00376     // Mark nodes that will allow this to happen, and error if no nodes will let it happen
00377     if (!Sel.AllowOp(&ObjChange))
00378     {
00379         ERROR3("AllowOp() returned FALSE, i.e. op should have been greyed out");
00380         goto EndOperation;
00381     }
00382 
00383     
00384     // We need to invalidate the region
00385     if (!DoInvalidateNodesRegions(Sel, TRUE))
00386         goto EndOperation; 
00387 
00388     // Prior to hiding anything lets get ourselves a set of compound nodes which 
00389     // we will need to factor out attributes on. We cannot use the SelRange because
00390     // this will be empty after the deletion has taken place. 
00391 
00392     if (!(Sel.GetCompoundObjectSet(&CompoundSet, 
00393                                     TRUE )))
00394     {
00395         goto EndOperation; 
00396     }
00397     
00398     // Removed 24/07/95 (We now leave it up to the text story deletion code to handle its own attributes)
00399 
00400     // Because of complex deletion we must also add the TextStory objects 
00401     // of selected chars. This code will hopefuly be removed in future. It is
00402     // inefficient cos we will need to factor out more than once for each line !.
00403     
00404     //if (!(Sel.AddTextStoryCompoundsForDel(&CompoundSet)))
00405     //{
00406     //  goto EndOperation;
00407     //} 
00408 
00409     // We now must localise the attributes on every item marked for deletion
00410     if (!DoLocaliseForAttrChange(&Sel, (AttrTypeSet*)NULL, TRUE ))
00411     {
00412         goto EndOperation;
00413     }
00414 
00415 
00416     // Copy the selection to the internal clipboard
00417     if (!DoCopyNodesToClipboard(Sel))
00418         goto EndOperation; 
00419 
00420     // Go through one pass, asking nodes to hide themselves
00421     if (!DoHideComplexRange(Sel))
00422         goto EndOperation;
00423     {
00424         SelRange* pRange = GetApplication()->FindSelection();
00425         if (pRange)
00426         {
00427             // freshen the selection range by counting the currently selected objects
00428             pRange->Update(FALSE,NULL);
00429 //          INT32 num = pRange->Count();
00430             pRange->Count();    // side-effect?
00431         
00432             // Obtain the current selection (should be up-to-date now!)
00433             Range NewSel(*pRange);
00434 
00435             // Now go through a second pass snip the remainder out of the tree
00436             if (!DoHideNodes(NewSel,TRUE))   // Hide the nodes (IncludeSubtreeSize)
00437                 goto EndOperation;
00438         }
00439     }
00440 
00441     // Finally try to factor out the attributes on all compounds. Note that if a particular compound
00442     // is hidden then it will  be it's parent that has its attributes factored out !
00443     if (!DoFactorOutAfterAttrChange(&CompoundSet,(AttrTypeSet*)NULL))
00444     {
00445         goto EndOperation; 
00446     }
00447     
00448     // We need to inform the DocComponents of the clipboard that the copy is complete
00449 
00450 EndOperation:
00451 
00452     CompoundSet.DeleteAll();    // Delete all items in the CompoundSet
00453         
00454     // Update all the changed nodes, i.e. tell all the parents of the children that have been effected
00455     ObjChange.Define(OBJCHANGE_FINISHED,cFlags,NULL,this);
00456     if (!UpdateChangedNodes(&ObjChange))
00457         FailAndExecute();
00458 
00459     // Invalidate sel range as it may have been cached in the middle of the op while the tree is
00460     // in an invalid state (i.e. some selected nodes may have had the PERMISSION_DENIED state set during caching).
00461     {
00462         SelRange* pRange = GetApplication()->FindSelection();
00463         if (pRange)
00464             pRange->Update(FALSE,NULL);
00465     }
00466 
00467     // Copy the doc's file-path etc, so that the clipboard doc knows if it is possible to
00468     // OLE link to the doc.
00469     Document* pSourceDoc = Document::GetSelected();
00470     if (pSourceDoc && !pSourceDoc->GetOilDoc()->GetKernelPathName().IsEmpty())
00471     {
00472         // Don't add to the MRU list!
00473         String_256 str = pSourceDoc->GetOilDoc()->GetKernelPathName();
00474         InternalClipboard::Instance()->GetOilDoc()->SetPathName(str, FALSE);
00475         TRACEUSER( "JustinF", _T("Copied file-path %s to clipboard document\n"),
00476                     (LPCTSTR) InternalClipboard::Instance()->GetOilDoc()->GetKernelPathName());
00477     }
00478     else
00479     {
00480         // Set the path-name to be empty (what a delicious potential bug!)
00481         InternalClipboard::Instance()->GetOilDoc()->SetPathNameEmpty();
00482         TRACEUSER( "JustinF", _T("Setting clipboard file-path to nothing\n"));
00483     }
00484 
00485     // And finally, tell the clipboard we've finished copying to it
00486     // (This is the point where it is made available to other applications, etc)
00487     if (!Clipboard->CopyCompleted())
00488         FailAndExecute();
00489 
00490     EndSlowJob();
00491     End();
00492 }           
00493 
00494 
00495 // ------------------------------------------------------------------------------------------
00496 // OpCopy methods
00497             
00498 /********************************************************************************************
00499 
00500 >   OpCopy::OpCopy() 
00501 
00502     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
00503     Created:    29/9/93
00504     Inputs:     -
00505     Outputs:    -
00506     Returns:    -
00507     Purpose:    OpCopy constructor
00508     Errors:     -
00509     SeeAlso:    -
00510 
00511 ********************************************************************************************/
00512             
00513             
00514 OpCopy::OpCopy(): SelOperation()                                
00515 {                              
00516 }
00517 
00518  /********************************************************************************************
00519 
00520 >   BOOL OpCopy::Init()
00521 
00522     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
00523     Created:    28/9/93
00524     Inputs:     -
00525     Outputs:    -
00526     Returns:    TRUE if the operation could be successfully initialised 
00527                 FALSE if no more memory could be allocated 
00528                 
00529     Purpose:    OpCopy initialiser method
00530     Errors:     ERROR will be called if there was insufficient memory to allocate the 
00531                 operation.
00532     SeeAlso:    -
00533 
00534 ********************************************************************************************/
00535 
00536 BOOL OpCopy::Init()
00537 {
00538     return (RegisterOpDescriptor(0,
00539                             _R(IDS_COPYOP),
00540                             CC_RUNTIME_CLASS(OpCopy),
00541                             OPTOKEN_COPY,
00542                             OpCopy::GetState,
00543                             0,                          // help ID
00544                             _R(IDBBL_COPY),
00545                             0,                          // bitmap ID
00546                             0,
00547                             SYSTEMBAR_ILLEGAL,          // For now !
00548                             TRUE,                       // Receive messages
00549                             FALSE,
00550                             FALSE,
00551                             0,
00552                             (DONT_GREY_WHEN_SELECT_INSIDE | GREY_WHEN_NO_CURRENT_DOC | GREY_WHEN_NO_SELECTION)
00553                             ));
00554 }               
00555     
00556 /********************************************************************************************
00557 
00558 >   OpState OpCopy::GetState(String_256*, OpDescriptor*)
00559 
00560     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
00561     Created:    28/9/93
00562     Inputs:     -
00563     Outputs:    -
00564     Returns:    The state of the OpCopy
00565     Purpose:    For finding OpCopy's state. 
00566     Errors:     -
00567     SeeAlso:    -
00568 
00569 ********************************************************************************************/
00570 
00571 OpState OpCopy::GetState(String_256* UIDescription, OpDescriptor*)
00572 {
00573     OpState OpSt;
00574 
00575     // BODGE Temporarily disable copying TextStory sub-selections
00576     Range Sel(*(GetApplication()->FindSelection()));
00577     /*Node* Current =*/ Sel.FindFirst();
00578 
00579     // Set up the ObjChangeParam so we can ask the selected nodes if they minds being copied
00580     ObjChangeFlags cFlags;
00581     cFlags.CopyNode=TRUE;
00582     ObjChangeParam ObjChange(OBJCHANGE_STARTING,cFlags,NULL,NULL);
00583 
00584     // Will one or more selected nodes allow this op?
00585     if (!Sel.AllowOp(&ObjChange,FALSE))
00586     {
00587         // None of the nodes can be deleted
00588         OpSt.Greyed = TRUE;
00589 
00590         // Load reason why operation is disabled
00591         UINT32 IDS = ObjChange.GetReasonForDenial();
00592         if (IDS == 0) IDS = _R(IDS_NO_OBJECTS_SELECTED);    // if 0 (i.e. not been set), then assume there's no selection
00593         *UIDescription = String_256(IDS);               // Resolve the string ID
00594     }
00595     else
00596         SetCutOpText(UIDescription, _R(IDS_CLIPBOARD_PRECOPY));
00597 
00598     return(OpSt);   
00599 }
00600 
00601 /********************************************************************************************
00602 
00603 >   void OpCopy::Do(OpDescriptor*)
00604 
00605     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
00606     Created:    16/8/93
00607     Inputs:     OpDescriptor (unused)
00608     Outputs:    -
00609     Returns:    -
00610     Purpose:    Performs the Copy operation. 
00611                 
00612     Errors:     -
00613     SeeAlso:    -
00614 
00615 ********************************************************************************************/
00616     
00617 void OpCopy::Do(OpDescriptor*)
00618 {   
00619     BeginSlowJob(-1, FALSE);        // Make sure we have a simple hourglass, without a delay
00620 
00621     // Check with the clipboard if it is OK to go ahead
00622     InternalClipboard *Clipboard = InternalClipboard::Instance();
00623     if (Clipboard == NULL || !Clipboard->PrepareForCopy())
00624     {
00625         FailAndExecute();
00626 
00627         EndSlowJob();
00628         End();
00629         return;
00630     }
00631 
00632     // Obtain the current selections 
00633     Range Sel(*(GetApplication()->FindSelection()));
00634 
00635     // DMc change to range control
00636     
00637     RangeControl rg = Sel.GetRangeControlFlags();
00638     rg.PromoteToParent = TRUE;
00639     Sel.Range::SetRangeControl(rg);
00640     
00641     // Prepare an ObjChangeParam so we can mark which nodes will allow this op to happen to them
00642     ObjChangeFlags cFlags;
00643     cFlags.CopyNode=TRUE;
00644     ObjChangeParam ObjChange(OBJCHANGE_STARTING,cFlags,NULL,this);
00645 
00646     // Find the first node which is selected 
00647     Node* FirstSelectedNode = Sel.FindFirst(); 
00648     
00649     ENSURE(FirstSelectedNode != NULL, "Called copy operation with no nodes selected"); 
00650     
00651     // In the retail build it is best to do nothing if we find there are no selected nodes 
00652     if (FirstSelectedNode != NULL) // No nodes selected so End
00653     {   
00654         if (!DoStartSelOp(TRUE,TRUE))  // Try to record the selection state
00655         {
00656             goto EndOperation;  
00657         }
00658 
00659         // Mark nodes that will allow this to happen, and error if no nodes will let it happen
00660         if (!Sel.AllowOp(&ObjChange))
00661         {
00662             ERROR3("AllowOp() returned FALSE, i.e. op should have been greyed out");
00663             goto EndOperation;
00664         }
00665         
00666         // Copy the selection to the internal clipboard
00667         if (!DoCopyNodesToClipboard(Sel))
00668         {
00669             goto EndOperation; 
00670         }
00671     }
00672 
00673 
00674 EndOperation:
00675     // Update all the changed nodes, i.e. tell all the parents of the children that have been effected
00676     ObjChange.Define(OBJCHANGE_FINISHED,cFlags,NULL,this);
00677     if (!UpdateChangedNodes(&ObjChange))
00678         FailAndExecute();
00679 
00680     // Copy the doc's file-path etc, so that the clipboard doc knows if it is possible to
00681     // OLE link to the doc.
00682     Document* pSourceDoc = Document::GetSelected();
00683     if (pSourceDoc && !pSourceDoc->GetOilDoc()->GetKernelPathName().IsEmpty())
00684     {
00685         // Don't add to the MRU list!
00686         String_256 str = pSourceDoc->GetOilDoc()->GetKernelPathName();
00687         InternalClipboard::Instance()->GetOilDoc()->SetPathName(str, FALSE);
00688         TRACEUSER( "JustinF", _T("Copied file-path %s to clipboard document\n"),
00689                     (LPCTSTR) InternalClipboard::Instance()->GetOilDoc()->GetKernelPathName());
00690     }
00691     else
00692     {
00693         // Set the path-name to be empty (what a delicious potential bug!)
00694         InternalClipboard::Instance()->GetOilDoc()->SetPathNameEmpty();
00695         TRACEUSER( "JustinF", _T("Setting clipboard file-path to nothing\n"));
00696     }
00697 
00698     // And finally, tell the clipboard we've finished copying to it
00699     // (This is the point where it is made available to other applications, etc)
00700     if (!Clipboard->CopyCompleted())
00701         FailAndExecute();
00702 
00703     EndSlowJob();
00704     End();
00705 }           
00706 
00707 
00708 // ------------------------------------------------------------------------------------------
00709 // OpPaste methods
00710             
00711 /********************************************************************************************
00712 
00713 >   OpPaste::OpPaste() 
00714 
00715     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
00716     Created:    29/9/93
00717     Inputs:     -
00718     Outputs:    -
00719     Returns:    -
00720     Purpose:    OpPaste constructor
00721     Errors:     -
00722     SeeAlso:    -
00723 
00724 ********************************************************************************************/
00725             
00726             
00727 OpPaste::OpPaste(): SelOperation()                              
00728 {                              
00729 }
00730 
00731  /********************************************************************************************
00732 
00733 >   BOOL OpPaste::Init()
00734 
00735     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
00736     Created:    28/9/93
00737     Inputs:     -
00738     Outputs:    -
00739     Returns:    TRUE if the operation could be successfully initialised 
00740                 FALSE if no more memory could be allocated 
00741                 
00742     Purpose:    OpPaste initialiser method
00743     Errors:     ERROR will be called if there was insufficient memory to allocate the 
00744                 operation.
00745     SeeAlso:    -
00746 
00747 ********************************************************************************************/
00748 
00749 BOOL OpPaste::Init()
00750 {
00751     BOOL ok =  (RegisterOpDescriptor(0,
00752                             _R(IDS_PASTEOP),
00753                             CC_RUNTIME_CLASS(OpPaste),
00754                             OPTOKEN_PASTE,
00755                             OpPaste::GetState,
00756                             0,                          // help ID
00757                             _R(IDBBL_PASTE),
00758                             0,                          // bitmap ID 
00759                             0,
00760                             SYSTEMBAR_ILLEGAL,          // For now !
00761                             TRUE,                       // Receive messages
00762                             FALSE,
00763                             FALSE,
00764                             0,
00765                             (GREY_WHEN_NO_CURRENT_DOC | DONT_GREY_WHEN_SELECT_INSIDE)
00766                             ));
00767     if (ok)
00768     {
00769         // Register a second OpDescriptor, for paste at same position. This OpDescriptor
00770         // will probably never appear directly on a bar/menu so it uses the same strings
00771         // as the above.
00772         ok =  (RegisterOpDescriptor(0,
00773                             _R(IDS_PASTEATSAMEPOSOP),
00774                             CC_RUNTIME_CLASS(OpPaste),
00775                             OPTOKEN_PASTEATSAMEPOS,
00776                             OpPaste::GetState,
00777                             0,                          // help ID
00778                             _R(IDBBL_PASTEATSAMEPOS),
00779                             _R(IDD_BARCONTROLSTORE),        // resource ID
00780                             _R(IDC_PASTEATSAMEPOS),         // control ID
00781                             SYSTEMBAR_EDIT,             // Bar ID
00782                             TRUE,                       // Receive messages
00783                             FALSE,
00784                             FALSE,
00785                             0,
00786                             (GREY_WHEN_NO_CURRENT_DOC | DONT_GREY_WHEN_SELECT_INSIDE)
00787                             ));
00788     }
00789 
00790     return ok;
00791 }               
00792     
00793 /********************************************************************************************
00794 
00795 >   OpState OpPaste::GetState(String_256*, OpDescriptor*)
00796 
00797     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
00798     Created:    28/9/93
00799     Inputs:     -
00800     Outputs:    -
00801     Returns:    The state of the OpPaste
00802     Purpose:    For finding OpPaste's state. 
00803     Errors:     -
00804     SeeAlso:    -
00805 
00806 ********************************************************************************************/
00807 
00808 OpState OpPaste::GetState(String_256* UIDescription, OpDescriptor* pOpDesc)
00809 {
00810     OpState OpSt;
00811 
00812     if (InternalClipboard::IsEmpty())
00813     {
00814         // There is no data to paste, so grey out, and return a reason for greying
00815         OpSt.Greyed = TRUE;
00816         *UIDescription = String_256(_R(IDS_CLIPBOARD_EMPTY));
00817     }
00818     else
00819     {
00820         // Otherwise, determine a string to display - always show "Paste", but if possible
00821         // add a description, e.g. "Paste quickshape", "Paste bitmap"
00822 
00823         String_64 Type;
00824         InternalClipboard::DescribeContents(&Type);
00825         if (!Type.IsEmpty() && pOpDesc->Token==String_256(OPTOKEN_PASTE))
00826         {
00827             *UIDescription = String_256(_R(IDS_CLIPBOARD_PREPASTE));
00828             *UIDescription += Type;
00829         }
00830     }
00831     return(OpSt);
00832 }
00833 
00834 
00835 
00836 /********************************************************************************************
00837 
00838 >   void OpPaste::Do(OpDescriptor*)
00839 
00840     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
00841     Created:    16/8/93
00842     Inputs:     OpDescriptor (unused)
00843     Outputs:    -
00844     Returns:    -
00845     Purpose:    Performs the Paste operation. 
00846                 
00847     Errors:     -
00848     SeeAlso:    -
00849 
00850 ********************************************************************************************/
00851     
00852 void OpPaste::Do(OpDescriptor* WhichOp)
00853 {
00854     DoPaste(WhichOp,NULL,NULL);
00855 }           
00856 
00857 /********************************************************************************************
00858 
00859 >   void OpPaste::DoWithParam(OpDescriptor* WhichOp, OpParam* pOpParam)
00860 
00861     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
00862     Created:    10/10/96
00863     Inputs:     WhichOp = ptr to op descriptor
00864                 pOpParam = ptr to the params (must be an OpParamPasteAtPosition object)
00865     Outputs:    -
00866     Returns:    -
00867     Purpose:    Performs the Paste operation using the give param object.  This mechanism
00868                 is used by the "paste at position" op, utilised by the OLE drag & drop system.
00869 
00870                 pOpParam should be a ptr to an OpParamPasteAtPosition, so that the spread 
00871                 and centre coord of the paste can be passed in.
00872 
00873                 If pOpParam is not an OpParamPasteAtPosition, the op fails
00874                 
00875     Errors:     -
00876     SeeAlso:    -
00877 
00878 ********************************************************************************************/
00879     
00880 void OpPaste::DoWithParam(OpDescriptor* WhichOp, OpParam* pOpParam)
00881 {
00882     if (pOpParam != NULL && IS_A(pOpParam,OpParamPasteAtPosition))
00883     {
00884         OpParamPasteAtPosition* pPasteParam = (OpParamPasteAtPosition*)pOpParam;
00885 
00886         Spread* pSpread = pPasteParam->GetSpread();
00887         DocCoord Centre = pPasteParam->GetCentre();
00888 
00889         DoPaste(WhichOp,pSpread,&Centre);
00890     }
00891     else
00892     {
00893         FailAndExecute();
00894         End();
00895     }
00896 }
00897 
00898 /********************************************************************************************
00899 
00900 >   void OpPaste::DoPaste(OpDescriptor* WhichOp,Spread* pSpread, DocCoord* pCentre)
00901 
00902     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
00903     Created:    10/10/96
00904     Inputs:     WhichOp = ptr to op descriptor
00905                 pSpread = ptr to spread to paste objects into (can be NULL)
00906                 pCentre = coord of centre point of paste destination (can be NULL if pSpread is NULL)
00907     Outputs:    -
00908     Returns:    -
00909     Purpose:    Helper function for Do() & DoWithParam(), which in turn interfaces with
00910                 DoPasteStandard() (don't look at me - I didn't design this stuff)
00911                 
00912     Errors:     -
00913     SeeAlso:    -
00914 
00915 ********************************************************************************************/
00916     
00917 void OpPaste::DoPaste(OpDescriptor* WhichOp,Spread* pSpread, DocCoord* pCentre)
00918 {
00919     BeginSlowJob(-1, FALSE);        // Make sure we have a simple hourglass, without a delay
00920 
00921     // Check with the clipboard if it is OK to go ahead. If the clipboard contents are
00922     // currently external, this means it'll have to import them into the InternalClipboard
00923     // before we can actually copy them into our document.
00924     InternalClipboard *Clipboard = InternalClipboard::Instance();
00925     BOOL ExternalObjects;
00926     BOOL ok = (Clipboard!=NULL && Clipboard->PrepareForPaste(&ExternalObjects));
00927 
00928     if (ok)
00929     {
00930         // We paste at the same position if the operation was invoked via the PASTEATSAMEPOS OpDescriptor
00931         // and the stuff we are pasting has not come from an external source.
00932         BOOL PasteAtSamePos = (((WhichOp->Token) == String(OPTOKEN_PASTEATSAMEPOS)) && (!ExternalObjects));
00933         ok = DoPasteStandard(PasteAtSamePos, ExternalObjects, pSpread, pCentre);
00934     }
00935 
00936     if (ok)
00937         ok = Clipboard->PasteCompleted();
00938 
00939     if (!ok)
00940         FailAndExecute();
00941 
00942     EndSlowJob();
00943     End();
00944 }           
00945 
00946 /********************************************************************************************
00947 
00948 >   BOOL OpPaste::FindCentreInsertionPosition(Spread** Spread, DocCoord* Position)
00949 
00950     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
00951     Created:    28/7/94
00952     Inputs:     -
00953     Outputs:    Spread:  The spread to place the clipboard objects on
00954                 Position:The centre of the view (Spread coords)
00955 
00956     Returns:    FALSE if it failed. In this case, Spread is returned containing a NULL, and
00957                 Position is returned as DocCoord(0,0)
00958 
00959     Purpose:    Finds the centre insertion position for clipboard objects
00960     Errors:     -
00961     Scope:      private
00962     SeeAlso:    -
00963 
00964 ********************************************************************************************/
00965 
00966 BOOL OpPaste::FindCentreInsertionPosition(Spread** Spread, DocCoord* Position)
00967 {
00968     // Let's chuck in a smattering of defensive programming
00969     ERROR3IF(Spread == NULL || Position == NULL, "Illegal NULL params");
00970     *Spread = NULL;
00971     *Position = DocCoord(0,0);  // A default value if we fail
00972 
00973     // ---------------------------------------------------------------------------------
00974     // Find out which spread is in the centre of the view 
00975     // this is the spread that the pasted objects will be placed on
00976 
00977     // Obtain the current DocView
00978     DocView* CurDocView = DocView::GetCurrent();
00979 
00980     ENSURE(CurDocView != NULL, "The current DocView is NULL"); 
00981     if (CurDocView == NULL)
00982     {
00983         return FALSE; // No DocView
00984     }
00985 
00986     // Get the view rect
00987     WorkRect WrkViewRect = CurDocView->GetViewRect();
00988 
00989     if (WrkViewRect.IsEmpty() || (!WrkViewRect.IsValid()) )
00990     {
00991         return FALSE; // Defensive
00992     }
00993     
00994     // Determine the centre of the view
00995     WorkCoord WrkCentreOfView; 
00996     WrkCentreOfView.x = WrkViewRect.lo.x    + (WrkViewRect.Width()/2); 
00997     WrkCentreOfView.y = WrkViewRect.lo.y    + (WrkViewRect.Height()/2);
00998     
00999     // FindEnclosing spread requires an OilCoord
01000     OilCoord OilCentreOfView = WrkCentreOfView.ToOil(CurDocView->GetScrollOffsets()); 
01001 
01002     // Find out which spread to insert the pasteboard objects onto
01003     (*Spread) = CurDocView->FindEnclosingSpread(OilCentreOfView);
01004     if ((*Spread) == NULL)
01005     {
01006         // There is no spread
01007         return FALSE; 
01008     }
01009 
01010     // Phew
01011     // ---------------------------------------------------------------------------------
01012     // Now lets find the spread coordinate of the centre of the view
01013     DocRect DocViewRect = CurDocView->GetDocViewRect(*Spread);
01014     
01015     if ( DocViewRect.IsEmpty() || (!DocViewRect.IsValid()) )
01016     {
01017         ERROR3("DocViewRect is invalid");
01018         return FALSE; // Defensive
01019     }
01020 
01021     // Find the centre of the DocViewRect
01022     DocCoord DocCentreOfView; 
01023     DocCentreOfView.x = DocViewRect.lo.x    + (DocViewRect.Width()/2); 
01024     DocCentreOfView.y = DocViewRect.lo.y    + (DocViewRect.Height()/2);
01025 
01026     // --------------------------------------------------------------------------------
01027     // Now convert from DocCoords to spread coords
01028     (*Spread)->DocCoordToSpreadCoord(&DocCentreOfView);
01029 
01030     // Finally, copy the result into the output parameter
01031     *Position = DocCentreOfView;
01032     
01033     return TRUE;  
01034 }
01035 
01036 
01037 /********************************************************************************************
01038 
01039 >   BOOL OpPaste::DoPasteStandard(BOOL PasteAtSamePos,  BOOL ExternalData, Spread* pSpread = NULL, DocCoord* pCentre = NULL)
01040 
01041     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com> / Mike
01042     Created:    28/7/94
01043     Inputs:     PasteAtSamePos: TRUE if we should paste the objects at their existing positions.
01044                                 FALSE if they should be pasted in the centre of the view
01045 
01046                 ExternalData:   This flag indicates that the data has come from the external
01047                                 clipboard. We transform all objects in the clipboard to the
01048                                 centre of the view whenever this flag is set. This is so that
01049                                 subsequent PasteAtSamePos operations position the objects somewhere
01050                                 sensible. 
01051 
01052                 pSpread:        ptr to insertion spread.  If NULL, spread in the centre
01053                                 of the current view is used AND pCentre is ignored.
01054                                 
01055                 pCentre:        ptr to coord defining the centre of the paste.
01056                                 if PasteAtSamePos is TRUE, this coord is not used (I think! - Markn)
01057                                 
01058     Outputs:    -
01059     Returns:    -
01060     Purpose:    Pastes the contents of the clipboard into the current document.
01061     Errors:     -
01062     Scope:      protected
01063     SeeAlso:    -
01064 
01065     Changes:    1/6/95  - Fixed the error recovery mechanisms so they don't leave the undo
01066                           system in a state.
01067                           Made this function more accessible to derived classes.
01068 
01069 ********************************************************************************************/
01070 
01071 BOOL OpPaste::DoPasteStandard(BOOL PasteAtSamePos, BOOL ExternalData, Spread* pSpread, DocCoord* pCentre)
01072 {
01073     // Assume failure until told otherwise
01074     BOOL HasFailed=TRUE;
01075 
01076     Spread*  InsertionSpread = pSpread;
01077     DocCoord PastePosition;
01078 
01079     if (InsertionSpread == NULL)
01080     {
01081         if (!FindCentreInsertionPosition(&InsertionSpread, &PastePosition))
01082             return FALSE;
01083     }
01084     else
01085     {
01086         ERROR3IF(pCentre == NULL,"You've supplied a spread, but no centre coord.  Are you mad?");
01087 
01088         if (pCentre != NULL)
01089             PastePosition = *pCentre;
01090     }
01091 
01092     ERROR3IF(InsertionSpread == NULL,"Spread is NULL");
01093 
01094     // -----------------------------------
01095     // This is an horrendous bodge!
01096     // What's the point passing in a centre of the routine insists on calculating it for itself?
01097     // Ho hum...
01098     // If the data has come from the outside world it's original position is unknown
01099     // So now that the user has committed themselves to pasting at a certain position
01100     // record that as being the data's "original position".
01101     if (ExternalData)
01102     {
01103         InternalClipboard::SetOriginalCentrePoint(PastePosition);
01104     }
01105 
01106     // If trying to paste data at its original position
01107     // Then retrieve that info from the clipboard and use it as the paste position
01108     if (PasteAtSamePos)
01109     {
01110         PastePosition = InternalClipboard::GetOriginalCentrePoint();
01111     }
01112     // -----------------------------------
01113 
01114     if (InsertionSpread != NULL)
01115     {
01116         Node* Current;
01117 
01118         InternalClipboard* IntClip = InternalClipboard::Instance();
01119         if (IntClip==NULL)
01120         {
01121             ERROR3("Could not find internal clipboard");
01122             goto EndOperation;
01123         }
01124 
01125         BOOL FirstObjectCopied = TRUE; 
01126         DocRect ObjectsBounds;
01127         DocCoord CentreOfObjectsBounds; 
01128 
01129         // Create the transform to be applied to all pasted objects
01130         ObjectsBounds = IntClip->GetObjectsBounds(); 
01131 
01132         CentreOfObjectsBounds.x = ObjectsBounds.lo.x    + (ObjectsBounds.Width()/2); 
01133         CentreOfObjectsBounds.y = ObjectsBounds.lo.y    + (ObjectsBounds.Height()/2);
01134 
01135         Trans2DMatrix ObjectTrans  (PastePosition.x - CentreOfObjectsBounds.x,
01136                                     PastePosition.y - CentreOfObjectsBounds.y); 
01137 
01138         // Set the selected spread (assume it is in the Selected Doc and DocView - an
01139         // ENSURE will occur if this turns out not to be the case!)
01140         // NOTE selected spread should change due to a change in the sel range!
01141         Document::SetSelectedViewAndSpread(NULL, NULL, InsertionSpread);
01142 
01143         // Get the current tool
01144         Tool* pTool = Tool::GetCurrent();
01145         Spread* pSelSpread = Document::GetSelectedSpread();
01146 
01147         // Get the tool to remove all its blobs before we deselect the nodes.
01148         // Only do this if the current tool dosent update itself on sel changed messages
01149         if (pSelSpread!=NULL && pTool!=NULL && !pTool->AreToolBlobsRenderedOnSelection())
01150             pTool->RenderToolBlobs(pSelSpread,NULL);
01151 
01152         if (!DoStartSelOp(FALSE, TRUE))  // Try to record the selection state
01153             goto EndOperation;  
01154 
01155         // After the operation ends we will need to inform all DocComponents in the 
01156         // destination document of the outcome.
01157         InformDocComponentsOfOperationsOutcome(GetWorkingDoc()); 
01158 
01159         // Inform all DocComponents in the destination doc that a copy is about to take place
01160         BOOL ok;
01161         CALL_WITH_FAIL((GetWorkingDoc()->StartComponentCopy()),this, ok)
01162         
01163         if (!ok)
01164         {
01165             // Start Component copy has failed so abort operation
01166             // Note that AbortComponentCopy will have been called
01167             goto EndOperation;
01168         } 
01169 
01170 
01171         Current = IntClip->GetInsertionLayer()->FindFirstChild();
01172 
01173         // Create a range of all clipboard objects which are about to be copied
01174         // Selected + Unselected + don't cross layers
01175 
01176         // DMc - also need to set the PromoteToParent flag
01177         Range ClipRange(Current,NULL,RangeControl(TRUE,TRUE,FALSE, FALSE, FALSE, FALSE, FALSE, FALSE));
01178               
01179         Node* FirstCopiedObj = NULL;
01180         Node* Copy; 
01181 
01182         // Now we try and copy all clipboard nodes
01183         while (Current != NULL)
01184         {
01185             // Make a copy of Current
01186             CALL_WITH_FAIL(Current->NodeCopy(&Copy), this, ok);
01187 
01188             if (!ok)
01189                 goto EndOperation; 
01190 
01191             // Sanity check, will vapourise in retails
01192             ENSURE(Copy->IsKindOf(CC_RUNTIME_CLASS(NodeRenderableBounded)), 
01193                 "Object being pasted is not a NodeRenderableBounded");
01194             
01195             // Remember the first copy we make
01196             if (FirstCopiedObj == NULL) 
01197                 FirstCopiedObj = Copy; 
01198 
01199             NodeRenderableBounded* pBoundCopy = (NodeRenderableBounded*)Copy;
01200          
01201             // Insert object
01202             if (!DoInsertNewNode(pBoundCopy,
01203                                  (Spread*)NULL, // Selected spread
01204                                  FALSE,         // Dont Invalidate region
01205                                  FirstObjectCopied  // Only clear the selection if this is the
01206                                                     // first object being copied
01207                                  ))
01208             {
01209                 // Tidyup
01210                 Copy->CascadeDelete(); 
01211                 delete Copy;
01212                 goto EndOperation; 
01213             }
01214 
01215             // Now translate the object into the correct position.
01216             // Note: This has to be done even when PasteInPlace is operational because
01217             // the clipboard may have moved the objects ready for OLE external use.
01218             pBoundCopy->Transform(ObjectTrans);
01219 
01220             // Now call PostDuplicate on the copied node aand all it's children
01221             BOOL ok = TRUE;
01222             Node* pCurrent = pBoundCopy->FindFirstDepthFirst();
01223             while (pCurrent!=NULL && ok)
01224             {
01225                 ok = pCurrent->PostDuplicate(this);
01226 
01227                 pCurrent = pCurrent->FindNextDepthFirst(pBoundCopy);
01228             }
01229         
01230             // We also need to invalidate the region of the node now that it has been transformed
01231             if (!ok || !DoInvalidateNodeRegion(pBoundCopy, TRUE, FALSE))
01232             {
01233                 // Tidyup
01234                 Copy->CascadeDelete(); 
01235                 delete Copy; 
01236                 goto EndOperation; 
01237             } 
01238 
01239             // Normalise the attributes
01240             ((NodeRenderableInk*) pBoundCopy)->NormaliseAttributes();
01241 
01242             // remove any name attr