// $Id: effects_stack.cpp 859 2006-04-22 15:42:02Z 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============================ */ // // Implementation of Effects Stack. // #include "camtypes.h" // pre-compiled header #include "effects_stack.h" #include "nodeliveeffect.h" #include "opliveeffects.h" // dynamic class creation stuff. CC_IMPLEMENT_DYNAMIC(PPStackLevel, ListItem); CC_IMPLEMENT_DYNAMIC(EffectsStack, List); DECLARE_SOURCE("$Revision: 859 $"); #define new CAM_DEBUG_NEW /////////////////////////////////////////////////////////////////////////////////////////////// // The EffectsStack class // /////////////////////////////////////////////////////////////////////////////////////////////// /******************************************************************************************** > EffectsStack::EffectsStack() Author: Phil_Martin (Xara Group Ltd) Created: 17/11/2004 Purpose: Constructor. Errors: See also: ********************************************************************************************/ EffectsStack::EffectsStack() { bConsistent = FALSE; m_pNewLevelRange = NULL; } /******************************************************************************************** > EffectsStack::~EffectsStack() Author: Phil_Martin (Xara Group Ltd) Created: 17/11/2004 Purpose: Destructor. Errors: See also: ********************************************************************************************/ EffectsStack::~EffectsStack() { // We own m_pNewLevelRange so we must delete it... if (m_pNewLevelRange) { delete m_pNewLevelRange; m_pNewLevelRange = NULL; } Clear(); } /******************************************************************************************** > EffectsStack* EffectsStack::GetEffectsStackFromSelection(Range* pRange = NULL, BOOL bEscapeDerived = TRUE, BOOL bIncludeLocked = FALSE) Author: Phil_Martin (Xara Group Ltd) Created: 05/10/2004 Inputs: - Outputs: - Returns: pStack - pointer to ListRange representing stack applied consistently to selection Purpose: Get a list of LE nodes that are common to all selected nodes Only returns a list of ALL the selected nodes have identical stacks above them... ********************************************************************************************/ EffectsStack* EffectsStack::GetEffectsStackFromSelection(Range* pRange, BOOL bEscapeDerived, BOOL bIncludeLocked) { // Loop through selection ensuring that all nodes are LiveEffects // and that they all share the same edit list if (pRange==NULL) pRange = GetApplication()->FindSelection(); ENSURE(pRange, "!"); ENSURE(pRange->GetRangeControlFlags().PromoteToParent==FALSE, "GetEffectsStackFromSel given a bad range"); EffectsStack* pPPStack = new EffectsStack(); // New stack is marked as being "inconsistent" BOOL bStartNewStack = TRUE; ListRange* pLocalStack = NULL; BOOL bLiveEffectsAreApplied = FALSE; // Ensure that pEditedLE has a useful value... // Loop through the current selection and search the parents of each selected node for LiveEffects... Node* pNode = pRange->FindFirst(); Node* pLastNode = NULL; while (pNode!=NULL) { if (pNode->IsAnObject()) { // Don't try to add the same stack to the system more than once // (For instance NodeShadows and their originals are siblings and can both be selected // below a NodeShadowController) if (!NodesSharePPStack(pNode, pLastNode, TRUE, bIncludeLocked)) { // Get the local stack pLocalStack = GetEffectsStackFromNode(pNode, TRUE, bEscapeDerived, bIncludeLocked); // If this node is in the middle of a stack, find the whole stack if (pLocalStack) { bLiveEffectsAreApplied = TRUE; if (bStartNewStack) { if (pPPStack->IsEmpty()) { pPPStack->Initialise(pLocalStack); } else { BOOL bOK = pPPStack->Intersect(pLocalStack); if (!bOK) { delete pLocalStack; pPPStack->Clear(); // Just to make sure! return pPPStack; } } } delete pLocalStack; } else { // A node in the selection doesn't have any LiveEffects applied but we can't stop // because we have to keep scanning to see whether ANY nodes have LiveEffects // Clear the effect stack out because this node has none. pPPStack->Clear(); bStartNewStack = FALSE; // Could test for lengthy scanning here and if it takes too long return // if (taking too long) code = SELTYPE_UNKNOWN; } } pLastNode = pNode; } pNode = pRange->FindNext(pNode); } if (pPPStack->IsEmpty() && bLiveEffectsAreApplied) pPPStack->bConsistent = FALSE; else pPPStack->bConsistent = TRUE; return pPPStack; } /******************************************************************************************** > BOOL EffectsStack::Initialise(ListRange* pRange) Author: Phil_Martin (Xara Group Ltd) Created: 18/10/2004 Inputs: Outputs: Returns: Purpose: Errors: See also: ********************************************************************************************/ BOOL EffectsStack::Initialise(ListRange* pRange) { ENSURE(pRange, "EffectsStack::Initialise must be given a range"); Node* pNode = pRange->FindFirst(); while (pNode) { ENSURE(pNode->IsEffect(), "PostProcessor list contains non-PostProcessor in EffectsStack::Initialise"); NodeEffect* pLE = (NodeEffect*)pNode; PPStackLevel* pNewItem = new PPStackLevel(); pNewItem->pPPNode = pLE; pNewItem->listSelNodes.AddNode(pLE); pNewItem->strPostProID = pLE->GetPostProcessorID(); AddTail(pNewItem); pNode = pRange->FindNext(pNode); } return TRUE; } /******************************************************************************************** > BOOL EffectsStack::Intersect(ListRange* pRange) Author: Phil_Martin (Xara Group Ltd) Created: 18/10/2004 Inputs: pRange - A stack of LiveEffects above a node in the selection Outputs: Returns: Purpose: Find whether the LE nodes in the presented range match those already in this LE stack. Remove any mismatches from the PPStack, add new nodes to the stack level lists. Errors: See also: ********************************************************************************************/ BOOL EffectsStack::Intersect(ListRange* pRange) { ENSURE(pRange, "EffectsStack::Intersect must be given a range"); Node* pNode = pRange->FindFirst(); while (pNode) { ENSURE(pNode->IsEffect(), "PostProcessor list contains non-PostProcessor in EffectsStack::Intersect"); NodeEffect* pLE = (NodeEffect*)pNode; PPStackLevel* pLastMatchItem = NULL; PPStackLevel* pFirstMatchItem = (PPStackLevel*)GetHead(); NodeEffect* pStackSample; while (pFirstMatchItem!=NULL && pLastMatchItem==NULL) { pStackSample = pFirstMatchItem->pPPNode; pLastMatchItem = FindMatch(pFirstMatchItem, pRange, pLE); if (pLastMatchItem) break; // Stop as soon as we find a match! pFirstMatchItem = (PPStackLevel*)GetNext(pFirstMatchItem); } if (pLastMatchItem) { // ------------------------------------------------------------ // We have a match so remove the stack levels that didn't match PPStackLevel* pItem = (PPStackLevel*)GetHead(); while (pItem != pFirstMatchItem) { PPStackLevel* pNextItem = (PPStackLevel*)GetNext(pItem); RemoveItem(pItem); pItem = pNextItem; } // ------------------------------------------------------------ // Add the nodes that do match to the ListRanges in the levels do { pItem->listSelNodes.AddNode(pLE); pLE = (NodeEffect*)pRange->FindNext(pLE); pItem = (PPStackLevel*)GetNext(pItem); } while (pItem!=NULL && pItem!=pLastMatchItem && pLE!=NULL); if (pItem!=NULL && pItem==pLastMatchItem && pLE!=NULL) { pItem->listSelNodes.AddNode(pLE); } // ------------------------------------------------------------ // Remove further levels that didn't match if (pItem) { pItem = (PPStackLevel*)GetNext(pItem); while (pItem != NULL) { PPStackLevel* pNextItem = (PPStackLevel*)GetNext(pItem); RemoveItem(pItem); pItem = pNextItem; } } return TRUE; } pNode = pRange->FindNext(pNode); } Clear(); return FALSE; } /******************************************************************************************** > BOOL EffectsStack::FindBestProcessor(String_256* pstrEffectID, INT32* piStackPos) Author: Phil_Martin (Xara Group Ltd) Created: 20/10/2004 Inputs: Outputs: Returns: TRUE if an effect was found FALSE otherwise Purpose: Find the most appropriate effect in this effect stack mathcing the given name and stack position. Errors: See also: ********************************************************************************************/ BOOL EffectsStack::FindBestProcessor(String_256* pstrEffectID, INT32* piStackPos) { INT32 pos = *piStackPos; PPStackLevel* pLevel = NULL; // Search up from the last known stack position for an effect of the same name do { pLevel = (PPStackLevel*)FindItem(pos); if (pLevel && pLevel->strPostProID==*pstrEffectID) { *piStackPos = pos; return TRUE; } pos++; } while (pLevel); // Search down from the last known stack position for an effect of the same name pos = *piStackPos-1; do { pLevel = (PPStackLevel*)FindItem(pos); if (pLevel && pLevel->strPostProID==*pstrEffectID) { *piStackPos = pos; return TRUE; } pos--; } while (pLevel); return FALSE; } /******************************************************************************************** > BOOL EffectsStack::GetLevelInfo(String_256* pstrEffectID, INT32* piStackPos) Author: Phil_Martin (Xara Group Ltd) Created: 08/11/2004 Inputs: Outputs: Returns: TRUE if an effect was found FALSE otherwise Purpose: Find the most appropriate effect in this effect stack mathcing the given name and stack position. Errors: See also: ********************************************************************************************/ BOOL EffectsStack::GetLevelInfo(String_256* pstrEffectID, INT32* piStackPos) { PPStackLevel* pLevel = NULL; if (*piStackPos>=STACKPOS_TOP) pLevel = (PPStackLevel*)GetTail(); else pLevel = (PPStackLevel*)FindItem(*piStackPos); if (pLevel) { *piStackPos = (INT32) this->FindPosition(pLevel); *pstrEffectID = pLevel->strPostProID; return TRUE; } return FALSE; } /******************************************************************************************** > void EffectsStack::Clear() Author: Phil_Martin (Xara Group Ltd) Created: 18/10/2004 Inputs: Outputs: Returns: Purpose: Errors: See also: ********************************************************************************************/ void EffectsStack::Clear() { // No match so clear out the Stack and return while (GetCount()!=0) { PPStackLevel* pItem = (PPStackLevel*)RemoveTail(); delete pItem; } } /******************************************************************************************** > PPStackLevel* EffectsStack::FindMatch(PPStackLevel* pFirstItem, ListRange* pRange, NodeEffect* pPP) Author: Phil_Martin (Xara Group Ltd) Created: 18/10/2004 Inputs: pRange - A stack of LiveEffects above a node in the selection Outputs: Returns: Purpose: Find whether the LE nodes in the presented range match those already in this LE stack. Remove any mismatches from the PPStack, add new nodes to the stack level lists. Errors: See also: ********************************************************************************************/ PPStackLevel* EffectsStack::FindMatch(PPStackLevel* pFirstItem, ListRange* pRange, NodeEffect* pPP) { PPStackLevel* pItem = pFirstItem; PPStackLevel* pLastMatchItem = NULL; BOOL bMatch = TRUE; // HRESULT hr; do { bMatch = pPP->CompareState(pItem->pPPNode); // Polymorphic compare for PostProcessors if (bMatch) pLastMatchItem = pItem; // Possible future change to detect inconsistent levels. But this would require changes elsewhere // bMatch = (pPP->GetRuntimeClass() == pItem->pPPNode->GetRuntimeClass()); // if (pItem->bConsistent) // { // pItem->bConsistent = pPP->CompareState(pItem->pPPNode); // } pPP = (NodeEffect*)pRange->FindNext(pPP); pItem = (PPStackLevel*)GetNext(pItem); } while (bMatch && pItem!=NULL && pPP); return pLastMatchItem; } /******************************************************************************************** > ListRange* EffectsStack::GetEffectsStackFromNode(Node* pNode, BOOL bNodeMaybeMidStack = FALSE, BOOL bEscapeDerived = TRUE, bIncludeLocked = FALSE) Author: Phil_Martin (Xara Group Ltd) Created: 18/10/2004 Inputs: pNode - pointer to node in tree which may have a LiveEffect stack above it Outputs: - Returns: pPPStack - NULL if - Empty if - Populated if consistent stack (caller becomes owner of this list!) Purpose: Get a list of LE nodes that are common to all selected nodes Only returns a list of ALL the selected nodes have identical stacks above them... Does nothing about setting current stack pos or current effect name SeeAlso: LiveEffectsTool::GetLEListFromSelAndStack ********************************************************************************************/ ListRange* EffectsStack::GetEffectsStackFromNode(Node* pNode, BOOL bNodeMaybeMidStack, BOOL bEscapeDerived, BOOL bIncludeLocked) { // First, test whether node CAN be in a PPStack or stands aside from it // (E.g. shadows) // if (!bEscapeDerived && pNode->NeedsParent(pNode->FindParent())) return NULL; ListRange* pStack = new ListRange(); Node* pUpNode = pNode; // If the node could be in the middle of a stack then we need to find the base // of the stack before we start our scan // (E.g. the node could be a selected Shadow node somewhere high above the // object it applies to) if (bNodeMaybeMidStack) { pUpNode = FindStackBaseFrom(pUpNode, bIncludeLocked); } // Special case handling for old legacy effects until they become true // PostProcessors... // Search for Contour, Bevel, ClipView controllers above selected node // and if found, make them the base for PostProcessor scanning pUpNode = EscapeOldControllers(pUpNode); // Now go up the post processor stack, adding every one we find to the list do { if (pUpNode->IsEffect()) pStack->AddNode(pUpNode); pUpNode = pUpNode->FindParent(); } while (pUpNode && pUpNode->IsEffect()); // Best to get rid of the list here if it's empty (don't trust caller) if (pStack->IsEmpty()) { delete pStack; pStack = NULL; } return pStack; } /******************************************************************************************** > BOOL EffectsStack::NodesSharePPStack(Node* pNode, Node* pLastNode, BOOL bNodeMaybeMidStack = FALSE, BOOL bIncludeLocked = FALSE) Author: Phil_Martin (Xara Group Ltd) Created: 16/11/2004 Inputs: pNode - pointer to node in tree which may have a LiveEffect stack above it pLastNode - pointer to another node whicyh may have a LiveEffect stack Outputs: - Returns: TRUE if both nodes have the same stack above them FALSE if the nodes have different stacks Purpose: Find out whether two sibling nodes in the tree share the same stack (So that we can avoid adding the same node stack to the combined stack more than once) SeeAlso: LiveEffectsTool::GetLEListFromSelAndStack ********************************************************************************************/ BOOL EffectsStack::NodesSharePPStack(Node* pNode, Node* pLastNode, BOOL bNodeMaybeMidStack, BOOL bIncludeLocked) { if (pNode==NULL || pLastNode==NULL) return FALSE; if (bNodeMaybeMidStack) { // Find the true stack base for both these pNode = FindStackBaseFrom(pNode, bIncludeLocked); pLastNode = FindStackBaseFrom(pLastNode, bIncludeLocked); } // Special case handling for old legacy effects until they become true // PostProcessors... // Search for Contour, Bevel, ClipView controllers above selected node // and if found, make them the base for PostProcessor scanning pNode = EscapeOldControllers(pNode); pLastNode = EscapeOldControllers(pLastNode); if (!bIncludeLocked) { if (pNode->IsEffect() && ((NodeEffect*)pNode)->IsLockedEffect()) return FALSE; if (pLastNode->IsEffect() && ((NodeEffect*)pLastNode)->IsLockedEffect()) return FALSE; } if (pNode->IsEffect() && !((NodeEffect*)pNode)->CanBeUnlocked()) return FALSE; if (pLastNode->IsEffect() && !((NodeEffect*)pLastNode)->CanBeUnlocked()) return FALSE; Node* pParent = pNode->FindParent(); if (pParent==NULL) return FALSE; return (pParent == pLastNode->FindParent() && pParent->IsEffect()); } /******************************************************************************************** > Node* EffectsStack::FindStackBaseFrom(Node* pNode, BOOL bIncludeLocked) Author: Phil_Martin (Xara Group Ltd) Created: 25/11/2004 Inputs: pNode - pointer to node in tree which may have a LiveEffect stack above it and below it! Outputs: - Returns: Pointer to node at the base of the stack containing the input node Purpose: From the specified node, find the base of the local stack containing that node ********************************************************************************************/ Node* EffectsStack::FindStackBaseFrom(Node* pNode, BOOL bIncludeLocked) { Node* pStackNode = pNode; if (!pStackNode->IsEffect()) { if (pStackNode->GetParentController()) pStackNode = pStackNode->GetParentController(); } while (pStackNode && pStackNode->IsEffect()) { // Stop immediately if we find a destructive post pro - the user cannot be // allowed to see or manipulate anything underneath one of those... if (!bIncludeLocked && ((NodeEffect*)pStackNode)->IsLockedEffect()) break; if (!((NodeEffect*)pStackNode)->CanBeUnlocked()) break; pStackNode = ((NodeEffect*)pStackNode)->GetInkNodeFromController(); } return pStackNode; } /******************************************************************************************** > Node* EffectsStack::EscapeOldControllers(Node* pUserNode) Author: Phil_Martin (Xara Group Ltd) Created: 29/11/2004 Inputs: pNode - pointer to node in tree which may have a LiveEffect stack above it Outputs: - Returns: Node* - pointer to node above presented node which is an old style non-postprocessor controller node Purpose: Find the node which PostProcessors should be wrapped around, taking old style Controller nodes into account. ********************************************************************************************/ Node* EffectsStack::EscapeOldControllers(Node* pUserNode) { Node* pTestNode = pUserNode->FindParent(); while (pTestNode && pTestNode->IsController() && !pTestNode->IsEffect()) { pUserNode = pTestNode; pTestNode = pTestNode->FindParent(); } return pUserNode; } /******************************************************************************************** > BOOL EffectsStack::LockableNonEmpty() Author: Phil_Martin (Xara Group Ltd) Created: 18/10/2004 Inputs: pNode - pointer to node in tree which may have a LiveEffect stack above it Outputs: - Returns: TRUE if stack has some lockable effects layers FALSE if no levels or any level contains an unlockable effect Purpose: Determine whether stack is empty or has destructive effects in it SeeAlso: LiveEffectsTool::GetLEListFromSelAndStack ********************************************************************************************/ BOOL EffectsStack::LockableNonEmpty() { if (IsEmpty()) return FALSE; PPStackLevel* pItem = (PPStackLevel*)GetHead(); NodeEffect* pLENode; while (pItem!=NULL) { pLENode = pItem->pPPNode; ENSURE(pLENode, "PPStackLevel node pointer is NULL in LockableNonEmpty"); if (!pLENode->CanBeUnlocked()) return FALSE; pItem = (PPStackLevel*)GetNext(pItem); } return TRUE; } /******************************************************************************************** > ListRange* EffectsStack::GetLevelRange(INT32* piStackPos, BOOL bEscapeOldControllers = TRUE) Author: Phil_Martin (Xara Group Ltd) Created: 29/09/2004 Inputs: iStackPos - position in LiveEffect stack to find nodes 0 means first stack pos above selection Outputs: Returns: A pointer to a list range owned by the EffectsStack - i.e. you don't have to delete it when you're finished. Purpose: Errors: See also: ********************************************************************************************/ ListRange* EffectsStack::GetLevelRange(INT32* piStackPos, BOOL bEscapeOldControllers) { ENSURE(*piStackPos>=0, "Invalid stack pos in GetLevelRange!"); // If the stack is inconsistent then we need to make a range of nodes that arch // over the selection and any stacked PostProcessors that might be on them // In this case we expect our caller to not have specified a valid stack position if (!bConsistent || IsEmpty() || *piStackPos>=STACKPOS_TOP) { ENSURE(*piStackPos>=STACKPOS_TOP, "Incorrect attempt to insert into an inconsistent PostPro stack"); // By definition, if someone had previously requested a new level range // during the lifetime of this effects stack then the same range must // still apply (otherwise the EffectsStack would have been destroyed). // So just return the range we already found... if (m_pNewLevelRange) { return m_pNewLevelRange; // delete m_pNewLevelRange; // m_pNewLevelRange = NULL; } m_pNewLevelRange = GetNewLevelRange(NULL, bEscapeOldControllers); // We own this listrange, so we can delete it later // Return new stack pos; correct number for consistent stack, INVALID for inconsistent stack if (bConsistent) *piStackPos = ((INT32)GetCount())-1; else *piStackPos = STACKPOS_INVALID; return m_pNewLevelRange; } // This is the typical case: Adding/Inserting to a consistent stack if (*piStackPos>=(INT32)GetCount()) *piStackPos = ((INT32)GetCount())-1; PPStackLevel* pLevel = (PPStackLevel*)FindItem(*piStackPos); ENSURE(pLevel, "Argh something's got out of line!"); ListRange* pLERange = &pLevel->listSelNodes; return pLERange; // The EffectsStack owns this listrange } /******************************************************************************************** > void EffectsStack::AttrsHaveChanged() Author: Phil_Martin (Xara Group Ltd) Created: 01/10/2005 Inputs: - Outputs: - Returns: - Purpose: Tell any stored ranges that any cahced attr data trhey have is now out of date ********************************************************************************************/ void EffectsStack::AttrsHaveChanged() { if (m_pNewLevelRange) { m_pNewLevelRange->AttrsHaveChanged(); } } /******************************************************************************************** > static ListRange* EffectsStack::GetNewLevelRange(Range* pRange, BOOL bEscapeOldControllers = TRUE) Author: Phil_Martin (Xara Group Ltd) Created: 26/10/2004 Inputs: - Outputs: Returns: A pointer to a list range owned by you! Purpose: Get a range of nodes forming a surface above the selection and above any live effects applied to the selection Errors: See also: ********************************************************************************************/ ListRange* EffectsStack::GetNewLevelRange(Range* pRange, BOOL bEscapeOldControllers) { // Loop through selection ensuring that all nodes are LiveEffects // and that they all share the same edit list if (pRange==NULL) pRange = GetApplication()->FindSelection(); ENSURE(pRange, "No selection in GetNewLevelRange!"); // We want to apply the LiveEffect to the entire selection, however inconsistent it is // // Loop through the current selection and search the parents of each selected node for LiveEffects... ListRange* pLERange = new ListRange(); Node* pNode = pRange->FindFirst(); INT32 pos = 0; while (pNode!=NULL) { NodeEffect* pFirstEditedLE = NULL; Node* pBaseNode = pNode; // Only traverse the effect stack if the selected node is 'in' the effect stack // (it could be offset, e.g. NodeShadow) if (!pNode->NeedsParent(pNode->FindParent())) { if (bEscapeOldControllers) pBaseNode = EscapeOldControllers(pNode); Node* pUpNode = pBaseNode; pos = 0; do { pUpNode = pUpNode->FindParent(); if (pUpNode && pUpNode->IsEffect()) pFirstEditedLE = (NodeEffect*)pUpNode; pos++; } while (pUpNode && pUpNode->IsEffect()); } if (pFirstEditedLE) { pLERange->AddNode(pFirstEditedLE); } else { pLERange->AddNode(pBaseNode); } pNode = pRange->FindNext(pNode); } return pLERange; } /******************************************************************************************** > static ListRange* EffectsStack::GetTopClassRange(CCRuntimeClass* pClass, BOOL bClassOnly = TRUE, BOOL bIgnorePassThroughEffects = FALSE Node** pMasterNode = NULL, INT32* piStackPos = NULL, Range* pRange = NULL, BOOL bEscapeOldControllers = TRUE ) Author: Phil_Martin (Xara Group Ltd) Created: 11/05/2005 Inputs: pClass - pointer to class of node to find in effects stack bClassOnly - Only allow nodes of the specified class to be added to the list pRange - pointer to range of nodes to scan bEscapeConts- TRUE if old controller nodes should be escaped Outputs: pMasterNode - pointer to one example of this class of node in the returned range piStackPos - pointer to stack pos INT32 Returns: A pointer to a list range owned by you! Purpose: Get a range of nodes forming a surface above the selection and within any live effects applied to the selection consisting of all effect nodes of type "class" applied to the selection Errors: ********************************************************************************************/ ListRange* EffectsStack::GetTopClassRange(CCRuntimeClass* pClass, BOOL bClassOnly, BOOL bIgnorePassThroughEffects, Node** ppMasterNode, INT32* piStackPos, Range* pRange, BOOL bEscapeOldControllers ) { // Loop through selection ensuring that all nodes are LiveEffects // and that they all share the same edit list if (pRange==NULL) pRange = GetApplication()->FindSelection(); ENSURE(pRange, "No selection in GetClassRange!"); if (ppMasterNode) *ppMasterNode = NULL; // We want to apply the LiveEffect to the entire selection, however inconsistent it is // // Loop through the current selection and search the parents of each selected node for LiveEffects... ListRange* pLERange = new ListRange(); Node* pNode = pRange->FindFirst(); INT32 pos = 0; while (pNode!=NULL) { NodeEffect* pFirstEditedLE = NULL; NodeEffect* pFirstEditedEffectClass = NULL; Node* pBaseNode = pNode; // Only traverse the effect stack if the selected node is 'in' the effect stack // (it could be offset/derived, e.g. NodeShadow) // In this context we know that we always want to go up effects stacks whether the selected node // is mainstream or derived... // if (!pNode->NeedsParent(pNode->FindParent())) // { if (bEscapeOldControllers) pBaseNode = EscapeOldControllers(pNode); Node* pUpNode = pBaseNode; pos = 0; do { pUpNode = pUpNode->FindParent(); if (pUpNode && pUpNode->IsEffect()) // if (!(bIgnorePassThroughEffects && ((NodeEffect*)pUpNode)->IsPassThroughEffect())) // The meaning of the bIgnorePassThroughEffects has changed to mean "ignore all effects" if (!bIgnorePassThroughEffects) pFirstEditedLE = (NodeEffect*)pUpNode; if (pUpNode && pUpNode->IsEffect() && pUpNode->GetRuntimeClass() == pClass) { pFirstEditedEffectClass = (NodeEffect*)pUpNode; // iClassPos = pos; } pos++; } while (pUpNode && pUpNode->IsEffect()); // } // Decide what effect node should be added to the listrange for this node in the input range // Add a node of the specified class if we found one if (pFirstEditedEffectClass) { pLERange->AddNode(pFirstEditedEffectClass); if (ppMasterNode && *ppMasterNode==NULL) *ppMasterNode = pFirstEditedEffectClass; } // Add the last effect if effects were found else if (!bClassOnly) { if (pFirstEditedLE) { pLERange->AddNode(pFirstEditedLE); } // Add the input node itself if no effects were found else { pLERange->AddNode(pBaseNode); } } pNode = pRange->FindNext(pNode); } // Temp hack if (piStackPos) *piStackPos = STACKPOS_INVALID; return pLERange; } /******************************************************************************************** > ListRange* EffectsStack::GetBaseLevelRange() Author: Phil_Martin (Xara Group Ltd) Created: 03/12/2004 Inputs: - Outputs: Returns: A pointer to a list range owned by you! Purpose: Get a range of nodes forming a surface above the selection and BELOW any live effects applied to the selection! (Used by legacy effects like Bevel, Contour) Errors: See also: ********************************************************************************************/ ListRange* EffectsStack::GetBaseLevelRange() { // Loop through selection ensuring that all nodes are LiveEffects // and that they all share the same edit list Range* pRange = GetApplication()->FindSelection(); ENSURE(pRange, "No selection in GetNewLevelRange!"); // We want to apply the LiveEffect to the entire selection, underneath any PostProcessors // // Loop through the current selection and search the parents of each selected node ListRange* pLERange = new ListRange(); Node* pNode = pRange->FindFirst(); // INT32 pos = 0; while (pNode!=NULL) { Node* pEscNode = pNode; // Only traverse the effect stack if the selected node is 'in' the effect stack // (it could be offset, e.g. NodeShadow) if (!pNode->NeedsParent(pNode->FindParent())) pEscNode = EscapeOldControllers(pNode); pLERange->AddNode(pEscNode); pNode = pRange->FindNext(pNode); } return pLERange; } /******************************************************************************************** > ListRange* EffectsStack::FindLevelContaining(NodeEffect* pNode, INT32* piStackPos) Author: Phil_Martin (Xara Group Ltd) Created: 18/11/2004 Inputs: pNode - Pointer to node to search for Outputs: - Returns: A pointer to a list range owned by the PostProcessor stack describing the layer of consistent nodes in the stack of which Node was a member NULL if couldn't find node in stack Purpose: Find a given node in the post processor stack Errors: See also: ********************************************************************************************/ ListRange* EffectsStack::FindLevelContaining(NodeEffect* pNode, INT32* piStackPos) { *piStackPos = STACKPOS_TOP; if (IsEmpty()) return NULL; PPStackLevel* pItem = (PPStackLevel*)GetHead(); *piStackPos = 0; while (pItem!=NULL) { Node* pTestNode = pItem->listSelNodes.FindFirst(); while (pTestNode) { if (pNode==pTestNode) return &pItem->listSelNodes; pTestNode = pItem->listSelNodes.FindNext(pTestNode); } (*piStackPos)++; pItem = (PPStackLevel*)GetNext(pItem); } *piStackPos = STACKPOS_TOP; return NULL; } /******************************************************************************************** > BOOL EffectsStack::NodeInTopLevel(NodeEffect* pNode) Author: Phil_Martin (Xara Group Ltd) Created: 22/11/2004 Inputs: pNode - Pointer to node to search for Outputs: - Returns: TRUE if the node is in the top level of the stack FALSE otherwise Purpose: Find a given node in the post processor stack Errors: See also: ********************************************************************************************/ BOOL EffectsStack::NodeInTopLevel(NodeEffect* pNode) { if (IsEmpty()) return TRUE; PPStackLevel* pItem = (PPStackLevel*)GetTail(); Node* pTestNode = pItem->listSelNodes.FindFirst(); while (pTestNode) { if (pNode==pTestNode) return TRUE; pTestNode = pItem->listSelNodes.FindNext(pTestNode); } return FALSE; } /******************************************************************************************** > INT32 EffectsStack::FindSelectionPos() Author: Phil_Martin (Xara Group Ltd) Created: 25/11/2004 Inputs: - Outputs: - Returns: The stack position of the selection or -1 Purpose: Find whether the selection has a consistent level INT32 he stack other than the usual base level, -1 Errors: See also: ********************************************************************************************/ INT32 EffectsStack::FindSelectionPos() { INT32 Pos = -1; Range* pRange = GetApplication()->FindSelection(); ENSURE(pRange, "!"); ENSURE(pRange->GetRangeControlFlags().PromoteToParent==FALSE, "GetEffectsStackFromSel given a bad range"); PPStackLevel* pFoundLevel = NULL; Node* pNode = pRange->FindFirst(); while (pNode) { Node* pStackNode = pNode->GetParentController(); if (pStackNode==NULL) return -1; if (pFoundLevel==NULL) { PPStackLevel* pItem = (PPStackLevel*)GetHead(); Pos = 0; while (pItem!=NULL && pFoundLevel==NULL) { Node* pTestNode = pItem->listSelNodes.FindFirst(); while (pTestNode && pTestNode!=pStackNode) { pTestNode = pItem->listSelNodes.FindNext(pTestNode); } if (pTestNode==pStackNode) { pFoundLevel = pItem; break; } Pos++; pItem = (PPStackLevel*)GetNext(pItem); } if (pFoundLevel==NULL) return -1; } else { // Just look in the level we have already identified... Node* pTestNode = pFoundLevel->listSelNodes.FindFirst(); while (pTestNode && pTestNode!=pStackNode) { pTestNode = pFoundLevel->listSelNodes.FindNext(pTestNode); } if (pTestNode!=pStackNode) { return -1; } } pNode = pRange->FindNext(pNode); } return Pos; } /******************************************************************************************** > BOOL EffectsStack::BuildEffectMenu(ContextMenu* pMenu) Author: Phil_Martin (Xara Group Ltd) Created: 22/06/2005 Inputs: pMenu - Pointer to ContextMenu we should add items to Outputs: - Returns: TRUE if everything is OK FALSE if something went wrong enough to require caller to stop building menus Purpose: Find whether the selection has a consistent level INT32 he stack other than the usual base level, -1 Errors: See also: ********************************************************************************************/ BOOL EffectsStack::BuildEffectMenu(ContextMenu* pMenu) { PORTNOTE("menu", "BuildEffectMenu can't do anything until we have XPEHost::GetEffectDetails or equivalent"); #ifndef EXCLUDE_FROM_XARALX PPStackLevel* pItem = (PPStackLevel*)GetHead(); // PPStackLevel* pNextItem = NULL; INT32 pos = 0; pMenu->BuildCommand(OPTOKEN_EDITEFFECTS); MenuItem* pRootItem = pMenu->GetLastItem(); while (pItem!=NULL) { // pNextItem = (PPStackLevel*)GetNext(pItem); String_64 strDisplayName; BOOL bDestructive = FALSE; XPEHost::GetEffectDetails(pItem->strPostProID, &strDisplayName, &bDestructive); OpLiveEffectParam* pParam = new OpLiveEffectParam(); if (pParam==NULL) return FALSE; pParam->strOpUnique = pItem->strPostProID; pParam->bIsDestructive = bDestructive; pParam->StackPosition = pos; pParam->pPPStack = this; if (pItem->strPostProID==String_256(POSTPRO_ID_SHADOW)) pMenu->BuildCommand(OPTOKEN_EDIT_LEGACYEFFECT, FALSE, pRootItem, strDisplayName, pParam); else if (pItem->strPostProID==String_256(POSTPRO_ID_FEATHER)) pMenu->BuildCommand(OPTOKEN_EDIT_LEGACYEFFECT, FALSE, pRootItem, strDisplayName, pParam); else pMenu->BuildCommand(OPTOKEN_EDIT_LIVEEFFECT, FALSE, pRootItem, strDisplayName, pParam); pItem = (PPStackLevel*)GetNext(pItem); pos++; } #endif return TRUE; }