// $Id: groupops.cpp 1282 2006-06-09 09:46:49Z alex $ /* @@tag:xara-cn@@ DO NOT MODIFY THIS LINE ================================XARAHEADERSTART=========================== Xara LX, a vector drawing and manipulation program. Copyright (C) 1993-2006 Xara Group Ltd. Copyright on certain contributions may be held in joint with their respective authors. See AUTHORS file for details. LICENSE TO USE AND MODIFY SOFTWARE ---------------------------------- This file is part of Xara LX. Xara LX is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. Xara LX and its component source files are distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Xara LX (see the file GPL in the root directory of the distribution); if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ADDITIONAL RIGHTS ----------------- Conditional upon your continuing compliance with the GNU General Public License described above, Xara Group Ltd grants to you certain additional rights. The additional rights are to use, modify, and distribute the software together with the wxWidgets library, the wxXtra library, and the "CDraw" library and any other such library that any version of Xara LX relased by Xara Group Ltd requires in order to compile and execute, including the static linking of that library to XaraLX. In the case of the "CDraw" library, you may satisfy obligation under the GNU General Public License to provide source code by providing a binary copy of the library concerned and a copy of the license accompanying it. Nothing in this section restricts any of the rights you have under the GNU General Public License. SCOPE OF LICENSE ---------------- This license applies to this program (XaraLX) and its constituent source files only, and does not necessarily apply to other Xara products which may in part share the same code base, and are subject to their own licensing terms. This license does not apply to files in the wxXtra directory, which are built into a separate library, and are subject to the wxWindows license contained within that directory in the file "WXXTRA-LICENSE". This license does not apply to the binary libraries (if any) within the "libs" directory, which are subject to a separate license contained within that directory in the file "LIBS-LICENSE". ARRANGEMENTS FOR CONTRIBUTION OF MODIFICATIONS ---------------------------------------------- Subject to the terms of the GNU Public License (see above), you are free to do whatever you like with your modifications. However, you may (at your option) wish contribute them to Xara's source tree. You can find details of how to do this at: http://www.xaraxtreme.org/developers/ Prior to contributing your modifications, you will need to complete our contributor agreement. This can be found at: http://www.xaraxtreme.org/developers/contribute/ Please note that Xara will not accept modifications which modify any of the text between the start and end of this header (marked XARAHEADERSTART and XARAHEADEREND). MARKS ----- Xara, Xara LX, Xara X, Xara X/Xtreme, Xara Xtreme, the Xtreme and Xara designs are registered or unregistered trademarks, design-marks, and/or service marks of Xara Group Ltd. All rights in these marks are reserved. Xara Group Ltd, Gaddesden Place, Hemel Hempstead, HP2 6EX, UK. http://www.xara.com/ =================================XARAHEADEREND============================ */ /* */ #include "camtypes.h" #include "camelot.h" //#include "grptrans.h" #include "groupops.h" //#include "group.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "undoop.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "resource.h" //#include "simon.h" //#include "errors.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "ensure.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "docview.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "opdesc.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "mario.h" //#include "nodeattr.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "ink.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "app.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "document.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "spread.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "layer.h" //#include "tool.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "bubbleid.h" #include "fthrattr.h" #include "opliveeffects.h" #include "effects_stack.h" //#include "phil.h" #include "ophist.h" DECLARE_SOURCE("$Revision: 1282 $"); CC_IMPLEMENT_DYNCREATE(OpGroup, SelOperation) CC_IMPLEMENT_DYNCREATE(OpUngroup, SelOperation) CC_IMPLEMENT_DYNCREATE(OpUngroupSpecial, OpUngroup) #define new CAM_DEBUG_NEW /********************************************************************************************* Preference: AskUngroupEffect Section: Groups Range: FALSE - TRUE Purpose: Flag controlling whether user is asked about factoring out effects during ungroup SeeAlso: - **********************************************************************************************/ BOOL OpGroup::bAskBeforeFactoringEffects = FALSE; /********************************************************************************************* Preference: UngroupLocaliseEffects Section: Groups Range: FALSE - TRUE Purpose: Flag controlling whether effects are localised by default or removed by default SeeAlso: - **********************************************************************************************/ BOOL OpGroup::bLocaliseEffects = FALSE; // ------------------------------------------------------------------------------------------ // OpGroup methods /******************************************************************************************** > OpGroup::OpGroup() Author: Simon_Maneggio (Xara Group Ltd) Created: 29/9/93 Purpose: OpGroup constructor ********************************************************************************************/ OpGroup::OpGroup(): SelOperation() { } /******************************************************************************************** > BOOL OpGroup::Init() Author: Simon_Maneggio (Xara Group Ltd) Created: 28/9/93 Returns: TRUE if the operation could be successfully initialised FALSE if no more memory could be allocated Purpose: OpGroup initialiser method Errors: ERROR will be called if there was insufficient memory to allocate the operation. ********************************************************************************************/ BOOL OpGroup::Init() { return (RegisterOpDescriptor(0, _R(IDS_GROUPOP), CC_RUNTIME_CLASS(OpGroup), OPTOKEN_GROUP, OpGroup::GetState, 0, /* help ID */ _R(IDBBL_GROUPOP), 0, /* bitmap ID */ 0, SYSTEMBAR_ILLEGAL, // For now ! TRUE, // Receive messages FALSE, FALSE, 0, (GREY_WHEN_NO_CURRENT_DOC | GREY_WHEN_NO_SELECTION) )); if (Camelot.DeclareSection(_T("Groups"), 2)) { Camelot.DeclarePref(_T("Groups"), _T("AskUngroupEffects"), &bAskBeforeFactoringEffects, FALSE, TRUE); Camelot.DeclarePref(_T("Groups"), _T("UngroupLocaliseEffects"), &bLocaliseEffects, FALSE, TRUE); } } /******************************************************************************************** > OpState OpGroup::GetState(String_256*, OpDescriptor*) Author: Simon_Maneggio (Xara Group Ltd) Created: 28/9/93 Returns: The state of the OpGroup Purpose: For finding the OpGroup's state. ********************************************************************************************/ OpState OpGroup::GetState(String_256* UIDescription, OpDescriptor*) { OpState OpSt; String_256 DisableReason; // Obtain the current selections Range Sel(*(GetApplication()->FindSelection())); RangeControl rg = Sel.GetRangeControlFlags(); rg.PromoteToParent = TRUE; Sel.Range::SetRangeControl(rg); // This operation is disabled if there are no nodes selected, or if the only selected node // is a node group. Node* FirstSelected = Sel.FindFirst(); OpSt.Greyed = FALSE; if (IS_A(FirstSelected,NodeGroup) && (Sel.FindNext(FirstSelected) == NULL) ) // Only a NodeGroup selected { OpSt.Greyed = TRUE; // Load reason why operation is disabled // Grouping only a single group is pointless DisableReason = String_256(_R(IDS_ILLEGAL_TO_GROUP_SINGLE_GROUP)); *UIDescription = DisableReason; } return(OpSt); } /******************************************************************************************** > BOOL OpGroup::NeedToInvalidateGroup() Author: Simon_Maneggio (Xara Group Ltd) Created: 12/7/94 Inputs: - Outputs: - Returns: TRUE if we need to invalidate Purpose: This function determines if the selected objects will need to be invalidated if they are to be made into a group. Errors: - Scope: private SeeAlso: - ********************************************************************************************/ BOOL OpGroup::NeedToInvalidateGroup() { // if (TRUE /*GetApplication()->IsBkRendering()*/) // return TRUE; // Always invalidate if Background Redraw is occuring // If all selected objects are contiguous then there will be no need to invalidate // the group's bounds. // Obtain the current selection SelRange Sel(*(GetApplication()->FindSelection())); RangeControl rg = Sel.GetRangeControlFlags(); rg.PromoteToParent = TRUE; Sel.Range::SetRangeControl(rg); Node* Current = Sel.FindFirst(); ERROR2IF(Current == NULL, TRUE, "There are no selected objects"); // Create a range of all objects starting with the first selected object // RangeControl rc = { TRUE, TRUE, TRUE }; // selected + unselected nodes, cross layer Range ObjectRange(Current, NULL, RangeControl(TRUE,TRUE,TRUE,TRUE)); BOOL Contiguous = TRUE; // Until we know better DocRect SelBounds; Node* SelObject = Current; Node* Object = Current; // Traverse both ranges simultaneously while (SelObject != NULL) { if (SelObject != Object) { // All selected objects are not contiguous if (Contiguous) { // We will need to calculate the bounds of the selection for what comes next SelBounds = Sel.GetBoundingRect(); Contiguous = FALSE; // So we don't calculate the bounds again } // Check if the uncontiguous object intersects with the bounds of the selection if (Object->IsAnObject()) { if ( ((NodeRenderableInk*)Object)->GetBoundingRect().IsIntersectedWith(SelBounds)) { return TRUE; // An unselected object intersects with a selected object } } // Objects don't intersect so it doesn't matter Object = ObjectRange.FindNext(Object); ERROR3IF(Object == NULL, "Reached end of object list"); } else { // Get next items in both ranges SelObject = Sel.FindNext(SelObject); Object = ObjectRange.FindNext(Object); } } return FALSE; // No need to invalidate } /******************************************************************************************** > void OpGroup::Do(OpDescriptor*) Author: Simon_Maneggio (Xara Group Ltd) Created: 16/8/93 Inputs: OpDescriptor (unused) Outputs: - Returns: - Purpose: Performs the Group operation. Errors: - SeeAlso: - ********************************************************************************************/ void OpGroup::Do(OpDescriptor*) { // Obtain the current selections Range Sel(*(GetApplication()->FindSelection())); RangeControl rg = Sel.GetRangeControlFlags(); rg.PromoteToParent = TRUE; Sel.Range::SetRangeControl(rg); // Find the first node which is selected Node* FirstSelectedNode = Sel.FindFirst(); ENSURE(FirstSelectedNode != NULL, "Called group operation with no nodes selected"); HideNodeAction* UndoHideNodeAction; // In the retail build it is best to do nothing if we find there are no selected nodes if (FirstSelectedNode != NULL) // No nodes selected so End { // Get the current tool Tool* pTool = Tool::GetCurrent(); Spread* pSelSpread = Document::GetSelectedSpread(); // Get the tool to remove all its blobs before we deselect the nodes. // Only do this if the current tool dosent update itself on sel changed messages if (pSelSpread!=NULL && pTool!=NULL && !pTool->AreToolBlobsRenderedOnSelection()) pTool->RenderToolBlobs(pSelSpread,NULL); // Determine if it will be neccessary to invalidate the groups bounds BOOL InvalidateBounds = TRUE; BOOL IfBgRedraw = !NeedToInvalidateGroup(); // Draw blobs if we are not invalidating the object bounds if (!DoStartSelOp(IfBgRedraw,IfBgRedraw)) { End(); return; } if (InvalidateBounds) { // We need to invalidate the region if (!DoInvalidateNodesRegions(Sel, TRUE, FALSE, IfBgRedraw)) { End(); return; } } // Go and find the spread that the selected objects are on #ifdef _DEBUG Spread* pSpread = FirstSelectedNode->FindParentSpread(); ENSURE(pSpread != NULL, "A selected nodes spread is NULL"); #endif // Find the selected node with the highest z-order position, as we will need to insert // the group node here Node* FinalNode = NULL; for(Node* n = Sel.FindFirst(); n != NULL; n = Sel.FindNext(n)) { FinalNode = n; } ENSURE(FinalNode != NULL, "The final selected node is NULL ?"); // Create a group node and attach it as a next sibling of the final selected node NodeGroup* Group; ALLOC_WITH_FAIL(Group, (new NodeGroup(FinalNode, NEXT)), this); // If the allocation fails the FailAndExecute will have been called if (Group != NULL) { // Create an action to hide the group when we undo if ( HideNodeAction::Init(this, &UndoActions, Group, FALSE, // Don't include subtree size (Action**)(&UndoHideNodeAction), FALSE) // Don't tell subtree when undone == AC_FAIL) { Group->UnlinkNodeFromTree(); delete (Group); End(); return; }; // Take each node in the range and attach it to the NodeGroup Node* n = Sel.FindFirst(); // Get first node to be added to group // Loop until all nodes in range have been added to the group Node* NxtInRange; Node* LastObjectMoved = NULL; DocRect GroupBounds; // bounding rectangle of the group while (n != NULL) // Stop when there are no more selected nodes. { // Ensure that the selected node is a NodeRenderableInk ENSURE(n->IsAnObject(), "Selected node is not a NodeRenderableInk"); NxtInRange = Sel.FindNext(n); // Neccessary because n is // just about to be moved // Attach n to NodeGroup if (LastObjectMoved == NULL) { if (!DoMoveNode((NodeRenderableInk*)n, Group, LASTCHILD)) { End(); return; } } else { // We can do it quicker if (!DoMoveNode((NodeRenderableInk*)n, LastObjectMoved, NEXT)) { End(); return; } } // Deselect the object // Botch - until selstate restores child states //if (!DoDeselectNode(((NodeRenderableInk*)n))) //{ // goto EndOperation; //} ((NodeRenderableInk*)n)->DeSelect(FALSE, TRUE); GroupBounds = GroupBounds.Union( ((NodeRenderableBounded*)n)->GetBoundingRect()); LastObjectMoved = n; n = NxtInRange; // Get the next node in the range } // Set the group's bounding rectangle Group->InvalidateBoundingRect(); // Select the group and draw its blobs ((NodeRenderable*)Group)->Select(FALSE); if (InvalidateBounds) { if (!DoInvalidateNodesRegions(*(GetApplication()->FindSelection()), TRUE, FALSE, IfBgRedraw)) { // We need to invalidate the region End(); return; } } // Now factor out all common attributes // bodge if (!DoFactorOutCommonChildAttributes(Group)) { End(); return; } } // Get the tool to remove all its blobs before we deselect the nodes. // Only do this if the current tool dosent update itself on sel changed messages if (pSelSpread!=NULL && pTool!=NULL && !pTool->AreToolBlobsRenderedOnSelection()) pTool->RenderToolBlobs(pSelSpread,NULL); } End(); } // ------------------------------------------------------------------------------------------ // OpUngroup methods /******************************************************************************************** > OpUngroup::OpUngroup() Author: Simon_Maneggio (Xara Group Ltd) Created: 29/9/93 Inputs: - Outputs: - Returns: - Purpose: OpUngroup constructor Errors: - SeeAlso: - ********************************************************************************************/ OpUngroup::OpUngroup(): SelOperation() { } /******************************************************************************************** > BOOL OpUngroup::Init() Author: Simon_Maneggio (Xara Group Ltd) Created: 28/9/93 Inputs: - Outputs: - Returns: TRUE if the operation could be successfully initialised FALSE if no more memory could be allocated Purpose: OpUngroup initialiser method Errors: ERROR will be called if there was insufficient memory to allocate the operation. SeeAlso: - ********************************************************************************************/ BOOL OpUngroup::Init() { return (RegisterOpDescriptor(0, _R(IDS_UNGROUPOP), CC_RUNTIME_CLASS(OpUngroup), OPTOKEN_UNGROUP, OpUngroup::GetState, 0, /* help ID */ _R(IDBBL_UNGROUPOP), 0, /* bitmap ID */ 0, SYSTEMBAR_ILLEGAL, // For now ! TRUE, // Receive messages FALSE, FALSE, 0, (GREY_WHEN_NO_CURRENT_DOC | GREY_WHEN_NO_SELECTION) )); } /******************************************************************************************** > OpState OpUngroup::GetState(String_256*, OpDescriptor*) Author: Simon_Maneggio (Xara Group Ltd) Created: 28/9/93 Inputs: - Outputs: - Returns: The state of the OpUngroup Purpose: For finding the OpUngroup's state. Errors: - SeeAlso: - Notes: Karim 06/10/2000 Modified this method to check for shadowed, bevelled and contoured groups, as these cannot be ungrouped until the shadow, bevel or contour is removed. ********************************************************************************************/ OpState OpUngroup::GetState(String_256* UIDescription, OpDescriptor*) { OpState OpSt; String_256 DisableReason; // Obtain the current selections Range Sel(*(GetApplication()->FindSelection())); // Determine if there are any group nodes selected, // and if so, whether they lie within controller nodes. Node* n = Sel.FindFirst(); // Node* pParentController = NULL; BOOL SelectedGroup = FALSE; // BOOL InsideControllerNode = FALSE; while (n != NULL) { if ( n->IsSelected() && IS_A(n,NodeGroup) ) { /* pParentController = n->FindParent(); if (pParentController != NULL) { if (pParentController->IsABevelController() || // pParentController->IsAShadowController() || pParentController->IsAContourController()) { InsideControllerNode = TRUE; break; } } */ SelectedGroup = TRUE; break; } n = Sel.FindNext(n); } // OpSt.Greyed = (InsideControllerNode || !SelectedGroup); OpSt.Greyed = !SelectedGroup; if (OpSt.Greyed) { // Load reason why operation is disabled. /* // we're inside a controller node. if (InsideControllerNode) { String_256 DisableReason(_R(IDS_GROUPINSIDECONTROLLER)); *UIDescription = DisableReason; } */ // No group nodes are selected. // else if (!SelectedGroup) if(!SelectedGroup) { String_256 DisableReason(_R(IDS_NO_GROUPS_SEL)); *UIDescription = DisableReason; } } return(OpSt); } /******************************************************************************************** > void OpUngroup::Do(OpDescriptor*) Author: Simon_Maneggio (Xara Group Ltd) Created: 16/8/93 Inputs: OpDescriptor (unused) Outputs: - Returns: - Purpose: Performs the Ungroup operation Errors: An ENSURE failure will occur if this function is called and there are no selected groups. SeeAlso: - ********************************************************************************************/ void OpUngroup::Do(OpDescriptor*) { if (DoStartSelOp(TRUE,TRUE)) // Try to record the selection state { if (!UngroupSelectedGroups()) { FailAndExecute(); } } End(); } /******************************************************************************************** > BOOL OpUngroup::ParentAllowsUngroup(NodeGroup* pChild) Author: Karim_MacDonald (Xara Group Ltd) Created: 09/10/2000 Inputs: pChild the group node to check. Returns: TRUE if our parent's type is good for ungrouping, FALSE otherwise. Purpose: Nasty BODGE code! Checks for the user trying to ungroup a shadowed (maybe bevelled or contoured too in future) group, disallows the operation and explains to them what the problem is/how to solve it. Errors: ERROR3 if the parent is an unexpected type - all such cases should never get this far into the ungroup code, they should have been caught by the GetState() checks. ********************************************************************************************/ BOOL OpUngroup::ParentAllowsUngroup(NodeGroup* pChild) { Node* pParent = pChild->FindParent(); UINT32 MessageID = 0; if (pParent != NULL) { // if the parent is a NodeShadowController, // then post a message informing the user. if (pParent->IsABevelController()) MessageID = _R(IDS_GROUPINSIDEBEVEL); PORTNOTE("other", "Ungroup can't localise effects yet") #ifndef EXCLUDE_FROM_XARALX // We don't need this clause when shadows can be localised in ungroup // else if (pParent->IsAShadowController()) // Shadows are now PostProcessors and allow ungrouping // MessageID = _R(IDS_GROUPINSIDESHADOW); #else else if (pParent->IsAShadowController()) // Shadows are now PostProcessors and allow ungrouping MessageID = _R(IDS_GROUPINSIDESHADOW); #endif else if (pParent->IsAContourController()) MessageID = _R(IDS_GROUPINSIDECONTOUR); if(MessageID != 0) { ::InformWarning(MessageID); return FALSE; } // we must catch any group-derived nodes which make it past any ungroup checks. // the GetState() code should catch all such instances, so if execution gets here, // then something somewhere else is broken! if (pParent->IsAGroup()) { ERROR3("OpUngroup::UngroupSelectedGroups; Group-derived parent node got past OpUngroup checks!"); return FALSE; } } return TRUE; } /******************************************************************************************** > BOOL OpUngroup::UngroupSelectedGroups() Author: Simon_Maneggio (Xara Group Ltd) Created: 22/10/93 Inputs: - Outputs: - Returns: TRUE if successful FALSE if the operation was aborted, or if there are no selected group nodes. Purpose: The function scans the tree and ungroups selected group nodes. It implements the main body of the OpUngroup Do function. The reason the Do function was split was so that the UngroupSpecial operation could re-use the code. Errors: - Scope: protected SeeAlso: OpUngroup::Do ********************************************************************************************/ BOOL OpUngroup::UngroupSelectedGroups() { // Obtain the current selections Range Sel(*(GetApplication()->FindSelection())); BOOL Ungrouped = FALSE; // Set to true when we ungroup a group BOOL bSelectGroupsHaveEffectAttrs = FALSE; Node* FirstSelectedNode = Sel.FindFirst(); if (FirstSelectedNode != NULL) // No nodes selected so End { BOOL bLocalisePostPros = FALSE; BOOL bDeletePostPros = FALSE; EffectsStack* pStack = GetApplication()->FindSelection()->GetEffectsStack(); bSelectGroupsHaveEffectAttrs = FindSelectedEffectAttrs(); if ((pStack && !pStack->IsEmpty()) || bSelectGroupsHaveEffectAttrs) { bLocalisePostPros = TRUE; // If the preference is set of the user forces it by holding down Shift // We will ask the user what he wanst to do with effects ClickModifiers clickmods = ClickModifiers::GetClickModifiers(); if (OpGroup::bAskBeforeFactoringEffects || clickmods.Adjust) { OpGroup::bAskBeforeFactoringEffects = TRUE; // We need to ask the user about removing or localising PostProcessors INT32 ret = AskQuestion(_R(IDS_LE_ASK_UNGROUPLOCALISE), _R(IDS_LE_COPY), _R(IDS_LE_ALWAYSCOPY), _R(IDS_LE_REMOVE), _R(IDS_CANCEL)); switch (ret) { case 1: bLocalisePostPros = TRUE; break; case 2: bLocalisePostPros = TRUE; OpGroup::bAskBeforeFactoringEffects = FALSE; OpGroup::bLocaliseEffects = TRUE; break; case 3: bDeletePostPros = TRUE; OpGroup::bLocaliseEffects = FALSE; // ? break; case 4: return FALSE; break; } } else { bLocalisePostPros = OpGroup::bLocaliseEffects; bDeletePostPros = !OpGroup::bLocaliseEffects; } } // Get the current tool Tool* pTool = Tool::GetCurrent(); Spread* pSelSpread = Document::GetSelectedSpread(); // Get the tool to remove all its blobs before we deselect the nodes. // Only do this if the current tool dosent update itself on sel changed messages if (pSelSpread!=NULL && pTool!=NULL && !pTool->AreToolBlobsRenderedOnSelection()) pTool->RenderToolBlobs(pSelSpread,NULL); // Find out which spread the node to be ungrouped is in // Spread* pSpread = FirstSelectedNode->FindParentSpread(); Node* CurrentNode = FirstSelectedNode; Node* NextCurrent; Node* GrpNode; Node* NxtGrpNode; Node* Anchor; // Ungroup all selected groups while (CurrentNode != NULL) { NextCurrent = Sel.FindNext(CurrentNode); if (CurrentNode->IsSelected() && IS_A(CurrentNode, NodeGroup)) { // Karim 09/10/2000 // Shadows BODGE - if our parent is a NodeShadowController, then we cannot ungroup. // Phil 2005, Shadows are different now but other controllers still disallow // ungrouping! if (ParentAllowsUngroup((NodeGroup*)CurrentNode)) { Ungrouped = TRUE; // Karim 09/10/2000 // Invalidate region ONLY if in the middle of a background redraw UNLESS we were feathered. // Invalidate if we are changing effects so they the changes get redrawn BOOL RedrawNow = (CurrentNode->FindFirstChild(CC_RUNTIME_CLASS(AttrFeather))!=NULL || bDeletePostPros || bLocalisePostPros || bSelectGroupsHaveEffectAttrs ); DoInvalidateNodeRegion( ((NodeRenderableBounded*)CurrentNode), TRUE, FALSE, !RedrawNow); // Localise any containing PostProcessors // (This will push down common attributes into the group) /* // Find topmost effect applied to the current node and localise attributes // down from there... Node* pTopNode = EffectsStack::EscapeOldControllers(CurrentNode); Node* pHighestLE = CurrentNode; do { if (pTopNode->IsPostProcessor()) pHighestLE = pTopNode; pTopNode = pTopNode->FindParent(); } while (pTopNode && pTopNode->IsPostProcessor()); */ if (bLocalisePostPros) { PORTNOTETRACE("other", "Ungroup can't localise effects yet"); #if !defined(EXCLUDE_FROM_XARALX) BOOL bOK = TRUE; bOK = bOK && OpLiveEffect::DoLocaliseEffectAttrs(this, CurrentNode); bOK = bOK && OpLiveEffect::DoLocaliseLiveEffects(this, CurrentNode); if (!bOK) return FALSE; #endif } else if (bDeletePostPros) { PORTNOTETRACE("other", "Ungroup can't localise effects yet"); #if !defined(EXCLUDE_FROM_XARALX) if (CurrentNode->IsAnObject()) { BOOL bOK = (OpLiveEffect::DoDeleteAllPostProcessors(this, (NodeRenderableInk*)CurrentNode, FALSE, TRUE)!=NULL); // Effect attrs will be deleted when the group and/or postpros are deleted // because of this we need to force the Op to redraw (see If BgRedraw above) if (!bOK) return FALSE; } #endif } // Localise the attributes if (!DoLocaliseCommonAttributes(((NodeGroup*)CurrentNode))) return FALSE; // Deselect the group node, cos we have got to remove its blobs ((NodeGroup*)CurrentNode)->DeSelect(FALSE); ENSURE(!(CurrentNode->IsSelected()), "Deselect failed to deselect current node"); // Hide the group node NodeHidden* Hidden; if(!DoHideNode(CurrentNode, FALSE, &Hidden, FALSE)) return FALSE; Anchor = Hidden; ENSURE(Anchor->IsNodeHidden(), "The parent of a hidden group node is not a NodeHidden"); // Move and select each node in turn. GrpNode = CurrentNode->FindFirstChild(); // Group's first child node // Make sure that the group has at least one child #ifdef _DEBUG if (GrpNode == NULL) { TRACE( _T("Whilst ungrouping a group node without any children was found - It must be a Corel file")); } #endif //ENSURE(GrpNode != NULL, "Trying to ungroup a node which has no children"); while (GrpNode != NULL) { // Find the next node to ungroup before we move the group node NxtGrpNode = GrpNode->FindNext(); // If the node is not a NodeHidden then // Move the node to its new location in the tree. There is no need // to render the node. if (!GrpNode->IsAnAttribute() && !GrpNode->IsNodeHidden()) { if (!DoMoveNode(GrpNode, Anchor, NEXT)) return FALSE; if (GrpNode->IsAnObject()) ((NodeRenderableInk*)GrpNode)->Select(FALSE); Anchor = GrpNode; // Attach the next node to ungroup to the last one // so maintaining z ordering. } GrpNode = NxtGrpNode; } } } CurrentNode = NextCurrent; } // Get the tool to remove all its blobs before we deselect the nodes. // Only do this if the current tool dosent update itself on sel changed messages if (pSelSpread!=NULL && pTool!=NULL && !pTool->AreToolBlobsRenderedOnSelection()) pTool->RenderToolBlobs(pSelSpread,NULL); } else return (FALSE); // No nodes selected return (Ungrouped); // Ungrouped will be TRUE if we have ungrouped any groups } BOOL OpUngroup::FindSelectedEffectAttrs() const { Range* pRange = GetApplication()->FindSelection(); Node* pNode = pRange->FindFirst(); while (pNode) { if (pNode->IsAGroup()) { NodeAttribute* pAttr = NodeAttribute::FindFirstAppliedAttr(pNode); while (pAttr && pAttr->FindParent() == pNode) { if (pAttr->IsEffectAttribute() && !pAttr->HasEquivalentDefaultValue(TRUE)) return TRUE; pAttr = NodeAttribute::FindPrevAppliedAttr(pAttr); } } pNode = pRange->FindNext(pNode); } return FALSE; } // ------------------------------------------------------------------------------------------ // OpUngroupSpecial methods /******************************************************************************************** > OpUngroupSpecial::OpUngroupSpecial() Author: Simon_Maneggio (Xara Group Ltd) Created: 29/9/93 Inputs: - Outputs: - Returns: - Purpose: OpUngroupSpecial constructor Errors: - SeeAlso: - ********************************************************************************************/ OpUngroupSpecial::OpUngroupSpecial(): OpUngroup() { } /******************************************************************************************** > BOOL OpUngroupSpecial::Init() Author: Simon_Maneggio (Xara Group Ltd) Created: 28/9/93 Inputs: - Outputs: - Returns: TRUE if the operation could be successfully initialised FALSE if no more memory could be allocated Purpose: OpUngroupSpecial initialiser method Errors: ERROR will be called if there was insufficient memory to allocate the operation. SeeAlso: - ********************************************************************************************/ BOOL OpUngroupSpecial::Init() { return (RegisterOpDescriptor(0, _R(IDS_UNGROUPSPECIALOP), CC_RUNTIME_CLASS(OpUngroupSpecial), OPTOKEN_UNGROUPSPECIAL, OpUngroupSpecial::GetState, 0, /* help ID */ _R(IDBBL_UNGROUPSPECIALOP), 0, 0, SYSTEMBAR_ILLEGAL, // For now ! TRUE, // Receive messages FALSE, FALSE, 0, (GREY_WHEN_NO_CURRENT_DOC | GREY_WHEN_NO_SELECTION) )); } /******************************************************************************************** > OpState OpUngroupSpecial::GetState(String_256*, OpDescriptor*) Author: Simon_Maneggio (Xara Group Ltd) Created: 28/9/93 Inputs: - Outputs: - Returns: The state of the OpUngroupSpecial Purpose: For finding the OpUngroupSpecial's state. Errors: - SeeAlso: - ********************************************************************************************/ OpState OpUngroupSpecial::GetState(String_256* s, OpDescriptor* o) { return(OpUngroup::GetState(s,o)); } /******************************************************************************************** > void OpUngroupSpecial::Do(OpDescriptor*) Author: Simon_Maneggio (Xara Group Ltd) Created: 16/8/93 Inputs: OpDescriptor (unused) Outputs: - Returns: - Purpose: Performs the Ungroup Special operation Errors: An ENSURE failure will occur if this function is called and there are no selected groups. SeeAlso: - ********************************************************************************************/ void OpUngroupSpecial::Do(OpDescriptor*) { // Call UngroupSelectedGroups until there are no more groups to ungroup or until // the operation fails. if (DoStartSelOp(TRUE,TRUE)) // Try to record the selection state { while (OpUngroup::UngroupSelectedGroups()); } End(); }