00001 // $Id: groupops.cpp 1282 2006-06-09 09:46:49Z 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 */ 00100 00101 #include "camtypes.h" 00102 #include "camelot.h" 00103 //#include "grptrans.h" 00104 #include "groupops.h" 00105 //#include "group.h" - in camtypes.h [AUTOMATICALLY REMOVED] 00106 //#include "undoop.h" - in camtypes.h [AUTOMATICALLY REMOVED] 00107 //#include "resource.h" 00108 //#include "simon.h" 00109 //#include "errors.h" - in camtypes.h [AUTOMATICALLY REMOVED] 00110 //#include "ensure.h" - in camtypes.h [AUTOMATICALLY REMOVED] 00111 //#include "docview.h" - in camtypes.h [AUTOMATICALLY REMOVED] 00112 //#include "opdesc.h" - in camtypes.h [AUTOMATICALLY REMOVED] 00113 //#include "mario.h" 00114 //#include "nodeattr.h" - in camtypes.h [AUTOMATICALLY REMOVED] 00115 //#include "ink.h" - in camtypes.h [AUTOMATICALLY REMOVED] 00116 //#include "app.h" - in camtypes.h [AUTOMATICALLY REMOVED] 00117 //#include "document.h" - in camtypes.h [AUTOMATICALLY REMOVED] 00118 //#include "spread.h" - in camtypes.h [AUTOMATICALLY REMOVED] 00119 #include "layer.h" 00120 //#include "tool.h" - in camtypes.h [AUTOMATICALLY REMOVED] 00121 #include "bubbleid.h" 00122 #include "fthrattr.h" 00123 #include "opliveeffects.h" 00124 #include "effects_stack.h" 00125 //#include "phil.h" 00126 #include "ophist.h" 00127 00128 DECLARE_SOURCE("$Revision: 1282 $"); 00129 00130 CC_IMPLEMENT_DYNCREATE(OpGroup, SelOperation) 00131 CC_IMPLEMENT_DYNCREATE(OpUngroup, SelOperation) 00132 CC_IMPLEMENT_DYNCREATE(OpUngroupSpecial, OpUngroup) 00133 00134 #define new CAM_DEBUG_NEW 00135 00136 /********************************************************************************************* 00137 00138 Preference: AskUngroupEffect 00139 Section: Groups 00140 Range: FALSE - TRUE 00141 Purpose: Flag controlling whether user is asked about factoring out effects during ungroup 00142 SeeAlso: - 00143 00144 **********************************************************************************************/ 00145 BOOL OpGroup::bAskBeforeFactoringEffects = FALSE; 00146 00147 00148 /********************************************************************************************* 00149 00150 Preference: UngroupLocaliseEffects 00151 Section: Groups 00152 Range: FALSE - TRUE 00153 Purpose: Flag controlling whether effects are localised by default or removed by default 00154 SeeAlso: - 00155 00156 **********************************************************************************************/ 00157 BOOL OpGroup::bLocaliseEffects = FALSE; 00158 00159 00160 // ------------------------------------------------------------------------------------------ 00161 // OpGroup methods 00162 00163 /******************************************************************************************** 00164 00165 > OpGroup::OpGroup() 00166 00167 Author: Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com> 00168 Created: 29/9/93 00169 Purpose: OpGroup constructor 00170 00171 ********************************************************************************************/ 00172 OpGroup::OpGroup(): SelOperation() 00173 { 00174 } 00175 00176 /******************************************************************************************** 00177 00178 > BOOL OpGroup::Init() 00179 00180 Author: Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com> 00181 Created: 28/9/93 00182 Returns: TRUE if the operation could be successfully initialised 00183 FALSE if no more memory could be allocated 00184 00185 Purpose: OpGroup initialiser method 00186 Errors: ERROR will be called if there was insufficient memory to allocate the 00187 operation. 00188 00189 ********************************************************************************************/ 00190 BOOL OpGroup::Init() 00191 { 00192 return (RegisterOpDescriptor(0, 00193 _R(IDS_GROUPOP), 00194 CC_RUNTIME_CLASS(OpGroup), 00195 OPTOKEN_GROUP, 00196 OpGroup::GetState, 00197 0, /* help ID */ 00198 _R(IDBBL_GROUPOP), 00199 0, /* bitmap ID */ 00200 0, 00201 SYSTEMBAR_ILLEGAL, // For now ! 00202 TRUE, // Receive messages 00203 FALSE, 00204 FALSE, 00205 0, 00206 (GREY_WHEN_NO_CURRENT_DOC | GREY_WHEN_NO_SELECTION) 00207 00208 )); 00209 00210 if (Camelot.DeclareSection(_T("Groups"), 2)) 00211 { 00212 Camelot.DeclarePref(_T("Groups"), _T("AskUngroupEffects"), &bAskBeforeFactoringEffects, FALSE, TRUE); 00213 Camelot.DeclarePref(_T("Groups"), _T("UngroupLocaliseEffects"), &bLocaliseEffects, FALSE, TRUE); 00214 } 00215 } 00216 00217 /******************************************************************************************** 00218 00219 > OpState OpGroup::GetState(String_256*, OpDescriptor*) 00220 00221 Author: Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com> 00222 Created: 28/9/93 00223 Returns: The state of the OpGroup 00224 Purpose: For finding the OpGroup's state. 00225 00226 ********************************************************************************************/ 00227 OpState OpGroup::GetState(String_256* UIDescription, OpDescriptor*) 00228 { 00229 OpState OpSt; 00230 String_256 DisableReason; 00231 00232 // Obtain the current selections 00233 Range Sel(*(GetApplication()->FindSelection())); 00234 RangeControl rg = Sel.GetRangeControlFlags(); 00235 rg.PromoteToParent = TRUE; 00236 Sel.Range::SetRangeControl(rg); 00237 00238 // This operation is disabled if there are no nodes selected, or if the only selected node 00239 // is a node group. 00240 Node* FirstSelected = Sel.FindFirst(); 00241 00242 OpSt.Greyed = FALSE; 00243 if (IS_A(FirstSelected,NodeGroup) 00244 && (Sel.FindNext(FirstSelected) == NULL) ) // Only a NodeGroup selected 00245 { 00246 OpSt.Greyed = TRUE; 00247 // Load reason why operation is disabled 00248 // Grouping only a single group is pointless 00249 DisableReason = String_256(_R(IDS_ILLEGAL_TO_GROUP_SINGLE_GROUP)); 00250 *UIDescription = DisableReason; 00251 } 00252 00253 return(OpSt); 00254 } 00255 00256 00257 /******************************************************************************************** 00258 00259 > BOOL OpGroup::NeedToInvalidateGroup() 00260 00261 Author: Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com> 00262 Created: 12/7/94 00263 Inputs: - 00264 Outputs: - 00265 Returns: TRUE if we need to invalidate 00266 Purpose: This function determines if the selected objects will need to be invalidated 00267 if they are to be made into a group. 00268 00269 00270 00271 Errors: - 00272 Scope: private 00273 SeeAlso: - 00274 00275 ********************************************************************************************/ 00276 00277 BOOL OpGroup::NeedToInvalidateGroup() 00278 { 00279 // if (TRUE /*GetApplication()->IsBkRendering()*/) 00280 // return TRUE; // Always invalidate if Background Redraw is occuring 00281 00282 // If all selected objects are contiguous then there will be no need to invalidate 00283 // the group's bounds. 00284 00285 // Obtain the current selection 00286 SelRange Sel(*(GetApplication()->FindSelection())); 00287 RangeControl rg = Sel.GetRangeControlFlags(); 00288 rg.PromoteToParent = TRUE; 00289 Sel.Range::SetRangeControl(rg); 00290 00291 Node* Current = Sel.FindFirst(); 00292 00293 ERROR2IF(Current == NULL, TRUE, "There are no selected objects"); 00294 00295 // Create a range of all objects starting with the first selected object 00296 00297 // RangeControl rc = { TRUE, TRUE, TRUE }; // selected + unselected nodes, cross layer 00298 00299 Range ObjectRange(Current, NULL, RangeControl(TRUE,TRUE,TRUE,TRUE)); 00300 00301 BOOL Contiguous = TRUE; // Until we know better 00302 DocRect SelBounds; 00303 00304 Node* SelObject = Current; 00305 Node* Object = Current; 00306 00307 // Traverse both ranges simultaneously 00308 while (SelObject != NULL) 00309 { 00310 if (SelObject != Object) 00311 { 00312 // All selected objects are not contiguous 00313 if (Contiguous) 00314 { 00315 // We will need to calculate the bounds of the selection for what comes next 00316 SelBounds = Sel.GetBoundingRect(); 00317 Contiguous = FALSE; // So we don't calculate the bounds again 00318 } 00319 00320 // Check if the uncontiguous object intersects with the bounds of the selection 00321 00322 if (Object->IsAnObject()) 00323 { 00324 00325 if ( ((NodeRenderableInk*)Object)->GetBoundingRect().IsIntersectedWith(SelBounds)) 00326 { 00327 return TRUE; // An unselected object intersects with a selected object 00328 } 00329 } 00330 // Objects don't intersect so it doesn't matter 00331 Object = ObjectRange.FindNext(Object); 00332 ERROR3IF(Object == NULL, "Reached end of object list"); 00333 } 00334 else 00335 { 00336 // Get next items in both ranges 00337 SelObject = Sel.FindNext(SelObject); 00338 Object = ObjectRange.FindNext(Object); 00339 } 00340 } 00341 return FALSE; // No need to invalidate 00342 00343 } 00344 00345 /******************************************************************************************** 00346 00347 > void OpGroup::Do(OpDescriptor*) 00348 00349 Author: Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com> 00350 Created: 16/8/93 00351 Inputs: OpDescriptor (unused) 00352 Outputs: - 00353 Returns: - 00354 Purpose: Performs the Group operation. 00355 00356 Errors: - 00357 SeeAlso: - 00358 00359 ********************************************************************************************/ 00360 00361 void OpGroup::Do(OpDescriptor*) 00362 { 00363 // Obtain the current selections 00364 Range Sel(*(GetApplication()->FindSelection())); 00365 RangeControl rg = Sel.GetRangeControlFlags(); 00366 rg.PromoteToParent = TRUE; 00367 Sel.Range::SetRangeControl(rg); 00368 00369 // Find the first node which is selected 00370 Node* FirstSelectedNode = Sel.FindFirst(); 00371 00372 ENSURE(FirstSelectedNode != NULL, "Called group operation with no nodes selected"); 00373 00374 HideNodeAction* UndoHideNodeAction; 00375 00376 // In the retail build it is best to do nothing if we find there are no selected nodes 00377 if (FirstSelectedNode != NULL) // No nodes selected so End 00378 { 00379 // Get the current tool 00380 Tool* pTool = Tool::GetCurrent(); 00381 Spread* pSelSpread = Document::GetSelectedSpread(); 00382 00383 // Get the tool to remove all its blobs before we deselect the nodes. 00384 // Only do this if the current tool dosent update itself on sel changed messages 00385 if (pSelSpread!=NULL && pTool!=NULL && !pTool->AreToolBlobsRenderedOnSelection()) 00386 pTool->RenderToolBlobs(pSelSpread,NULL); 00387 00388 // Determine if it will be neccessary to invalidate the groups bounds 00389 BOOL InvalidateBounds = TRUE; 00390 BOOL IfBgRedraw = !NeedToInvalidateGroup(); 00391 00392 // Draw blobs if we are not invalidating the object bounds 00393 if (!DoStartSelOp(IfBgRedraw,IfBgRedraw)) 00394 { 00395 End(); 00396 return; 00397 } 00398 00399 if (InvalidateBounds) 00400 { 00401 // We need to invalidate the region 00402 if (!DoInvalidateNodesRegions(Sel, TRUE, FALSE, IfBgRedraw)) 00403 { 00404 End(); 00405 return; 00406 } 00407 } 00408 00409 // Go and find the spread that the selected objects are on 00410 #ifdef _DEBUG 00411 Spread* pSpread = FirstSelectedNode->FindParentSpread(); 00412 ENSURE(pSpread != NULL, "A selected nodes spread is NULL"); 00413 #endif 00414 00415 // Find the selected node with the highest z-order position, as we will need to insert 00416 // the group node here 00417 Node* FinalNode = NULL; 00418 00419 for(Node* n = Sel.FindFirst(); n != NULL; n = Sel.FindNext(n)) 00420 { 00421 FinalNode = n; 00422 } 00423 00424 ENSURE(FinalNode != NULL, "The final selected node is NULL ?"); 00425 00426 // Create a group node and attach it as a next sibling of the final selected node 00427 NodeGroup* Group; 00428 ALLOC_WITH_FAIL(Group, (new NodeGroup(FinalNode, NEXT)), this); 00429 // If the allocation fails the FailAndExecute will have been called 00430 if (Group != NULL) 00431 { 00432 // Create an action to hide the group when we undo 00433 if ( HideNodeAction::Init(this, 00434 &UndoActions, 00435 Group, 00436 FALSE, // Don't include subtree size 00437 (Action**)(&UndoHideNodeAction), 00438 FALSE) // Don't tell subtree when undone 00439 == AC_FAIL) 00440 { 00441 Group->UnlinkNodeFromTree(); 00442 delete (Group); 00443 00444 End(); 00445 return; 00446 }; 00447 00448 // Take each node in the range and attach it to the NodeGroup 00449 Node* n = Sel.FindFirst(); // Get first node to be added to group 00450 00451 // Loop until all nodes in range have been added to the group 00452 Node* NxtInRange; 00453 00454 Node* LastObjectMoved = NULL; 00455 00456 DocRect GroupBounds; // bounding rectangle of the group 00457 while (n != NULL) // Stop when there are no more selected nodes. 00458 { 00459 // Ensure that the selected node is a NodeRenderableInk 00460 ENSURE(n->IsAnObject(), 00461 "Selected node is not a NodeRenderableInk"); 00462 NxtInRange = Sel.FindNext(n); // Neccessary because n is 00463 // just about to be moved 00464 // Attach n to NodeGroup 00465 if (LastObjectMoved == NULL) 00466 { 00467 if (!DoMoveNode((NodeRenderableInk*)n, Group, LASTCHILD)) 00468 { 00469 End(); 00470 return; 00471 } 00472 } 00473 else 00474 { 00475 // We can do it quicker 00476 if (!DoMoveNode((NodeRenderableInk*)n, LastObjectMoved, NEXT)) 00477 { 00478 End(); 00479 return; 00480 } 00481 } 00482 00483 // Deselect the object 00484 // Botch - until selstate restores child states 00485 //if (!DoDeselectNode(((NodeRenderableInk*)n))) 00486 //{ 00487 // goto EndOperation; 00488 //} 00489 ((NodeRenderableInk*)n)->DeSelect(FALSE, TRUE); 00490 00491 GroupBounds = 00492 GroupBounds.Union( ((NodeRenderableBounded*)n)->GetBoundingRect()); 00493 00494 00495 LastObjectMoved = n; 00496 00497 n = NxtInRange; // Get the next node in the range 00498 } 00499 00500 00501 // Set the group's bounding rectangle 00502 Group->InvalidateBoundingRect(); 00503 00504 // Select the group and draw its blobs 00505 ((NodeRenderable*)Group)->Select(FALSE); 00506 00507 if (InvalidateBounds) 00508 { 00509 if (!DoInvalidateNodesRegions(*(GetApplication()->FindSelection()), TRUE, FALSE, IfBgRedraw)) 00510 { 00511 // We need to invalidate the region 00512 End(); 00513 return; 00514 } 00515 } 00516 00517 // Now factor out all common attributes 00518 // bodge 00519 if (!DoFactorOutCommonChildAttributes(Group)) 00520 { 00521 End(); 00522 return; 00523 } 00524 } 00525 00526 // Get the tool to remove all its blobs before we deselect the nodes. 00527 // Only do this if the current tool dosent update itself on sel changed messages 00528 if (pSelSpread!=NULL && pTool!=NULL && !pTool->AreToolBlobsRenderedOnSelection()) 00529 pTool->RenderToolBlobs(pSelSpread,NULL); 00530 } 00531 End(); 00532 } 00533 00534 00535 00536 // ------------------------------------------------------------------------------------------ 00537 // OpUngroup methods 00538 00539 /******************************************************************************************** 00540 00541 > OpUngroup::OpUngroup() 00542 00543 Author: Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com> 00544 Created: 29/9/93 00545 Inputs: - 00546 Outputs: - 00547 Returns: - 00548 Purpose: OpUngroup constructor 00549 Errors: - 00550 SeeAlso: - 00551 00552 ********************************************************************************************/ 00553 00554 00555 OpUngroup::OpUngroup(): SelOperation() 00556 { 00557 } 00558 00559 /******************************************************************************************** 00560 00561 > BOOL OpUngroup::Init() 00562 00563 Author: Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com> 00564 Created: 28/9/93 00565 Inputs: - 00566 Outputs: - 00567 Returns: TRUE if the operation could be successfully initialised 00568 FALSE if no more memory could be allocated 00569 00570 Purpose: OpUngroup initialiser method 00571 Errors: ERROR will be called if there was insufficient memory to allocate the 00572 operation. 00573 SeeAlso: - 00574 00575 ********************************************************************************************/ 00576 00577 BOOL OpUngroup::Init() 00578 { 00579 return (RegisterOpDescriptor(0, 00580 _R(IDS_UNGROUPOP), 00581 CC_RUNTIME_CLASS(OpUngroup), 00582 OPTOKEN_UNGROUP, 00583 OpUngroup::GetState, 00584 0, /* help ID */ 00585 _R(IDBBL_UNGROUPOP), 00586 0, /* bitmap ID */ 00587 0, 00588 SYSTEMBAR_ILLEGAL, // For now ! 00589 TRUE, // Receive messages 00590 FALSE, 00591 FALSE, 00592 0, 00593 (GREY_WHEN_NO_CURRENT_DOC | GREY_WHEN_NO_SELECTION) 00594 00595 )); 00596 } 00597 00598 /******************************************************************************************** 00599 00600 > OpState OpUngroup::GetState(String_256*, OpDescriptor*) 00601 00602 Author: Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com> 00603 Created: 28/9/93 00604 Inputs: - 00605 Outputs: - 00606 Returns: The state of the OpUngroup 00607 Purpose: For finding the OpUngroup's state. 00608 Errors: - 00609 SeeAlso: - 00610 00611 Notes: Karim 06/10/2000 00612 Modified this method to check for shadowed, bevelled and contoured groups, 00613 as these cannot be ungrouped until the shadow, bevel or contour is removed. 00614 00615 ********************************************************************************************/ 00616 00617 OpState OpUngroup::GetState(String_256* UIDescription, OpDescriptor*) 00618 { 00619 OpState OpSt; 00620 00621 String_256 DisableReason; 00622 00623 // Obtain the current selections 00624 Range Sel(*(GetApplication()->FindSelection())); 00625 00626 // Determine if there are any group nodes selected, 00627 // and if so, whether they lie within controller nodes. 00628 Node* n = Sel.FindFirst(); 00629 // Node* pParentController = NULL; 00630 BOOL SelectedGroup = FALSE; 00631 // BOOL InsideControllerNode = FALSE; 00632 while (n != NULL) 00633 { 00634 if ( n->IsSelected() && IS_A(n,NodeGroup) ) 00635 { 00636 /* pParentController = n->FindParent(); 00637 if (pParentController != NULL) 00638 { 00639 if (pParentController->IsABevelController() || 00640 // pParentController->IsAShadowController() || 00641 pParentController->IsAContourController()) 00642 { 00643 InsideControllerNode = TRUE; 00644 break; 00645 } 00646 } 00647 */ 00648 SelectedGroup = TRUE; 00649 break; 00650 } 00651 n = Sel.FindNext(n); 00652 } 00653 // OpSt.Greyed = (InsideControllerNode || !SelectedGroup); 00654 OpSt.Greyed = !SelectedGroup; 00655 if (OpSt.Greyed) 00656 { 00657 // Load reason why operation is disabled. 00658 /* 00659 // we're inside a controller node. 00660 if (InsideControllerNode) 00661 { 00662 String_256 DisableReason(_R(IDS_GROUPINSIDECONTROLLER)); 00663 *UIDescription = DisableReason; 00664 } 00665 */ 00666 // No group nodes are selected. 00667 // else if (!SelectedGroup) 00668 if(!SelectedGroup) 00669 { 00670 String_256 DisableReason(_R(IDS_NO_GROUPS_SEL)); 00671 *UIDescription = DisableReason; 00672 } 00673 } 00674 return(OpSt); 00675 } 00676 00677 /******************************************************************************************** 00678 00679 > void OpUngroup::Do(OpDescriptor*) 00680 00681 Author: Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com> 00682 Created: 16/8/93 00683 Inputs: OpDescriptor (unused) 00684 Outputs: - 00685 Returns: - 00686 Purpose: Performs the Ungroup operation 00687 Errors: An ENSURE failure will occur if this function is called and there are no 00688 selected groups. 00689 SeeAlso: - 00690 00691 ********************************************************************************************/ 00692 00693 void OpUngroup::Do(OpDescriptor*) 00694 { 00695 if (DoStartSelOp(TRUE,TRUE)) // Try to record the selection state 00696 { 00697 if (!UngroupSelectedGroups()) 00698 { 00699 FailAndExecute(); 00700 } 00701 } 00702 End(); 00703 } 00704 00705 00706 00707 /******************************************************************************************** 00708 00709 > BOOL OpUngroup::ParentAllowsUngroup(NodeGroup* pChild) 00710 00711 Author: Karim_MacDonald (Xara Group Ltd) <camelotdev@xara.com> 00712 Created: 09/10/2000 00713 00714 Inputs: pChild the group node to check. 00715 00716 Returns: TRUE if our parent's type is good for ungrouping, 00717 FALSE otherwise. 00718 00719 Purpose: Nasty BODGE code! 00720 00721 Checks for the user trying to ungroup a shadowed (maybe bevelled 00722 or contoured too in future) group, disallows the operation and 00723 explains to them what the problem is/how to solve it. 00724 00725 Errors: ERROR3 if the parent is an unexpected type - all such cases should 00726 never get this far into the ungroup code, they should have been caught 00727 by the GetState() checks. 00728 00729 ********************************************************************************************/ 00730 BOOL OpUngroup::ParentAllowsUngroup(NodeGroup* pChild) 00731 { 00732 Node* pParent = pChild->FindParent(); 00733 UINT32 MessageID = 0; 00734 00735 if (pParent != NULL) 00736 { 00737 // if the parent is a NodeShadowController, 00738 // then post a message informing the user. 00739 if (pParent->IsABevelController()) 00740 MessageID = _R(IDS_GROUPINSIDEBEVEL); 00741 PORTNOTE("other", "Ungroup can't localise effects yet") 00742 #ifndef EXCLUDE_FROM_XARALX 00743 // We don't need this clause when shadows can be localised in ungroup 00744 // else if (pParent->IsAShadowController()) // Shadows are now PostProcessors and allow ungrouping 00745 // MessageID = _R(IDS_GROUPINSIDESHADOW); 00746 #else 00747 else if (pParent->IsAShadowController()) // Shadows are now PostProcessors and allow ungrouping 00748 MessageID = _R(IDS_GROUPINSIDESHADOW); 00749 #endif 00750 else if (pParent->IsAContourController()) 00751 MessageID = _R(IDS_GROUPINSIDECONTOUR); 00752 00753 if(MessageID != 0) 00754 { 00755 ::InformWarning(MessageID); 00756 return FALSE; 00757 } 00758 00759 // we must catch any group-derived nodes which make it past any ungroup checks. 00760 // the GetState() code should catch all such instances, so if execution gets here, 00761 // then something somewhere else is broken! 00762 if (pParent->IsAGroup()) 00763 { 00764 ERROR3("OpUngroup::UngroupSelectedGroups; Group-derived parent node got past OpUngroup checks!"); 00765 return FALSE; 00766 } 00767 } 00768 00769 return TRUE; 00770 } 00771 00772 00773 00774 /******************************************************************************************** 00775 00776 > BOOL OpUngroup::UngroupSelectedGroups() 00777 00778 Author: Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com> 00779 Created: 22/10/93 00780 Inputs: - 00781 Outputs: - 00782 Returns: TRUE if successful 00783 FALSE if the operation was aborted, or if there are no selected group nodes. 00784 00785 Purpose: The function scans the tree and ungroups selected group nodes. It implements 00786 the main body of the OpUngroup Do function. 00787 00788 The reason the Do function was split was so that the UngroupSpecial operation 00789 could re-use the code. 00790 00791 Errors: - 00792 Scope: protected 00793 SeeAlso: OpUngroup::Do 00794 00795 ********************************************************************************************/ 00796 00797 BOOL OpUngroup::UngroupSelectedGroups() 00798 { 00799 // Obtain the current selections 00800 00801 Range Sel(*(GetApplication()->FindSelection())); 00802 00803 BOOL Ungrouped = FALSE; // Set to true when we ungroup a group 00804 BOOL bSelectGroupsHaveEffectAttrs = FALSE; 00805 Node* FirstSelectedNode = Sel.FindFirst(); 00806 if (FirstSelectedNode != NULL) // No nodes selected so End 00807 { 00808 BOOL bLocalisePostPros = FALSE; 00809 BOOL bDeletePostPros = FALSE; 00810 EffectsStack* pStack = GetApplication()->FindSelection()->GetEffectsStack(); 00811 bSelectGroupsHaveEffectAttrs = FindSelectedEffectAttrs(); 00812 if ((pStack && !pStack->IsEmpty()) || bSelectGroupsHaveEffectAttrs) 00813 { 00814 bLocalisePostPros = TRUE; 00815 // If the preference is set of the user forces it by holding down Shift 00816 // We will ask the user what he wanst to do with effects 00817 ClickModifiers clickmods = ClickModifiers::GetClickModifiers(); 00818 if (OpGroup::bAskBeforeFactoringEffects || clickmods.Adjust) 00819 { 00820 OpGroup::bAskBeforeFactoringEffects = TRUE; 00821 00822 // We need to ask the user about removing or localising PostProcessors 00823 INT32 ret = AskQuestion(_R(IDS_LE_ASK_UNGROUPLOCALISE), _R(IDS_LE_COPY), _R(IDS_LE_ALWAYSCOPY), _R(IDS_LE_REMOVE), _R(IDS_CANCEL)); 00824 switch (ret) 00825 { 00826 case 1: bLocalisePostPros = TRUE; 00827 break; 00828 case 2: bLocalisePostPros = TRUE; 00829 OpGroup::bAskBeforeFactoringEffects = FALSE; 00830 OpGroup::bLocaliseEffects = TRUE; 00831 break; 00832 case 3: bDeletePostPros = TRUE; 00833 OpGroup::bLocaliseEffects = FALSE; // ? 00834 break; 00835 case 4: return FALSE; 00836 break; 00837 } 00838 } 00839 else 00840 { 00841 bLocalisePostPros = OpGroup::bLocaliseEffects; 00842 bDeletePostPros = !OpGroup::bLocaliseEffects; 00843 } 00844 } 00845 00846 // Get the current tool 00847 Tool* pTool = Tool::GetCurrent(); 00848 Spread* pSelSpread = Document::GetSelectedSpread(); 00849 00850 // Get the tool to remove all its blobs before we deselect the nodes. 00851 // Only do this if the current tool dosent update itself on sel changed messages 00852 if (pSelSpread!=NULL && pTool!=NULL && !pTool->AreToolBlobsRenderedOnSelection()) 00853 pTool->RenderToolBlobs(pSelSpread,NULL); 00854 00855 // Find out which spread the node to be ungrouped is in 00856 // Spread* pSpread = FirstSelectedNode->FindParentSpread(); 00857 00858 Node* CurrentNode = FirstSelectedNode; 00859 Node* NextCurrent; 00860 Node* GrpNode; 00861 Node* NxtGrpNode; 00862 Node* Anchor; 00863 00864 // Ungroup all selected groups 00865 while (CurrentNode != NULL) 00866 { 00867 NextCurrent = Sel.FindNext(CurrentNode); 00868 00869 if (CurrentNode->IsSelected() && IS_A(CurrentNode, NodeGroup)) 00870 { 00871 // Karim 09/10/2000 00872 // Shadows BODGE - if our parent is a NodeShadowController, then we cannot ungroup. 00873 // Phil 2005, Shadows are different now but other controllers still disallow 00874 // ungrouping! 00875 if (ParentAllowsUngroup((NodeGroup*)CurrentNode)) 00876 { 00877 Ungrouped = TRUE; 00878 00879 // Karim 09/10/2000 00880 // Invalidate region ONLY if in the middle of a background redraw UNLESS we were feathered. 00881 // Invalidate if we are changing effects so they the changes get redrawn 00882 BOOL RedrawNow = (CurrentNode->FindFirstChild(CC_RUNTIME_CLASS(AttrFeather))!=NULL || 00883 bDeletePostPros || bLocalisePostPros || 00884 bSelectGroupsHaveEffectAttrs 00885 ); 00886 DoInvalidateNodeRegion( ((NodeRenderableBounded*)CurrentNode), TRUE, FALSE, !RedrawNow); 00887 00888 // Localise any containing PostProcessors 00889 // (This will push down common attributes into the group) 00890 /* // Find topmost effect applied to the current node and localise attributes 00891 // down from there... 00892 Node* pTopNode = EffectsStack::EscapeOldControllers(CurrentNode); 00893 Node* pHighestLE = CurrentNode; 00894 do 00895 { 00896 if (pTopNode->IsPostProcessor()) 00897 pHighestLE = pTopNode; 00898 00899 pTopNode = pTopNode->FindParent(); 00900 } 00901 while (pTopNode && pTopNode->IsPostProcessor()); 00902 */ 00903 if (bLocalisePostPros) 00904 { 00905 PORTNOTETRACE("other", "Ungroup can't localise effects yet"); 00906 #if !defined(EXCLUDE_FROM_XARALX) 00907 BOOL bOK = TRUE; 00908 bOK = bOK && OpLiveEffect::DoLocaliseEffectAttrs(this, CurrentNode); 00909 bOK = bOK && OpLiveEffect::DoLocaliseLiveEffects(this, CurrentNode); 00910 if (!bOK) return FALSE; 00911 #endif 00912 } 00913 else if (bDeletePostPros) 00914 { 00915 PORTNOTETRACE("other", "Ungroup can't localise effects yet"); 00916 #if !defined(EXCLUDE_FROM_XARALX) 00917 if (CurrentNode->IsAnObject()) 00918 { 00919 BOOL bOK = (OpLiveEffect::DoDeleteAllPostProcessors(this, (NodeRenderableInk*)CurrentNode, FALSE, TRUE)!=NULL); 00920 // Effect attrs will be deleted when the group and/or postpros are deleted 00921 // because of this we need to force the Op to redraw (see If BgRedraw above) 00922 if (!bOK) return FALSE; 00923 } 00924 #endif 00925 } 00926 00927 // Localise the attributes 00928 if (!DoLocaliseCommonAttributes(((NodeGroup*)CurrentNode))) 00929 return FALSE; 00930 00931 // Deselect the group node, cos we have got to remove its blobs 00932 ((NodeGroup*)CurrentNode)->DeSelect(FALSE); 00933 00934 ENSURE(!(CurrentNode->IsSelected()), "Deselect failed to deselect current node"); 00935 00936 // Hide the group node 00937 NodeHidden* Hidden; 00938 if(!DoHideNode(CurrentNode, FALSE, &Hidden, FALSE)) 00939 return FALSE; 00940 00941 Anchor = Hidden; 00942 ENSURE(Anchor->IsNodeHidden(), 00943 "The parent of a hidden group node is not a NodeHidden"); 00944 00945 // Move and select each node in turn. 00946 GrpNode = CurrentNode->FindFirstChild(); // Group's first child node 00947 00948 // Make sure that the group has at least one child 00949 #ifdef _DEBUG 00950 if (GrpNode == NULL) 00951 { 00952 TRACE( _T("Whilst ungrouping a group node without any children was found - It must be a Corel file")); 00953 } 00954 #endif 00955 00956 //ENSURE(GrpNode != NULL, "Trying to ungroup a node which has no children"); 00957 while (GrpNode != NULL) 00958 { 00959 00960 // Find the next node to ungroup before we move the group node 00961 NxtGrpNode = GrpNode->FindNext(); 00962 00963 // If the node is not a NodeHidden then 00964 // Move the node to its new location in the tree. There is no need 00965 // to render the node. 00966 if (!GrpNode->IsAnAttribute() && !GrpNode->IsNodeHidden()) 00967 { 00968 if (!DoMoveNode(GrpNode, Anchor, NEXT)) 00969 return FALSE; 00970 00971 if (GrpNode->IsAnObject()) 00972 ((NodeRenderableInk*)GrpNode)->Select(FALSE); 00973 00974 Anchor = GrpNode; // Attach the next node to ungroup to the last one 00975 // so maintaining z ordering. 00976 } 00977 GrpNode = NxtGrpNode; 00978 } 00979 } 00980 } 00981 CurrentNode = NextCurrent; 00982 } 00983 00984 // Get the tool to remove all its blobs before we deselect the nodes. 00985 // Only do this if the current tool dosent update itself on sel changed messages 00986 if (pSelSpread!=NULL && pTool!=NULL && !pTool->AreToolBlobsRenderedOnSelection()) 00987 pTool->RenderToolBlobs(pSelSpread,NULL); 00988 } 00989 else 00990 return (FALSE); // No nodes selected 00991 00992 return (Ungrouped); // Ungrouped will be TRUE if we have ungrouped any groups 00993 } 00994 00995 00996 BOOL OpUngroup::FindSelectedEffectAttrs() const 00997 { 00998 Range* pRange = GetApplication()->FindSelection(); 00999 Node* pNode = pRange->FindFirst(); 01000 while (pNode) 01001 { 01002 if (pNode->IsAGroup()) 01003 { 01004 NodeAttribute* pAttr = NodeAttribute::FindFirstAppliedAttr(pNode); 01005 while (pAttr && pAttr->FindParent() == pNode) 01006 { 01007 if (pAttr->IsEffectAttribute() && !pAttr->HasEquivalentDefaultValue(TRUE)) 01008 return TRUE; 01009 01010 pAttr = NodeAttribute::FindPrevAppliedAttr(pAttr); 01011 } 01012 } 01013 01014 pNode = pRange->FindNext(pNode); 01015 } 01016 01017 return FALSE; 01018 } 01019 01020 01021 01022 // ------------------------------------------------------------------------------------------ 01023 // OpUngroupSpecial methods 01024 01025 /******************************************************************************************** 01026 01027 > OpUngroupSpecial::OpUngroupSpecial() 01028 01029 Author: Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com> 01030 Created: 29/9/93 01031 Inputs: - 01032 Outputs: - 01033 Returns: - 01034 Purpose: OpUngroupSpecial constructor 01035 Errors: - 01036 SeeAlso: - 01037 01038 ********************************************************************************************/ 01039 01040 01041 OpUngroupSpecial::OpUngroupSpecial(): OpUngroup() 01042 { 01043 } 01044 01045 /******************************************************************************************** 01046 01047 > BOOL OpUngroupSpecial::Init() 01048 01049 Author: Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com> 01050 Created: 28/9/93 01051 Inputs: - 01052 Outputs: - 01053 Returns: TRUE if the operation could be successfully initialised 01054 FALSE if no more memory could be allocated 01055 01056 Purpose: OpUngroupSpecial initialiser method 01057 Errors: ERROR will be called if there was insufficient memory to allocate the 01058 operation. 01059 SeeAlso: - 01060 01061 ********************************************************************************************/ 01062 01063 BOOL OpUngroupSpecial::Init() 01064 { 01065 return (RegisterOpDescriptor(0, 01066 _R(IDS_UNGROUPSPECIALOP), 01067 CC_RUNTIME_CLASS(OpUngroupSpecial), 01068 OPTOKEN_UNGROUPSPECIAL, 01069 OpUngroupSpecial::GetState, 01070 0, /* help ID */ 01071 _R(IDBBL_UNGROUPSPECIALOP), 01072 0, 01073 0, 01074 SYSTEMBAR_ILLEGAL, // For now ! 01075 TRUE, // Receive messages 01076 FALSE, 01077 FALSE, 01078 0, 01079 (GREY_WHEN_NO_CURRENT_DOC | GREY_WHEN_NO_SELECTION) 01080 01081 )); 01082 01083 } 01084 01085 /******************************************************************************************** 01086 01087 > OpState OpUngroupSpecial::GetState(String_256*, OpDescriptor*) 01088 01089 Author: Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com> 01090 Created: 28/9/93 01091 Inputs: - 01092 Outputs: - 01093 Returns: The state of the OpUngroupSpecial 01094 Purpose: For finding the OpUngroupSpecial's state. 01095 Errors: - 01096 SeeAlso: - 01097 01098 ********************************************************************************************/ 01099 01100 OpState OpUngroupSpecial::GetState(String_256* s, OpDescriptor* o) 01101 { 01102 return(OpUngroup::GetState(s,o)); 01103 } 01104 01105 /******************************************************************************************** 01106 01107 > void OpUngroupSpecial::Do(OpDescriptor*) 01108 01109 Author: Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com> 01110 Created: 16/8/93 01111 Inputs: OpDescriptor (unused) 01112 Outputs: - 01113 Returns: - 01114 Purpose: Performs the Ungroup Special operation 01115 Errors: An ENSURE failure will occur if this function is called and there are no 01116 selected groups. 01117 SeeAlso: - 01118 01119 ********************************************************************************************/ 01120 01121 void OpUngroupSpecial::Do(OpDescriptor*) 01122 { 01123 // Call UngroupSelectedGroups until there are no more groups to ungroup or until 01124 // the operation fails. 01125 if (DoStartSelOp(TRUE,TRUE)) // Try to record the selection state 01126 { 01127 while (OpUngroup::UngroupSelectedGroups()); 01128 } 01129 End(); 01130 } 01131