// $Id: nodecomp.cpp 1385 2006-06-28 16:48:01Z gerry $ /* @@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 "app.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "node.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "ink.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "nodecomp.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "objchge.h" // Resource headers //#include "resource.h" // operation headers #include "ophist.h" #include "transop.h" #include "attrappl.h" // attribute headers #include "lineattr.h" //#include "txtattr.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "fillattr.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "attrbev.h" #include "cutop.h" #include "nodetxts.h" #include "nodetext.h" #include "opbevel.h" #include "textops.h" #include "blobs.h" //#include "docview.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "spread.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "nodebldr.h" #include "blendatt.h" //#include "mario.h" #include "pathops.h" //#include "group.h" - in camtypes.h [AUTOMATICALLY REMOVED] DECLARE_SOURCE( "$Revision: 1385 $" ); CC_IMPLEMENT_DYNCREATE(CompoundNodeTreeFactory, CCObject) CC_IMPLEMENT_DYNCREATE(NodeCompound, NodeRenderableInk) // Declare smart memory handling in Debug builds #define new CAM_DEBUG_NEW // static variables that are used by the blending system when blending compound nodes .... NodeCompound* NodeCompound::shadowThisNode = NULL; NodeCompound* NodeCompound::shadowDeleteThis = NULL; List* NodeCompound::blndConsBecomeAShadows = NULL; // list of shadow controllers that were generated when blending (internally via PASSBACK) List* NodeCompound::blndConsBecomeABevels = NULL; // list of bevel controllers that were generated when blending (internally via PASSBACK) List* NodeCompound::blndConsBecomeAContours = NULL; // list of contour controllers that were generated when blending (internally via PASSBACK) BOOL NodeCompound::isForPrinting = FALSE; #ifdef _DEBUG INT32 NodeCompound::shadowLastID = 0; INT32 NodeCompound::bevelLastID = 0; INT32 NodeCompound::contourLastID = 0; INT32 NodeCompound::shadowLastBecomeAID = 0; INT32 NodeCompound::bevelLastBecomeAID = 0; INT32 NodeCompound::contourLastBecomeAID = 0; #endif /*********************************************************************************************** > NodeCompound::NodeCompound(Node* ContextNode, AttachNodeDirection Direction, const DocRect& BoundingRect, BOOL Locked = FALSE, BOOL Mangled = FALSE, BOOL Marked = FALSE, BOOL Selected = FALSE, BOOL Renderable = FALSE ) Author: David_McClarnon (Xara Group Ltd) Created: 11/5/99 Inputs: ContextNode: Pointer to a node which this node is to be attached to. MonoOn Direction: MonoOff Specifies the direction in which the node is to be attached to the ContextNode. The values this variable can take are as follows: PREV : Attach node as a previous sibling of the context node NEXT : Attach node as a next sibling of the context node FIRSTCHILD: Attach node as the first child of the context node LASTCHILD : Attach node as a last child of the context node BoundingRect: Bounding rectangle The remaining inputs specify the status of the node: Locked: Is node locked ? Mangled: Is node mangled ? Marked: Is node marked ? Selected: Is node selected ? Purpose: This constructor initialises the nodes flags and links it to ContextNode in the direction specified by Direction. All neccesary tree links are updated. Note: SetUpShape() must be called before the NodeRegularShape is in a state in which it can be used. SeeAlso: NodeRegularShape::SetUpShape Errors: An ENSURE will occur if ContextNode is NULL ***********************************************************************************************/ NodeCompound::NodeCompound(Node* ContextNode, AttachNodeDirection Direction, BOOL Locked, BOOL Mangled, BOOL Marked, BOOL Selected ) : NodeRenderableInk(ContextNode, Direction, Locked, Mangled, Marked, Selected ) { m_DPI = 96.0; m_bPrinting = FALSE; // blending attributes m_bBlendStartNode = FALSE; m_bBlendEndNode = FALSE; m_pBlendCreatedByNode = NULL; } /********************************************************************************************* > NodeCompound::NodeCompound() Author: David_McClarnon (Xara Group Ltd) Created: 11/5/99 Purpose: Note: SeeAlso: **********************************************************************************************/ NodeCompound::NodeCompound() : NodeRenderableInk() { m_DPI = 96.0; m_bPrinting = FALSE; m_bBlendStartNode = FALSE; m_bBlendEndNode = FALSE; m_pBlendCreatedByNode = NULL; } /********************************************************************************************* > NodeCompound::~NodeCompound() Author: David_McClarnon (Xara Group Ltd) Created: 11/5/99 Purpose: Destructor Note: SeeAlso: **********************************************************************************************/ NodeCompound::~NodeCompound() { } /********************************************************************************************* > ChangeCode NodeCompound::OnChildChange(ObjChangeParam* pParam) Author: David_McClarnon (Xara Group Ltd) Created: 11/5/99 Purpose: Processes child change messages Note: SeeAlso: **********************************************************************************************/ ChangeCode NodeCompound::OnChildChange(ObjChangeParam* pParam) { if (IsNodeHidden()) return CC_OK; ERROR2IF(pParam == NULL,CC_FAIL,"pParam == NULL"); BOOL bRegen = TRUE; BOOL bCache = FALSE; // Node * pChild = NULL; // Node * pThis = this; // Node * pNextChild = NULL; UndoableOperation* pOp = NULL; if (pParam->GetOpPointer()) { if (pParam->GetOpPointer()->IsKindOf(CC_RUNTIME_CLASS(UndoableOperation))) { pOp = pParam->GetOpPointer(); } } // CCRuntimeClass * pClass = NULL; // ActionList * pActionList = NULL; // Action *pAction = NULL; // BOOL bDelete = FALSE; // INT32 NumChildrenThatNeedMe = 0; // the last code failled to give a spread for nodes recently divorced Spread * pSpread = Document::GetSelectedSpread(); //(Spread *)FindParent(CC_RUNTIME_CLASS(Spread)); ERROR2IF(pSpread==NULL, CC_FAIL, "Can't find selected spread"); List TextNodeList; NodeListItem * pItem = NULL; DocRect Bounds = BoundingRectangle; BlobManager* BlobMgr = GetApplication()->GetBlobManager(); INT32 BlobSize = 0; if (!pOp && pParam->GetChangeType() == OBJCHANGE_FINISHED && pParam->GetDirection() == OBJCHANGE_CALLEDBYCHILD && pParam->GetChangeFlags().RegenerateNode) { RegenerateNode(NULL, FALSE, FALSE); return CC_OK; } if (BlobMgr) { BlobSize = BlobMgr->GetBlobSize(); } if (pParam->GetChangeType() == OBJCHANGE_STARTING) { } else if (pParam->GetChangeType() == OBJCHANGE_FINISHED) { if (pParam->GetChangeFlags().DeleteNode) { TRACEUSER( "DavidM", _T("Delete node\n")); } if (pOp != NULL) { // Ok! Tell all my children to regenerate (in some cases !) // check for ops which we do nothing with if (pOp->IsKindOf(CC_RUNTIME_CLASS(TransOperation)) && !pOp->IS_KIND_OF(OpMovePathPoint)) { // ok, we have a transform operation TRACEUSER( "DavidM", _T("Transform operation!")); if (((TransOperation *)pOp)->GetCurrentMatrix().IsTranslation()) { // find out if the selected object is a child of myself Range Sel(*(GetApplication()->FindSelection())); Node * pNode = Sel.FindFirst(FALSE); bRegen = TRUE; while (pNode && bRegen) { if (pNode->FindParent()) { if (pNode->FindParent()->ShouldITransformWithChildren()) { bRegen = FALSE; } } pNode = Sel.FindNext(pNode, FALSE); } } else { // re-format all text nodes under me ReleaseCached(); pOp->DoInvalidateRegion(pSpread, BoundingRectangle); BevelTools::GetAllNodesUnderNode(this, &TextNodeList, CC_RUNTIME_CLASS(TextStory)); pItem = (NodeListItem *)TextNodeList.GetHead(); while (pItem) { if (pItem->pNode) { PrePostTextAction::DoFormatStory(pOp, (TextStory *) pItem->pNode, TRUE); } pItem = (NodeListItem *)TextNodeList.GetNext(pItem); } TextNodeList.DeleteAll(); bRegen = TRUE; bCache = FALSE; } } else if (pOp->IsKindOf(CC_RUNTIME_CLASS(OpApplyAttrib))) { // has there been a change which causes a geometry change ? // if so, we need to regen CCRuntimeClass * pClass = ((OpApplyAttrib *)pOp)->GetValueChangeType(); if (pClass) { if (pClass->IsKindOf(CC_RUNTIME_CLASS(AttrTxtBase)) || pClass->IsKindOf(CC_RUNTIME_CLASS(AttrBevelIndent)) || pClass->IsKindOf(CC_RUNTIME_CLASS(AttrTranspFillGeometry)) || pClass->IsKindOf(CC_RUNTIME_CLASS(AttrLineWidth)) || pClass->IsKindOf(CC_RUNTIME_CLASS(AttrStrokeColour)) || pClass->IsKindOf(CC_RUNTIME_CLASS(AttrStartCap)) || pClass->IsKindOf(CC_RUNTIME_CLASS(AttrStartArrow)) || pClass->IsKindOf(CC_RUNTIME_CLASS(AttrDashPattern)) || pClass->IsKindOf(CC_RUNTIME_CLASS(AttrEndArrow)) || pClass->IsKindOf(CC_RUNTIME_CLASS(AttrJoinType)) || pClass->IsKindOf(CC_RUNTIME_CLASS(AttrStrokeColourChange)) || pClass->IsKindOf(CC_RUNTIME_CLASS(AttrStrokeTranspChange)) || pClass->IsKindOf(CC_RUNTIME_CLASS(AttrStrokeTranspTypeChange)) ) { bRegen = TRUE; } else { bRegen = FALSE; } } } // this operation is called for changes in fills (e.g. dragging a linear fill blob, // and then dropping it // we need to stop regeneration for normal fills, but regen for transparency fills else if (pOp->IsKindOf(CC_RUNTIME_CLASS(OpReplaceAttributes))) { // find out the op name String_256 OpName; // don't do colour fills, but do transparent fills NodeAttribute * pAttr = ((OpReplaceAttributes *)pOp)->GetAttribute(); if (pAttr) { if (pAttr->IsAFillAttr()) { bRegen = FALSE; } if (pAttr->IsATranspFill()) { bRegen = TRUE; } } } else if (pOp->IsKindOf(CC_RUNTIME_CLASS(OpDelete))) { bRegen = FALSE; } else if (pOp->IsKindOf(CC_RUNTIME_CLASS(OpCopy))) { bRegen = FALSE; } else if (pOp->IsKindOf(CC_RUNTIME_CLASS(OpCut))) { bRegen = FALSE; } else if (pOp->IsKindOf(CC_RUNTIME_CLASS(OpPaste))) { bRegen = FALSE; } else if (pOp->IsKindOf(CC_RUNTIME_CLASS(OpTextFormat))) { // indicates a change in the text (e.g. the user has typed in) // find out if my children have all disappeared // invalidate and hide me Bounds.lo.x = BoundingRectangle.lo.x - BlobSize; Bounds.lo.y = BoundingRectangle.lo.y - BlobSize; Bounds.hi.x = BoundingRectangle.hi.x + BlobSize; Bounds.hi.y = BoundingRectangle.hi.y + BlobSize; ReleaseCached(); pOp->DoInvalidateRegion(pSpread, Bounds); // regen if we've still got a valid interior if (GetInsideBoundingRect().IsEmpty()) { bRegen = FALSE; } else { bRegen = TRUE; } bCache = TRUE; } else if (pOp->IsKindOf(CC_RUNTIME_CLASS(OpDeleteTextStory))) { // indicates that a text story has been deleted - so if my inside // bounding rect is empty then hide me if (GetInsideBoundingRect().IsEmpty()) { // hide me ! pOp->DoHideNode(this, TRUE, NULL, TRUE); bRegen = FALSE; } } else { // regen for all other ops bRegen = TRUE; } if (bRegen) { // Document * pDoc = Document::GetCurrent(); pOp->DoInvalidateRegion(pSpread, Bounds); // Invalidate original bounds BOOL bDidRegen = RegenerateNode(pOp, bCache, FALSE); // ForceRedraw is called internally by RegenerateNode... if (bDidRegen) { ReleaseCached(); pOp->DoInvalidateRegion(pSpread, GetBoundingRect()); } } } else { // were being called by with a pOp of NULL (i.e. the change is occuring // in a none undoable way) // NOTE: we need to regenerate here as well, because we will be called with // a pOp of NULL when being called through OpRepeatApplyAttribToSelected .... // Document * pDoc = Document::GetCurrent(); RegenerateNode(NULL, bCache, FALSE); // ForceRedraw is called internally by RegenerateNode... // we should also probably check whether it really is OpRepeatApplyAttribToSelected // that is causing this block of code to execute (BUT, there is no way of doing this!) } } // exit (bearing in mind this has probably been called by my inherited classes) return NodeRenderableInk::OnChildChange(pParam); } /********************************************************************************************* > BOOL NodeCompound::RegenChildren() Author: David_McClarnon (Xara Group Ltd) Created: 11/5/99 Purpose: Calls all the regenerate functions on all children (bottom to top) Note: SeeAlso: **********************************************************************************************/ /* BOOL NodeCompound::RegenChildren(UndoableOperation * pOp) { // runs through all children doing a regeneration RegenerateNode(pOp); return TRUE; } */ /********************************************************************************************* > BOOL NodeCompound::RegenerateNode(Node * pNode, UndoableOperation * pOp) Author: David_McClarnon (Xara Group Ltd) Created: 11/5/99 Purpose: Recursive function to regenerate all nodes Note: SeeAlso: **********************************************************************************************/ /* BOOL NodeCompound::RegenerateNode(Node * pNode, UndoableOperation * pOp) { Node * pChildNode = pNode->FindFirstChild(); while (pChildNode) { if (!RegenerateNode(pChildNode, pOp)) return FALSE; pChildNode = pChildNode->FindNext(); } return pNode->RegenerateNode(pOp); } */ /******************************************************************************************** > void NodeCompound::SetSelected(BOOL Status) Author: Karim_MacDonald (Xara Group Ltd) Created: 17/07/2000 Inputs: Status whether we should become selected or deselected. Outputs: If (Status) then all children are selected. Purpose: Selects this node. Note that in most cases you'll want to call Select() or DeSelect(), which are virtual methods in NodeRenderable. This override of Node::SetSelected() contains special case code for the usual suspects - shadows, bevels and contours - which sometimes behave like groups, but try to fool the user into thinking they aren't. Any attempt to select NodeBevelController or its sisters of murphy will result in their *children* being selected instead. It's generally better to use Ranges with PromoteToParent to do rendering and similar with these nodes - eg to iterate over the selection, don't step through the tree, querying each node - use a SelRange instead. It should be quicker, and you can choose to have controller nodes automatically included. ********************************************************************************************/ void NodeCompound::SetSelected(BOOL Status) { // normal nodes get a normal set-selected. if (PromoteHitTestOnChildrenToMe() || !Status) Node::SetSelected(Status); // contour, bevel & shadow controller nodes instead // select all of their ink-children (arbitrary). // NOTE - doesn't happen if Status is FALSE. else { Node* pKid = FindFirstChild(); while (pKid != NULL) { if (pKid->IsAnObject()) ((NodeRenderableInk*)pKid)->SetSelected(Status); pKid = pKid->FindNext(); } } } // Karim 17/07/2000 - I left these in for a while in case I needed to implement them within // this class. It seems like I don't, so they should probably be removed at some point. // This will cause a large rebuild due to their declarations in nodecomp.h . // Simon 9/8/2000 - The controllers (bev/shadow/contour) never want to be drawn so dont try it matey void NodeCompound::Select(BOOL ReDraw) { NodeRenderableInk::Select(ReDraw && PromoteHitTestOnChildrenToMe()); } void NodeCompound::DeSelect(BOOL ReDraw, BOOL bDeselectChildren) { NodeRenderableInk::DeSelect(ReDraw && PromoteHitTestOnChildrenToMe(), bDeselectChildren); } /******************************************************************************************** > virtual DocRect NodeCompound::GetInsideBoundingRect() Author: David_McClarnon (Xara Group Ltd) Created: 21/6/99 Inputs: Purpose: Gets the bounding rectangle of all nodes under me, except for the 'needs parent' nodes SeeAlso: ********************************************************************************************/ DocRect NodeCompound::GetInsideBoundingRect() { Node * pNode = FindFirstChild(); DocRect dr; while (pNode) { if (pNode->IsAnObject()) { if (!pNode->NeedsParent(NULL)) { dr = dr.Union(((NodeRenderableInk *)pNode)->GetBoundingRect(FALSE, FALSE)); } } pNode = pNode->FindNext(); } return dr; } /******************************************************************************************** > void NodeCompound::PreInformParentsOfRegenerate() Author: David_McClarnon (Xara Group Ltd) Created: 6/9/99 Inputs: None Purpose: Informs all parents of a regeneration of the node Notes: Call after the RegenerateNode function in each compound node SeeAlso: ********************************************************************************************/ void NodeCompound::PreInformParentsOfRegenerate() { ObjChangeFlags flgs; flgs.RegenerateNode = TRUE; ObjChangeParam OP(OBJCHANGE_STARTING, flgs, this, NULL, OBJCHANGE_CALLEDBYCHILD); Node * pParent = FindParent(); while (pParent) { pParent->OnChildChange(&OP); pParent = pParent->FindParent(); } return; } /******************************************************************************************** > virtual void NodeCompound::PostInformParentsOfRegenerate() Author: David_McClarnon (Xara Group Ltd) Created: 6/9/99 Inputs: None Purpose: Informs all parents of a regeneration of the node Notes: Call after the RegenerateNode function in each compound node SeeAlso: ********************************************************************************************/ void NodeCompound::PostInformParentsOfRegenerate() { ObjChangeFlags flgs; flgs.RegenerateNode = TRUE; ObjChangeParam OP(OBJCHANGE_FINISHED, flgs, this, NULL, OBJCHANGE_CALLEDBYCHILD); Node * pParent = FindParent(); while (pParent) { if (pParent->IsAnObject()) { pParent->OnChildChange(&OP); } pParent = pParent->FindParent(); } return; } /******************************************************************************************** > NodeCompound * NodeCompound::FindAssociatedBlendNode(BlendNodeParam * pParam) Author: David_McClarnon (Xara Group Ltd) Created: 5/3/2000 Inputs: The blend node param (see blender & blendatt.h) Returns: The node compound that has been found Purpose: Gets the associated compound node I am to blend to, in the end blend path structure in the blend node param NULL if non found Notes: Only applies to blends Use in conjunction with overrided Blend functions in derived classes SeeAlso: ********************************************************************************************/ NodeCompound * NodeCompound::FindAssociatedBlendNode(BlendNodeParam * pParam) { // count how many parents I have Node * pParent = FindParent(); UINT32 NumParents = 0; UINT32 NumSameNodesAbove = 0; while (pParent) { if (pParent->GetRuntimeClass() == this->GetRuntimeClass()) { NumSameNodesAbove++; } NumParents ++; pParent = pParent->FindParent(); } UINT32 NumSameNodesBelow = 0; Node * pChild = FindFirstChild(CC_RUNTIME_CLASS(NodeCompound)); while (pChild) { if (pChild->GetRuntimeClass() == this->GetRuntimeClass()) { NumSameNodesBelow ++; } pChild = pChild->FindFirstChild(CC_RUNTIME_CLASS(NodeCompound)); } // thus deduce how many similar nodes to myself are in the tree (including myself) UINT32 NumSameNodesInStartTree = NumSameNodesAbove + NumSameNodesBelow + 1; // ok, lets search the end node for similar nodes Node * pEndNode = pParam->GetEndBlendPath()->GetBlendNode(); // if the end node isn't a compound node, then I can't find anything if (!pEndNode->IsCompoundClass()) return NULL; // find the node with the closest number of parents to mine Node * pEndNodeStep = pEndNode; // build a list of all similar nodes in the end tree List EndNodeList; NodeListItem * pItem = NULL; while (pEndNodeStep) { if (pEndNodeStep->GetRuntimeClass() == this->GetRuntimeClass()) { pItem = new NodeListItem(pEndNodeStep); ERRORIF(pItem == NULL, _R(IDE_NOMORE_MEMORY), NULL); EndNodeList.AddTail(pItem); } pEndNodeStep = pEndNodeStep->FindFirstChild(CC_RUNTIME_CLASS(NodeCompound)); } // ok, now we have a list of nodes we have found in the end node list, // and we know how many exist above me in the start node list // therefore, pick a node in the list to match to if (EndNodeList.IsEmpty()) { // can't match to any ! return NULL; } NodeCompound * pRetnNode = NULL; if (NumSameNodesInStartTree == EndNodeList.GetCount()) { // 1 to 1 relationship therefore easy ! UINT32 Count = 0; pItem = (NodeListItem *)EndNodeList.GetHead(); while (pItem) { if (Count == NumSameNodesAbove) { // get the node to return pRetnNode = (NodeCompound *)pItem->pNode; EndNodeList.DeleteAll(); return pRetnNode; } } ERROR3("Number of nodes in list isn't the same as the number of nodes in start tree"); EndNodeList.DeleteAll(); return NULL; } EndNodeList.DeleteAll(); // many to one and one to many relations are not implemented yet return NULL; } /******************************************************************************************** > virtual NodeRenderableInk * NodeCompound::GetNodeToBlend() Author: David_McClarnon (Xara Group Ltd) Created: 6/9/99 Inputs: None Purpose: Gets the blend node inside of this compound node Notes: Only applies to blends SeeAlso: ********************************************************************************************/ NodeRenderableInk * NodeCompound::GetNodeToBlend() { Node * pChild = FindFirstChild(); while (pChild) { if (pChild->IsAnObject()) { if (!pChild->NeedsParent(NULL)) { // we've found it if (pChild->IsCompoundClass()) { return ((NodeCompound*)pChild)->GetNodeToBlend(); } return (NodeRenderableInk*)pChild; } } pChild = pChild->FindNext(); } // failure ! return NULL; } /********************************************************************************************* > BOOL NodeCompound::AllocBlendConsList (ListType listType) Author: Chris_Snook (Xara Group Ltd) Created: 22/6/2000 Inputs: The type of list to allocate Outputs: Returns: - Purpose: Allocates the relevant list Errors: If the blendConstructedCompoundChildren list has already been allocated. **********************************************************************************************/ BOOL NodeCompound::AllocBlendConsList (ListType listType) { #ifdef _DEBUG List* accessList = InitListPtr (listType); if (accessList != NULL) { ERROR3IF (accessList->GetCount () > 0, "Trying to re-allocate a list that still has items in it!"); } #endif switch (listType) { case LT_BECOMEA_SHADOWSLIST: blndConsBecomeAShadows = new List; break; case LT_BECOMEA_BEVELSLIST: blndConsBecomeABevels = new List; break; case LT_BECOMEA_CONTOURSLIST: blndConsBecomeAContours = new List; break; default: ERROR3 ("NodeCompound::AllocBlendConsList - You wan't what??? !!!"); return (TRUE); } return (TRUE); } /********************************************************************************************* > BOOL NodeCompound::AllocatedBlendConsList (ListType listType) Author: Chris_Snook (Xara Group Ltd) Created: 22/6/2000 Inputs: The type of list to check Outputs: Returns: TRUE if list is not empty, FALSE otherwise Purpose: Determins whether blending has generated intermediate compound nodes Errors: - **********************************************************************************************/ BOOL NodeCompound::AllocatedBlendConsList (ListType listType) { List* accessList = InitListPtr (listType); if (accessList == NULL) { return (FALSE); } else { if (accessList->GetCount () > 0) { return (TRUE); } } return (FALSE); } /********************************************************************************************* > BOOL NodeCompound::KillBlendConsList (ListType listType, BOOL killAll) Author: Chris_Snook (Xara Group Ltd) Created: 22/6/2000 Inputs: The type of list that we are killing killAll - do we kill all items in the list, or just the ones that are a part of the current document? Outputs: Returns: TRUE if success, FALSE otherwise Purpose: Clears out the list of nodes that were generated when blending compound nodes. Errors: ERROR3IF the list == NULL **********************************************************************************************/ BOOL NodeCompound::KillBlendConsList (ListType listType, BOOL killAll, BOOL isPrinting) { List* accessList = InitListPtr (listType); ERROR3IF(accessList == NULL, "KillBlendConsList (): Trying to delete an empty list! - fruitless function call!"); NodeListItemWithDocPtr* pCurrent = (NodeListItemWithDocPtr*) accessList->GetHead (); if (!killAll) { if (!isPrinting) { // user has closed a document - clear out any data that was required for rendering Document* pCurrentDoc = Document::GetCurrent (); ERROR3IF (pCurrentDoc == NULL, "No current document!"); while (pCurrent) { if (pCurrent->pOwner == pCurrentDoc) { NodeListItemWithDocPtr* pNext = (NodeListItemWithDocPtr*) accessList->GetNext (pCurrent); NodeListItemWithDocPtr* markedForDelete = (NodeListItemWithDocPtr*) accessList->RemoveItem (pCurrent); markedForDelete->pNode->CascadeDelete (); delete (markedForDelete->pNode); pCurrent = pNext; } else { // the node is NOT linked to the current document - so we can't delete it .... pCurrent = (NodeListItemWithDocPtr*) accessList->GetNext (pCurrent); } } } else { // were printing .... while (pCurrent) { if (pCurrent->isForPrinting) { NodeListItemWithDocPtr* pNext = (NodeListItemWithDocPtr*) accessList->GetNext (pCurrent); NodeListItemWithDocPtr* markedForDelete = (NodeListItemWithDocPtr*) accessList->RemoveItem (pCurrent); markedForDelete->pNode->CascadeDelete (); delete (markedForDelete->pNode); pCurrent = pNext; } else { // the node was not generated for printing - so we can't delete it .... pCurrent = (NodeListItemWithDocPtr*) accessList->GetNext (pCurrent); } } } } else { // camelot is dying - clear out all data .... while (pCurrent) { pCurrent->pNode->CascadeDelete (); delete (pCurrent->pNode); pCurrent = (NodeListItemWithDocPtr*) accessList->GetNext (pCurrent); } accessList->DeleteAll (); delete (accessList); accessList = NULL; switch (listType) { case LT_BECOMEA_SHADOWSLIST: blndConsBecomeAShadows = NULL; break; case LT_BECOMEA_BEVELSLIST: blndConsBecomeABevels = NULL; break; case LT_BECOMEA_CONTOURSLIST: blndConsBecomeAContours = NULL; break; default: ERROR3 ("NodeCompound::AllocBlendConsList - You wan't what??? !!!"); return (FALSE); } } return (TRUE); } /********************************************************************************************* > BOOL NodeCompound::KillBlendConsListItem (ListType listType, NodeBlend* nodeBlend) Author: Chris_Snook (Xara Group Ltd) Created: 22/6/2000 Inputs: The type of list that we are killing, the blend that the item is associated with Outputs: Returns: TRUE if success, FALSE otherwise Purpose: Clears out the list of nodes that were generated when blending compound nodes which are associated (i.e. created) with the supplied NodeBlend NOTE: The associated blend node is set by calling NodeCompound::SetBlenderNode () Errors: ERROR3IF the list == NULL **********************************************************************************************/ BOOL NodeCompound::KillBlendConsListItem (ListType listType, NodeBlend* nodeBlend) { List* accessList = InitListPtr (listType); ERROR3IF(accessList == NULL, "KillBlendConsList (): Trying to delete an empty list! - fruitless function call!"); NodeListItemWithDocPtr* pCurrent = (NodeListItemWithDocPtr*) accessList->GetHead (); while (pCurrent) { if (pCurrent->pNode->IsCompoundClass()) { NodeCompound* ptrGroup = (NodeCompound*) pCurrent->pNode; NodeBlend* ptrBlender = (NodeBlend*) ptrGroup->GetBlenderNode(); if (ptrBlender == nodeBlend) { NodeListItemWithDocPtr* pNext = (NodeListItemWithDocPtr*) accessList->GetNext (pCurrent); NodeListItemWithDocPtr* markedForDelete = (NodeListItemWithDocPtr*) accessList->RemoveItem (pCurrent); markedForDelete->pNode->CascadeDelete (); delete (markedForDelete->pNode); pCurrent = pNext; } else { pCurrent = (NodeListItemWithDocPtr*) accessList->GetNext (pCurrent); } } else { pCurrent = (NodeListItemWithDocPtr*) accessList->GetNext (pCurrent); } } return (TRUE); } /********************************************************************************************* > BOOL NodeCompound::KillAllBlendBecomeAConsLists (BOOL killAll = FALSE) Author: Chris_Snook (Xara Group Ltd) Created: 29/6/2000 Inputs: killAll - do we kill all items in the list, or just the ones that are a part of the current document? Outputs: Returns: TRUE if success, FALSE otherwise Purpose: Clears out the list of nodes that were generated when blending compound nodes (that were instructed to DOBECOMEA). Errors: **********************************************************************************************/ BOOL NodeCompound::KillAllBlendBecomeAConsLists (BOOL killAll /*= FALSE*/, BOOL isPrinting /*= FALSE*/) { if (NodeCompound::AllocatedBlendConsList (LT_BECOMEA_BEVELSLIST)) { NodeCompound::KillBlendConsList (LT_BECOMEA_BEVELSLIST, killAll, isPrinting); } if (NodeCompound::AllocatedBlendConsList (LT_BECOMEA_CONTOURSLIST)) { NodeCompound::KillBlendConsList (LT_BECOMEA_CONTOURSLIST, killAll, isPrinting); } if (NodeCompound::AllocatedBlendConsList (LT_BECOMEA_SHADOWSLIST)) { NodeCompound::KillBlendConsList (LT_BECOMEA_SHADOWSLIST, killAll, isPrinting); } return (TRUE); } /********************************************************************************************* > BOOL NodeCompound::KillAllBlendBecomeAConsListItem () Author: Chris_Snook (Xara Group Ltd) Created: 29/6/2000 Inputs: The blend that the item is associated with Outputs: Returns: TRUE if success, FALSE otherwise Purpose: Clears out the list of nodes (dobecomea) that were generated when blending compound nodes which are associated (i.e. created) with the supplied NodeBlend NOTE: The associated blend node is set by calling NodeCompound::SetBlenderNode () **********************************************************************************************/ BOOL NodeCompound::KillAllBlendBecomeAConsListItem (NodeBlend* associatedWith) { if (NodeCompound::AllocatedBlendConsList (LT_BECOMEA_BEVELSLIST)) { TRACEUSER( "ChrisS", _T("Invoking delete all on CALLBACK BEVELS\n") ); NodeCompound::KillBlendConsListItem (LT_BECOMEA_BEVELSLIST, associatedWith); } if (NodeCompound::AllocatedBlendConsList (LT_BECOMEA_CONTOURSLIST)) { TRACEUSER ("ChrisS", _T("Invoking delete all on CALLBACK CONTOURS\n") ); NodeCompound::KillBlendConsListItem (LT_BECOMEA_CONTOURSLIST, associatedWith); } // shadows MUST be deleted last (cause they could be shadowing a bevel, and if we // calls cascade delete on the shadow then this also deletes the bevel (leaving the // bevels list bevel vaped - but the list reference to it still valid). if (NodeCompound::AllocatedBlendConsList (LT_BECOMEA_SHADOWSLIST)) { TRACEUSER ("ChrisS", _T("Invoking delete all on CALLBACK SHADOWS\n") ); NodeCompound::KillBlendConsListItem (LT_BECOMEA_SHADOWSLIST, associatedWith); } return (TRUE); } /********************************************************************************************* > BOOL NodeCompound::BlendConsListInsert (ListType listType, Node* insertNode) Author: Chris_Snook (Xara Group Ltd) Created: 22/6/2000 Inputs: The type of list to insert into, and the node to be inserted Outputs: Returns: TRUE Purpose: Inserts a blends intermediate node into our list. This is necessary so that they may be deleted at the correct time. Errors: ERROR3IF insertItem == NULL, ERROR3IF list == NULL **********************************************************************************************/ BOOL NodeCompound::BlendConsListInsert (ListType listType, Node* insertNode) { ERROR3IF(insertNode == NULL, "BlendConsListInsert (): Trying to insert a NULL item into a list!"); List* accessList = InitListPtr (listType); ERROR3IF(accessList == NULL, "BlendConsListInsert (): Trying to insert item into a list which is NULL!"); NodeListItemWithDocPtr* pInsert = new NodeListItemWithDocPtr (); if (pInsert) // and insert into the paths list .... { pInsert->pNode = insertNode; pInsert->pOwner = Document::GetCurrent (); pInsert->isForPrinting = isForPrinting; accessList->AddHead (pInsert); TRACEUSER ("ChrisS", _T("Blend constructed list just inserted a node\n") ); } return (TRUE); } /********************************************************************************************* > List* NodeCompound::InitListPtr (ListType listType) Author: Chris_Snook (Xara Group Ltd) Created: 22/6/2000 Inputs: The type of list to retrieve Outputs: Returns: Ptr to the relevant list Purpose: Returns a ptr to the relevant list of nodes (that when allocated when blending). Errors: - **********************************************************************************************/ List* NodeCompound::InitListPtr (ListType listType) { List* list = NULL; switch (listType) { case LT_BECOMEA_SHADOWSLIST: list = blndConsBecomeAShadows; break; case LT_BECOMEA_BEVELSLIST: list = blndConsBecomeABevels; break; case LT_BECOMEA_CONTOURSLIST: list = blndConsBecomeAContours; break; default: list = NULL; } return (list); } /******************************************************************************************** > virtual INT32 NodeCompound::EstimateNodeComplexity(OpParam* details) Author: Chris_Snook (Xara Group Ltd) Created: 6/09/2000 Inputs: details any data that should be used for the calculation Outputs: - Returns: an estimate of the nodes complexity Purpose: This function estimates a complexity value for the node. The complexity value is based upon the total length of all paths in the node. See Also: OpBlendNodes::DeterminBlendObjectsProcessorHit () ********************************************************************************************/ INT32 NodeCompound::EstimateNodeComplexity(OpParam* details) { INT32 total = 0; // Call DoBecomeA on all the group's children Node* Current = FindFirstChild(); Node* Next; while (Current != NULL) { Next = Current->FindNext(); total += Current->EstimateNodeComplexity (details); Current = Next; } return (total); } /******************************************************************************************** > virtual BOOL NodeCompound::HasVisibleChildren() const Author: Phil_Martin (Xara Group Ltd) Created: 01/04/2005 Inputs: - Outputs: - Returns: TRUE if this compound is empty and can thus be deleted or hidden without the user seeing any difference Purpose: Determine whether this compound node is really doing anything useful ********************************************************************************************/ BOOL NodeCompound::HasVisibleChildren() const { if (!this->IsRenderable()) return FALSE; Node* pChild = FindFirstChild(); while (pChild) { if (pChild->IsAnObject() && pChild->IsRenderable()) { if (!pChild->NeedsParent((Node*)this)) return TRUE; } pChild = pChild->FindNext(); } return FALSE; } /******************************************************************************************** > DocRect NodeCompound::GetExtendTargetBounds(const ExtendParams& ExtParams) Author: Karim_MacDonald (Xara Group Ltd) Created: 02/06/2000 Inputs: ExtParams const ref to an ExtendParams class describing the extension. Outputs: Returns: The DocRect to treat as the bounding rect of this node when extending. Purpose: Return a DocRect which contains the bounds of this node as defined and required by the Extending mechanism. We treat x- and y- directions separately, and we always return our true bounds, unless we're extending in a direction, in which case we return the bounds of our extend control points, which currently means the union of the ExtendTargetBounds of our children. Notes: Note that we're assuming that we always have kids. No kids => pear-shapey. See also: SliceHelper::BoundingNodeSize(). ********************************************************************************************/ DocRect NodeCompound::GetExtendTargetBounds(const ExtendParams& ExtParams) { DocRect drBounds(0, 0, 0, 0); Node* pKid = FindFirstChild(); while (pKid != NULL) { if (pKid->IsAnObject()) drBounds = drBounds.Union(((NodeRenderableInk*)pKid)->GetExtendTargetBounds(ExtParams)); } return drBounds; } /******************************************************************************************** > virtual String_32& NodeCompound::GetName() Author: Simon_Maneggio (Xara Group Ltd) Created: 29/6/94 Inputs: - Outputs: - Returns: The name of the group Purpose: This routine is used to obtain the name of a group Errors: - SeeAlso: - ********************************************************************************************/ String_32& NodeCompound::GetName() { return CompoundName; } /******************************************************************************************** > virtual void SetName(String_32& Name) Author: Simon_Maneggio (Xara Group Ltd) Created: 29/6/94 Inputs: Name: The new group name Outputs: - Returns: - Purpose: For setting the group's name Errors: - SeeAlso: - ********************************************************************************************/ void NodeCompound::SetName(String_32& Name) { CompoundName = Name; } /******************************************************************************************** > virtual BOOL NodeCompound::AllowOp_AccountForCompound(ObjChangeParam* pParam, BOOL SetOpPermissionState, BOOL DoPreTriggerEdit) Author: Karim_MacDonald (Xara Group Ltd) Created: 24/10/2000 Inputs: pParam information about the requested change, eg which Op. SetOpPermissionState whether flags should be set ready for OnChildChange. DoPreTriggerEdit if TRUE then NameGallery::PreTriggerEdit is called. *Must* be TRUE if the calling Op may make any nodes change their bounds, eg move, line width, cut. Use TRUE if unsure. Returns: TRUE if ANY of the objects in the compound node have allowed the op. Purpose: Overrides Node::AllowOp_AccountForCompound - see definition of that function for a general explanation. Controller nodes need to inform all of their other children whenever one child is queried via AllowOp. This is because unlike a normal group, editing one child may well affect its siblings, who must be informed of the edit. See Also: Node::AllowOp, Node::AllowOp_AccountForCompound Notes: Karim 20/12/2000 Added the OpTextFormat bodge for feathers on bevelled text - sorry! ********************************************************************************************/ BOOL NodeCompound::AllowOp_AccountForCompound(ObjChangeParam* pParam, BOOL SetOpPermissionState, BOOL DoPreTriggerEdit) { BOOL AnyAllowed = FALSE; Node* pCallingChild = (pParam->GetDirection() == OBJCHANGE_CALLEDBYCHILD) ? pParam->GetCallingChild() : NULL; pParam->SetCallingChild(NULL); ObjChangeFlags Flags = pParam->GetChangeFlags(); UndoableOperation* pChangeOp = pParam->GetOpPointer(); if( Flags.Attribute || Flags.TransformNode || Flags.RegenerateNode || ( pChangeOp != NULL && pChangeOp->IS_KIND_OF( OpTextUndoable ) ) ) { ObjChangeDirection OldDirection = pParam->GetDirection(); pParam->SetDirection(OBJCHANGE_CALLEDBYPARENT); Node* pNode = FindFirstChild(); BOOL InformGeomLinkedAttrs = SetOpPermissionState && pChangeOp != NULL && pChangeOp->MayChangeNodeBounds(); while (pNode != NULL) { if (pNode != pCallingChild) { if (pNode->IsAnObject()) AnyAllowed |= pNode->AllowOp(pParam, SetOpPermissionState, DoPreTriggerEdit); else { if (pNode->IsAnAttribute() && ((NodeAttribute*)pNode)->IsLinkedToNodeGeometry()) if (InformGeomLinkedAttrs) ((NodeAttribute*)pNode)->LinkedNodeGeometryHasChanged(pChangeOp); } } pNode = pNode->FindNext(); } pParam->SetDirection(OldDirection); } // if setting flags and any child allowed it, set this permission allowed if (SetOpPermissionState && AnyAllowed) SetOpPermission(PERMISSION_ALLOWED); return AnyAllowed; } /******************************************************************************************** > virtual BOOL NodeCompound::CanBecomeA(BecomeA* pBecomeA) Author: Simon_Maneggio (Xara Group Ltd) Created: 29/4/94 Inputs: InkClass: The class of object pNumObjects = ptr to place number of objects of type pClass that will be created (Note: can be NULL). *pNumObects in undefined on entry Outputs: - Returns: TRUE if the node, or any of its children can transmogrify themselves to become an InkClass object Purpose: This function is used by the convert to shapes operation. It determines if the node or any of its children can convert themselves into an InkClass object. The number you put into pNumObjects (if it's not NULL) should exactly equal the total number of pClass objects you create. It should NOT contain any additional objects you may produce such as group objects for containing the pClass object, or attributes. Also, the entry value of *pNumObjects cannot be assumed to be 0. Errors: - ********************************************************************************************/ BOOL NodeCompound::CanBecomeA(BecomeA* pBecomeA) { BOOL bOK=FALSE; Node* Current = FindFirstChild(); while (Current != NULL) { if (Current->CanBecomeA(pBecomeA)) // Increments count { bOK = TRUE; if (!pBecomeA->IsCounting()) return TRUE; // Don't want total, so return now } Current = Current->FindNext(); } return bOK; } /******************************************************************************************** > virtual BOOL NodeCompound::DoBecomeA(BecomeA* pBecomeA) Author: Simon_Maneggio (Xara Group Ltd) (and Markn) Created: 29/4/94 Inputs: pBecomeA = ptr to a class that contains all the info needed to become a new type of node. Outputs: - Returns: TRUE if the object has been transformed, FALSE if we run out of memory Purpose: Transforms the object into another type of object. Note: changed 7/10/94 by MarkN to take the pBecomeA param, so that more data could be passed to these functions in the future without causing massive rebuilds due to the editing of node.h 14/6/95 (Markn): This now localises attributes before calling it's children, then factors them out at the end. This is so that common attrs trickle down to ALL leaf nodes, no matter how deep they may be. This is important when a child node is a blend, because the action of DoBecomeA() in the blend's case creates new child attrs for the path it creates, hence making any common attrs illegal. Errors: - SeeAlso: Node::CanBecomeA ********************************************************************************************/ BOOL NodeCompound::DoBecomeA(BecomeA* pBecomeA) { ERROR2IF(pBecomeA == NULL,FALSE,"NULL BecomeA ptr"); UndoableOperation* pOp = pBecomeA->GetUndoOp(); BOOL ok; if (pBecomeA->GetReason() == BECOMEA_REPLACE) { // ERROR2IF(pBecomeA->GetUndoOp() == NULL,FALSE,"Trying to replace a NodeGroup, but pUndoOp == NULL"); // Make all attrs children of the child nodes if (pOp) ok = pOp->DoLocaliseCommonAttributes(this); else ok = LocaliseCommonAttributes(); if (!ok) return FALSE; } // Call DoBecomeA on all the group's children Node* Current = FindFirstChild(); Node* Next; while (Current != NULL) { Next = Current->FindNext(); if(!(Current->DoBecomeA(pBecomeA))) { return FALSE; // Out of memory } Current = Next; } if (pBecomeA->GetReason() == BECOMEA_REPLACE) { // ERROR2IF(pBecomeA->GetUndoOp() == NULL,FALSE,"Trying to replace a NodeGroup, but pUndoOp == NULL"); // Factor out the attrs (needed after a call to DoLocaliseCommonAttributes if (pOp) ok = pOp->DoFactorOutCommonChildAttributes(this); else ok = FactorOutCommonChildAttributes(); if (!ok) return FALSE; } return TRUE; } /******************************************************************************************** > virtual NodeGroup* NodeCompound::BecomeAGroup(UndoableOperation* pUndoOp) Author: Karim_MacDonald (Xara Group Ltd) Created: 20/07/2000 Inputs: pUndoOp ptr to an Op to use for all the undoable bits. Outputs: This node is converted into a group. Returns: A pointer to the new group node if successful, NULL otherwise. Purpose: This method is for the convenience of any compound node descended from NodeGroup. It 'changes' the node into a group by following these steps: * Attributes are undoably localised on this node. * A group node is created. * All children of this node are undoably moved under the group node. * The group is undoably inserted into the tree as the next sibling. * Attributes are undoably factored out on the group node. * This node is undoably hidden. Notes: The group's selection state is maintained - if this node is selected now, then the new group will be selected - although this may not be enough for some nodes, eg NodeShadowController et al. Although calling this method on a normal NodeGroup should have no visible effect, it *is* wasteful of time and memory. So DON'T call this method on a plain NodeGroup! Errors: We require a valid Op ptr, so will ERROR3 and return FALSE if we don't get one (maybe this should be an ERROR2). ********************************************************************************************/ NodeGroup* NodeCompound::BecomeAGroup(UndoableOperation* pUndoOp) { // we can't do a thing without an Op... // if (pUndoOp == NULL) // { // ERROR3("NodeGroup::BecomeAGroup; NULL Op pointer!"); // return NULL; // } // localise any attributes into our children, to ensure that none of our direct children // are attributes. we do this because we'll be moving all our children, and directly // moving attrs is forbidden (see DoMoveNode() for more info). BOOL ok; if (pUndoOp) ok = pUndoOp->DoLocaliseCommonAttributes(this); else ok = LocaliseCommonAttributes(FALSE, FALSE, NULL); // create the new group node for our children. NodeGroup* pGroup = NULL; if (ok) { ALLOC_WITH_FAIL(pGroup, new NodeGroup, pUndoOp); ok = (pGroup != NULL); } // insert the new group into the tree as our right-sibling. if (ok) { if (pUndoOp) ok = pUndoOp->DoInsertNewNode(pGroup, this, NEXT, TRUE, FALSE, IsSelected(), FALSE); else pGroup->AttachNode(this, NEXT, FALSE, FALSE); } // move all our children into the new group. // some of our children may be non-optimisable attributes, which DoMoveNode() won't move. // In these cases, we non-undoably put copies of them under the new group node (which is // undoably inserted anyway, so the non-undoable stuff should be ok). if (ok) { Node* pNextKid = NULL; Node* pThisKid = FindFirstChild(); while (ok && pThisKid != NULL) { pNextKid = pThisKid->FindNext(); if (pThisKid->IsAnObject()) { if (pUndoOp) { CALL_WITH_FAIL(pUndoOp->DoMoveNode(pThisKid, pGroup, LASTCHILD), pUndoOp, ok) } else { pThisKid->MoveNode(pGroup, LASTCHILD); } if (ok && pThisKid->IsSelected()) pGroup->SetParentOfSelected(TRUE); } else if ( pThisKid->IsAnAttribute() && !((NodeAttribute*)pThisKid)->ShouldBeOptimized() ) CALL_WITH_FAIL(pThisKid->CopyNode(pGroup, LASTCHILD), pUndoOp, ok) pThisKid = pNextKid; } } // factor out attributes on the new group - this matches the localise call up top. if (ok) { if (pUndoOp) ok = pUndoOp->DoFactorOutCommonChildAttributes(pGroup); else ok = pGroup->FactorOutCommonChildAttributes(); } // invalidate the group's bounding box. if (ok) pGroup->InvalidateBoundingRect(); // finally, hide ourself. if (ok) { if (pUndoOp) ok = pUndoOp->DoHideNode(this, TRUE); else { CascadeDelete(); delete this; // Scary! } } return ok ? pGroup : NULL; } /******************************************************************************************** > virtual NodeRenderableInk* NodeCompound::GetInkNodeFromController() const Author: Diccon_Yamanaka (Xara Group Ltd) Created: 16/10/2000 Inputs: - Outputs: - Returns: The ink node that is used by bevels, shadows and contours Purpose: If we had a Controller node inherited class from NodeGroup then this function really belongs there. But we don't, so this function only returns anything if your node also returns TRUE to IsAController(). If so it will return the node that is shadowed, bevelled or contoured. SeeAlso: ********************************************************************************************/ NodeRenderableInk* NodeCompound::GetInkNodeFromController() const { return NULL; } /******************************************************************************************** > DocRect NodeCompound::GetChildBoundingRect(BOOL bIncludeAttrs = TRUE) Author: Phil_Martin (Xara Group Ltd) Created: 21/09/2004 Returns: The bounding box of the children of the live effect Purpose: Get the bounding rect of the children without updating BOundingRectangle or IsBOundingRectValid for this node... ********************************************************************************************/ DocRect NodeCompound::GetChildBoundingRect(BOOL bIncludeAttrs) { // just set it to be an empty rectangle DocRect BoundRect(0,0,0,0); Node* pNode = FindFirstChild(); while (pNode!=NULL) { // Add in the bounding rect of this node with all the others if (pNode->IsBounded()) BoundRect = BoundRect.Union(((NodeRenderableBounded*)pNode)->GetBoundingRect(!bIncludeAttrs)); // And find the next node pNode = pNode->FindNext(); } return BoundRect; } /******************************************************************************************** > virtual BOOL NodeCompound::HasEffectAttrs() const Author: Phil_Martin (Xara Group Ltd) Created: 11/01/2005 Returns: TRUE if the node has effect attributes FALSE otherwise Purpose: Detect Effect Attributes ********************************************************************************************/ BOOL NodeCompound::HasEffectAttrs() const { Node* pn = FindLastChild(); while (pn && !pn->IsBounded()) { if (pn->IsAnAttribute() && ((NodeAttribute*)pn)->IsEffectAttribute()) return TRUE; pn = pn->FindPrevious(); } return FALSE; } /******************************************************************************************** > virtual BOOL NodeCompound::ContainsEffects() const Author: Phil_Martin (Xara Group Ltd) Created: 17/08/2005 Returns: TRUE if the node contains effects nodes FALSE otherwise Purpose: Detect Effects ********************************************************************************************/ BOOL NodeCompound::ContainsEffects() const { Node* pn = FindFirstChild(); while (pn) { if (pn->IsEffect()) return TRUE; if (pn->IsCompoundClass() && ((NodeCompound*)pn)->ContainsEffects()) return TRUE; pn = pn->FindNext(); } return FALSE; } /******************************************************************************************** > void NodeCompound::TransformEffectAttrs(TransformBase& Trans) Author: Phil_Martin (Xara Group Ltd) Created: 30/06/2005 Inputs: Trans - The transform that should be applied to all the children Purpose: Transforms the Child nodes of this node. This allows fills etc to move with the objects. ********************************************************************************************/ void NodeCompound::TransformEffectAttrs(TransformBase& Trans) { // Find all Effect Attributes (in subtree after last bounded node // and transform them. Node* pn = FindLastChild(); while (pn && !pn->IsBounded()) { if (pn->IsAnAttribute() && ((NodeAttribute*)pn)->IsEffectAttribute()) ((NodeAttribute*)pn)->Transform( Trans ); pn = pn->FindPrevious(); } }