00001 // $Id: effects_stack.cpp 859 2006-04-22 15:42:02Z alex $ 00002 /* @@tag:xara-cn@@ DO NOT MODIFY THIS LINE 00003 ================================XARAHEADERSTART=========================== 00004 00005 Xara LX, a vector drawing and manipulation program. 00006 Copyright (C) 1993-2006 Xara Group Ltd. 00007 Copyright on certain contributions may be held in joint with their 00008 respective authors. See AUTHORS file for details. 00009 00010 LICENSE TO USE AND MODIFY SOFTWARE 00011 ---------------------------------- 00012 00013 This file is part of Xara LX. 00014 00015 Xara LX is free software; you can redistribute it and/or modify it 00016 under the terms of the GNU General Public License version 2 as published 00017 by the Free Software Foundation. 00018 00019 Xara LX and its component source files are distributed in the hope 00020 that it will be useful, but WITHOUT ANY WARRANTY; without even the 00021 implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 00022 See the GNU General Public License for more details. 00023 00024 You should have received a copy of the GNU General Public License along 00025 with Xara LX (see the file GPL in the root directory of the 00026 distribution); if not, write to the Free Software Foundation, Inc., 51 00027 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 00028 00029 00030 ADDITIONAL RIGHTS 00031 ----------------- 00032 00033 Conditional upon your continuing compliance with the GNU General Public 00034 License described above, Xara Group Ltd grants to you certain additional 00035 rights. 00036 00037 The additional rights are to use, modify, and distribute the software 00038 together with the wxWidgets library, the wxXtra library, and the "CDraw" 00039 library and any other such library that any version of Xara LX relased 00040 by Xara Group Ltd requires in order to compile and execute, including 00041 the static linking of that library to XaraLX. In the case of the 00042 "CDraw" library, you may satisfy obligation under the GNU General Public 00043 License to provide source code by providing a binary copy of the library 00044 concerned and a copy of the license accompanying it. 00045 00046 Nothing in this section restricts any of the rights you have under 00047 the GNU General Public License. 00048 00049 00050 SCOPE OF LICENSE 00051 ---------------- 00052 00053 This license applies to this program (XaraLX) and its constituent source 00054 files only, and does not necessarily apply to other Xara products which may 00055 in part share the same code base, and are subject to their own licensing 00056 terms. 00057 00058 This license does not apply to files in the wxXtra directory, which 00059 are built into a separate library, and are subject to the wxWindows 00060 license contained within that directory in the file "WXXTRA-LICENSE". 00061 00062 This license does not apply to the binary libraries (if any) within 00063 the "libs" directory, which are subject to a separate license contained 00064 within that directory in the file "LIBS-LICENSE". 00065 00066 00067 ARRANGEMENTS FOR CONTRIBUTION OF MODIFICATIONS 00068 ---------------------------------------------- 00069 00070 Subject to the terms of the GNU Public License (see above), you are 00071 free to do whatever you like with your modifications. However, you may 00072 (at your option) wish contribute them to Xara's source tree. You can 00073 find details of how to do this at: 00074 http://www.xaraxtreme.org/developers/ 00075 00076 Prior to contributing your modifications, you will need to complete our 00077 contributor agreement. This can be found at: 00078 http://www.xaraxtreme.org/developers/contribute/ 00079 00080 Please note that Xara will not accept modifications which modify any of 00081 the text between the start and end of this header (marked 00082 XARAHEADERSTART and XARAHEADEREND). 00083 00084 00085 MARKS 00086 ----- 00087 00088 Xara, Xara LX, Xara X, Xara X/Xtreme, Xara Xtreme, the Xtreme and Xara 00089 designs are registered or unregistered trademarks, design-marks, and/or 00090 service marks of Xara Group Ltd. All rights in these marks are reserved. 00091 00092 00093 Xara Group Ltd, Gaddesden Place, Hemel Hempstead, HP2 6EX, UK. 00094 http://www.xara.com/ 00095 00096 =================================XARAHEADEREND============================ 00097 */ 00098 // 00099 // Implementation of Effects Stack. 00100 // 00101 00102 #include "camtypes.h" // pre-compiled header 00103 #include "effects_stack.h" 00104 00105 #include "nodeliveeffect.h" 00106 #include "opliveeffects.h" 00107 00108 // dynamic class creation stuff. 00109 CC_IMPLEMENT_DYNAMIC(PPStackLevel, ListItem); 00110 CC_IMPLEMENT_DYNAMIC(EffectsStack, List); 00111 00112 DECLARE_SOURCE("$Revision: 859 $"); 00113 00114 #define new CAM_DEBUG_NEW 00115 00116 00117 00118 00120 // The EffectsStack class // 00122 00123 /******************************************************************************************** 00124 00125 > EffectsStack::EffectsStack() 00126 00127 Author: Phil_Martin (Xara Group Ltd) <camelotdev@xara.com> 00128 Created: 17/11/2004 00129 Purpose: Constructor. 00130 Errors: 00131 See also: 00132 00133 ********************************************************************************************/ 00134 EffectsStack::EffectsStack() 00135 { 00136 bConsistent = FALSE; 00137 m_pNewLevelRange = NULL; 00138 } 00139 00140 00141 00142 /******************************************************************************************** 00143 00144 > EffectsStack::~EffectsStack() 00145 00146 Author: Phil_Martin (Xara Group Ltd) <camelotdev@xara.com> 00147 Created: 17/11/2004 00148 Purpose: Destructor. 00149 Errors: 00150 See also: 00151 00152 ********************************************************************************************/ 00153 EffectsStack::~EffectsStack() 00154 { 00155 // We own m_pNewLevelRange so we must delete it... 00156 if (m_pNewLevelRange) 00157 { 00158 delete m_pNewLevelRange; 00159 m_pNewLevelRange = NULL; 00160 } 00161 00162 Clear(); 00163 } 00164 00165 00166 00167 /******************************************************************************************** 00168 00169 > EffectsStack* EffectsStack::GetEffectsStackFromSelection(Range* pRange = NULL, BOOL bEscapeDerived = TRUE, BOOL bIncludeLocked = FALSE) 00170 00171 Author: Phil_Martin (Xara Group Ltd) <camelotdev@xara.com> 00172 Created: 05/10/2004 00173 Inputs: - 00174 Outputs: - 00175 Returns: pStack - pointer to ListRange representing stack applied consistently to selection 00176 Purpose: Get a list of LE nodes that are common to all selected nodes 00177 Only returns a list of ALL the selected nodes have identical stacks 00178 above them... 00179 00180 ********************************************************************************************/ 00181 00182 EffectsStack* EffectsStack::GetEffectsStackFromSelection(Range* pRange, BOOL bEscapeDerived, BOOL bIncludeLocked) 00183 { 00184 // Loop through selection ensuring that all nodes are LiveEffects 00185 // and that they all share the same edit list 00186 if (pRange==NULL) 00187 pRange = GetApplication()->FindSelection(); 00188 ENSURE(pRange, "!"); 00189 ENSURE(pRange->GetRangeControlFlags().PromoteToParent==FALSE, "GetEffectsStackFromSel given a bad range"); 00190 00191 EffectsStack* pPPStack = new EffectsStack(); // New stack is marked as being "inconsistent" 00192 BOOL bStartNewStack = TRUE; 00193 ListRange* pLocalStack = NULL; 00194 BOOL bLiveEffectsAreApplied = FALSE; 00195 00196 // Ensure that pEditedLE has a useful value... 00197 // Loop through the current selection and search the parents of each selected node for LiveEffects... 00198 Node* pNode = pRange->FindFirst(); 00199 Node* pLastNode = NULL; 00200 while (pNode!=NULL) 00201 { 00202 if (pNode->IsAnObject()) 00203 { 00204 // Don't try to add the same stack to the system more than once 00205 // (For instance NodeShadows and their originals are siblings and can both be selected 00206 // below a NodeShadowController) 00207 if (!NodesSharePPStack(pNode, pLastNode, TRUE, bIncludeLocked)) 00208 { 00209 // Get the local stack 00210 pLocalStack = GetEffectsStackFromNode(pNode, TRUE, bEscapeDerived, bIncludeLocked); // If this node is in the middle of a stack, find the whole stack 00211 00212 if (pLocalStack) 00213 { 00214 bLiveEffectsAreApplied = TRUE; 00215 00216 if (bStartNewStack) 00217 { 00218 if (pPPStack->IsEmpty()) 00219 { 00220 pPPStack->Initialise(pLocalStack); 00221 } 00222 else 00223 { 00224 BOOL bOK = pPPStack->Intersect(pLocalStack); 00225 if (!bOK) 00226 { 00227 delete pLocalStack; 00228 pPPStack->Clear(); // Just to make sure! 00229 return pPPStack; 00230 } 00231 } 00232 } 00233 00234 delete pLocalStack; 00235 } 00236 else 00237 { 00238 // A node in the selection doesn't have any LiveEffects applied but we can't stop 00239 // because we have to keep scanning to see whether ANY nodes have LiveEffects 00240 // Clear the effect stack out because this node has none. 00241 pPPStack->Clear(); 00242 bStartNewStack = FALSE; 00243 00244 // Could test for lengthy scanning here and if it takes too long return <UNKNOWN> 00245 // if (taking too long) code = SELTYPE_UNKNOWN; 00246 } 00247 } 00248 pLastNode = pNode; 00249 } 00250 00251 pNode = pRange->FindNext(pNode); 00252 } 00253 00254 if (pPPStack->IsEmpty() && bLiveEffectsAreApplied) 00255 pPPStack->bConsistent = FALSE; 00256 else 00257 pPPStack->bConsistent = TRUE; 00258 00259 return pPPStack; 00260 } 00261 00262 00263 00264 00265 /******************************************************************************************** 00266 00267 > BOOL EffectsStack::Initialise(ListRange* pRange) 00268 00269 Author: Phil_Martin (Xara Group Ltd) <camelotdev@xara.com> 00270 Created: 18/10/2004 00271 Inputs: 00272 Outputs: 00273 Returns: 00274 Purpose: 00275 Errors: 00276 See also: 00277 00278 ********************************************************************************************/ 00279 BOOL EffectsStack::Initialise(ListRange* pRange) 00280 { 00281 ENSURE(pRange, "EffectsStack::Initialise must be given a range"); 00282 00283 Node* pNode = pRange->FindFirst(); 00284 while (pNode) 00285 { 00286 ENSURE(pNode->IsEffect(), "PostProcessor list contains non-PostProcessor in EffectsStack::Initialise"); 00287 NodeEffect* pLE = (NodeEffect*)pNode; 00288 00289 PPStackLevel* pNewItem = new PPStackLevel(); 00290 00291 pNewItem->pPPNode = pLE; 00292 00293 pNewItem->listSelNodes.AddNode(pLE); 00294 pNewItem->strPostProID = pLE->GetPostProcessorID(); 00295 00296 AddTail(pNewItem); 00297 00298 pNode = pRange->FindNext(pNode); 00299 } 00300 00301 return TRUE; 00302 } 00303 00304 00305 00306 00307 /******************************************************************************************** 00308 00309 > BOOL EffectsStack::Intersect(ListRange* pRange) 00310 00311 Author: Phil_Martin (Xara Group Ltd) <camelotdev@xara.com> 00312 Created: 18/10/2004 00313 Inputs: pRange - A stack of LiveEffects above a node in the selection 00314 Outputs: 00315 Returns: 00316 Purpose: Find whether the LE nodes in the presented range match those already in this 00317 LE stack. Remove any mismatches from the PPStack, add new nodes to the 00318 stack level lists. 00319 Errors: 00320 See also: 00321 00322 ********************************************************************************************/ 00323 BOOL EffectsStack::Intersect(ListRange* pRange) 00324 { 00325 ENSURE(pRange, "EffectsStack::Intersect must be given a range"); 00326 00327 Node* pNode = pRange->FindFirst(); 00328 while (pNode) 00329 { 00330 ENSURE(pNode->IsEffect(), "PostProcessor list contains non-PostProcessor in EffectsStack::Intersect"); 00331 NodeEffect* pLE = (NodeEffect*)pNode; 00332 00333 PPStackLevel* pLastMatchItem = NULL; 00334 PPStackLevel* pFirstMatchItem = (PPStackLevel*)GetHead(); 00335 NodeEffect* pStackSample; 00336 while (pFirstMatchItem!=NULL && pLastMatchItem==NULL) 00337 { 00338 pStackSample = pFirstMatchItem->pPPNode; 00339 00340 pLastMatchItem = FindMatch(pFirstMatchItem, pRange, pLE); 00341 if (pLastMatchItem) 00342 break; // Stop as soon as we find a match! 00343 00344 pFirstMatchItem = (PPStackLevel*)GetNext(pFirstMatchItem); 00345 } 00346 00347 if (pLastMatchItem) 00348 { 00349 // ------------------------------------------------------------ 00350 // We have a match so remove the stack levels that didn't match 00351 PPStackLevel* pItem = (PPStackLevel*)GetHead(); 00352 while (pItem != pFirstMatchItem) 00353 { 00354 PPStackLevel* pNextItem = (PPStackLevel*)GetNext(pItem); 00355 00356 RemoveItem(pItem); 00357 00358 pItem = pNextItem; 00359 } 00360 00361 // ------------------------------------------------------------ 00362 // Add the nodes that do match to the ListRanges in the levels 00363 do 00364 { 00365 pItem->listSelNodes.AddNode(pLE); 00366 00367 pLE = (NodeEffect*)pRange->FindNext(pLE); 00368 pItem = (PPStackLevel*)GetNext(pItem); 00369 } 00370 while (pItem!=NULL && pItem!=pLastMatchItem && pLE!=NULL); 00371 00372 if (pItem!=NULL && pItem==pLastMatchItem && pLE!=NULL) 00373 { 00374 pItem->listSelNodes.AddNode(pLE); 00375 } 00376 00377 // ------------------------------------------------------------ 00378 // Remove further levels that didn't match 00379 if (pItem) 00380 { 00381 pItem = (PPStackLevel*)GetNext(pItem); 00382 while (pItem != NULL) 00383 { 00384 PPStackLevel* pNextItem = (PPStackLevel*)GetNext(pItem); 00385 00386 RemoveItem(pItem); 00387 00388 pItem = pNextItem; 00389 } 00390 } 00391 00392 return TRUE; 00393 } 00394 00395 pNode = pRange->FindNext(pNode); 00396 } 00397 00398 Clear(); 00399 return FALSE; 00400 } 00401 00402 00403 00404 00405 /******************************************************************************************** 00406 00407 > BOOL EffectsStack::FindBestProcessor(String_256* pstrEffectID, INT32* piStackPos) 00408 00409 Author: Phil_Martin (Xara Group Ltd) <camelotdev@xara.com> 00410 Created: 20/10/2004 00411 Inputs: 00412 Outputs: 00413 Returns: TRUE if an effect was found 00414 FALSE otherwise 00415 Purpose: Find the most appropriate effect in this effect stack mathcing the given 00416 name and stack position. 00417 Errors: 00418 See also: 00419 00420 ********************************************************************************************/ 00421 BOOL EffectsStack::FindBestProcessor(String_256* pstrEffectID, INT32* piStackPos) 00422 { 00423 INT32 pos = *piStackPos; 00424 PPStackLevel* pLevel = NULL; 00425 00426 // Search up from the last known stack position for an effect of the same name 00427 do 00428 { 00429 pLevel = (PPStackLevel*)FindItem(pos); 00430 00431 if (pLevel && pLevel->strPostProID==*pstrEffectID) 00432 { 00433 *piStackPos = pos; 00434 return TRUE; 00435 } 00436 00437 pos++; 00438 } 00439 while (pLevel); 00440 00441 // Search down from the last known stack position for an effect of the same name 00442 pos = *piStackPos-1; 00443 do 00444 { 00445 pLevel = (PPStackLevel*)FindItem(pos); 00446 00447 if (pLevel && pLevel->strPostProID==*pstrEffectID) 00448 { 00449 *piStackPos = pos; 00450 return TRUE; 00451 } 00452 00453 pos--; 00454 } 00455 while (pLevel); 00456 00457 return FALSE; 00458 } 00459 00460 00461 00462 00463 /******************************************************************************************** 00464 00465 > BOOL EffectsStack::GetLevelInfo(String_256* pstrEffectID, INT32* piStackPos) 00466 00467 Author: Phil_Martin (Xara Group Ltd) <camelotdev@xara.com> 00468 Created: 08/11/2004 00469 Inputs: 00470 Outputs: 00471 Returns: TRUE if an effect was found 00472 FALSE otherwise 00473 Purpose: Find the most appropriate effect in this effect stack mathcing the given 00474 name and stack position. 00475 Errors: 00476 See also: 00477 00478 ********************************************************************************************/ 00479 BOOL EffectsStack::GetLevelInfo(String_256* pstrEffectID, INT32* piStackPos) 00480 { 00481 PPStackLevel* pLevel = NULL; 00482 00483 if (*piStackPos>=STACKPOS_TOP) 00484 pLevel = (PPStackLevel*)GetTail(); 00485 else 00486 pLevel = (PPStackLevel*)FindItem(*piStackPos); 00487 00488 if (pLevel) 00489 { 00490 *piStackPos = (INT32) this->FindPosition(pLevel); 00491 *pstrEffectID = pLevel->strPostProID; 00492 return TRUE; 00493 } 00494 00495 return FALSE; 00496 } 00497 00498 00499 00500 00501 /******************************************************************************************** 00502 00503 > void EffectsStack::Clear() 00504 00505 Author: Phil_Martin (Xara Group Ltd) <camelotdev@xara.com> 00506 Created: 18/10/2004 00507 Inputs: 00508 Outputs: 00509 Returns: 00510 Purpose: 00511 Errors: 00512 See also: 00513 00514 ********************************************************************************************/ 00515 void EffectsStack::Clear() 00516 { 00517 // No match so clear out the Stack and return 00518 while (GetCount()!=0) 00519 { 00520 PPStackLevel* pItem = (PPStackLevel*)RemoveTail(); 00521 delete pItem; 00522 } 00523 } 00524 00525 00526 00527 00528 /******************************************************************************************** 00529 00530 > PPStackLevel* EffectsStack::FindMatch(PPStackLevel* pFirstItem, ListRange* pRange, NodeEffect* pPP) 00531 00532 Author: Phil_Martin (Xara Group Ltd) <camelotdev@xara.com> 00533 Created: 18/10/2004 00534 Inputs: pRange - A stack of LiveEffects above a node in the selection 00535 Outputs: 00536 Returns: 00537 Purpose: Find whether the LE nodes in the presented range match those already in this 00538 LE stack. Remove any mismatches from the PPStack, add new nodes to the 00539 stack level lists. 00540 Errors: 00541 See also: 00542 00543 ********************************************************************************************/ 00544 PPStackLevel* EffectsStack::FindMatch(PPStackLevel* pFirstItem, ListRange* pRange, NodeEffect* pPP) 00545 { 00546 PPStackLevel* pItem = pFirstItem; 00547 PPStackLevel* pLastMatchItem = NULL; 00548 BOOL bMatch = TRUE; 00549 // HRESULT hr; 00550 00551 do 00552 { 00553 bMatch = pPP->CompareState(pItem->pPPNode); // Polymorphic compare for PostProcessors 00554 if (bMatch) 00555 pLastMatchItem = pItem; 00556 // Possible future change to detect inconsistent levels. But this would require changes elsewhere 00557 // bMatch = (pPP->GetRuntimeClass() == pItem->pPPNode->GetRuntimeClass()); 00558 // if (pItem->bConsistent) 00559 // { 00560 // pItem->bConsistent = pPP->CompareState(pItem->pPPNode); 00561 // } 00562 00563 pPP = (NodeEffect*)pRange->FindNext(pPP); 00564 pItem = (PPStackLevel*)GetNext(pItem); 00565 } 00566 while (bMatch && pItem!=NULL && pPP); 00567 00568 return pLastMatchItem; 00569 } 00570 00571 00572 00573 00574 /******************************************************************************************** 00575 00576 > ListRange* EffectsStack::GetEffectsStackFromNode(Node* pNode, BOOL bNodeMaybeMidStack = FALSE, BOOL bEscapeDerived = TRUE, bIncludeLocked = FALSE) 00577 00578 Author: Phil_Martin (Xara Group Ltd) <camelotdev@xara.com> 00579 Created: 18/10/2004 00580 Inputs: pNode - pointer to node in tree which may have a LiveEffect stack above it 00581 Outputs: - 00582 Returns: pPPStack - NULL if <None> 00583 - Empty if <Many> 00584 - Populated if consistent stack (caller becomes owner of this list!) 00585 Purpose: Get a list of LE nodes that are common to all selected nodes 00586 Only returns a list of ALL the selected nodes have identical stacks 00587 above them... 00588 Does nothing about setting current stack pos or current effect name 00589 SeeAlso: LiveEffectsTool::GetLEListFromSelAndStack 00590 00591 ********************************************************************************************/ 00592 00593 ListRange* EffectsStack::GetEffectsStackFromNode(Node* pNode, BOOL bNodeMaybeMidStack, BOOL bEscapeDerived, BOOL bIncludeLocked) 00594 { 00595 // First, test whether node CAN be in a PPStack or stands aside from it 00596 // (E.g. shadows) 00597 // 00598 if (!bEscapeDerived && pNode->NeedsParent(pNode->FindParent())) 00599 return NULL; 00600 00601 ListRange* pStack = new ListRange(); 00602 Node* pUpNode = pNode; 00603 00604 // If the node could be in the middle of a stack then we need to find the base 00605 // of the stack before we start our scan 00606 // (E.g. the node could be a selected Shadow node somewhere high above the 00607 // object it applies to) 00608 if (bNodeMaybeMidStack) 00609 { 00610 pUpNode = FindStackBaseFrom(pUpNode, bIncludeLocked); 00611 } 00612 00613 // Special case handling for old legacy effects until they become true 00614 // PostProcessors... 00615 // Search for Contour, Bevel, ClipView controllers above selected node 00616 // and if found, make them the base for PostProcessor scanning 00617 pUpNode = EscapeOldControllers(pUpNode); 00618 00619 // Now go up the post processor stack, adding every one we find to the list 00620 do 00621 { 00622 if (pUpNode->IsEffect()) 00623 pStack->AddNode(pUpNode); 00624 00625 pUpNode = pUpNode->FindParent(); 00626 } 00627 while (pUpNode && pUpNode->IsEffect()); 00628 00629 // Best to get rid of the list here if it's empty (don't trust caller) 00630 if (pStack->IsEmpty()) 00631 { 00632 delete pStack; 00633 pStack = NULL; 00634 } 00635 00636 return pStack; 00637 } 00638 00639 00640 00641 00642 /******************************************************************************************** 00643 00644 > BOOL EffectsStack::NodesSharePPStack(Node* pNode, Node* pLastNode, BOOL bNodeMaybeMidStack = FALSE, BOOL bIncludeLocked = FALSE) 00645 00646 Author: Phil_Martin (Xara Group Ltd) <camelotdev@xara.com> 00647 Created: 16/11/2004 00648 Inputs: pNode - pointer to node in tree which may have a LiveEffect stack above it 00649 pLastNode - pointer to another node whicyh may have a LiveEffect stack 00650 Outputs: - 00651 Returns: TRUE if both nodes have the same stack above them 00652 FALSE if the nodes have different stacks 00653 Purpose: Find out whether two sibling nodes in the tree share the same stack 00654 (So that we can avoid adding the same node stack to the combined stack 00655 more than once) 00656 SeeAlso: LiveEffectsTool::GetLEListFromSelAndStack 00657 00658 ********************************************************************************************/ 00659 00660 BOOL EffectsStack::NodesSharePPStack(Node* pNode, Node* pLastNode, BOOL bNodeMaybeMidStack, BOOL bIncludeLocked) 00661 { 00662 if (pNode==NULL || pLastNode==NULL) 00663 return FALSE; 00664 00665 if (bNodeMaybeMidStack) 00666 { 00667 // Find the true stack base for both these 00668 pNode = FindStackBaseFrom(pNode, bIncludeLocked); 00669 pLastNode = FindStackBaseFrom(pLastNode, bIncludeLocked); 00670 } 00671 00672 // Special case handling for old legacy effects until they become true 00673 // PostProcessors... 00674 // Search for Contour, Bevel, ClipView controllers above selected node 00675 // and if found, make them the base for PostProcessor scanning 00676 pNode = EscapeOldControllers(pNode); 00677 pLastNode = EscapeOldControllers(pLastNode); 00678 00679 if (!bIncludeLocked) 00680 { 00681 if (pNode->IsEffect() && ((NodeEffect*)pNode)->IsLockedEffect()) 00682 return FALSE; 00683 00684 if (pLastNode->IsEffect() && ((NodeEffect*)pLastNode)->IsLockedEffect()) 00685 return FALSE; 00686 } 00687 00688 if (pNode->IsEffect() && !((NodeEffect*)pNode)->CanBeUnlocked()) 00689 return FALSE; 00690 00691 if (pLastNode->IsEffect() && !((NodeEffect*)pLastNode)->CanBeUnlocked()) 00692 return FALSE; 00693 00694 Node* pParent = pNode->FindParent(); 00695 if (pParent==NULL) 00696 return FALSE; 00697 00698 return (pParent == pLastNode->FindParent() && pParent->IsEffect()); 00699 } 00700 00701 00702 00703 00704 /******************************************************************************************** 00705 00706 > Node* EffectsStack::FindStackBaseFrom(Node* pNode, BOOL bIncludeLocked) 00707 00708 Author: Phil_Martin (Xara Group Ltd) <camelotdev@xara.com> 00709 Created: 25/11/2004 00710 Inputs: pNode - pointer to node in tree which may have a LiveEffect stack above it 00711 and below it! 00712 Outputs: - 00713 Returns: Pointer to node at the base of the stack containing the input node 00714 Purpose: From the specified node, find the base of the local stack containing that 00715 node 00716 00717 ********************************************************************************************/ 00718 00719 Node* EffectsStack::FindStackBaseFrom(Node* pNode, BOOL bIncludeLocked) 00720 { 00721 Node* pStackNode = pNode; 00722 00723 if (!pStackNode->IsEffect()) 00724 { 00725 if (pStackNode->GetParentController()) 00726 pStackNode = pStackNode->GetParentController(); 00727 } 00728 00729 while (pStackNode && pStackNode->IsEffect()) 00730 { 00731 // Stop immediately if we find a destructive post pro - the user cannot be 00732 // allowed to see or manipulate anything underneath one of those... 00733 if (!bIncludeLocked && ((NodeEffect*)pStackNode)->IsLockedEffect()) 00734 break; 00735 00736 if (!((NodeEffect*)pStackNode)->CanBeUnlocked()) 00737 break; 00738 00739 pStackNode = ((NodeEffect*)pStackNode)->GetInkNodeFromController(); 00740 } 00741 00742 return pStackNode; 00743 } 00744 00745 00746 00747 00748 /******************************************************************************************** 00749 00750 > Node* EffectsStack::EscapeOldControllers(Node* pUserNode) 00751 00752 Author: Phil_Martin (Xara Group Ltd) <camelotdev@xara.com> 00753 Created: 29/11/2004 00754 Inputs: pNode - pointer to node in tree which may have a LiveEffect stack above it 00755 Outputs: - 00756 Returns: Node* - pointer to node above presented node which is an old style 00757 non-postprocessor controller node 00758 Purpose: Find the node which PostProcessors should be wrapped around, taking 00759 old style Controller nodes into account. 00760 00761 ********************************************************************************************/ 00762 Node* EffectsStack::EscapeOldControllers(Node* pUserNode) 00763 { 00764 Node* pTestNode = pUserNode->FindParent(); 00765 while (pTestNode && pTestNode->IsController() && !pTestNode->IsEffect()) 00766 { 00767 pUserNode = pTestNode; 00768 pTestNode = pTestNode->FindParent(); 00769 } 00770 00771 return pUserNode; 00772 } 00773 00774 00775 00776 00777 /******************************************************************************************** 00778 00779 > BOOL EffectsStack::LockableNonEmpty() 00780 00781 Author: Phil_Martin (Xara Group Ltd) <camelotdev@xara.com> 00782 Created: 18/10/2004 00783 Inputs: pNode - pointer to node in tree which may have a LiveEffect stack above it 00784 Outputs: - 00785 Returns: TRUE if stack has some lockable effects layers 00786 FALSE if no levels or any level contains an unlockable effect 00787 Purpose: Determine whether stack is empty or has destructive effects in it 00788 SeeAlso: LiveEffectsTool::GetLEListFromSelAndStack 00789 00790 ********************************************************************************************/ 00791 00792 BOOL EffectsStack::LockableNonEmpty() 00793 { 00794 if (IsEmpty()) 00795 return FALSE; 00796 00797 PPStackLevel* pItem = (PPStackLevel*)GetHead(); 00798 NodeEffect* pLENode; 00799 while (pItem!=NULL) 00800 { 00801 pLENode = pItem->pPPNode; 00802 ENSURE(pLENode, "PPStackLevel node pointer is NULL in LockableNonEmpty"); 00803 00804 if (!pLENode->CanBeUnlocked()) 00805 return FALSE; 00806 00807 pItem = (PPStackLevel*)GetNext(pItem); 00808 } 00809 00810 return TRUE; 00811 } 00812 00813 00814 00815 00816 /******************************************************************************************** 00817 00818 > ListRange* EffectsStack::GetLevelRange(INT32* piStackPos, BOOL bEscapeOldControllers = TRUE) 00819 00820 Author: Phil_Martin (Xara Group Ltd) <camelotdev@xara.com> 00821 Created: 29/09/2004 00822 Inputs: iStackPos - position in LiveEffect stack to find nodes 00823 0 means first stack pos above selection 00824 Outputs: 00825 Returns: A pointer to a list range owned by the EffectsStack - i.e. you don't have to 00826 delete it when you're finished. 00827 Purpose: 00828 Errors: 00829 See also: 00830 00831 ********************************************************************************************/ 00832 ListRange* EffectsStack::GetLevelRange(INT32* piStackPos, BOOL bEscapeOldControllers) 00833 { 00834 ENSURE(*piStackPos>=0, "Invalid stack pos in GetLevelRange!"); 00835 00836 // If the stack is inconsistent then we need to make a range of nodes that arch 00837 // over the selection and any stacked PostProcessors that might be on them 00838 // In this case we expect our caller to not have specified a valid stack position 00839 if (!bConsistent || IsEmpty() || *piStackPos>=STACKPOS_TOP) 00840 { 00841 ENSURE(*piStackPos>=STACKPOS_TOP, "Incorrect attempt to insert into an inconsistent PostPro stack"); 00842 00843 // By definition, if someone had previously requested a new level range 00844 // during the lifetime of this effects stack then the same range must 00845 // still apply (otherwise the EffectsStack would have been destroyed). 00846 // So just return the range we already found... 00847 if (m_pNewLevelRange) 00848 { 00849 return m_pNewLevelRange; 00850 // delete m_pNewLevelRange; 00851 // m_pNewLevelRange = NULL; 00852 } 00853 00854 m_pNewLevelRange = GetNewLevelRange(NULL, bEscapeOldControllers); // We own this listrange, so we can delete it later 00855 00856 // Return new stack pos; correct number for consistent stack, INVALID for inconsistent stack 00857 if (bConsistent) 00858 *piStackPos = ((INT32)GetCount())-1; 00859 else 00860 *piStackPos = STACKPOS_INVALID; 00861 00862 return m_pNewLevelRange; 00863 } 00864 00865 // This is the typical case: Adding/Inserting to a consistent stack 00866 if (*piStackPos>=(INT32)GetCount()) 00867 *piStackPos = ((INT32)GetCount())-1; 00868 00869 PPStackLevel* pLevel = (PPStackLevel*)FindItem(*piStackPos); 00870 ENSURE(pLevel, "Argh something's got out of line!"); 00871 ListRange* pLERange = &pLevel->listSelNodes; 00872 00873 return pLERange; // The EffectsStack owns this listrange 00874 } 00875 00876 00877 00878 00879 /******************************************************************************************** 00880 00881 > void EffectsStack::AttrsHaveChanged() 00882 00883 Author: Phil_Martin (Xara Group Ltd) <camelotdev@xara.com> 00884 Created: 01/10/2005 00885 Inputs: - 00886 Outputs: - 00887 Returns: - 00888 Purpose: Tell any stored ranges that any cahced attr data trhey have is now out 00889 of date 00890 00891 ********************************************************************************************/ 00892 00893 void EffectsStack::AttrsHaveChanged() 00894 { 00895 if (m_pNewLevelRange) 00896 { 00897 m_pNewLevelRange->AttrsHaveChanged(); 00898 } 00899 } 00900 00901 00902 00903 00904 /******************************************************************************************** 00905 00906 > static ListRange* EffectsStack::GetNewLevelRange(Range* pRange, BOOL bEscapeOldControllers = TRUE) 00907 00908 Author: Phil_Martin (Xara Group Ltd) <camelotdev@xara.com> 00909 Created: 26/10/2004 00910 Inputs: - 00911 Outputs: 00912 Returns: A pointer to a list range owned by you! 00913 Purpose: Get a range of nodes forming a surface above the selection and above 00914 any live effects applied to the selection 00915 Errors: 00916 See also: 00917 00918 ********************************************************************************************/ 00919 ListRange* EffectsStack::GetNewLevelRange(Range* pRange, BOOL bEscapeOldControllers) 00920 { 00921 // Loop through selection ensuring that all nodes are LiveEffects 00922 // and that they all share the same edit list 00923 if (pRange==NULL) 00924 pRange = GetApplication()->FindSelection(); 00925 ENSURE(pRange, "No selection in GetNewLevelRange!"); 00926 00927 // We want to apply the LiveEffect to the entire selection, however inconsistent it is 00928 // 00929 // Loop through the current selection and search the parents of each selected node for LiveEffects... 00930 ListRange* pLERange = new ListRange(); 00931 Node* pNode = pRange->FindFirst(); 00932 INT32 pos = 0; 00933 while (pNode!=NULL) 00934 { 00935 NodeEffect* pFirstEditedLE = NULL; 00936 Node* pBaseNode = pNode; 00937 00938 // Only traverse the effect stack if the selected node is 'in' the effect stack 00939 // (it could be offset, e.g. NodeShadow) 00940 if (!pNode->NeedsParent(pNode->FindParent())) 00941 { 00942 if (bEscapeOldControllers) 00943 pBaseNode = EscapeOldControllers(pNode); 00944 00945 Node* pUpNode = pBaseNode; 00946 00947 pos = 0; 00948 do 00949 { 00950 pUpNode = pUpNode->FindParent(); 00951 00952 if (pUpNode && pUpNode->IsEffect()) 00953 pFirstEditedLE = (NodeEffect*)pUpNode; 00954 00955 pos++; 00956 } 00957 while (pUpNode && pUpNode->IsEffect()); 00958 } 00959 00960 if (pFirstEditedLE) 00961 { 00962 pLERange->AddNode(pFirstEditedLE); 00963 } 00964 else 00965 { 00966 pLERange->AddNode(pBaseNode); 00967 } 00968 00969 pNode = pRange->FindNext(pNode); 00970 } 00971 00972 return pLERange; 00973 } 00974 00975 00976 00977 00978 /******************************************************************************************** 00979 00980 > static ListRange* EffectsStack::GetTopClassRange(CCRuntimeClass* pClass, 00981 BOOL bClassOnly = TRUE, 00982 BOOL bIgnorePassThroughEffects = FALSE 00983 Node** pMasterNode = NULL, 00984 INT32* piStackPos = NULL, 00985 Range* pRange = NULL, 00986 BOOL bEscapeOldControllers = TRUE 00987 ) 00988 00989 Author: Phil_Martin (Xara Group Ltd) <camelotdev@xara.com> 00990 Created: 11/05/2005 00991 Inputs: pClass - pointer to class of node to find in effects stack 00992 bClassOnly - Only allow nodes of the specified class to be added to the list 00993 pRange - pointer to range of nodes to scan 00994 bEscapeConts- TRUE if old controller nodes should be escaped 00995 Outputs: pMasterNode - pointer to one example of this class of node in the returned range 00996 piStackPos - pointer to stack pos INT32 00997 Returns: A pointer to a list range owned by you! 00998 Purpose: Get a range of nodes forming a surface above the selection and within 00999 any live effects applied to the selection consisting of all effect nodes of type 01000 "class" applied to the selection 01001 Errors: 01002 01003 ********************************************************************************************/ 01004 ListRange* EffectsStack::GetTopClassRange(CCRuntimeClass* pClass, 01005 BOOL bClassOnly, 01006 BOOL bIgnorePassThroughEffects, 01007 Node** ppMasterNode, 01008 INT32* piStackPos, 01009 Range* pRange, 01010 BOOL bEscapeOldControllers 01011 ) 01012 { 01013 // Loop through selection ensuring that all nodes are LiveEffects 01014 // and that they all share the same edit list 01015 if (pRange==NULL) 01016 pRange = GetApplication()->FindSelection(); 01017 ENSURE(pRange, "No selection in GetClassRange!"); 01018 01019 if (ppMasterNode) 01020 *ppMasterNode = NULL; 01021 01022 // We want to apply the LiveEffect to the entire selection, however inconsistent it is 01023 // 01024 // Loop through the current selection and search the parents of each selected node for LiveEffects... 01025 ListRange* pLERange = new ListRange(); 01026 Node* pNode = pRange->FindFirst(); 01027 INT32 pos = 0; 01028 while (pNode!=NULL) 01029 { 01030 NodeEffect* pFirstEditedLE = NULL; 01031 NodeEffect* pFirstEditedEffectClass = NULL; 01032 Node* pBaseNode = pNode; 01033 01034 // Only traverse the effect stack if the selected node is 'in' the effect stack 01035 // (it could be offset/derived, e.g. NodeShadow) 01036 // In this context we know that we always want to go up effects stacks whether the selected node 01037 // is mainstream or derived... 01038 // if (!pNode->NeedsParent(pNode->FindParent())) 01039 // { 01040 if (bEscapeOldControllers) 01041 pBaseNode = EscapeOldControllers(pNode); 01042 01043 Node* pUpNode = pBaseNode; 01044 01045 pos = 0; 01046 do 01047 { 01048 pUpNode = pUpNode->FindParent(); 01049 01050 if (pUpNode && pUpNode->IsEffect()) 01051 // if (!(bIgnorePassThroughEffects && ((NodeEffect*)pUpNode)->IsPassThroughEffect())) 01052 // The meaning of the bIgnorePassThroughEffects has changed to mean "ignore all effects" 01053 if (!bIgnorePassThroughEffects) 01054 pFirstEditedLE = (NodeEffect*)pUpNode; 01055 01056 if (pUpNode && pUpNode->IsEffect() && pUpNode->GetRuntimeClass() == pClass) 01057 { 01058 pFirstEditedEffectClass = (NodeEffect*)pUpNode; 01059 // iClassPos = pos; 01060 } 01061 01062 pos++; 01063 } 01064 while (pUpNode && pUpNode->IsEffect()); 01065 // } 01066 01067 // Decide what effect node should be added to the listrange for this node in the input range 01068 // Add a node of the specified class if we found one 01069 if (pFirstEditedEffectClass) 01070 { 01071 pLERange->AddNode(pFirstEditedEffectClass); 01072 01073 if (ppMasterNode && *ppMasterNode==NULL) 01074 *ppMasterNode = pFirstEditedEffectClass; 01075 } 01076 // Add the last effect if effects were found 01077 else if (!bClassOnly) 01078 { 01079 if (pFirstEditedLE) 01080 { 01081 pLERange->AddNode(pFirstEditedLE); 01082 } 01083 // Add the input node itself if no effects were found 01084 else 01085 { 01086 pLERange->AddNode(pBaseNode); 01087 } 01088 } 01089 01090 pNode = pRange->FindNext(pNode); 01091 } 01092 01093 // Temp hack 01094 if (piStackPos) 01095 *piStackPos = STACKPOS_INVALID; 01096 01097 return pLERange; 01098 } 01099 01100 01101 01102 01103 /******************************************************************************************** 01104 01105 > ListRange* EffectsStack::GetBaseLevelRange() 01106 01107 Author: Phil_Martin (Xara Group Ltd) <camelotdev@xara.com> 01108 Created: 03/12/2004 01109 Inputs: - 01110 Outputs: 01111 Returns: A pointer to a list range owned by you! 01112 Purpose: Get a range of nodes forming a surface above the selection and BELOW 01113 any live effects applied to the selection! 01114 (Used by legacy effects like Bevel, Contour) 01115 Errors: 01116 See also: 01117 01118 ********************************************************************************************/ 01119 ListRange* EffectsStack::GetBaseLevelRange() 01120 { 01121 // Loop through selection ensuring that all nodes are LiveEffects 01122 // and that they all share the same edit list 01123 Range* pRange = GetApplication()->FindSelection(); 01124 ENSURE(pRange, "No selection in GetNewLevelRange!"); 01125 01126 // We want to apply the LiveEffect to the entire selection, underneath any PostProcessors 01127 // 01128 // Loop through the current selection and search the parents of each selected node 01129 ListRange* pLERange = new ListRange(); 01130 Node* pNode = pRange->FindFirst(); 01131 // INT32 pos = 0; 01132 while (pNode!=NULL) 01133 { 01134 Node* pEscNode = pNode; 01135 01136 // Only traverse the effect stack if the selected node is 'in' the effect stack 01137 // (it could be offset, e.g. NodeShadow) 01138 if (!pNode->NeedsParent(pNode->FindParent())) 01139 pEscNode = EscapeOldControllers(pNode); 01140 01141 pLERange->AddNode(pEscNode); 01142 01143 pNode = pRange->FindNext(pNode); 01144 } 01145 01146 return pLERange; 01147 } 01148 01149 01150 01151 01152 /******************************************************************************************** 01153 01154 > ListRange* EffectsStack::FindLevelContaining(NodeEffect* pNode, INT32* piStackPos) 01155 01156 Author: Phil_Martin (Xara Group Ltd) <camelotdev@xara.com> 01157 Created: 18/11/2004 01158 Inputs: pNode - Pointer to node to search for 01159 Outputs: - 01160 Returns: A pointer to a list range owned by the PostProcessor stack describing 01161 the layer of consistent nodes in the stack of which Node was a member 01162 NULL if couldn't find node in stack 01163 Purpose: Find a given node in the post processor stack 01164 Errors: 01165 See also: 01166 01167 ********************************************************************************************/ 01168 ListRange* EffectsStack::FindLevelContaining(NodeEffect* pNode, INT32* piStackPos) 01169 { 01170 *piStackPos = STACKPOS_TOP; 01171 if (IsEmpty()) 01172 return NULL; 01173 01174 PPStackLevel* pItem = (PPStackLevel*)GetHead(); 01175 *piStackPos = 0; 01176 while (pItem!=NULL) 01177 { 01178 Node* pTestNode = pItem->listSelNodes.FindFirst(); 01179 while (pTestNode) 01180 { 01181 if (pNode==pTestNode) 01182 return &pItem->listSelNodes; 01183 01184 pTestNode = pItem->listSelNodes.FindNext(pTestNode); 01185 } 01186 01187 (*piStackPos)++; 01188 pItem = (PPStackLevel*)GetNext(pItem); 01189 } 01190 01191 *piStackPos = STACKPOS_TOP; 01192 return NULL; 01193 } 01194 01195 01196 01197 01198 /******************************************************************************************** 01199 01200 > BOOL EffectsStack::NodeInTopLevel(NodeEffect* pNode) 01201 01202 Author: Phil_Martin (Xara Group Ltd) <camelotdev@xara.com> 01203 Created: 22/11/2004 01204 Inputs: pNode - Pointer to node to search for 01205 Outputs: - 01206 Returns: TRUE if the node is in the top level of the stack 01207 FALSE otherwise 01208 Purpose: Find a given node in the post processor stack 01209 Errors: 01210 See also: 01211 01212 ********************************************************************************************/ 01213 BOOL EffectsStack::NodeInTopLevel(NodeEffect* pNode) 01214 { 01215 if (IsEmpty()) 01216 return TRUE; 01217 01218 PPStackLevel* pItem = (PPStackLevel*)GetTail(); 01219 Node* pTestNode = pItem->listSelNodes.FindFirst(); 01220 while (pTestNode) 01221 { 01222 if (pNode==pTestNode) 01223 return TRUE; 01224 01225 pTestNode = pItem->listSelNodes.FindNext(pTestNode); 01226 } 01227 01228 return FALSE; 01229 } 01230 01231 01232 01233 01234 /******************************************************************************************** 01235 01236 > INT32 EffectsStack::FindSelectionPos() 01237 01238 Author: Phil_Martin (Xara Group Ltd) <camelotdev@xara.com> 01239 Created: 25/11/2004 01240 Inputs: - 01241 Outputs: - 01242 Returns: The stack position of the selection or -1 01243 Purpose: Find whether the selection has a consistent level INT32 he stack other 01244 than the usual base level, -1 01245 Errors: 01246 See also: 01247 01248 ********************************************************************************************/ 01249 INT32 EffectsStack::FindSelectionPos() 01250 { 01251 INT32 Pos = -1; 01252 01253 Range* pRange = GetApplication()->FindSelection(); 01254 ENSURE(pRange, "!"); 01255 ENSURE(pRange->GetRangeControlFlags().PromoteToParent==FALSE, "GetEffectsStackFromSel given a bad range"); 01256 01257 PPStackLevel* pFoundLevel = NULL; 01258 Node* pNode = pRange->FindFirst(); 01259 while (pNode) 01260 { 01261 Node* pStackNode = pNode->GetParentController(); 01262 if (pStackNode==NULL) 01263 return -1; 01264 01265 if (pFoundLevel==NULL) 01266 { 01267 PPStackLevel* pItem = (PPStackLevel*)GetHead(); 01268 Pos = 0; 01269 while (pItem!=NULL && pFoundLevel==NULL) 01270 { 01271 Node* pTestNode = pItem->listSelNodes.FindFirst(); 01272 while (pTestNode && pTestNode!=pStackNode) 01273 { 01274 pTestNode = pItem->listSelNodes.FindNext(pTestNode); 01275 } 01276 01277 if (pTestNode==pStackNode) 01278 { 01279 pFoundLevel = pItem; 01280 break; 01281 } 01282 01283 Pos++; 01284 pItem = (PPStackLevel*)GetNext(pItem); 01285 } 01286 01287 if (pFoundLevel==NULL) 01288 return -1; 01289 } 01290 else 01291 { 01292 // Just look in the level we have already identified... 01293 Node* pTestNode = pFoundLevel->listSelNodes.FindFirst(); 01294 while (pTestNode && pTestNode!=pStackNode) 01295 { 01296 pTestNode = pFoundLevel->listSelNodes.FindNext(pTestNode); 01297 } 01298 01299 if (pTestNode!=pStackNode) 01300 { 01301 return -1; 01302 } 01303 } 01304 01305 pNode = pRange->FindNext(pNode); 01306 } 01307 01308 return Pos; 01309 } 01310 01311 01312 01313 01314 /******************************************************************************************** 01315 01316 > BOOL EffectsStack::BuildEffectMenu(ContextMenu* pMenu) 01317 01318 Author: Phil_Martin (Xara Group Ltd) <camelotdev@xara.com> 01319 Created: 22/06/2005 01320 Inputs: pMenu - Pointer to ContextMenu we should add items to 01321 Outputs: - 01322 Returns: TRUE if everything is OK 01323 FALSE if something went wrong enough to require caller to stop building menus 01324 Purpose: Find whether the selection has a consistent level INT32 he stack other 01325 than the usual base level, -1 01326 Errors: 01327 See also: 01328 01329 ********************************************************************************************/ 01330 BOOL EffectsStack::BuildEffectMenu(ContextMenu* pMenu) 01331 { 01332 PORTNOTE("menu", "BuildEffectMenu can't do anything until we have XPEHost::GetEffectDetails or equivalent"); 01333 #ifndef EXCLUDE_FROM_XARALX 01334 PPStackLevel* pItem = (PPStackLevel*)GetHead(); 01335 // PPStackLevel* pNextItem = NULL; 01336 INT32 pos = 0; 01337 01338 pMenu->BuildCommand(OPTOKEN_EDITEFFECTS); 01339 MenuItem* pRootItem = pMenu->GetLastItem(); 01340 01341 while (pItem!=NULL) 01342 { 01343 // pNextItem = (PPStackLevel*)GetNext(pItem); 01344 01345 String_64 strDisplayName; 01346 BOOL bDestructive = FALSE; 01347 XPEHost::GetEffectDetails(pItem->strPostProID, &strDisplayName, &bDestructive); 01348 OpLiveEffectParam* pParam = new OpLiveEffectParam(); 01349 if (pParam==NULL) 01350 return FALSE; 01351 01352 pParam->strOpUnique = pItem->strPostProID; 01353 pParam->bIsDestructive = bDestructive; 01354 pParam->StackPosition = pos; 01355 pParam->pPPStack = this; 01356 01357 if (pItem->strPostProID==String_256(POSTPRO_ID_SHADOW)) 01358 pMenu->BuildCommand(OPTOKEN_EDIT_LEGACYEFFECT, FALSE, pRootItem, strDisplayName, pParam); 01359 01360 else if (pItem->strPostProID==String_256(POSTPRO_ID_FEATHER)) 01361 pMenu->BuildCommand(OPTOKEN_EDIT_LEGACYEFFECT, FALSE, pRootItem, strDisplayName, pParam); 01362 01363 else 01364 pMenu->BuildCommand(OPTOKEN_EDIT_LIVEEFFECT, FALSE, pRootItem, strDisplayName, pParam); 01365 01366 pItem = (PPStackLevel*)GetNext(pItem); 01367 pos++; 01368 } 01369 01370 #endif 01371 return TRUE; 01372 } 01373 01374 01375 01376