// $Id: undoop.cpp 1282 2006-06-09 09:46:49Z alex $ /* @@tag:xara-cn@@ DO NOT MODIFY THIS LINE ================================XARAHEADERSTART=========================== Xara LX, a vector drawing and manipulation program. Copyright (C) 1993-2006 Xara Group Ltd. Copyright on certain contributions may be held in joint with their respective authors. See AUTHORS file for details. LICENSE TO USE AND MODIFY SOFTWARE ---------------------------------- This file is part of Xara LX. Xara LX is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. Xara LX and its component source files are distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Xara LX (see the file GPL in the root directory of the distribution); if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ADDITIONAL RIGHTS ----------------- Conditional upon your continuing compliance with the GNU General Public License described above, Xara Group Ltd grants to you certain additional rights. The additional rights are to use, modify, and distribute the software together with the wxWidgets library, the wxXtra library, and the "CDraw" library and any other such library that any version of Xara LX relased by Xara Group Ltd requires in order to compile and execute, including the static linking of that library to XaraLX. In the case of the "CDraw" library, you may satisfy obligation under the GNU General Public License to provide source code by providing a binary copy of the library concerned and a copy of the license accompanying it. Nothing in this section restricts any of the rights you have under the GNU General Public License. SCOPE OF LICENSE ---------------- This license applies to this program (XaraLX) and its constituent source files only, and does not necessarily apply to other Xara products which may in part share the same code base, and are subject to their own licensing terms. This license does not apply to files in the wxXtra directory, which are built into a separate library, and are subject to the wxWindows license contained within that directory in the file "WXXTRA-LICENSE". This license does not apply to the binary libraries (if any) within the "libs" directory, which are subject to a separate license contained within that directory in the file "LIBS-LICENSE". ARRANGEMENTS FOR CONTRIBUTION OF MODIFICATIONS ---------------------------------------------- Subject to the terms of the GNU Public License (see above), you are free to do whatever you like with your modifications. However, you may (at your option) wish contribute them to Xara's source tree. You can find details of how to do this at: http://www.xaraxtreme.org/developers/ Prior to contributing your modifications, you will need to complete our contributor agreement. This can be found at: http://www.xaraxtreme.org/developers/contribute/ Please note that Xara will not accept modifications which modify any of the text between the start and end of this header (marked XARAHEADERSTART and XARAHEADEREND). MARKS ----- Xara, Xara LX, Xara X, Xara X/Xtreme, Xara Xtreme, the Xtreme and Xara designs are registered or unregistered trademarks, design-marks, and/or service marks of Xara Group Ltd. All rights in these marks are reserved. Xara Group Ltd, Gaddesden Place, Hemel Hempstead, HP2 6EX, UK. http://www.xara.com/ =================================XARAHEADEREND============================ */ #include "camtypes.h" //#include "undoop.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "ophist.h" //#include "range.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "document.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "ensure.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "selstate.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "tranform.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "docview.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "mario.h" //#include "ink.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "spread.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "fixmem.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "nodepath.h" //#include "app.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "cpyact.h" #include "clipint.h" #include "pathedit.h" #include "ndoptmz.h" #include "sprdmsg.h" #include "layer.h" //#include "markn.h" #include "progress.h" #include "invalid.h" //#include "becomea.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "nodeattr.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "objchge.h" //#include "group.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "nodeblnd.h" #include "nodetxtl.h" #include "nodetxts.h" #include "ndbldpth.h" #include "textops.h" #include "cutop.h" #include "camdoc.h" #include "effects_stack.h" // for EffectsStack #include "attrmap.h" #ifdef _DEBUG #include "layermsg.h" // layer messages for redraw layer message in debug builds #endif #include "ngcore.h" // for NameGallery stretching implementation. #include "chapter.h" #include "opdupbar.h" CC_IMPLEMENT_DYNCREATE( UndoableOperation, Operation ) // Declare smart memory handling in Debug builds #define new CAM_DEBUG_NEW // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ObjectSet methods /******************************************************************************************** > BOOL ObjectSet::AddToSet(NodeRenderableInk* pObject) Author: Simon_Maneggio (Xara Group Ltd) Created: 12/5/95 Returns: FALSE if we run out of memory Purpose: Adds pObject to the set SeeAlso: ObjectSet::InSet SeeAlso: ObjectSet::CopySet ********************************************************************************************/ BOOL ObjectSet::AddToSet(NodeRenderableInk* pObject) { ERROR3IF(pObject == NULL, "AddToSet: pObject is NULL"); // Determine if the pObject is already in the set ObjectItem* pObjItem = (ObjectItem*) GetHead(); while (pObjItem != NULL) { if (pObjItem->pObject == pObject) { return TRUE; // Already in set so return } pObjItem = (ObjectItem*)GetNext(pObjItem); } // The attribute type is not in the set so let's add it pObjItem = new ObjectItem; if (!pObjItem) return FALSE; // out of memory (error has been set) pObjItem->pObject = pObject; AddHead(pObjItem); // Add attribute to the head of the list return TRUE; } /******************************************************************************************** > BOOL ObjectSet::CopySet() Author: Simon_Maneggio (Xara Group Ltd) Created: 15/5/95 Inputs: - Outputs: - Returns: A copy of the set, or NULL if we run out of memory Purpose: Makes a copy of the object set Errors: - SeeAlso: ObjectSet::AddToSet SeeAlso: ObjectSet::InSet ********************************************************************************************/ ObjectSet* ObjectSet::CopySet() { ObjectSet* pSetCopy; pSetCopy = new ObjectSet; if (!pSetCopy) return NULL; // Out of memory // Copy each item in turn ObjectItem* pItemCopy; ObjectItem* pCurrent = (ObjectItem*)GetHead(); while (pCurrent) { pItemCopy = new ObjectItem; if (!pItemCopy) { // Tidyup pSetCopy->DeleteAll(); delete pSetCopy; return NULL; } pItemCopy->pObject = pCurrent->pObject; pSetCopy->AddTail(pItemCopy); pCurrent = (ObjectItem*)GetNext(pCurrent); } return pSetCopy; } /******************************************************************************************** > BOOL ObjectSet::InSet(NodeRenderableInk* pObject) Author: Simon_Maneggio (Xara Group Ltd) Created: 15/5/95 Inputs: pObject: A pointer to an object Outputs: - Returns: TRUE if pObject is in the set Purpose: To determine if pObject is in the set Errors: - SeeAlso: ObjectSet::CopySet SeeAlso: ObjectSet::AddToSet ********************************************************************************************/ BOOL ObjectSet::InSet(NodeRenderableInk* pObject) { ObjectItem* pCurrent = (ObjectItem*)GetHead(); while (pCurrent) { if (pCurrent->pObject == pObject) return TRUE; pCurrent = (ObjectItem*)GetNext(pCurrent); } return FALSE; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // UndoableOperation methods BOOL UndoableOperation::MovingNode = FALSE; /******************************************************************************************** > UndoableOperation::UndoableOperation() Author: Simon_Maneggio (Xara Group Ltd) Created: 9/3/94 Inputs: - Outputs: - Returns: - Purpose: Constructs a new undoable operation object: Errors: - SeeAlso: Operation::Operation ********************************************************************************************/ UndoableOperation::UndoableOperation(): Operation() { // This var is a flag that ensures that this warning will only appear once for the lifetime of this op WarnInsertObjsOntoLockedLayers = TRUE; } /******************************************************************************************** > virtual UndoableOperation::~UndoableOperation() Author: Simon_Maneggio (Xara Group Ltd) Created: 9/3/94 Inputs: - Outputs: - Returns: - Purpose: UndoableOperation destructor Errors: - SeeAlso: - ********************************************************************************************/ UndoableOperation::~UndoableOperation() { } // --------------------------------------------------------------------------------------- // The Do functions /******************************************************************************************** > BOOL UndoableOperation::DoInsertNewNode(NodeRenderableBounded* NewNode, Spread *pSpread, BOOL InvalidateRgn, BOOL ClearSelection = TRUE, BOOL SelectNewObject = TRUE, BOOL NormaliseAttributes=TRUE) Author: Simon_Maneggio (Xara Group Ltd) Created: 13/9/93 Inputs: NewNode: The new node to be added pSpread: The spread to add the new node to *Set this to NULL if you want to insert the node onto the selected spread. InvalidateRgn: Invalidate the node's region (including blobs) ClearSelection: Clear all selected objects prior to the new object being inserted (Default TRUE) SelectNewObject: Select the new object (Default TRUE) NormaliseAttributes:When TRUE redundant attributes are removed from the node. If you know that the node being inserted has the correct set of attributes then you can set this to FALSE. Note: Do not set the bounding rectangle of NewNode prior to calling this function, or the bounds will not be propagated. Returns: TRUE if successful FALSE if the operation should be aborted (TIDYUP THEN CALL End()!) Purpose: Inserts the node as a last child of the active layer of the pSpread. If the NewNode is a NodeRenderableInk then the node becomes selected and all other nodes are deselected. If pSpread is NULL then the object is inserted on the selected spread. SeeAlso: UndoableOperation::DoInsertNewNode ********************************************************************************************/ BOOL UndoableOperation::DoInsertNewNode(NodeRenderableBounded* NewNode, Spread *pSpread, BOOL InvalidateRgn, BOOL ClearSelection, // = TRUE BOOL SelectNewObject, // = TRUE BOOL NormaliseAttributes) // = TRUE { ENSURE( pOurDoc!=NULL, "No current document in DoInsertNode" ); // Insert node as the active layer of the spread pOurDoc->InsertNewNode(NewNode, pSpread); NewNode->SetSelected(FALSE); // This is a brand new node, so we have to make sure that all its parents // are invalidated as well. // if this nodes bounding rect was already invalid when it was added the parent // will not be invalidated as well, so we first mark its bounds as valid. // This is very important - Ask Rik if you think it looks silly. NewNode->ValidateBoundingRect(); NewNode->InvalidateBoundingRect(); // If inserting a node onto a layer that is locked or invisible, warn the user before // proceeding, and ensure that the node is not selected. if (NewNode->IsKindOf(CC_RUNTIME_CLASS(NodeRenderableInk))) { Layer* pLayer = (Layer*)NewNode->FindParent(CC_RUNTIME_CLASS(Layer)); if (pLayer != NULL) { if (!pLayer->IsVisible() || pLayer->IsLocked()) { if (WarnInsertObjsOntoLockedLayers) { #if (_OLE_VER >= 0x200) // Only warn the user if the document is visible to him/her. if (pOurDoc && pOurDoc->GetOilDoc() && pOurDoc->GetOilDoc()->IsVisible()) #endif { InformWarning(_R(IDS_WARNING_INSERTLAYER)); } // Set flag to FALSE so it won't be reported back again in this op WarnInsertObjsOntoLockedLayers = FALSE; } SelectNewObject = FALSE; } // Note that this layer has been edited pLayer->SetEdited(TRUE); #ifdef _DEBUG // Tell the frame gallery to update its display of the frame BROADCAST_TO_ALL( LayerMsg( pLayer, LayerMsg::REDRAW_LAYER ) ); #endif } } /* if (InvalidateRgn) { if (!DoInvalidateNodeRegion((NodeRenderable*)NewNode, TRUE)) { // Remove the NewNodeFrom the tree NewNode->UnlinkNodeFromTree(); return FALSE; } } */ // Create a hide node action to hide the node when we undo HideNodeAction* UndoHideNodeAction; if (HideNodeAction::Init(this, &UndoActions, NewNode, TRUE, // Include subtree size ( Action**)(&UndoHideNodeAction)) == AC_FAIL) { // Remove the NewNodeFrom the tree NewNode->UnlinkNodeFromTree(); return FALSE; } // Make it the currently selected object if it is an Ink object if ( NewNode->IsKindOf(CC_RUNTIME_CLASS(NodeRenderableInk)) ) { if (ClearSelection) { NodeRenderableInk::DeselectAll(); GetApplication()->UpdateSelection(); } if (SelectNewObject) { // Select the new node, and don't redraw it yet ((NodeRenderableInk*)NewNode)->Select(FALSE); } if (NormaliseAttributes) { ((NodeRenderableInk*)NewNode)->NormaliseAttributes(); // Removes redundant attributes } } // Update the selection if (NewNode->IsSelected()) GetApplication()->UpdateSelection(); if ( NewNode->IsKindOf(CC_RUNTIME_CLASS(NodeRenderableBounded)) ) { if (InvalidateRgn) { if (!DoInvalidateNodeRegion((NodeRenderableBounded*)NewNode, TRUE)) { return FALSE; } } } // Added by Jim: Broadcast the layerchanged message when inserting a new layer if (NewNode->GetRuntimeClass() == CC_RUNTIME_CLASS(Layer)) { Spread* pSpread = (Spread*) NewNode->FindParent(CC_RUNTIME_CLASS(Spread)); BROADCAST_TO_ALL(SpreadMsg(pSpread,SpreadMsg::LAYERCHANGES)); } return (TRUE); } /******************************************************************************************** > BOOL UndoableOperation::DoInsertNewNode(NodeRenderableBounded* NewNode, Node* ContextNode, AttachNodeDirection Direction, BOOL InvalidateRegion, BOOL ClearSelection = TRUE, BOOL SelectNewObject = TRUE, BOOL NormaliseAttributes=TRUE) Author: Simon_Maneggio (Xara Group Ltd) Created: 13/9/93 Inputs: NewNode: The new node to be added ContextNode: The node to which NewNode is to be attached Direction: The direction the new node should be attached to the context node. InvalidateRgn: Invalidate the node's region (including blobs) ClearSelection: Clear all selected objects prior to the new object being inserted (Default TRUE) SelectNewObject: Select the new object (Default TRUE) NormaliseAttributes:When TRUE redundant attributes are removed from the node. If you know that the node being inserted has the correct set of attributes then you can set this to FALSE. Note: Do not set the bounding rectangle of NewNode prior to calling this function, or the bounds will not be propagated. Returns: TRUE if successful FALSE if the operation should be aborted (TIDYUP THEN CALL End()!) Purpose: This high level do function attaches the node to ContextNode in the direction specified. If the NewNode is a NodeRenderableInk then the node becomes selected and all other nodes are deselected. SeeAlso: UndoableOperation::DoInsertNewNode ********************************************************************************************/ BOOL UndoableOperation::DoInsertNewNode(NodeRenderableBounded* NewNode, Node* ContextNode, AttachNodeDirection Direction, BOOL InvalidateRegion, BOOL ClearSelection, BOOL SelectNewObject, BOOL NormaliseAttributes) { // Insert the NewNode into the tree NewNode->AttachNode(ContextNode, Direction); NewNode->SetSelected(FALSE); // This is a brand new node, so we have to make sure that all its parents // are invalidated as well. // if this nodes bounding rect was already invalid when it was added the parent // will not be invalidated as well, so we first mark its bounds as valid. // This is very important - Ask Rik if you think it looks silly. NewNode->ValidateBoundingRect(); NewNode->InvalidateBoundingRect(); // If inserting a node onto a layer that is locked or invisible, warn the user before // proceeding, and ensure that the node is not selected. if (NewNode->IsKindOf(CC_RUNTIME_CLASS(NodeRenderableInk))) { Layer* pLayer = (Layer*)NewNode->FindParent(CC_RUNTIME_CLASS(Layer)); if (pLayer != NULL) { if (!pLayer->IsVisible() || pLayer->IsLocked()) { if (WarnInsertObjsOntoLockedLayers) { #if (_OLE_VER >= 0x200) // Only warn the user if the document is visible to him/her. if (pOurDoc && pOurDoc->GetOilDoc() && pOurDoc->GetOilDoc()->IsVisible()) #endif { // ignore this warning on some ops - like if we have text syncing between layers // or other bar ops that manipulate invisible layers // or doing the right click from drag op since the msg upsets the drag PORTNOTE("text","Removed OpTextFormat reference") #ifndef EXCLUDE_FROM_XARALX if (!IS_A(this, OpTextFormat) && !IS_A(this, OpDuplicateBar) && !IS_A(this, OpCopyAndTransform)) InformWarning(_R(IDS_WARNING_INSERTLAYER)); #endif } // Set flag to FALSE so it won't be reported back again in this op WarnInsertObjsOntoLockedLayers = FALSE; } SelectNewObject = FALSE; } // Note that this layer has been edited pLayer->SetEdited(TRUE); #ifdef _DEBUG // Tell the frame gallery to update its display of the frame BROADCAST_TO_ALL(LayerMsg(pLayer, LayerMsg::/*LayerReason::*/REDRAW_LAYER)); #endif } } // Create a hide node action to hide the node when we undo HideNodeAction* UndoHideNodeAction; if (HideNodeAction::Init(this, &UndoActions, NewNode, TRUE, // Include subtree size ( Action**)(&UndoHideNodeAction)) == AC_FAIL) { return FALSE; } // Make it the currently selected object if it is an Ink object if ( NewNode->IsKindOf(CC_RUNTIME_CLASS(NodeRenderableInk)) ) { if (ClearSelection) { NodeRenderableInk::DeselectAll(); GetApplication()->UpdateSelection(); } if (SelectNewObject) { // Select the new node ((NodeRenderableInk*)NewNode)->Select(FALSE); } if (NormaliseAttributes) { ((NodeRenderableInk*)NewNode)->NormaliseAttributes(); // Removes redundant attributes } } // Update the selection if (NewNode->IsSelected()) GetApplication()->UpdateSelection(); if ( NewNode->IsKindOf(CC_RUNTIME_CLASS(NodeRenderableBounded)) ) { if (InvalidateRegion) { NewNode->ReleaseCached(TRUE, FALSE, TRUE, TRUE); if (!DoInvalidateNodeRegion((NodeRenderableBounded*)NewNode, TRUE, FALSE, FALSE, FALSE)) // Don't recache automatically! { return FALSE; } } } // #ifdef _DEBUG // // If the node being Inserted is a layer then people need to know about it // if (NewNode->GetRuntimeClass() == CC_RUNTIME_CLASS(Layer)) // { // TRACE( _T("Layer inserted. Have you remembered to broadcast a SpreadMsg::LAYERCHANGES message?")); // //Spread* pSpread = (Spread*) NewNode->FindParent(CC_RUNTIME_CLASS(Spread)); // //BROADCAST_TO_ALL(SpreadMsg(pSpread,SpreadMsg::LAYERCHANGES)); // } // #endif // Added by Jim: Broadcast the layerchanged message when inserting a new layer if (NewNode->GetRuntimeClass() == CC_RUNTIME_CLASS(Layer)) { Spread* pSpread = (Spread*) NewNode->FindParent(CC_RUNTIME_CLASS(Spread)); BROADCAST_TO_ALL(SpreadMsg(pSpread,SpreadMsg::LAYERCHANGES)); } return (TRUE); } /******************************************************************************************** > BOOL UndoableOperation::DoHideNode(Node* NodeToHide, BOOL IncludeSubtreeSize, NodeHidden** nodeHidden = NULL, BOOL TellSubtree = TRUE) Author: Simon_Maneggio (Xara Group Ltd) Created: 18/10/93 Inputs: Node: The Node to be hidden IncludeSubtreeSize: Flag indicating if the subtree being hidden should be considered as belonging to the Operation history or not. (See the note in ops.h) Outputs: nodeHidden: A pointer to the NodeHidden hiding the NodeToHide. This output is optional. Returns: TRUE if successful FALSE if the operation should be aborted (TIDYUP THEN CALL End()!) Purpose: This low level Do function hides the subtree with root NodeToHide Errors: - SeeAlso: UndoableOperation::DoHideNodes ********************************************************************************************/ BOOL UndoableOperation::DoHideNode(Node* NodeToHide, BOOL IncludeSubtreeSize, NodeHidden** nodeHidden, BOOL TellSubtree) { ERROR3IF(!OpFlags.SucceedAndDiscard && NodeToHide->IsAnAttribute() && NodeToHide->FindParent()->DiscardsAttributeChildren(), "DoHide called under Caret"); if (NodeToHide->DiscardsAttributeChildren()) // If the node to hide doesn't want to produce undo (CaretNode) return TRUE; // Then bomb out now before we do anything... // We shouldn't tell the node that it's being hidden if we are only // moving it. // Also if we are Hiding a Node Group, then there is no // need to tell anyone either. As the children are unaffected. if (TellSubtree) { // Tell the node and it's subtree, that they are being hidden Node* pNode = NodeToHide->FindFirstDepthFirst(); while (pNode!=NULL) { if (!pNode->HidingNode()) return FALSE; // And find the next node pNode = pNode->FindNextDepthFirst(NodeToHide); } // Invalidate the region, we need to do this here because we are deleting the node if (NodeToHide->IsBounded()) ((NodeRenderableBounded*)NodeToHide)->InvalidateBoundingRect(); } // removed by Ed 23/5/95 'cos it caused text redraw problems // and nobody could justify why just moving an object in the tree should // cause the renderable bound associated with it to be invalidated // // Invalidate the region // if (NodeToHide->IsBounded()) // ((NodeRenderableBounded*)NodeToHide)->InvalidateBoundingRect(); // Try to hide the Node NodeHidden* Hidden; ALLOC_WITH_FAIL(Hidden,(new NodeHidden(NodeToHide)),this); if (Hidden == NULL) { return (FALSE); } // Try to create a ShowNodeAction which will show the node that we have just hidden. ShowNodeAction* UndoShowNodeAction; if (ShowNodeAction::Init(this, &UndoActions, Hidden, IncludeSubtreeSize, (Action**)(&UndoShowNodeAction), TellSubtree) == AC_FAIL) { // We must unhide the NodeToHide manually, cos no action will do it for us Hidden->ShowNode(); // Hidden is deleted don't worry return FALSE; } if (NodeToHide->IsSelected()) { // Update the selection range Camelot.UpdateSelection(); } // If the user provided a nodeHidden parameter then return a pointer to the NodeHidden if (nodeHidden != NULL) *nodeHidden = Hidden; // Added by Jim: Broadcast the layerchanged message when inserting a new layer if (NodeToHide->GetRuntimeClass() == CC_RUNTIME_CLASS(Layer)) { Spread* pSpread = (Spread*) Hidden->FindParent(CC_RUNTIME_CLASS(Spread)); BROADCAST_TO_ALL(SpreadMsg(pSpread,SpreadMsg::LAYERCHANGES)); } // Success return (TRUE); } /******************************************************************************************** > BOOL UndoableOperation::DoHideNodes(Range NodeRange, BOOL IncludeSubtreeSize, BOOL TellSubtree = TRUE) Author: Simon_Maneggio (Xara Group Ltd) Created: 03/03/94 Inputs: NodeRange: The Range of nodes to be hidden IncludeSubtreeSize: Flag indicating if the subtrees being hidden should be considered as belonging to the Operation history or not. (See the note in ops.h) Returns: TRUE if successful FALSE if the operation should be aborted (TIDYUP THEN CALL End()!) Purpose: This low level Do function hides a range of the subtrees Errors: - SeeAlso: UndoableOperation::DoHideNode ********************************************************************************************/ BOOL UndoableOperation::DoHideNodes(Range NodeRange, BOOL IncludeSubtreeSize, BOOL TellSubtree) { Node* Current = NodeRange.FindFirst(); while (Current != NULL) { Node* Next = NodeRange.FindNext(Current); if (!DoHideNode(Current, IncludeSubtreeSize, NULL, TellSubtree)) { return FALSE; // Failure } Current = Next; } return TRUE; // Success } /******************************************************************************************** > BOOL UndoableOperation::DoHideComplexRange(Range& RangeToHide) Author: Mike_Kenny (Xara Group Ltd) Created: 3/5/95 Inputs: RangeToHide = the current range of nodes which need hiding Outputs: RangeToHide = the updated range of nodes which need hiding Returns: TRUE then all necessary objects were hidden correctly FALSE if failed to hide one of the objects Purpose: This function is a pre process to DoHideNodes(). It calls each object in the range to hide themselves in a complex way. It may be that no objects in the selection require this facility, which means the RangeToHide object will be returned from this function unaffected. DoHideNodes() will then go through hidding all nodes in the range itself. If however a node responds to ComplexHide() and hides itself, the node will be removed from the range. This allows complex group nodes to control how they are hidden. Note, it is expected that ComplexHide() responders will hide nodes themselves This will have the automatic effect of removing them from the range. Hidden nodes do not appear in ranges. ********************************************************************************************/ BOOL UndoableOperation::DoHideComplexRange(Range& RangeToHide) { BOOL Failed=FALSE; Node *Next, *Current = RangeToHide.FindFirst(); while (Current!=NULL) { // set the next node pointer. Next=RangeToHide.FindNext(Current); INT32 success = Current->ComplexHide(this, Next); // success=-1 then error unable to hide node // success= 0 then ignore // success= 1 then node has been hidden Failed=(success<0); if (Failed) break; Current=Next; } return (!Failed); } /******************************************************************************************** > BOOL UndoableOperation::DoMoveNodes(Range NodeRange, Node* Destination, AttachNodeDirection Direction) Author: Simon_Maneggio (Xara Group Ltd) Created: 13/9/93 Inputs: NodeRange: The node range to be moved Destination: The context node Direction: The direction Node is to be attached to Destination Outputs: - Returns: TRUE if successful FALSE if the operation should be aborted (TIDYUP THEN CALL End()!) Purpose: This high level function moves the range of nodes from their current position in the tree, and attaches them to the Destination node in the direction specified by Direction. The nodes in the range maintain their relative z-order when they move. Errors: - SeeAlso: UndoableOperation::DoMoveNode ********************************************************************************************/ BOOL UndoableOperation::DoMoveNodes(Range NodeRange, Node* Destination, AttachNodeDirection Direction) { Node* Current = NodeRange.FindFirst(); // Get the first node to move ENSURE(Current != NULL, "Trying to move an empty range"); Node* NextNode = NodeRange.FindNext(Current); // Attach the first node in the range to the Destination node in the direction specified if (!DoMoveNode(Current, Destination, Direction)) { return FALSE; // Failed to move Current } Node* PrevNode = Current; // Get the next node in the range Current = NextNode; // Attach all other nodes in the range while (Current != NULL) { NextNode = NodeRange.FindNext(Current); if (!DoMoveNode(Current, PrevNode, NEXT)) { return FALSE; // Failed to move Current } PrevNode = Current; Current = NextNode; } return TRUE; } /******************************************************************************************** > BOOL UndoableOperation::DoMoveNode(Node* NodeToMove, Node* Destination, AttachNodeDirection Direction) Author: Simon_Maneggio (Xara Group Ltd) Created: 13/9/93 Inputs: NodeToMove: The node to be moved, cannot be an attribute Destination: The context node Direction: The direction Node is to be attached to Destination Outputs: - Returns: TRUE if successful FALSE if the operation should be aborted (TIDYUP THEN CALL End()!) Purpose: This high level function moves NodeToMove from its current position in the tree, and attaches it to Destination in the direction specified by Direction. Note: It is illegal to move an attribute in the tree. This is because it may be deleted as a result of attribute optimisation. It is unlikely that you would want to move an attribute anyway.An ERROR2 will be generated if this occurs. Update, 22/03/2005: It is legal to move an effect attribute because they are not optimised in the same way as other attributes. Errors: - SeeAlso: UndoableOperation::DoMoveNodes ********************************************************************************************/ BOOL UndoableOperation::DoMoveNode(Node* NodeToMove, Node* Destination, AttachNodeDirection Direction) { ERROR2IF(NodeToMove->IsAnAttribute() && !((NodeAttribute*)NodeToMove)->IsEffectAttribute(), FALSE, "Trying to move an attribute, this is bad"); // -------------------------------------------------------------------- // We have to move attributes by copying... if (NodeToMove->IsAnAttribute()) { // Copy the attribute to the destination location // Then hide the original // Copy NodeAttribute* pCopyAttr = NULL; BOOL bOK = FALSE; CALL_WITH_FAIL(((NodeAttribute*)NodeToMove)->NodeCopy((Node**)&pCopyAttr), this, bOK); if (!bOK) return FALSE; // No room to take a copy of the node // Create a hide node action to hide the node when we undo pCopyAttr->AttachNode(Destination, Direction); HideNodeAction* UndoHideNodeAction; if (HideNodeAction::Init(this, this->GetUndoActions(), pCopyAttr, TRUE, (Action**)(&UndoHideNodeAction)) == AC_FAIL) { pCopyAttr->CascadeDelete(); delete pCopyAttr; return FALSE; } // Hide if (!DoHideNode(NodeToMove, TRUE, NULL, TRUE)) return FALSE; return TRUE; } // -------------------------------------------------------------------- // Objects are physically moved, leaving a NodeHidden in the original location // with a "hyperspace" reference to the object in its final location. // // We must flag that we are only moving the node, so we don't // try and called the 'Hiding Node' function, which should only // be called when we are 'Really' hiding the node. MovingNode = TRUE; // Find out if the node is selected BOOL IsSelected = NodeToMove->IsSelected(); // Before we move the object determine what the source layer is Layer* pSrcLayer = NULL; if (NodeToMove->IsAnObject()) { // Only consider ink nodes pSrcLayer = (Layer*) (NodeToMove->FindParent(CC_RUNTIME_CLASS(Layer))); } // Hide it HideNodeAction* UndoHideNodeAction; // NodeRenderableBounded* pBounded = NULL; // hide the node if (!DoHideNode(NodeToMove, FALSE, NULL, FALSE)) goto Failed; // build the action if ( HideNodeAction::Init(this, &UndoActions, NodeToMove, FALSE, // Don't include the // subtree size (Action**)(&UndoHideNodeAction), FALSE) // Don't tell subtree == AC_FAIL) { goto Failed; }; // Move the node to its new location NodeToMove->MoveNode(Destination, Direction); if (IsSelected) NodeToMove->SetSelected(IsSelected); MovingNode = FALSE; if (NodeToMove->IsAnObject()) { // If the object has moved to a new layer then we need to invalidate both old and new // layers Layer* pDestLayer = (Layer*) (NodeToMove->FindParent(CC_RUNTIME_CLASS(Layer))); if ((pSrcLayer != pDestLayer) && pSrcLayer && pDestLayer) { pSrcLayer->InvalidateBoundingRect(); pDestLayer->InvalidateBoundingRect(); } if (pSrcLayer) { // Note that this layer has been edited pSrcLayer->SetEdited(TRUE); //#ifdef _DEBUG // // Tell the frame gallery to update its display of the frame // BROADCAST_TO_ALL(LayerMsg(pSrcLayer, LayerMsg::LayerReason::REDRAW_LAYER)); //#endif } if (pDestLayer && (pSrcLayer != pDestLayer)) { // Note that this layer has been edited pDestLayer->SetEdited(TRUE); //#ifdef _DEBUG // // Tell the frame gallery to update its display of the frame // BROADCAST_TO_ALL(LayerMsg(pDestLayer, LayerMsg::LayerReason::REDRAW_LAYER)); //#endif } } // finish happy return (TRUE); Failed: MovingNode = FALSE; // Ensure we clear this is an error occurred return FALSE; } /******************************************************************************************** > BOOL UndoableOperation::DoSaveCopyOfNodes(Range NodeRange) Author: Simon_Maneggio (Xara Group Ltd) Created: 7/3/94 Inputs: NodeRange: The range of nodes to save Outputs: - Returns: - Purpose: This fn takes a copy of the nodes in the range. The copies are restored when the operation is undone. Errors: - SeeAlso: UndoableOperation::DoSaveCopyOfNode ********************************************************************************************/ BOOL UndoableOperation::DoSaveCopyOfNodes(Range NodeRange) { Node* Current = NodeRange.FindFirst(); BOOL CopiedOK; Node* TheCopy; HideNodeAction* UndoHideNodeAction; while (Current != NULL) { // Salary check if (!Current->IsBounded()) { ERROR3("Current is not a NodeRenderable node"); continue; } // Make a copy of Current CALL_WITH_FAIL(Current->NodeCopy(&TheCopy), this, CopiedOK); if (!CopiedOK) { return FALSE; // No room to take a copy of the node } // We need to insert the new node into the tree, but we can't use DoInsertNewNode // as this will insert an unwanted NideNode action TheCopy->AttachNode(Current, PREV); TheCopy->SetSelected(FALSE); ((NodeRenderableBounded*)TheCopy)->ValidateBoundingRect(); ((NodeRenderableBounded*)TheCopy)->InvalidateBoundingRect(); // Hide the copy, creating a ShowNodeAction to show the copy when we undo if (!DoHideNode(TheCopy, TRUE // The hidden node is in the operation history ) ) { return FALSE; } // Create a Hide node action to hide the current node when we undo if ( HideNodeAction::Init(this, &UndoActions, Current, FALSE, (Action**)(&UndoHideNodeAction)) == AC_FAIL) { return FALSE; }; Current = NodeRange.FindNext(Current); } return TRUE; } /******************************************************************************************** > BOOL UndoableOperation::DoSaveCopyOfNode(NodeRenderable* Node) Author: Simon_Maneggio (Xara Group Ltd) Created: 7/3/94 Inputs: Node: Outputs: - Returns: - Purpose: This fn saves a copy of the node in the operation history. When the operation is undone the copy is restored. Errors: - SeeAlso: UndoableOperation::DoSaveCopyOfNodes ********************************************************************************************/ BOOL UndoableOperation::DoSaveCopyOfNode(NodeRenderable* Node) { ENSURE((!OpFlags.Failed), "Calling a DO function after operation has failed"); RangeControl rc( TRUE, TRUE, TRUE ); // All nodes in range Range range( Node, Node, rc ); return DoSaveCopyOfNodes( range ); } /******************************************************************************************** > BOOL UndoableOperation::DoTransformNodes(Range NodeRange, TransformBase* Trans) Author: Simon_Maneggio (Xara Group Ltd) Created: 13/9/93 Inputs: Range: The range of nodes to transform Trans: The transform to apply to every node in the range Outputs: - Returns: TRUE if successful FALSE if the operation should be aborted (TIDYUP THEN CALL End()!) The Trans is deleted. Purpose: This high level Do function applies the transform Trans to each object in the range. Don't delete the Trans object, it's deleted in TransformNodeAction slaughter method. Errors: - SeeAlso: UndoableOperation::DoTransformNode ********************************************************************************************/ BOOL UndoableOperation::DoTransformNodes(Range NodeRange, TransformBase* Trans) { // If the Trans is invertible then create a Transform node action to undo the transform // Note at this point Trans is not the inverse Transformation, but it will be soon !! // Node * pParent = NULL; // Node * pNodeToTransform = NULL; // DMc 17/5/99 // include the parent nodes in the range if they wish to be transformed with // their children by changing the range control flags RangeControl rg = NodeRange.GetRangeControlFlags(); rg.PromoteToParent = TRUE; NodeRange.Range::SetRangeControl(rg); // DoInvalidateNodesRegions(NodeRange, TRUE); DoInvalidateNodesRegions(NodeRange, TRUE, FALSE, FALSE, FALSE); // Don't forcibly recache (see bHaveTransformedAllCached) if (Trans->IsInvertable()) { TransformNodeAction* UndoTransformNodeAction; if ( TransformNodeAction::Init(this, &UndoActions, NodeRange, Trans, NULL, (Action**)(&UndoTransformNodeAction)) == AC_FAIL) { delete Trans; // for consistency return FALSE; // Let's get out while we can }; // Scan the range and transform each node Node* Current = NodeRange.FindFirst(); Node* Next; // Node* Parent = NULL; // BOOL bTransform = FALSE; while (Current != NULL) { Next = NodeRange.FindNext(Current); if (Current->IsNodeRenderableClass()) { // Prepare Control and Feedback flags Trans->bHaveTransformedCached = TRUE; Trans->bHaveTransformedChildren = TRUE; Trans->bTransformYourChildren = TRUE; ((NodeRenderable *)Current)->Transform(*Trans); // Compute feedback flags for caller Trans->bHaveTransformedAllCached = Trans->bHaveTransformedAllCached && Trans->bHaveTransformedCached; Trans->bHaveTransformedAllChildren = Trans->bHaveTransformedAllChildren && Trans->bHaveTransformedChildren; // ---------------------------------------------- // Now do smart testing to see whether we can retain any cached info the node was holding if (Current->IsBounded()) { NodeRenderableBounded* pCurrentBound = (NodeRenderableBounded*) Current; // If we have transformed all cached data successfully // And // We are translating (in which case the cached data will remain usable) // Or // The node was Direct (in which case the cached data comes from outside the tree where it can't have been transformed) // Then // Don't force the recacheing of the data for this node, just its parents if (Trans->bHaveTransformedCached && (Trans->IsTranslation() || pCurrentBound->HasCachedDirectBitmap())) // Yes, we can keep our cached info but we must tell our parents we have changed pCurrentBound->ReleaseCached(TRUE, FALSE, FALSE, TRUE); // Parents and derived data only else // No we can't keep our cached info pCurrentBound->ReleaseCached(TRUE, TRUE); } // ---------------------------------------------- } // JCF BODGE #ifdef _DEBUG else { TRACE( _T("Warning: UndoableOperation::DoTransformNodes: ") _T("Current node %s is not a NodeRenderableInk\n"), (LPCTSTR) Current->Name()); } #endif Progress::Update(); Current = Next; } // Invert the Trans which is in the TransformNodeAction ((TransInvertable*)Trans)->Invert(); } else // The Trans is not invertable { // We will have to go through all node in the range, making a copy and transforming it. // This used to use DoSaveCopyOfNodes, but this dosen't work as it is not completly undoable Node* Current = NodeRange.FindFirst(); Node* Next = NULL; while (Current != NULL) { Next = NodeRange.FindNext(Current); if (Current->IsNodeRenderableClass()) { // Make a copy of Current BOOL CopiedOK = TRUE; Node* TheCopy = NULL; CALL_WITH_FAIL(Current->NodeCopy(&TheCopy), this, CopiedOK); if (!CopiedOK) return FALSE; // transform the copy ((NodeRenderable*)TheCopy)->Transform(*Trans); // put the copy into the tree if (!DoInsertNewNode((NodeRenderableBounded*)TheCopy, Current, PREV, TRUE, FALSE)) { delete TheCopy; return FALSE; } // Hide the original if (!DoHideNode(Current, FALSE)) return FALSE; } // JCF BODGE #ifdef _DEBUG else { TRACE( _T("Warning: UndoableOperation::DoTransformNodes: ") _T("Current node %s is not a NodeRenderableInk\n"), (LPCTSTR) Current->Name()); } #endif Progress::Update(); Current = Next; } } return (TRUE); // Success } /******************************************************************************************** > BOOL UndoableOperation::DoTransformNode(NodeRenderableInk* NodeToTransform, TransformBase* Trans); Author: Simon_Maneggio (Xara Group Ltd) Created: 13/9/93 Inputs: NodeToTransform: The node to transform Trans: The transform to apply to the node Outputs: - Returns: TRUE if successful FALSE if the operation should be aborted (TIDYUP THEN CALL End()!) Purpose: This high level Do function applies the transform Trans to the NodeToTransform Errors: - SeeAlso: UndoableOperation::DoTransformNodes ********************************************************************************************/ BOOL UndoableOperation::DoTransformNode(NodeRenderableInk* NodeToTransform, TransformBase* Trans) { ENSURE((!OpFlags.Failed), "Calling a DO function after operation has failed"); // RangeControl rc = { TRUE, TRUE, TRUE }; // All nodes in range Range range( NodeToTransform, NodeToTransform, RangeControl( TRUE, TRUE, TRUE ) ); return( DoTransformNodes( range, Trans ) ); } /******************************************************************************************** > BOOL UndoableOperation::DoSelectNode(NodeRenderableInk* NodeToSelect) Author: Simon_Maneggio (Xara Group Ltd) Created: 20/10/93 Inputs: NodeToSelect: The node that is to be selected Outputs: - Returns: TRUE if successful FALSE if the operation should be aborted (TIDYUP THEN CALL End()!) Purpose: The function selects NodeToSelect. It also generates an action which will deselect the node when executed. Errors: - SeeAlso: UndoableOperation::DoDeselectNode ********************************************************************************************/ BOOL UndoableOperation::DoSelectNode(NodeRenderableInk* NodeToSelect, Spread *Parent) { SelectDeselectAction* UndoSelectDeselectAction; // Create an action to deselect the NodeToSelect when we undo. if ( SelectDeselectAction::Init(this, &UndoActions, NodeToSelect, Parent, (Action**)(&UndoSelectDeselectAction)) != AC_FAIL) { // We shall be able to deselect the object if we fail so select it. NodeToSelect->Select(FALSE); return (TRUE); } else return (FALSE); } /******************************************************************************************** > BOOL UndoableOperation::DoInvalidateNodesRegions(Range NodeRange, BOOL IncludeBlobs, BOOL UndoBlobs = FALSE, BOOL IfBgRedraw = FALSE, BOOL bForceRecache = TRUE) Author: Simon_Maneggio (Xara Group Ltd) Created: 28/2/94 Inputs: NodeRange: The range of nodes IncludeBlobs: When TRUE we take the bounding rectangle of the objects and their blobs. UndoBlobs: TRUE, Invert the 'IncludeBlobs' for undo/redo this op. FALSE, use the same 'IncludeBlobs' for undo/redo. Outputs: - Returns: TRUE if successful FALSE if the operation should be aborted (TIDYUP THEN CALL End()!) Purpose: This low level Do function invalidates a region which is the union of the bounding rectangles of each node in the range. When the includeBlobs flag is TRUE the region which gets invalidated includes the objects blobs. constraints: All members of the range must be on the same spread No member of the range can be hidden Errors: An ENSURE will occur if the range of nodes is empty, in a retail build the function will return TRUE but do nothing. SeeAlso: UndoableOperation::DoInvalidateNodeRegion ********************************************************************************************/ BOOL UndoableOperation::DoInvalidateNodesRegions(Range NodeRange, BOOL IncludeBlobs, BOOL UndoBlobs, BOOL IfBgRedraw, BOOL bForceRecache) { ENSURE((!OpFlags.Failed), "Calling a DO function after operation has failed"); if (UndoBlobs) { // Undo blobs = TRUE // We will use different 'IncludeBlobs' when we undo/redo this op // to the one we use initially. UndoBlobs = !IncludeBlobs; } else { // Undo blobs = FALSE // We will use the same 'IncludeBlobs' when we undo/redo this op // as the one we use initially. UndoBlobs = IncludeBlobs; } BOOL bOldValue = NodeRange.SetPromoteToParent(TRUE); NodeRenderableBounded* CurrentNode = (NodeRenderableBounded*)NodeRange.FindFirst(); NodeRange.SetPromoteToParent(bOldValue); if (CurrentNode == NULL) return TRUE; // The range is empty so NOP // ENSURE the range of nodes is not empty // ENSURE (CurrentNode != NULL, "Calling DoInvalidateNodesRegions with an empty range"); // ENSURE(CurrentNode->IsKindOf(CC_RUNTIME_CLASS(NodeRenderable)), // "Trying to invalidate a non NodeRenderable node"); // Find the spread Spread* pSpread = (Spread*)CurrentNode->FindParent(CC_RUNTIME_CLASS(Spread)); // Matt 27/11/2000 // OK, here's the beef... At present, if you group a shadowed object, the tree structure correctly // reflects this as a NodeShadowController with a NodeGroup parent... Now, when we've clicked on the // 'Set New Design' button, the NodeGroup is passed OK and is hidden after being copied (as was intended) // but the NodeShadowController beneath this is then tested: When asked what it's parent spread is, it // will promptly keel over, triggering the following ENSURE as its parent (the NodeGroup) no longer has // a pointer to its parent (ie it is NULL) - to cater for this, if we reach this point and we have no // parent spread, we can assume one of two things: // 1. The node above us has been hidden, therefore we are already implicitly hidden and we shouldn't // undertake any special work // 2. Everything is stuffed so bad in our tree that it really doesn't matter what we do here - we're // about to die anyhow... // Therefore, we can just return without failing with an ensure... // // ENSURE(pSpread != NULL, "Spread is NULL"); if (pSpread) { if (IfBgRedraw) { // ------------------------------------------------------------------------------------- // Try and create an InvalidateRegionAction InvalidateRegionIfBgRedrawAction* UndoInvalidateRgnAction; // Create an InvalidateRegionAction // Note that unfortunatly the action has to store the node range, rather than the // bounding rectangle because GetBlobBoundingRect must be calculated on the fly. if ( InvalidateRegionIfBgRedrawAction::Init(this, &UndoActions, NodeRange, pSpread, UndoBlobs, ( Action**)(&UndoInvalidateRgnAction)) == AC_FAIL) { return FALSE; // Failed to create action } if (!GetApplication()->IsBgRendering()) { return TRUE; // Ignore if no Bg Rendering in progress } // GetApplication()->DeleteRenderRegions(DocView::GetSelected()); // GetApplication()->BgRendering = FALSE; } else { // ------------------------------------------------------------------------------------- // Try and create an InvalidateRegionAction InvalidateRegionAction* UndoInvalidateRgnAction; // Create an InvalidateRegionAction // Note that unfortunatly the action has to store the node range, rather than the // bounding rectangle because GetBlobBoundingRect must be calculated on the fly. if ( InvalidateRegionAction::Init(this, &UndoActions, NodeRange, pSpread, UndoBlobs, ( Action**)(&UndoInvalidateRgnAction)) == AC_FAIL) { return FALSE; // Failed to create action } } ENSURE(pOurDoc != 0, "There was no document in DoInvalidateNodesRegions"); // Invalidate the bounds of each node in the range if (pOurDoc != NULL) { DocRect TempRect; BOOL bOldValue = NodeRange.SetPromoteToParent(TRUE); while (CurrentNode != NULL) { // Because we had a Range param, we could possibly be given any legal node in the tree, // e.g. the Insert node, not just renderable bounded objects, hence this 'if' statement if (CurrentNode->IsAnObject() || CurrentNode->IsPaper()) { ENSURE(CurrentNode->IsKindOf(CC_RUNTIME_CLASS(NodeRenderableBounded)), "Trying to invalidate a non NodeRenderable node"); TempRect = (IncludeBlobs ? (CurrentNode->GetUnionBlobBoundingRect()): (CurrentNode->GetBoundingRect()) ); TempRect = TempRect.Union(CurrentNode->GetEffectStackBounds()); // Accumulate this invalid region in a pending list until the Op // finishes // Mark the region as requiring object to recache any cached // data they may be holding because the Op may have changed it // pOurDoc->ForceRedraw(pSpread, TempRect, TRUE, bForceRecache ? CurrentNode : NULL); pOurDoc->ForceRedraw(pSpread, TempRect, TRUE, CurrentNode, bForceRecache); } CurrentNode = (NodeRenderableBounded*) NodeRange.FindNext(CurrentNode); } NodeRange.SetPromoteToParent(bOldValue); } } // Now invalidate the region return TRUE; } /******************************************************************************************** > BOOL UndoableOperation::DoInvalidateNodeRegion(NodeRenderableBounded* Node, BOOL IncludeBlobs, BOOL UndoBlobs, BOOL IfBgRedraw, BOOL bForceRecache) Author: Simon_Maneggio (Xara Group Ltd) Created: 28/2/94 Inputs: Node: The node that needs to be invalidated IncludeBlobs: When TRUE we take the blob bounding rectangle of the node UndoBlobs: TRUE, Invert the 'IncludeBlobs' for undo/redo this op. FALSE, use the same 'IncludeBlobs' for undo/redo. Outputs: - Returns: TRUE if successful FALSE if the operation should be aborted (TIDYUP THEN CALL End()!) Purpose: Invalidates the nodes bounding rectangle, or blob bounding rectangle Errors: - SeeAlso: UndoableOperation::DoInvalidateNodesRegions ********************************************************************************************/ BOOL UndoableOperation::DoInvalidateNodeRegion(NodeRenderableBounded* Node, BOOL IncludeBlobs, BOOL UndoBlobs, BOOL IfBgRedraw, BOOL bForceRecache) { ENSURE((!OpFlags.Failed), "Calling a DO function after operation has failed"); // Range = Sel nodes, Unsel nodes, cross layers, Locked layers, Ignore Non-renderables, Ignore invisible layers RangeControl rc( TRUE, TRUE, TRUE, FALSE, TRUE, TRUE ); Range range( Node, Node, rc ); return( DoInvalidateNodesRegions( range, IncludeBlobs, UndoBlobs, IfBgRedraw, bForceRecache ) ); } /******************************************************************************************** > BOOL UndoableOperation::DoInvalidateRegion(Spread* pSpread, DocRect& InvalidRegion) Author: Rik_Heywood (Xara Group Ltd) Created: 25/8/94 Inputs: pSpread - The spread that the region to invalidate is on InvalidRegion - The region that you want to invalidate Returns: TRUE if it all worked, FALSE if not (TIDYUP THEN CALL End()!) Purpose: Invalidates the region specified and adds the appropriate action to the action list for the current operation. If you are invalidating a region associated with a node in the tree then you should be using DoInvalidateNodeRegion() or DoInvalidateNodesRegion(). SeeAlso: UndoableOperation::DoInvalidateNodeRegion ********************************************************************************************/ BOOL UndoableOperation::DoInvalidateRegion( Spread *pSpread, const DocRect &InvalidRegion ) { // make sure that things are worth doing ENSURE((!OpFlags.Failed), "Calling a DO function after operation has failed"); ENSURE(pSpread!=NULL, "Attempt to invalidate a region on a null spread."); // If the region is empty we could save everyone a lot of trouble if (InvalidRegion.IsEmpty()) return TRUE; // Try and create an InvalidateArbitaryRegionAction and fail if it does not work InvalidateArbitaryAction* pAction; ActionCode ActCode = InvalidateArbitaryAction::Init( this, &UndoActions, pSpread, InvalidRegion, (Action**)(&pAction)); // See if it worked if (ActCode==AC_FAIL) return FALSE; // Now we can invalidate the region for the caller // First we need a document ENSURE(pOurDoc != NULL, "There was no document in DoInvalidateRegions"); // Invalidate the bounds of each node in the range if (pOurDoc != NULL) // Accumulate this invalid region in a pending list until the Op // finishes // Mark the region as requiring object to recache any cached // data they may be holding because the Op may have changed it pOurDoc->ForceRedraw(pSpread, InvalidRegion, TRUE, pSpread); // Tell them that everything worked out just fine return TRUE; } /******************************************************************************************** > BOOL UndoableOperation::DoDeselectNode(NodeRenderableInk* NodeToDeselect) Author: Simon_Maneggio (Xara Group Ltd) Created: 20/10/93 Inputs: NodeToDeselect: The node that is to be de-selected Outputs: - Returns: TRUE if successful FALSE if the operation should be aborted (TIDYUP THEN CALL End()!) Purpose: The function de-selects NodeToDeselect. It also generates an action which will select the node when executed. Errors: - SeeAlso: UndoableOperation::DoDeselectNode ********************************************************************************************/ BOOL UndoableOperation::DoDeselectNode(NodeRenderableInk* NodeToDeselect, Spread *Parent) { SelectDeselectAction* UndoSelectDeselectAction; // Create an action to deselect the NodeToSelect when we undo. if ( SelectDeselectAction::Init(this, &UndoActions, NodeToDeselect, Parent, (Action**)(&UndoSelectDeselectAction)) != AC_FAIL) { // We shall be able to select the object if we fail so deselect it. NodeToDeselect->DeSelect(FALSE); return (TRUE); } else return (FALSE); } /******************************************************************************************** > BOOL UndoableOperation::DoMakeShapes(Range& NodeRange) Author: Simon_Maneggio (Xara Group Ltd) Created: 03/05/94 Inputs: NodeRange: The Range of nodes to make into shapes Returns: TRUE if successful FALSE if the operation should be aborted (TIDYUP THEN CALL End()!) Purpose: This low level Do function calls the DoBecomeA virtual function on all SelectedNodes. It is the node's DoBecomeA function which is responsible for generating actions. Errors: - SeeAlso: Node::DoBecomeA ********************************************************************************************/ BOOL UndoableOperation::DoMakeShapes(Range NodeRange) { Node* CurrentNode = NodeRange.FindFirst(); Node* Next; while (CurrentNode != NULL) { // BODGE - since the group is selected and won't be replaced by anything else, no need to reselect it // this fixes a bug where make shapes on grouped text stories/molds/blends don't leave the parent group // selected but selects the objects inside! BOOL reselect = !IS_A(CurrentNode,NodeGroup) && !IS_A(CurrentNode,NodeBlend); BecomeA BecomeAPath(BECOMEA_REPLACE,CC_RUNTIME_CLASS(NodePath), this, reselect); Next = NodeRange.FindNext(CurrentNode); if (!(CurrentNode->DoBecomeA(&BecomeAPath))) { // Operation failed return FALSE; } CurrentNode = Next; } return TRUE; // Success } /******************************************************************************************** > BOOL UndoableOperation::DoCopyNodesToClipboard(Range NodeRange) Author: Simon_Maneggio (Xara Group Ltd) Created: 03/05/94 Inputs: NodeRange: The Range of nodes to copy Returns: TRUE if successful FALSE if the operation should be aborted (TIDYUP THEN CALL End()!) Purpose: This Do function copies all nodes in the range to the internal clipboard. The objects are made attribute complete prior to being moved. Before the copy begins StartComponentCopy is called on each DocComponent in the clipboard. The nodes are then copied to the clipboard CopyComponentData is then called on each node which has been copied to allow component data to be copied to the clipboard. In the operations End method: If the operation has not failed then EndComponentCopy is called on each doc component do give them a chance to commit changes. If the operation has failed then AbortComponentCopy is called instead. Errors: - SeeAlso: NodeRenderableInk::MakeAttributeComplete ********************************************************************************************/ BOOL UndoableOperation::DoCopyNodesToClipboard(Range NodeRange) { BOOL CopiedOK; BOOL ok; InternalClipboard* pInternalClip = InternalClipboard::Instance(); // After the operation ends we will need to inform all DocComponents in the clipboard // of the outcome. InformDocComponentsOfOperationsOutcome(pInternalClip); // Inform all DocComponents in the clipboard that a copy is about to take place CALL_WITH_FAIL(pInternalClip->StartComponentCopy(), this, ok) if (!ok) { // Start Component copy has failed so abort operation // Note that AbortComponentCopy will get called in the ops end method return FALSE; } // Try to copy the selection to the clipboard CALL_WITH_FAIL(InternalClipboard::CopyObjects(NodeRange, this), this, CopiedOK) if (!CopiedOK) { return FALSE; } // Now try and copy accross the component data CALL_WITH_FAIL(InternalClipboard::CopyComponentData(pOurDoc), this, CopiedOK) if (!CopiedOK) { return FALSE; } // Create action, this action will do nothing for undo but it's twin redo action will // copy the selection to the clipboard. CopyObjectsToClipboardAction* UndoCopyObjectsToClipboardAction; if ( CopyObjectsToClipboardAction::Init(this, &UndoActions, NodeRange, ( Action**)(&UndoCopyObjectsToClipboardAction)) == AC_FAIL) { return FALSE; // Failed to create action } return TRUE; // Success } /******************************************************************************************** > BOOL UndoableOperation::DoFlattenRange(Range NodeRange) Author: Simon_Maneggio (Xara Group Ltd) Created: 3/5/94 Inputs: - Outputs: - Returns: - Purpose: Not implemented yet Errors: - SeeAlso: - ********************************************************************************************/ BOOL UndoableOperation::DoFlattenRange(Range NodeRange) { return FALSE; } /******************************************************************************************** BOOL UndoableOperation::DoRemoveAttrTypeFromSubtree(Node* Subtree, RuntimeClass* AttrType, Node* pExceptThis = NULL); Author: Simon_Maneggio (Xara Group Ltd) Created: 24/6/94 Inputs: Subtree: Subtree to remove nodes from NodeClass: The type of attribute to remove from the subtree pExceptThis: The node that shouldn't be removed! Outputs: - Returns: TRUE if successful FALSE if the operation should be aborted (TIDYUP THEN CALL End()!) Purpose: Searches the subtree and every attribute which has type AttrType is hidden. Errors: - Scope: private SeeAlso: - ********************************************************************************************/ BOOL UndoableOperation::DoRemoveAttrTypeFromSubtree(Node* Subtree, CCRuntimeClass* AttrType, Node* pExceptThis) { // Traverse the subtree depth first Node* Current = Subtree->FindFirstDepthFirst(); Node* Next; while (Current != NULL) { Next = Current->FindNextDepthFirst(Subtree); // Determine if the Current node is to be hidden if (Current!=pExceptThis && Current->IsKindOf(CC_RUNTIME_CLASS(NodeAttribute))) { if (((NodeAttribute*)Current)->GetAttributeType() == AttrType) { if (Current->FindParent()->DiscardsAttributeChildren()) { Current->CascadeDelete(); delete Current; } else { if(!DoHideNode(Current, TRUE // Include the subtree size )) { return FALSE; // Operation failed } } } } Current = Next; } return TRUE; } /******************************************************************************************** > BOOL UndoableOperation::DoChangeSelection(NodePath* ThisNode, INT32 Index, BOOL NewState) Author: Jim_Lynn (Xara Group Ltd) Created: 19/7/94 Inputs: ThisNode points at the NodePath whose state we want to change Index is the element we want to change NewState is the new state of selection we want to set Outputs: - Returns: TRUE if succeeded, FALSE otherwise Purpose: This 'Do' function changes the selected state of a particular element in a path it creates all the necessary undo information so that the selection can be undone Errors: - SeeAlso: - ********************************************************************************************/ BOOL UndoableOperation::DoChangeSelection(NodePath* ThisNode, INT32 Index, BOOL NewState) { #ifndef STANDALONE ModifyFlagsAction* UnAction; PathFlags* Flags = ThisNode->InkPath.GetFlagArray(); if (Flags[Index].IsSelected != NewState) { ActionCode Act = ModifyFlagsAction::Init(this, &UndoActions, Flags[Index], Index, ThisNode, (Action**)&UnAction); if (Act == AC_FAIL) return FALSE; Flags[Index].IsSelected = NewState; return TRUE; } #endif return TRUE; } /******************************************************************************************** > BOOL UndoableOperation::DoDeletePathSection(NodePath* ThisPath, INT32 Index, INT32 NumElements, BOOL RedrawPath = TRUE) Author: Jim_Lynn (Xara Group Ltd) Created: 19/7/94 Inputs: ThisPath points at the NodePath whose state we want to change Index is the element we want to change NumElements is the number of elements to be deleted RedrawPath - TRUE if the path ares should be redrawn, FALSE if the caller will do it Outputs: - Returns: TRUE if succeeded, FALSE otherwise Purpose: This 'Do' function changes the selected state of a particular element in a path it creates all the necessary undo information so that the selection can be undone Errors: - SeeAlso: - ********************************************************************************************/ BOOL UndoableOperation::DoDeletePathSection(NodePath* ThisPath, INT32 Index, INT32 NumElements, BOOL RedrawPath) { #ifndef STANDALONE InsertPathElementAction* UnAction = NULL; ActionCode Act; Act = InsertPathElementAction::Init(this, &UndoActions, NumElements, Index, (Action**)(&UnAction)); if (Act == AC_FAIL) return FALSE; Document* pDocument = GetWorkingDoc(); ERROR2IF(pDocument == NULL, FALSE, "There was no Document when deleteing path elements"); Spread* pSpread = ThisPath->FindParentSpread(); ERROR2IF(pSpread == NULL, FALSE, "Path had no parent spread"); // Force a re-draw of the place where the path used to be if (RedrawPath) { DocRect Invalid = ThisPath->GetUnionBlobBoundingRect(); // Mark the region as requiring object to recache any cached // data they may be holding because the Op may have changed it pDocument->ForceRedraw(pSpread, Invalid, FALSE, ThisPath); } if ((Act != AC_NORECORD) && (UnAction != NULL)) { // I have to claim some memory to store the elements I'm deleting... PathVerb* ChangedVerbs = NULL; DocCoord* ChangedCoords = NULL; PathFlags* ChangedFlags = NULL; ALLOC_WITH_FAIL(ChangedVerbs,(PathVerb*) CCMalloc(NumElements * sizeof(PathVerb)),this); ALLOC_WITH_FAIL(ChangedCoords,(DocCoord*) CCMalloc(NumElements * sizeof(DocCoord)),this); ALLOC_WITH_FAIL(ChangedFlags,(PathFlags*) CCMalloc(NumElements * sizeof(PathFlags)),this); if (!ChangedVerbs || !ChangedCoords || !ChangedFlags) { if (ChangedVerbs) CCFree(ChangedVerbs); if (ChangedCoords) CCFree(ChangedCoords); if (ChangedFlags) CCFree(ChangedFlags); return FALSE; } // Get pointers to all the arrays of the path PathVerb* Verbs = NULL; DocCoord* Coords = NULL; PathFlags* Flags = NULL; ThisPath->InkPath.GetPathArrays(&Verbs, &Coords, &Flags); // Now copy the data from the path into the arrays for (INT32 i=0;iRecordPath(ChangedVerbs, ChangedFlags, ChangedCoords, ThisPath); } // Now we've recorded the data we're about to delete, let's delete it ThisPath->InkPath.DeleteSection(Index, NumElements); if (RedrawPath) { DocRect Invalid = ThisPath->GetUnionBlobBoundingRect(); // Accumulate this invalid region in a pending list until the Op // finishes // Mark the region as requiring object to recache any cached // data they may be holding because the Op may have changed it pDocument->ForceRedraw(pSpread, Invalid, FALSE, ThisPath); } #endif return TRUE; } /******************************************************************************************** > BOOL UndoableOperation::DoAlterPathElement( NodePath* ThisPath, INT32 Index, DocCoord NewCoord, PathFlags NewFlags, PathVerb NewVerb); Author: Jim_Lynn (Xara Group Ltd) Created: 27/7/94 Inputs: ThisPath points at the NodePath whose state we want to change Index is the element we want to change NewCoord is the new coordinate value NewFlags is the new flags value NewVerb is the new verb value RedrawPath - TRUE if the screen area of the path should be invalidated, FALSE if not Outputs: - Returns: TRUE if succeeded, FALSE otherwise Purpose: This 'Do' function changes the selected element, recording undo information Errors: - SeeAlso: - ********************************************************************************************/ BOOL UndoableOperation::DoAlterPathElement( NodePath* ThisPath, INT32 Index, DocCoord NewCoord, PathFlags NewFlags, PathVerb NewVerb, BOOL RedrawPath) { #ifndef STANDALONE // Here we're changing an element, so we have to change the element in the path, // recording undo information at the same time. ModifyElementAction* ModAction = NULL; // Get pointers to all the arrays of the path PathVerb* Verbs = NULL; DocCoord* Coords = NULL; PathFlags* Flags = NULL; ThisPath->InkPath.GetPathArrays(&Verbs, &Coords, &Flags); // Create an undo action for this action, which is a ModifyElementAction ActionCode Act; Act = ModifyElementAction::Init(this, &UndoActions, Verbs[Index], Flags[Index], Coords[Index], Index, ThisPath, (Action**)(&ModAction)); if (Act == AC_FAIL) return FALSE; Document* pDocument = GetWorkingDoc(); ERROR2IF(pDocument == NULL, FALSE, "There was no Document when deleteing path elements"); Spread* pSpread = ThisPath->FindParentSpread(); ERROR2IF(pSpread == NULL, FALSE, "Path had no parent spread"); // Force a re-draw of the place where the path used to be if (RedrawPath) { DocRect Invalid = ThisPath->GetUnionBlobBoundingRect(); // Mark the region as requiring object to recache any cached // data they may be holding because the Op may have changed it pDocument->ForceRedraw( pSpread, Invalid, FALSE, ThisPath ); } // Update the coords Verbs[Index] = NewVerb; Flags[Index] = NewFlags; Coords[Index] = NewCoord; // The bounding rect may have changed ThisPath->InvalidateBoundingRect(); // redraw the new area if (RedrawPath) { // redraw the new area DocRect Invalid = ThisPath->GetUnionBlobBoundingRect(); // Mark the region as requiring object to recache any cached // data they may be holding because the Op may have changed it pDocument->ForceRedraw( pSpread, Invalid, FALSE, ThisPath ); } #endif return TRUE; } /******************************************************************************************** > BOOL UndoableOperation::DoSmoothNodePath(NodePath* pThisNode, double smoothacc) Author: Mike_Kenny (Xara Group Ltd) Created: 16/11/94 Inputs: pThisNode - a pointer to a node to smooth. smoothacc - the smooth accuracy, usually in the range (256^2, 8000^2) Outputs: a new tree node will be created as a result Returns: TRUE if the operation completed FALSE if the operation failed due to lack of memory. The environment matches that on entry to the function. Purpose: This function will smooth a node path given an accuracy. It will create a new node in the tree after the node specified and hide node passed if all is succesfull. ********************************************************************************************/ BOOL UndoableOperation::DoSmoothNodePath(NodePath* pThisNode, double smoothacc) { #ifndef STANDALONE // Copy the nodepath and all its children, without placing the copy in the tree Node* pnode; if (!pThisNode->NodeCopy(&pnode)) return FALSE; NodePath* pSmoothNode = (NodePath*)pnode; // ok, smooth the nodepaths path data if (!pSmoothNode->InkPath.SmoothRegions(smoothacc, FALSE, TRUE)) goto smoothfailed; // update the smoothed paths bounding rectangle pSmoothNode->InvalidateBoundingRect(); // Now stick the new path into the tree if (!DoInsertNewNode(pSmoothNode, pThisNode, NEXT, TRUE)) goto smoothfailed; // Now we've formed a smoothed path, let's hide the original if (!DoHideNode(pThisNode,TRUE)) goto smoothfailed; #endif // return all is well return TRUE; #ifndef STANDALONE // failed to smooth the node so time to tidy up. smoothfailed: pSmoothNode->CascadeDelete(); delete pSmoothNode; return FALSE; #endif } /******************************************************************************************** > BOOL UndoableOperation::DoMakeNodeFromPath( NodePath* pContextNode, AttachNodeDirection Direction, Path* pParentPath BOOL CopyAttributes ) Author: Mike_Kenny (Xara Group Ltd) Created: 22/11/94 Inputs: pContextNode = pointer to a context node in the tree to attach our path to direction = direction on which to attach the new NodePath object created by this call. pParentPath = Path to place in the new NodePath object created CopyAttributes = if TRUE then take the attributes applied to the context node and apply them to the new node. Outputs: - Returns: TRUE if the node was created successfully FALSE if the node could not be created (out of memory) Purpose: This functio will create a new nodepath and attach it in the specified direction to the context node specified. It will also copy attributes applied as children of the ConextNode to the new node if required. ********************************************************************************************/ BOOL UndoableOperation::DoMakeNodeFromPath( NodePath* pParentNode, Path* pParentPath, AttachNodeDirection Direction, BOOL CopyAttributes) { // Create the node path object we will stick in the tree. NodePath* pChildNode; ALLOC_WITH_FAIL(pChildNode, new NodePath(), this); if (!pChildNode) return FALSE; BOOL ok; // make room for the new path in the node path. CALL_WITH_FAIL ( pChildNode->SetUpPath(pParentPath->GetNumCoords(),12), this,ok ); if (!ok) { delete pChildNode; return FALSE; } // now copy the path data in there. pChildNode->InkPath.CopyPathDataFrom(pParentPath); if (CopyAttributes) { CCAttrMap * AttribMap = NULL; AttribMap = CCAttrMap::MakeAppliedAttrMap(pParentNode); if (AttribMap) { AttribMap->ApplyAttributesToNode (pChildNode); } } // Now stick the new path into the tree CALL_WITH_FAIL ( DoInsertNewNode(pChildNode, pParentNode, Direction, TRUE, FALSE), this,ok ); // if we've failed tidy up if (!ok) { pChildNode->CascadeDelete(); delete pChildNode; return FALSE; } return TRUE; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Do functions to localise/factor out attributes for attribute application // Range functions first /******************************************************************************************** > BOOL UndoableOperation::DoLocaliseForAttrChange(Range* pRange, AttrTypeSet* pAffectedAttrTypes, BOOL ExcludeTextObjects = FALSE) Author: Simon_Maneggio (Xara Group Ltd) Created: 17/5/95 Inputs: pRange: The range of objects which need their attributes localising pAffectedAttrTypes: A set of the types of the attributes which are to be applied to the Range. This function will localise all attributes which have a type in this set. All attriute types in this set must be localised on the same compound node (See note below). ExcludeTextObjects: When TRUE atttributes will not be localised on Text compounds. This is useful for selection deletions, where TextStory localisation will be left to the Story itself. This is a bit unpleasant. Outputs: - Returns: TRUE if successful, FALSE if we run out of memory. Tidyup then call End() Purpose: This Do function must be called on a range prior to a set of attributes being applied to it. It globally localises those attributes which have a type in the pAffectedAttrTypes set. After the attribute has been applied you should call DoFactorOutAfterAttrChange If an object in the range Discards its attribute children then its attributes are not localised. Note: When applying attributes, we are not always going to apply the attributes to the object itself, sometimes we need to apply them to the object's parent. Eg. when we apply a line based attribute to a text character the attributes will get applied to the parent TextLine. In this situation we do not localise the objects attributes, but its parents attributes instead. Warning ~~~~~~~ All attribute types in the AttrTypeSet must be localised on the same compound node. This means for example that the AttrType set cannot contain a Bold and a Line space attribute. If we need to do this in future then the routine will need to be changed. Errors: - SeeAlso: AttrTypeSet SeeAlso: NodeAttribute::GetAttributeType SeeAlso: UndoableOperation::DoFactorOutAfterAttrChange SeeAlso: UndoableOperation::DoLocaliseForAttrChange SeeAlso: Range::DoFactorOutAfterAttrChange ********************************************************************************************/ BOOL UndoableOperation::DoLocaliseForAttrChange(Range* pRange, AttrTypeSet* pAffectedAttrTypes, BOOL ExcludeTextObjects /* = FALSE */) { ERROR3IF(pRange->Count() == 0, "Range::DoLocaliseForAttrChange called on an empty range"); // Iterate over the top of any liveeffects applied to the selection // Its important that this iteration matches those used in DoApplyAttribute functions // so that attributes are put in the places where mutation will find them ListRange* pLevel = EffectsStack::GetNewLevelRange(pRange, FALSE); // We DO own this range Node* pCurrent = pLevel->FindFirst(); // There is no need to localise a compound more than once so we remember the last localised NodeRenderableInk* pLocalisedCompound = NULL; CCRuntimeClass* AttrType; Node* pParent; Node* pObject; while (pCurrent) { pObject = pCurrent; // Determine if the attribute will get applied to this object, or an alternative object // (probably its parent) if (pAffectedAttrTypes && (!pAffectedAttrTypes->IsEmpty())) { AttrType = ((AttrTypeItem*)pAffectedAttrTypes->GetHead())->AttributeType; ERROR3IF(!AttrType, "AttrType set contains NULL attribute type"); pObject = ((NodeRenderableInk*)pObject)->GetObjectToApplyTo(AttrType); } if (pObject != NULL) { // We only need to localise those nodes which have a compound parent pParent = pObject->FindParent(); ERROR3IF(pParent == NULL, "Range::DoLocaliseForAttrChange, node found without a parent"); // Only localise the parent if it is a compound node which has not already been localised if ((pParent->IsCompound()) && (pParent != pLocalisedCompound)) { if (!(ExcludeTextObjects && ((IS_A(pParent, TextLine)) || (IS_A(pParent, TextStory)))) ) { // Attempt to localise the compound if (!DoLocaliseForAttrChange((NodeRenderableInk*)pObject, pAffectedAttrTypes, NULL)) { delete pLevel; return FALSE; } pLocalisedCompound = (NodeRenderableInk*)pParent; // Remember that it's been localised } } } pCurrent = pLevel->FindNext(pCurrent); } delete pLevel; return TRUE; } /******************************************************************************************** > BOOL UndoableOperation::DoLocaliseForAttrChange(Range* pRange, CCRuntimeClass* pAffectedAttrType) Author: Simon_Maneggio (Xara Group Ltd) Created: 17/5/95 Inputs: pRange: The range of objects which need their attributes localising pAffectedAttrType: The type of the attribute that you are about to apply to the range Outputs: - Returns: TRUE if successful, FALSE if we run out of memory. Tidyup then call End() Purpose: This Do function must be called on a range prior to an attribute being applied to it. If you are going to apply multiple attributes to the range then it is more efficient to call the other version of this function which takes a set of attribute types. The function globally localises those attributes which have a type in the pAffectedAttrTypes set. If an object in the range Discards its attribute children then its attributes are not localised. Note: When applying an attribute, we are not always going to apply it to the object itself, sometimes we need to apply it to the object's parent. Eg. when we apply a line based attribute to a text character the attributes will get applied to the parent TextLine. In this situation we do not localise the objects attributes, but its parents attributes instead. Errors: - SeeAlso: AttrTypeSet SeeAlso: NodeAttribute::GetAttributeType SeeAlso: UndoableOperation::DoFactorOutAfterAttrChange SeeAlso: UndoableOperation::DoLocaliseForAttrChange SeeAlso: Range::DoFactorOutAfterAttrChange ********************************************************************************************/ BOOL UndoableOperation::DoLocaliseForAttrChange(Range* pRange, CCRuntimeClass* pAffectedAttrType) { ERROR3IF(pRange->Count() == 0, "Range::DoLocaliseForAttrChange called on an empty range"); // Iterate over the top of any liveeffects applied to the selection // Its important that this iteration matches those used in DoApplyAttribute functions // so that attributes are put in the places where mutation will find them ListRange* pLevel = EffectsStack::GetNewLevelRange(pRange, FALSE); // We DO own this range Node* pCurrent = pLevel->FindFirst(); // There is no need to localise a compound more than once so we remember the last localised NodeRenderableInk* pLocalisedCompound = NULL; Node* pParent; Node* pObject; while (pCurrent) { pObject = pCurrent; // Determine if the attribute will get applied to this object, or an alternative object // (probably its parent) ERROR3IF(!pAffectedAttrType, "AttrType is NULL"); pObject = ((NodeRenderableInk*)pObject)->GetObjectToApplyTo(pAffectedAttrType); { // We only need to localise those nodes which have a compound parent pParent = pObject->FindParent(); ERROR3IF(pParent == NULL, "Range::DoLocaliseForAttrChange, node found without a parent"); // Only localise the parent if it is a compound node which has not already been localised if ((pParent->IsCompound()) && (pParent != pLocalisedCompound)) { // Attempt to localise the compound if (!DoLocaliseForAttrChange((NodeRenderableInk*)pObject, pAffectedAttrType, NULL)) { delete pLevel; return FALSE; } pLocalisedCompound = (NodeRenderableInk*)pParent; // Remember that it's been localised } } pCurrent = pLevel->FindNext(pCurrent); } delete pLevel; return TRUE; } /******************************************************************************************** > BOOL UndoableOperation::DoFactorOutAfterAttrChange(Range* pRange, AttrTypeSet* pAffectedAttrTypes) Author: Simon_Maneggio (Xara Group Ltd) Created: 18/5/95 Inputs: pRange: The objects that need their attributes factoring out pAffectedAttrTypes: The types of the attributes that have just been applied to the range. Outputs: - Returns: TRUE if successful, FALSE if we run out of memory. Tidyup then call End() Purpose: This Do function must be called on the range after attributes have being applied to it. The function globally factors out those attributes which have a type in the pAffectedAttrTypes set. Before calling the function you should have localised attributes by calling DoLocaliseForAttrChange. If an object in the range Discards its attribute children then its attributes are ignored when factoring (eg. the caret) Note: When applying attributes, we are not always going to apply the attributes to the object itself, sometimes we need to apply them to the object's parent. Eg. when we apply a line based attribute to a text character the attributes will get applied to the parent TextLine. In this situation we do not factor out the objects attributes, but its parents attributes instead. Warning ~~~~~~~ All attribute types in the AttrTypeSet must have been localised (and so need factoring out) on the same compound node. This means for example that the AttrType set cannot contain a Bold and a Line space attribute. If we need to do this in future then the routine will need to be changed. SeeAlso: AttrTypeSet SeeAlso: NodeAttribute::GetAttributeType SeeAlso: UndoableOperation::DoFactorOutAfterAttrChange SeeAlso: Range::DoLocaliseForAttrChange ********************************************************************************************/ BOOL UndoableOperation::DoFactorOutAfterAttrChange(Range* pRange, AttrTypeSet* pAffectedAttrTypes) { ERROR3IF(pRange->Count() == 0, "Range::DoFactorOutAfterAttrChange called on an empty range"); // Iterate over the top of any liveeffects applied to the selection // Its important that this iteration matches those used in DoApplyAttribute functions // so that attributes are put in the places where mutation will find them ListRange* pLevel = EffectsStack::GetNewLevelRange(pRange, FALSE); // We DO own this range Node* pCurrent = pLevel->FindFirst(); // There is no need to factor out the attributes on a compound more than once, so we // remember the last compound node which has had its attributes factored out NodeRenderableInk* pFactoredOutCompound = NULL; Node* pParent; Node* pObject; while (pCurrent) { // If the object can discard its attribute children then we should not try to factor // out its attributes CCRuntimeClass* AttrType; pObject = pCurrent; if (pAffectedAttrTypes && (!pAffectedAttrTypes->IsEmpty())) { AttrType = ((AttrTypeItem*)pAffectedAttrTypes->GetHead())->AttributeType; ERROR3IF(!AttrType, "AttrType set contains NULL attribute type"); pObject = ((NodeRenderableInk*)pObject)->GetObjectToApplyTo(AttrType); } // DY added test for NULL, which can occur with bevels if ((pObject != NULL)) { // We only need to factor out attributes on nodes which have a compound parent pParent = pObject->FindParent(); ERROR3IF(pParent == NULL, "Range::DoFactorOutAfterAttrChange, node found without a parent"); // Only factor out attribs if the parent has not already had its attribs factored out if ((pParent->IsCompound()) && (pParent != pFactoredOutCompound)) { // Attempt to localise the compound if (!DoFactorOutAfterAttrChange((NodeRenderableInk*)pObject, pAffectedAttrTypes)) { delete pLevel; return FALSE; } pFactoredOutCompound = (NodeRenderableInk*)pParent; // Remember that it's been localised } } pCurrent = pLevel->FindNext(pCurrent); } delete pLevel; return TRUE; } /******************************************************************************************** > BOOL UndoableOperation::DoFactorOutAfterAttrChange(Range* pRange, CCRuntimeClass* pAffectedAttrType) Author: Simon_Maneggio (Xara Group Ltd) Created: 18/5/95 Inputs: pRange: The objects that need their attributes factoring out pAffectedAttrType: The type of the attribute that have just been applied to the range. Outputs: - Returns: TRUE if successful, FALSE if we run out of memory. Tidyup then call End() Purpose: This Do function must be called on the range after an attribute being applied to it. If you have applied multiple attributes to the range then it is more efficient to call the other version of this function which takes a set of attribute types. The function globally factors out the attribute which has type pAffectedAttrType Before calling the function you should have localised the attribute by calling DoLocaliseForAttrChange. If an object in the range Discards its attribute children then its attributes are ignored when factoring (eg. the caret) Note: When applying an attribute, we are not always going to apply it to the object itself, sometimes we need to apply it to the object's parent. Eg. when we apply a line based attribute to a text character the attributes will get applied to the parent TextLine. In this situation we do not factor out the objects attributes, but its parents attributes instead. Note: When applying attributes, we are not always going to apply the attributes to the object itself, sometimes we need to apply them to the object's parent. Eg. when we apply a line based attribute to a text character the attributes will get applied to the parent TextLine. In this situation we do not factor out the object's attributes, but its parents attributes instead. SeeAlso: AttrTypeSet SeeAlso: NodeAttribute::GetAttributeType SeeAlso: UndoableOperation::DoLocaliseForAttrChange SeeAlso: Range::DoLocaliseForAttrChange SeeAlso: Range::DoFactorOutAfterAttrChange ********************************************************************************************/ BOOL UndoableOperation::DoFactorOutAfterAttrChange(Range* pRange, CCRuntimeClass* pAffectedAttrType) { ERROR3IF(pRange->Count() == 0, "Range::DoFactorOutAfterAttrChange called on an empty range"); // Scan the range // Iterate over the top of any liveeffects applied to the selection // Its important that this iteration matches those used in DoApplyAttribute functions // so that attributes are put in the places where mutation will find them ListRange* pLevel = EffectsStack::GetNewLevelRange(pRange, FALSE); // We DO own this range Node* pCurrent = pLevel->FindFirst(); // There is no need to factor out the attributes on a compound more than once, so we // remember the last compound node which has had its attributes factored out NodeRenderableInk* pFactoredOutCompound = NULL; Node* pParent; Node* pObject; while (pCurrent) { pObject = pCurrent; // Get the object that the attribute has been applied to ERROR3IF(!pAffectedAttrType, "AttrType is NULL"); pObject = ((NodeRenderableInk*)pObject)->GetObjectToApplyTo(pAffectedAttrType); // If the object can discard its attribute children then we should not try to factor // out its attributes { // We only need to factor out attributes on nodes which have a compound parent pParent = pObject->FindParent(); ERROR3IF(pParent == NULL, "Range::DoFactorOutAfterAttrChange, node found without a parent"); // Only factor out attribs if the parent has not already had its attribs factored out if ((pParent->IsCompound()) && (pParent != pFactoredOutCompound)) { // Attempt to localise the compound if (!DoFactorOutAfterAttrChange((NodeRenderableInk*)pObject, pAffectedAttrType)) { delete pLevel; return FALSE; } pFactoredOutCompound = (NodeRenderableInk*)pParent; // Remember that it's been localised } } pCurrent = pLevel->FindNext(pCurrent); } delete pLevel; return TRUE; } /******************************************************************************************** > BOOL UndoableOperation::DoLocaliseForAttrChange(NodeRenderableInk* Object, AttrTypeSet* pAffectedAttrTypes, ObjectSet* pLocalisedCompounds) Author: Simon_Maneggio (Xara Group Ltd) Created: 17/5/95 Inputs: Object: The object that you are about to apply attributes to pAffectedAttrTypes: A set of the types of the attributes which are to be applied to the Object. This function will localise all attributes which have a type in this set. pLocalisedCompounds: An optional set of compounds which have already had all attributes in the pAffectedAttrTypes localised. If Object has a parent compound which is in this set then its attributes will not need to be localised again. Outputs: - Returns: TRUE if successful, FALSE if we run out of memory. Tidyup then call End() Purpose: This Do function must be called on an object prior to a set of attributes being applied to it. It globally localises those attributes which have a type in the pAffectedAttrTypes set. If the function is called on a object which does not have a compound parent then there will be no attributes to localise so the function will simply return TRUE without having done anything. After a compound object has localised its children it will be added to the pLocalisedCompounds set if this has been provided. After the attribute has been applied you should call DoFactorOutAfterAttrChange If the object discards its attribute children then the routine does nothing (eg. the caret) Note: When applying attributes, we are not always going to apply the attributes to the object itself, sometimes we need to apply them to the object's parent. Eg. when we apply a line based attribute to a text character the attributes will get applied to the parent TextLine. In this situation we do not localise the objects attributes, but its parents attributes instead. Warning ~~~~~~~ All attribute types in the AttrTypeSet must be localised on the same compound node. This means for example that the AttrType set cannot contain a Bold and a Line space attribute. If we need to do this in future then the routine will need to be changed. Errors: - SeeAlso: AttrTypeSet SeeAlso: NodeAttribute::GetAttributeType SeeAlso: UndoableOperation::DoFactorOutAfterAttrChange SeeAlso: Range::DoLocaliseForAttrChange SeeAlso: Range::DoFactorOutAfterAttrChange ********************************************************************************************/ // This function must get called on a compound node before a new attribute is attached to it BOOL UndoableOperation::DoLocaliseForAttrChange(NodeRenderableInk* Object, AttrTypeSet* pAffectedAttrTypes, ObjectSet* pLocalisedCompounds) { CCRuntimeClass* AttrType; // Determine if the attribute will get applied to this object, or an alternative object // (probably its parent) if (pAffectedAttrTypes && (!pAffectedAttrTypes->IsEmpty())) { AttrType = ((AttrTypeItem*)pAffectedAttrTypes->GetHead())->AttributeType; ERROR3IF(!AttrType, "AttrType set contains NULL attribute type"); Object = Object->GetObjectToApplyTo(AttrType); } { // If the object has not got a compound parent then there will be no attributes // to localise, so we need not do anything Node* pParent; pParent = Object->FindParent(); ERROR3IF(!pParent, "UndoableOperation::DoLocaliseForAttrChange called on an object which has no parent"); if (pParent->IsCompound()) { // Before we attempt to localise the compound's attributes let's check that they are not // already localised. if (pLocalisedCompounds) { if (pLocalisedCompounds->InSet((NodeRenderableInk*)pParent)) { return TRUE; // Attributes are already localised so there is no need to localise again ! } else { pLocalisedCompounds->AddToSet((NodeRenderableInk*)pParent); // Record that the compound has been localised } } return (DoLocaliseCommonAttributes((NodeRenderableInk*)pParent, // We are localising the compound's attrs FALSE, // No need to check for duplicates TRUE, // Localise globally pAffectedAttrTypes)); // Only attributes of these types } } return TRUE; } /******************************************************************************************** > BOOL UndoableOperation::DoLocaliseForAttrChange(NodeRenderableInk* Object, CCRuntimeClass* pAffectedAttrType, ObjectSet* pLocalisedCompounds) Author: Simon_Maneggio (Xara Group Ltd) Created: 18/5/95 Inputs: Object: The object that you are about to apply the attribute to pAffectedAttrType: The type of the attribute that you are about to apply to the object pLocalisedCompounds: An optional set of compounds which have already had their pAffectedAttrType localised. If Object has a parent compound which is in this set then its attributes will not need to be localised again. Outputs: - Returns: TRUE if successful, FALSE if we run out of memory. Tidyup then call End() Purpose: This Do function must be called on an object prior to an attribute being applied to it. If you are going to apply multiple attributes to the Object then it is more efficient to call the other version of this function which takes a set of attribute types. The function globally localises those attributes which have a type in the pAffectedAttrTypes set. If the function is called on a object which does not have a compound parent then there will be no attributes to localise so the function will simply return TRUE without having done anything. If the object discards its attribute children then the routine does nothing (eg. the caret) Note: When applying attributes, we are not always going to apply the attributes to the object itself, sometimes we need to apply them to the object's parent. Eg. when we apply a line based attribute to a text character the attributes will get applied to the parent TextLine. In this situation we do not localise the objects attributes, but its parents attributes instead. SeeAlso: AttrTypeSet SeeAlso: NodeAttribute::GetAttributeType SeeAlso: UndoableOperation::DoFactorOutAfterAttrChange SeeAlso: Range::DoLocaliseForAttrChange SeeAlso: Range::DoFactorOutAfterAttrChange ********************************************************************************************/ BOOL UndoableOperation::DoLocaliseForAttrChange(NodeRenderableInk* Object, CCRuntimeClass* pAffectedAttrType, ObjectSet* pLocalisedCompounds) { BOOL ok = TRUE; // Determine if the attribute will get applied to this object, or an alternative object // (probably its parent) ERROR3IF(!pAffectedAttrType, "AttrType is NULL"); Object = Object->GetObjectToApplyTo(pAffectedAttrType); { Node* pParent; pParent = Object->FindParent(); ERROR3IF(!pParent, "UndoableOperation::DoLocaliseForAttrChange called on an object which has no parent"); if (pParent->IsCompound()) { // We an attribute type set AttrTypeSet Set; // Add the attributes type to the set if (!Set.AddToSet(pAffectedAttrType)) { ok = FALSE; } if (ok) { // Before we attempt to localise the compound's attributes let's check that they are not // already localised. if (pLocalisedCompounds) { if (pLocalisedCompounds->InSet((NodeRenderableInk*)pParent)) { return TRUE; // Attributes are already localised so there is no need to localise again ! } else { pLocalisedCompounds->AddToSet((NodeRenderableInk*)pParent); // Record that the compound has been localised } } ok = DoLocaliseCommonAttributes((NodeRenderableInk*)pParent,// We are localising the compound's attrs FALSE, // No need to check for duplicates TRUE, // Localise globally &Set); // Only attributes of these types } // The attribute type set is no longer required Set.DeleteAll(); } } return ok; } /******************************************************************************************** > BOOL UndoableOperation::DoFactorOutAfterAttrChange(NodeRenderableInk* Object, AttrTypeSet* pAffectedAttrTypes) Author: Simon_Maneggio (Xara Group Ltd) Created: 18/5/95 Inputs: Object: The object that you have just applied attributes to pAffectedAttrTypes: The types of the attributes that have just been applied to the object. Outputs: - Returns: TRUE if successful, FALSE if we run out of memory. Tidyup then call End() Purpose: This Do function must be called on an object after attributes have being applied to it. The function globally factors out those attributes which have a type in the pAffectedAttrTypes set. If the function is called on a object which does not have a compound parent then there will be no attributes to factor out so the function will simply return TRUE without having done anything. Before calling the function you should have localised attributes by calling DoLocaliseForAttrChange. If the object discards its attribute children then the routine does nothing (eg. the caret) Note: When applying attributes, we are not always going to apply the attributes to the object itself, sometimes we need to apply them to the object's parent. Eg. when we apply a line based attribute to a text character the attributes will get applied to the parent TextLine. In this situation we do not factor out the objects attributes, but its parents attributes instead. Warning ~~~~~~~ All attribute types in the AttrTypeSet must have been localised (and so need factoring out) on the same compound node. This means for example that the AttrType set cannot contain a Bold and a Line space attribute. If we need to do this in future then the routine will need to be changed. SeeAlso: AttrTypeSet SeeAlso: NodeAttribute::GetAttributeType SeeAlso: UndoableOperation::DoLocaliseForAttrChange SeeAlso: Range::DoLocaliseForAttrChange SeeAlso: Range::DoFactorOutAfterAttrChange ********************************************************************************************/ BOOL UndoableOperation::DoFactorOutAfterAttrChange(NodeRenderableInk* Object, AttrTypeSet* pAffectedAttrTypes) { CCRuntimeClass* AttrType; // Get object which attrib was applied to if (pAffectedAttrTypes && (!pAffectedAttrTypes->IsEmpty())) { AttrType = ((AttrTypeItem*)pAffectedAttrTypes->GetHead())->AttributeType; ERROR3IF(!AttrType, "AttrType set contains NULL attribute type"); Object = Object->GetObjectToApplyTo(AttrType); } { Node* pParent; pParent = Object->FindParent(); ERROR3IF(!pParent, "UndoableOperation::DoFactorOutAfterAttrChange called on an object which has no parent"); if (pParent->IsCompound()) { return (DoFactorOutCommonChildAttributes((NodeRenderableInk*)pParent, TRUE, // Global pAffectedAttrTypes)); } } return TRUE; } /******************************************************************************************** > BOOL UndoableOperation::DoFactorOutAfterAttrChange(NodeRenderableInk* Object, CCRuntimeClass* pAffectedAttrType) Author: Simon_Maneggio (Xara Group Ltd) Created: 18/5/95 Inputs: Object: The object that you have just applied attributes to pAffectedAttrType: The types of the attribute that have just been applied to the object. Outputs: - Returns: TRUE if successful, FALSE if we run out of memory. Tidyup then call End() Purpose: This Do function must be called on an object after an attribute being applied to it. If you have applied multiple attributes to the Object then it is more efficient to call the other version of this function which takes a set of attribute types. The function globally factors out the attribute with type pAffectedAttrType If the function is called on a object which does not have a compound parent then there will be no attributes to factor out so the function will simply return TRUE without having done anything. Before calling the function you should have localised attributes by calling DoLocaliseForAttrChange. If the object discards its attribute children then the routine does nothing (eg. the caret) Note: When applying attributes, we are not always going to apply the attributes to the object itself, sometimes we need to apply them to the object's parent. Eg. when we apply a line based attribute to a text character the attributes will get applied to the parent TextLine. In this situation we do not factor out the objects attributes, but its parents attributes instead. SeeAlso: AttrTypeSet SeeAlso: NodeAttribute::GetAttributeType SeeAlso: UndoableOperation::DoLocaliseForAttrChange SeeAlso: Range::DoLocaliseForAttrChange SeeAlso: Range::DoFactorOutAfterAttrChange ********************************************************************************************/ BOOL UndoableOperation::DoFactorOutAfterAttrChange(NodeRenderableInk* Object, CCRuntimeClass* pAffectedAttrType) { BOOL ok = TRUE; // Get object attr was applied to ERROR3IF(!pAffectedAttrType, "AttrType is NULL"); Object = Object->GetObjectToApplyTo(pAffectedAttrType); { Node* pParent; pParent = Object->FindParent(); ERROR3IF(!pParent, "UndoableOperation::DoFactorOutAfterAttrChange called on an object which has no parent"); if (pParent->IsCompound()) { // We need an attribute type set AttrTypeSet Set; // Add the attributes type to the set if (!(Set.AddToSet(pAffectedAttrType))) { ok = FALSE; } if (ok) { return (DoFactorOutCommonChildAttributes((NodeRenderableInk*)pParent, TRUE, // Global &Set)); } Set.DeleteAll(); } } return ok; } /******************************************************************************************** > BOOL UndoableOperation::DoFactorOutAfterAttrChange(ObjectSet* pLocalisedCompounds, AttrTypeSet* pAffectedAttrTypes) Author: Simon_Maneggio (Xara Group Ltd) Created: 18/5/95 Inputs: pLocalisedCompounds: A set of compound nodes which have localised their pAffectedAttrTypes attributes pAffectedAttrTypes: The types of the attributes that have been applied to the range. Outputs: - Returns: TRUE if successful, FALSE if we run out of memory. Tidyup then call End() Purpose: This Do function is designed to be called after all attributes in the pAffectedAttrTypes set have been localised on all compound nodes in the pLocalisedCompounds set. The function globally factors out the attributes which have a type which is in the pAffectedAttrTypes set. Before calling the function you should have localised the attribute by calling DoLocaliseForAttrChange. SeeAlso: AttrTypeSet SeeAlso: NodeAttribute::GetAttributeType SeeAlso: UndoableOperation::DoLocaliseForAttrChange ********************************************************************************************/ BOOL UndoableOperation::DoFactorOutAfterAttrChange(ObjectSet* pLocalisedCompounds, AttrTypeSet* pAffectedAttrTypes) { ERROR3IF(!pLocalisedCompounds, "DoFactorOutAfterAttrChange called with a NULL compound set"); //ERROR3IF(!pAffectedAttrTypes, "DoFactorOutAfterAttrChange called with a NULL attr type set"); // Scan the range ObjectItem* pObjItem = (ObjectItem*)(pLocalisedCompounds->GetHead()); NodeRenderableInk* pCurrent; while (pObjItem) { pCurrent = pObjItem->pObject; ERROR3IF(!(pCurrent->IsCompound()), "Set should only contain compound objects"); if (pCurrent->IsCompound()) { if (!DoFactorOutCommonChildAttributes((NodeRenderableInk*)pCurrent, TRUE, // Global pAffectedAttrTypes)) { return FALSE; } } pObjItem = (ObjectItem*)pLocalisedCompounds->GetNext(pObjItem); } return TRUE; } /******************************************************************************************** > BOOL UndoableOperation::DoFactorOutAfterAttrChange(ObjectSet* pLocalisedCompounds, CCRuntimeClass* pAffectedAttrType) Author: Simon_Maneggio (Xara Group Ltd) Created: 18/5/95 Inputs: pLocalisedCompounds: A set of compound nodes which have localised their pAffectedAttrType attributes. pAffectedAttrType: The type of the attribute that have just been applied to the range. Outputs: - Returns: TRUE if successful, FALSE if we run out of memory. Tidyup then call End() Purpose: This Do function is designed to be called after pAffectedAttrType has been localised on all compound nodes in the pLocalisedCompounds set. The function globally factors out the attribute which has type pAffectedAttrType Before calling the function you should have localised the attribute by calling DoLocaliseForAttrChange. SeeAlso: AttrTypeSet SeeAlso: NodeAttribute::GetAttributeType SeeAlso: UndoableOperation::DoLocaliseForAttrChange ********************************************************************************************/ BOOL UndoableOperation::DoFactorOutAfterAttrChange(ObjectSet* pLocalisedCompounds, CCRuntimeClass* pAffectedAttrType) { ERROR3IF(!pLocalisedCompounds, "DoFactorOutAfterAttrChange called with a NULL compound set"); ERROR3IF(!pAffectedAttrType, "DoFactorOutAfterAttrChange called with a NULL attr type"); // Scan the range ObjectItem* pObjItem = (ObjectItem*)(pLocalisedCompounds->GetHead()); NodeRenderableInk* pCurrent; // We need an attribute type set AttrTypeSet Set; // Add the attributes type to the set if (!(Set.AddToSet(pAffectedAttrType))) { return FALSE; } while (pObjItem) { pCurrent = pObjItem->pObject; ERROR3IF(!(pCurrent->IsCompound()), "Set should only contain compound objects"); if (pCurrent->IsCompound()) { if (!DoFactorOutCommonChildAttributes((NodeRenderableInk*)pCurrent, TRUE, // Global &Set)) { Set.DeleteAll(); return FALSE; } } pObjItem = (ObjectItem*)pLocalisedCompounds->GetNext(pObjItem); } Set.DeleteAll(); return TRUE; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // These functions are for attribute optimisation when deleting objects in a range // They are now defunct. They were a nice idea but they could not cope with attribute children // of empty groups ! /******************************************************************************************** > BOOL UndoableOperation::GetPreDelFactorOutInfo(Range* pRange, List* pPreDelFactorOutInfoList) Author: Simon_Maneggio (Xara Group Ltd) Created: 31/5/95 Inputs: pRange: The range of objects that will be deleted Outputs: pPreDelFactorOutInfoList: Information to be used by the DoFactorOutAfterDeletion function. Returns: TRUE if succesful FALSE if we ran out of memory. In this situation all items on the pPreDelFactorOutInfoList are deleted. Purpose: This function is an information gatherer for the DoFactorOutAfterDeletion function. See this function for a full description of its purpose. Errors: - SeeAlso: UndoableOperation::DoFactorOutAfterDeletion ********************************************************************************************/ /* BOOL UndoableOperation::GetPreDelFactorOutInfo(Range* pRange, List* pPreDelFactorOutInfoList) { // On entry to this function the PreDelFactorOutInfoList should be empty ERROR3IF(!(pPreDelFactorOutInfoList->IsEmpty()), "PreDelFactorOutInfoList is not empty"); // Lets make sure pPreDelFactorOutInfoList->DeleteAll(); NodeRenderableInk* pCurrentCompound; // A pointer to the compound parent of the objects we are // currently processing pCurrentCompound = NULL; PreDelFactorOutInfoItem* pCurrentCompoundInfo = NULL; // Scan the range Node* pCurrent = pRange->FindFirst(); Node* pCurrentsParent = NULL; while (pCurrent) { pCurrentsParent = pCurrent->FindParent(); ERROR3IF(pCurrentsParent == NULL, "Current has a NULL parent"); if (pCurrentsParent->IsCompound()) { if (pCurrentsParent != pCurrentCompound) { pCurrentCompound = (NodeRenderableInk*)pCurrentsParent; // We are processing a new Compound node. Create a new PreDelFactorOutInfoItem // so that we can record those attributes that must be factored out in it. // Check if the last compound node we processed needs any attrs factoring out. // if it doesn't then we can safely delete its entry (every little helps) if (pCurrentCompoundInfo && pCurrentCompoundInfo->AttrTypesToFactorOut.IsEmpty()) { // bye-bye pPreDelFactorOutInfoList->RemoveItem(pCurrentCompoundInfo); delete pCurrentCompoundInfo; } pCurrentCompoundInfo = new PreDelFactorOutInfoItem(); if (!pCurrentCompoundInfo) goto OutOfMemory; pCurrentCompoundInfo->pCompound = pCurrentCompound; // Add the item to the list pPreDelFactorOutInfoList->AddHead(pCurrentCompoundInfo); } ((NodeRenderableInk*)pCurrent)->AddChildAttrTypesToSet(&(pCurrentCompoundInfo->AttrTypesToFactorOut)); } // Get next object to process pCurrent = pRange->FindNext(pCurrent); } return TRUE; // success OutOfMemory: // Delete all items on the pPreDelFactorOutInfoList pCurrentCompoundInfo = (PreDelFactorOutInfoItem*)(pPreDelFactorOutInfoList->GetHead()); while (pCurrentCompoundInfo) { pCurrentCompoundInfo->AttrTypesToFactorOut.DeleteAll(); pCurrentCompoundInfo = (PreDelFactorOutInfoItem*) (pPreDelFactorOutInfoList->GetNext(pCurrentCompoundInfo)); } pPreDelFactorOutInfoList->DeleteAll(); return FALSE; } */ /******************************************************************************************** > BOOL UndoableOperation::DoFactorOutAfterDeletion(List* pPreDelFactorOutInfoList) Author: Simon_Maneggio (Xara Group Ltd) Created: 31/5/95 Inputs: pPreDelFactorOutInfoList: given to you by the GetPreDelFactorOutInfo function All items on this list are helpfully deleted after this function returns. Outputs: - Returns: TRUE if successful, FALSE if we run out of memory. Tidyup then call End() Purpose: Whenever we delete a range of objects, if any objects in the range have compound parents then we must adjust the common attributes on these compounds. For example if a compound has three red objects, and two yellow objects. If we delete the yellow objects, then red should become a common attribute of the compound. We could acheive this feat by firstly localising all attributes on the range, hiding the range, and then factoring out after. However this will result in unecessary attributes dangling off hidden nodes, so we try to be a bit cleverer than this. Prior to deleting the range we call GetPreDelFactorOutInfo. This function returns us a list containing information about which attributes need to be factored out on which compounds. After deleting the range this function should be called to factor out the neccessary attributes. Errors: - SeeAlso: UndoableOperation::GetPreDelFactorOutInfo ********************************************************************************************/ // This function should get called after a range of objects have been deleted ('Hidden') /* BOOL UndoableOperation::DoFactorOutAfterDeletion(List* pPreDelFactorOutInfoList) { PreDelFactorOutInfoItem* pCurrentCompoundInfo = (PreDelFactorOutInfoItem*)(pPreDelFactorOutInfoList->GetHead()); NodeRenderableInk* pCompound; BOOL ok = TRUE; // Process each record while (ok && pCurrentCompoundInfo) { // Are there any attributes to factor out ?? if (!(pCurrentCompoundInfo->AttrTypesToFactorOut.IsEmpty())) { pCompound = pCurrentCompoundInfo->pCompound; ERROR3IF(!(pCompound->IsCompound()), "List should only contain compound objects"); if (pCompound->IsCompound()) { if (!DoFactorOutCommonChildAttributes((NodeRenderableInk*)pCompound, TRUE, // Global &(pCurrentCompoundInfo->AttrTypesToFactorOut))) { ok = FALSE; } } } pCurrentCompoundInfo = (PreDelFactorOutInfoItem*) (pPreDelFactorOutInfoList->GetNext(pCurrentCompoundInfo)); } // Delete all items on the pPreDelFactorOutInfoList pCurrentCompoundInfo = (PreDelFactorOutInfoItem*)(pPreDelFactorOutInfoList->GetHead()); while (pCurrentCompoundInfo) { pCurrentCompoundInfo->AttrTypesToFactorOut.DeleteAll(); pCurrentCompoundInfo = (PreDelFactorOutInfoItem*) (pPreDelFactorOutInfoList->GetNext(pCurrentCompoundInfo)); } pPreDelFactorOutInfoList->DeleteAll(); if (!ok) FailAndExecute(); return ok; } */ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /******************************************************************************************** > BOOL UndoableOperation::DoFactorOutCommonChildAttributes(NodeRenderableInk* CompoundObject, BOOL Global = FALSE, List* pAffectedAttrTypes = NULL) Author: Simon_Maneggio (Xara Group Ltd) Created: 7/8/94 Inputs: CompoundObject: The object to factor out Global: When TRUE we recursively factor out attributes in all parent compounds of the CompoundObject (Bottom up) pAffectedAttrTypes: An optional set of attribute types. If this is specified then we only consider factoring out those attributes which have a type which is in this set. Returns: TRUE if successful FALSE if the operation should be aborted (TIDYUP THEN CALL End()!) Purpose: This function factors out all attributes which are common to all children of the compound object. All common attributes become first children of the compound object. If the compound contains a node which can discard its attributes (like the caret) then its attributes are not considered when factoring out. As with all Do functions actions are generated to undo/redo Errors: - SeeAlso: UndoableOperation::DoLocaliseCommonAttributes SeeAlso: NodeRenderableInk::FactorOutCommonChildAttributes ********************************************************************************************/ BOOL UndoableOperation::DoFactorOutCommonChildAttributes(NodeRenderableInk* CompoundObject, BOOL Global, AttrTypeSet* pAffectedAttrTypes /* = NULL */) { LocaliseCommonAttrAct* UndoAct; if (!CompoundObject->DiscardsAttributeChildren()) { // Make a copy of the attribute type set to store in the Action AttrTypeSet* pSet = NULL; if (pAffectedAttrTypes) { pSet = pAffectedAttrTypes->CopySet(); if (!pSet) return FALSE; // Failed to create set } // Create an action to localise attributes which have been factored out if ( LocaliseCommonAttrAct::Init(this, &UndoActions, CompoundObject, Global, pSet, (Action**)(&UndoAct)) == AC_FAIL) return FALSE; } if (!CompoundObject->FactorOutCommonChildAttributes(Global, pAffectedAttrTypes)) { FailAndExecuteAllButLast(); return FALSE; } return TRUE; } /******************************************************************************************** > BOOL UndoableOperation::DoFactorOutCommonAttributes(NodeRenderableInk* CompoundObject, BOOL Global = FALSE, List* pAffectedAttrTypes = NULL) Author: Phil_Martin (Xara Group Ltd) Created: 13/10/2004 Inputs: CompoundObject: The object to factor out Global: When TRUE we recursively factor out attributes in all parent compounds of the CompoundObject (Bottom up) pAffectedAttrTypes: An optional set of attribute types. If this is specified then we only consider factoring out those attributes which have a type which is in this set. Returns: TRUE if successful FALSE if the operation should be aborted (TIDYUP THEN CALL End()!) Purpose: Just a wrapper to get function name consistency and do the common IsCompound test to prevent DFOCCA from barfing... Errors: - SeeAlso: UndoableOperation::DoLocaliseCommonAttributes SeeAlso: NodeRenderableInk::FactorOutCommonChildAttributes ********************************************************************************************/ BOOL UndoableOperation::DoFactorOutCommonAttributes(NodeRenderableInk* pRootNode, BOOL bGlobal, AttrTypeSet* pAffectedAttrTypes /* = NULL */) { if (pRootNode->IsCompound()) DoFactorOutCommonChildAttributes(pRootNode, bGlobal, pAffectedAttrTypes); return TRUE; } /******************************************************************************************** > BOOL UndoableOperation::DoLocaliseCommonAttributes(NodeRenderableInk* CompoundObject, BOOL CheckForDuplicates = FALSE, BOOL Global = FALSE, AttrTypeSet* pAffectedAttrTypes = NULL) Author: Simon_Maneggio (Xara Group Ltd) Created: 7/8/94 Inputs: CompoundObject: The compound object to localise CheckForDuplicates: Checks for duplicate attributes being attached to children of the compound. This should never be the case as it would indicate that common attributes are incorrect. Global: TRUE indicates that we should localise the attributes on all parent compound nodes (top down), before localising the attributes for this compound. pAffectedAttrTypes: An optional set of attribute types. If this is specified then we only consider localising those attributes which have a type which is in this set. Outputs: - Returns: TRUE if successful FALSE if the operation should be aborted (TIDYUP THEN CALL End()!) Purpose: This function is the opposite of DoFactorOutCommonChildAttributes it copies all attributes common to the compound object to each child object within the group which requires each attribute. The groups common attributes are deleted. As with all Do functions actions are generated to undo/redo Errors: - SeeAlso: UndoableOperation::DoFactorOutCommonChildAttributes SeeAlso: NodeRenderableInk::LocaliseCommonAttributes ********************************************************************************************/ BOOL UndoableOperation::DoLocaliseCommonAttributes(NodeRenderableInk* CompoundObject, BOOL CheckForDuplicates /* FALSE */, BOOL Global /*= FALSE*/, AttrTypeSet* pAffectedAttrTypes /*= NULL*/) { FactorOutCommonChildAttrAct* UndoAct; // Create an action to localise attributes which have been factored out if (!CompoundObject->DiscardsAttributeChildren()) { // Make a copy of the attribute type set to store in the Action AttrTypeSet* pSet = NULL; if (pAffectedAttrTypes) { pSet = pAffectedAttrTypes->CopySet(); if (!pSet) return FALSE; // Failed to create set } if ( FactorOutCommonChildAttrAct::Init(this, &UndoActions, CompoundObject, Global, pSet, (Action**)(&UndoAct)) == AC_FAIL) return FALSE; } if (!CompoundObject->LocaliseCommonAttributes(CheckForDuplicates, Global, pAffectedAttrTypes)) { FailAndExecuteAllButLast(); return FALSE; } return TRUE; } /******************************************************************************************** static BOOL UndoableOperation::RegisterOpDescriptor( UINT32 toolID, UINT32 txID, CCRuntimeClass* RuntimeClass, TCHAR* tok, pfnGetState gs, UINT32 helpId, UINT32 bubbleID , UINT32 resourceID, UINT32 controlID, SystemBarType GroupBarID, BOOL ReceiveMessages, BOOL Smart, BOOL Clean, UINT32 OneOpenInstID, UINT32 AutoStateFlags) Author: Simon_Maneggio (Xara Group Ltd) Created: 22/3/94 Inputs: toolID : Module Identifier txID : String Resource ID RuntimeClass : The runtime class of the Opdescriptor's operation tok, : pointer to the token string gs, : pointer to the GetState function helpId : help identifier bubbleID : string resource for bubble help bmpID = 0, : bitmap ("icon") resource ID ReceiveMessages : Does the OpDescriptor need to respond to system messages Smart ; A smart duplicate operation ? Clean : Does the operation change the document OneOpenInstID : When not 0 only one live instance of the operation is permitted, and OneOpenInstID is the string resource ID describing why.This will be useful for 1 open instance dialogs. AutoStateFlags : Flags which indicate conditions when the operation should automatically be greyed/ticked etc..(This cuts down the number of tests that need to be made in the GetState function). See Opdesc.h for a description Outputs: - Returns: TRUE if successful, else ERROR is called and FALSE returned. Purpose: This function should be called from the operations Init method. It creates an OpDescriptor for the operation SeeAlso: UndoableOpDescriptor::UndoableOpDescriptor ********************************************************************************************/ BOOL UndoableOperation::RegisterOpDescriptor( UINT32 toolID, UINT32 txID, CCRuntimeClass* RuntimeClass, TCHAR* tok, pfnGetState gs, UINT32 helpId, UINT32 bubbleID , UINT32 resourceID, UINT32 controlID, SystemBarType GroupBarID, BOOL ReceiveMessages, BOOL Smart, BOOL Clean, UINT32 OneOpenInstID, UINT32 AutoStateFlags) { // tok is going into a string which turns out to be a string 32, so do a sanity check (Neville 26/6/97) size_t len = camStrclen(tok); ERROR2IF(len > 32,FALSE,"UndoableOperation::RegisterOpDescriptor token buffer overflow!"); // Try to create the OpDescriptor UndoableOpDescriptor* OpDesc = new UndoableOpDescriptor( toolID, txID, RuntimeClass, tok, gs, helpId, bubbleID, resourceID, controlID, ReceiveMessages, Smart, Clean, OneOpenInstID, AutoStateFlags ); ERRORIF(!OpDesc, _R(IDE_NOMORE_MEMORY), FALSE); return TRUE; } /******************************************************************************************** > BOOL UndoableOperation::DoInsertPathElement(NodePath* ThisPath, INT32 Index, DocCoord NewCoord, PathFlags NewFlags, PathVerb NewVerb, BOOL RedrawPath = TRUE); Author: Peter_Arnold (Xara Group Ltd) Created: 30/9/94 Inputs: ThisPath points at the NodePath into which we want to insert an element Index - the the element will be inserted after this element NewCoord is the new element's coordinate value NewFlags is the new element's flags value NewVerb is the new element's verb value RedrawPath - TRUE if the screen area of the path should be redrawn, FALSE if the caller is responsible Outputs: - Returns: TRUE if succeeded, FALSE otherwise Purpose: This 'Do' function adds an element into a path, recording undo information Errors: - SeeAlso: - ********************************************************************************************/ BOOL UndoableOperation::DoInsertPathElement(NodePath* ThisPath, INT32 Index, DocCoord NewCoord, PathFlags NewFlags, PathVerb NewVerb, BOOL RedrawPath) { #ifndef STANDALONE // Create an undo action for this action, which is a RemoveElementAction RemovePathElementAction* pAction = NULL; ActionCode Act; Act = RemovePathElementAction::Init(this, &UndoActions, 1, Index+1, (Action**)(&pAction)); if (Act == AC_FAIL) return FALSE; // Tell the operation where the path is if (pAction != NULL) pAction->RecordPath(ThisPath); Document* pDocument = GetWorkingDoc(); ERROR2IF(pDocument == NULL, FALSE, "There was no Document when deleteing path elements"); Spread* pSpread = ThisPath->FindParentSpread(); ERROR2IF(pSpread == NULL, FALSE, "Path had no parent spread"); // Force a re-draw of the place where the path used to be if (RedrawPath) { DocRect Invalid = ThisPath->GetUnionBlobBoundingRect(); // Mark the region as requiring object to recache any cached // data they may be holding because the Op may have changed it pDocument->ForceRedraw( pSpread, Invalid, FALSE, ThisPath ); } // Insert the new element ThisPath->InkPath.InsertSection(Index,1); // Get pointers to all the arrays of the path PathVerb* Verbs = NULL; DocCoord* Coords = NULL; PathFlags* Flags = NULL; ThisPath->InkPath.GetPathArrays(&Verbs, &Coords, &Flags); // Update the coords Verbs[Index+1] = NewVerb; Flags[Index+1] = NewFlags; Coords[Index+1] = NewCoord; // The bounding rect may have changed ThisPath->InvalidateBoundingRect(); // Redraw the new area if (RedrawPath) { DocRect Invalid = ThisPath->GetUnionBlobBoundingRect(); // Mark the region as requiring object to recache any cached // data they may be holding because the Op may have changed it pDocument->ForceRedraw( pSpread, Invalid, FALSE, ThisPath ); } #endif return TRUE; } /******************************************************************************************** > BOOL UndoableOperation::DoReversePath (NodePath* ThisPath, BOOL RedrawPath = TRUE) Author: Chris_Snook (Xara Group Ltd) Created: 11/5/2000 Inputs: ThisPath points at the NodePath that we want to reverse RedrawPath - TRUE if the screen area of the path should be redrawn, FALSE if the caller is responsible Outputs: The reversed path in ThisPath->InkPath Returns: TRUE if succeeded, FALSE otherwise Purpose: This 'Do' function reverse a path, recording undo information Errors: - SeeAlso: - ********************************************************************************************/ BOOL UndoableOperation::DoReversePath (NodePath* ThisPath, BOOL RedrawPath /*= TRUE*/) { PathVerb* Verbs = NULL; PathFlags* Flags = NULL; DocCoord* Coords = NULL; ThisPath->InkPath.GetPathArrays(&Verbs, &Coords, &Flags); INT32 NumCoords = ThisPath->InkPath.GetNumCoords(); //BOOL PrevSelected = FALSE; //INT32 PrevPos = 0; Document* pDocument = GetWorkingDoc(); Spread* pSpread = ThisPath->FindParentSpread(); if (RedrawPath) { // Mark the region as requiring object to recache any cached // data they may be holding because the Op may have changed it DocRect Invalid = ThisPath->GetUnionBlobBoundingRect(); pDocument->ForceRedraw( pSpread, Invalid, FALSE, ThisPath ); } Path* NewEditPath = new Path (); if (!NewEditPath) { return (FALSE); } if (!NewEditPath->Initialise(NumCoords, 24)) { return FALSE; } // for the new reversed path .... PathVerb* Verbs2 = NULL; PathFlags* Flags2 = NULL; DocCoord* Coords2 = NULL; NewEditPath->GetPathArrays(&Verbs2, &Coords2, &Flags2); NewEditPath->CopyPathDataFrom (&(ThisPath->InkPath)); // the following loop as been optimised since this is the main part of the alg // (which just reverses the supplied path) .... NewEditPath->Reverse (); // now lets build an undo record .... ModifyPathAction* ModAction; // since thats what were doing ActionCode Act; Act = ModifyPathAction::Init(this, &UndoActions, NumCoords, (Action**)(&ModAction)); if (Act == AC_FAIL) { FailAndExecute(); End(); return FALSE; } // for use with the action .... PathVerb* ChangedVerbs = NULL; DocCoord* ChangedCoords = NULL; PathFlags* ChangedFlags = NULL; INT32* ChangedIndices = NULL; // If the function returned AC_NO_RECORD we shouldn't record any undo information in the action // NOTE - during unwind all actions return AC_OK with a NULL ModAction pointer!! if ((Act!=AC_NORECORD) && (ModAction!=NULL)) { // This next bit is a bit hellish. Any one of these four allocations can fail, in which case // we have to tidy up afterwards. Cue a lot of nested ifs and elses. ALLOC_WITH_FAIL(ChangedVerbs,(PathVerb*) CCMalloc(NumCoords * sizeof(PathVerb)),this); if (ChangedVerbs) { ALLOC_WITH_FAIL(ChangedCoords,(DocCoord*) CCMalloc(NumCoords * sizeof(DocCoord)),this); if (ChangedCoords) { ALLOC_WITH_FAIL(ChangedFlags,(PathFlags*) CCMalloc(NumCoords * sizeof(PathFlags)),this); if (ChangedFlags) { ALLOC_WITH_FAIL(ChangedIndices,(INT32*) CCMalloc(NumCoords * sizeof(INT32)),this); if (!ChangedIndices) { CCFree( ChangedFlags ); CCFree( ChangedCoords ); CCFree( ChangedVerbs); FailAndExecute(); End(); return FALSE; } } else { CCFree( ChangedCoords ); CCFree( ChangedVerbs ); FailAndExecute(); End(); return FALSE; } } else { CCFree( ChangedVerbs); FailAndExecute(); End(); return FALSE; } } // Now to put the undo data into the undo action for (INT32 loop = 0; loop < NumCoords; loop++) { ChangedVerbs [loop] = Verbs [loop]; ChangedFlags [loop] = Flags [loop]; ChangedCoords [loop] = Coords [loop]; ChangedIndices [loop] = loop; } // Now we've allocated the arrays, let's tell the action about 'em ModAction->StoreArrays (ChangedVerbs, ChangedFlags, ChangedCoords, ChangedIndices, ThisPath);//pOrigPath); } ///////////////////////////////////////////////////////////////////////////////////////////////// // now we update the supplied path with the new path .... ThisPath->InkPath.CopyPathDataFrom (NewEditPath); delete (NewEditPath); // and clean up if (ThisPath->IsANodeBlendPath ()) { ((NodeBlendPath*) ThisPath)->DestroyCachedInformation (); } ///////////////////////////////////////////////////////////////////////////////////////////////// ThisPath->InvalidateBoundingRect(); if (RedrawPath) { DocRect Invalid = ThisPath->GetUnionBlobBoundingRect(); // Mark the region as requiring object to recache any cached // data they may be holding because the Op may have changed it pDocument->ForceRedraw( pSpread, Invalid, FALSE, ThisPath ); } return (TRUE); } /******************************************************************************************** > virtual void UndoableOperation::PerformMergeProcessing() Author: Simon_Maneggio (Xara Group Ltd) Created: 04/11/94 Inputs: Outputs: - Returns: - Purpose: This function gets called from the Operation::EndOp method after the operation has ended successfuly and been added to the operation history. If the operation could potentially merge itself with the previous operation then perform this merging here. Note that this base class function does nothing. Here are some rules: 1. Note that this function cannot fail - doing so would really be quite scarry. If however you find yourself running out of memory in this function then simply tidyup (leaving things how they were) and then return. 2. After this function has been invoked the operation history size must not be greater than it was prior to the function getting called. It would be quite unimpressive if this was the case anyway. 3. In many cases you will want to delete 'this' operation from this function. so just be careful huh! The way that you merge the operation is basically up to you, however here are a few pointers. * The first thing you should do on entering this function is to check that the last Operation added to the operation history is this operation. Call OperationHistory::FindLastOp to verify this within an ERROR2 Macro. * Next you will probably want to check if the previous operation performed can be merged with this op. To obtain the previous op call OperationHistory::FindPrevToLastOp. Testing the runtime class of this op is probably enough for you to determine if a merge can take place or not. * Usually you will want to augment the previous operation in some way so that it includes the function which was performed by this operation. This could involve changing an action in the previous operation, adding new actions etc. Errors: - SeeAlso: OperationHistory::FindLastOp OperationHistory::FindPrevToLastOp OperationHistory::DeleteLastOp ActionList:: ********************************************************************************************/ void UndoableOperation::PerformMergeProcessing() { return; // The base class function does nowt } /******************************************************************************************** > void UndoableOperation::MergeWithPrevious() Author: Peter_Arnold (Xara Group Ltd) Created: 10/1/96 Inputs: - Outputs: - Returns: - Purpose: Moves all the actions in this operation to the end of the previous action, then deletes this action SeeAlso: UndoableOperation::PerformMergeProcessing() ********************************************************************************************/ void UndoableOperation::MergeWithPrevious() { // Obtain a pointer to the operation history for the operation's document OperationHistory* pOpHist = &GetWorkingDoc()->GetOpHistory(); if (pOpHist == NULL) { ERROR3("There's no OpHistory!?"); return; } // Ensure that we are the last operation added to the operation history if (pOpHist->FindLastOp() != this) { ERROR3("Last Op should be this op"); return; } Operation* pPrevOp = pOpHist->FindPrevToLastOp(); if (pPrevOp != NULL) { // Copy all the actions from us to the end of the action list in the previous op. ActionList* pPrevActions = pPrevOp->GetUndoActionList(); ActionList* pPrevOtherActions = pPrevOp->GetRedoActionList(); if ((pPrevActions == NULL) || (pPrevOtherActions == NULL)) { ERROR3("Previous actions list pointer was NULL"); return; } // Copy actions across Action* pCurrentAction = (Action*)UndoActions.RemoveHead(); while (pCurrentAction != NULL) { pCurrentAction->TransferToOtherOp(pPrevOp, pPrevActions, pPrevOtherActions); pCurrentAction = (Action*)UndoActions.RemoveHead(); } } // And remove ourself pOpHist->DeleteLastOp(); } /******************************************************************************************** > void UndoableOperation::IncludeParentTransformNodesInRange(Range * Rng) Author: David_McClarnon (Xara Group Ltd) Created: 15/5/99 Inputs: - Outputs: A new range (to be deleted outside of this function) Returns: - Purpose: Transforms the node, checking if any parents need to transform too SeeAlso: ********************************************************************************************/ Range * UndoableOperation::IncludeParentTransformNodesInRange(Range * Rng) { // do this by changing the selection, and then restoring it // first, save the current selection List * pSelList = GetApplication()->FindSelection()->MakeListOfNodes(TRUE); List * pRngList = Rng->MakeListOfNodes(FALSE); NodeRenderableInk::DeselectAll(); NodeListItem * pItem = (NodeListItem *)pRngList->GetHead(); Node * pParent = NULL; while (pItem) { if (pItem->pNode) { if (pItem->pNode->IsRenderable()) { pItem->pNode->SetSelected(TRUE); pParent = pItem->pNode->FindParent(); while (pParent) { if (pParent->ShouldITransformWithChildren()) { pParent->SetSelected(TRUE); } pParent = pParent->FindParent(); } } } pItem = (NodeListItem *)pRngList->GetNext(pItem); } pRngList->DeleteAll(); delete pRngList; // update the selection range and make a copy of it GetApplication()->UpdateSelection(); Range * RetnRng = new Range(*(GetApplication()->FindSelection())); // restore the selection NodeRenderableInk::DeselectAll(); pItem = (NodeListItem *)pSelList->GetHead(); while (pItem) { if (pItem->pNode) { pItem->pNode->SetSelected(TRUE); } pItem = (NodeListItem *)pSelList->GetNext(pItem); } // update the selection range and make a copy of it GetApplication()->UpdateSelection(); pSelList->DeleteAll(); delete pSelList; // set the range control flags RangeControl rg = RetnRng->GetRangeControlFlags(); rg.PromoteToParent = TRUE; RetnRng->SetRangeControl(rg); return RetnRng; } /******************************************************************************************** > virtual BOOL UndoableOperation::UpdateChangedNodes( ObjChangeParam* pParam, Spread* pSpread = NULL ) Author: Karim_MacDonald (Xara Group Ltd) Created: 19/01/2000 Inputs: pParam describes the way an op has changes the node(s) pSpread pointer to the spread containing the nodes affected by the Op NULL => use the currently selected spread. Returns: TRUE if all affected nodes were able to cope with the Op, FALSE otherwise. Purpose: This function overrides its namesake in Operation, calling the base function first and then adding a call to NameGallery::PostTriggerEdit if this Op admits it may change the bounds of nodes within the tree. Errors: ERROR2 if pParam is NULL. SeeAlso: Operation::UpdateChangedNodes(ObjChangeParam*, Spread*) ********************************************************************************************/ BOOL UndoableOperation::UpdateChangedNodes(ObjChangeParam* pParam,Spread* pSpread) { ERROR2IF(pParam == NULL, FALSE, "pParam == NULL"); BOOL ok = Operation::UpdateChangedNodes(pParam, pSpread); ///////////////////////////////////////////////////////////////////////////////////////// // Karim MacDonald 19/01/2000. // If the call to the base class was successful, then we can try to call // NameGallery::PostTriggerEdit, to allow any nodes marked for stretching to // change in response to any bounds changes in the tree. if (ok) { // When querying the Op responsible for the change, I've decided to use 'this'. // Could've asked pParam for the Op's pointer, but 'this' is probably more reliable. UndoableOperation* pChangeOp = this;// pParam->GetOpPointer(); if (pChangeOp != NULL && pChangeOp->MayChangeNodeBounds() && NameGallery::Instance()) { NameGallery::Instance()->PostTriggerEdit(pChangeOp, pParam); } } ///////////////////////////////////////////////////////////////////////////////////////// return ok; } /******************************************************************************************** > BOOL UndoableOperation::NoStretchUpdateChangedNodes(ObjChangeParam* pParam, Document* pDoc) Author: Karim_MacDonald (Xara Group Ltd) Created: 19/01/2000 Inputs: pParam description of the change. pDoc pointer to the document. Outputs: Causes any nodes marked for updating to update themselves. Returns: TRUE if successful, FALSE otherwise. Purpose: This is *not* an override of Operation::UpdateChangedNodes(..., Document*), although it performs *almost* exactly the same job. It is provided solely for NameGallery::PostTriggerEdit to call in order to get all affected nodes to update themselves correctly after a stretch. PostTriggerEdit is itself called in response to normal UpdateChangedNodes calls, so this avoids recursion problems. Errors: We ERROR2 if either of the parameters is NULL. See also: Operation::UpdateChangedNodes(ObjChangeParam*, Document*). ********************************************************************************************/ BOOL UndoableOperation::NoStretchUpdateChangedNodes(ObjChangeParam* pParam, Document* pDoc) { ERROR2IF(pParam == NULL, FALSE, "pParam == NULL"); ERROR2IF(pDoc == NULL, FALSE, "pDoc == NULL"); BOOL ok = TRUE; Chapter* pChapter = Node::FindFirstChapter(pDoc); while (pChapter != NULL && ok) { // Now find the first child of the Chapter, because this is the level at which Spreads hang out Node* pNode = pChapter->FindFirstChild(); // Update changed nodes in each of the chapter's spreads. We *don't* call // Operation::UpdateChangedNodes(..., Spread*), as that would lead to recursion... while (pNode != NULL && ok) { if (pNode->IsSpread()) { Spread* pSpread = (Spread*)pNode; ok = UpdateChangedNode(pParam, pSpread->FindFirstChild()); } pNode = pNode->FindNext(); } pChapter = pChapter->FindNextChapter(); } return ok; }