attrappl.cpp

Go to the documentation of this file.
00001 // $Id: attrappl.cpp 1664 2006-08-04 10:06:16Z gerry $
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 // AttrAppl.cpp - Implementation of the OpApplyAttribToSelected class
00099 
00100 #include "camtypes.h"
00101 
00102 #include "attrappl.h"
00103 //#include "attrmgr.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00104 //#include "document.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00105 //#include "ink.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00106 #include "lineattr.h"
00107 #include "linwthop.h"
00108 //#include "mario.h"
00109 #include "nodedoc.h"
00110 //#include "simon.h"
00111 //#include "group.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00112 //#include "fillattr.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00113 //#include "app.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00114 #include "toollist.h"
00115 //#include "will.h"     // for _R(IDS_APPLYTONODEOP)
00116 #include "ndoptmz.h"    // for NodeAttributePtrItem
00117 #include "objchge.h"
00118 //#include "spread.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00119 #include "progress.h"
00120 #include "fillndge.h"
00121 //#include "txtattr.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00122 #include "nodetxtl.h"
00123 //#include "jason.h"
00124 #include "colormgr.h"
00125 //#include "bubbleid.h"
00126 #include "nodecont.h"
00127 #include "lineattr.h"
00128 #include "opdrbrsh.h"
00129 #include "brshattr.h"
00130 #include "effects_stack.h"
00131 #include "ophist.h"
00132 //#include "docview.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00133 #include "nodetext.h"
00134 #include "ppbrush.h"
00135 
00136 CC_IMPLEMENT_DYNCREATE(OpApplyAttrib, SelOperation)
00137 CC_IMPLEMENT_DYNCREATE(OpApplyAttribToSelected, OpApplyAttrib)
00138 CC_IMPLEMENT_DYNCREATE(OpApplyAttrInteractive, OpApplyAttrib)
00139 CC_IMPLEMENT_DYNCREATE(OpApplyAttribsToSelected, OpApplyAttrib)
00140 CC_IMPLEMENT_DYNCREATE(OpRepeatApplyAttribToSelected, Operation)
00141 CC_IMPLEMENT_DYNCREATE(OpApplyAttribToNode, OpApplyAttrib)
00142 CC_IMPLEMENT_DYNCREATE(OpReplaceAttributes, SelOperation)
00143 CC_IMPLEMENT_DYNCREATE(ReplaceAttributesParam, OpParam)
00144 CC_IMPLEMENT_MEMDUMP(ApplyAttribsToSelectedParam, OpParam)
00145 
00146 // Declare smart memory handling in Debug builds
00147 #define new CAM_DEBUG_NEW
00148 
00149 // Static Fill Blob selection state, used to determine whether or not to
00150 // merge apply ops
00151 FillBlobSelectionState OpRepeatApplyAttribToSelected::FillBlobState;
00152 
00153 
00154 /********************************************************************************************
00155 
00156 >   NodeRenderableInk * OpApplyAttrib::FindCompoundNodeWhichRequiresAttribute(
00157                                                                 NodeRenderableInk * pStartNode)
00158 
00159     Author:     David_McClarnon (Xara Group Ltd) <camelotdev@xara.com>
00160     Created:    8/4/2000
00161     Inputs:     pStartNode : the node to start from
00162     Outputs:    The compound node which requires the attribute
00163 
00164     Returns:    pStartNode when not finding any appropriate nodes
00165     Purpose:    When applying an attribute it's necessary to test its parents to see if
00166                 the attribute application should be promoted
00167                 Uses the PromoteAttributeApplicationToMe virtual function in Node to
00168                 discover whether to promote or not
00169     
00170 
00171 ********************************************************************************************/
00172 NodeRenderableInk * OpApplyAttrib::FindCompoundNodeWhichRequiresAttribute(
00173                                                     NodeRenderableInk * pStartNode,
00174                                                     CCRuntimeClass * pClass)
00175 {
00176     NodeRenderableInk * pNodeToApply = pStartNode;
00177 
00178     if (!pStartNode->NeedsParent(NULL))
00179     {
00180         Node * pParent = pNodeToApply->FindParent();
00181 
00182         while (pParent)
00183         {
00184             if (pParent->IsCompoundClass())
00185             {
00186                 if (((NodeCompound*)pParent)->PromoteAttributeApplicationToMe(pClass))
00187                 {
00188                     pNodeToApply = (NodeRenderableInk *)pParent;
00189                 }
00190             }
00191 
00192             // we need to go all the way up the tree to find the top-most node
00193             pParent = pParent->FindParent();
00194         }
00195     }
00196 
00197     return pNodeToApply;
00198 }
00199 
00200 /********************************************************************************************
00201 
00202 >   BOOL SelOperation::DoApply(Node* CurrentNode,
00203                                 NodeAttribute* Attrib,
00204                                 BOOL Mutate, 
00205                                 BOOL InvalidateRegion = TRUE
00206                                 BOOL KeepExistingCharacteristics  = TRUE,
00207                                 BOOL* AttribWasRequired = NULL,
00208                                 BOOL* pbCanDiscardUndo = NULL)
00209 
00210     Author:     Will_Cowling (Xara Group Ltd) <camelotdev@xara.com> 
00211     Created:    5/4/95
00212     Inputs:     InvalidateRegion: When TRUE DoInvalidateNodeRegion is called before and
00213                                   after the attribute has been applied. If the attribute
00214                                   is being applied to a range of objects then it is 
00215                                   inefficient to do this.
00216                 KeepExistingCols: When TRUE the new attribute takes on the colour
00217                                   characteristics of the attribute it is replacing.
00218     Outputs:    AttribWasRequired:This output is optional. It indicates if  the
00219                                   Attrib was required by the object.
00220     Returns:    TRUE if successful 
00221                 FALSE if the operation should be aborted (TIDYUP THEN CALL End()!) 
00222     Purpose:    
00223                 Will not apply the attribute to CurrentNode if it does not require it
00224     Errors:     -
00225     SeeAlso:    NodeRenderableInk::GetObjectToApplyTo
00226 
00227 ********************************************************************************************/
00228 
00229 BOOL SelOperation::DoApply(Node* CurrentNode, 
00230                             NodeAttribute* Attrib, 
00231                             BOOL Mutate, 
00232                             BOOL InvalidateRegion,
00233                             BOOL KeepExistingCols, /* = TRUE */
00234                             BOOL* pbAttribWasRequired,    /* = NULL */
00235                             BOOL* pbCanDiscardUndo /* = NULL */
00236                             )
00237 {
00238     return SelOperation::DoApply(this, CurrentNode, Attrib, Mutate, InvalidateRegion, KeepExistingCols, TRUE, pbAttribWasRequired, pbCanDiscardUndo);
00239 }
00240 
00241 BOOL SelOperation::DoApply( SelOperation* pOp,
00242                             Node* CurrentNode, 
00243                             NodeAttribute* Attrib, 
00244                             BOOL Mutate, 
00245                             BOOL InvalidateRegion,
00246                             BOOL KeepExistingCols, /* = TRUE */
00247                             BOOL bOptimise,
00248                             BOOL* pbAttribWasRequired,    /* = NULL */
00249                             BOOL* pbCanDiscardUndo /* = NULL */
00250                             )
00251 {
00252     BOOL AttributeExists = FALSE;  // Until we find that the attribute does exist
00253 
00254     CCRuntimeClass* AttrType = Attrib->GetAttributeType();
00255     
00256     ERROR3IF(!CurrentNode->IsAnObject(), "Trying to apply an attribute to a non-NodeRenderableInk");
00257     
00258     // We don't allow attributes like 'Quality' to be applied to objects directly.
00259     ERROR3IF(!(Attrib->CanBeAppliedToObject()), "Trying to apply an illegal attribute to the object"); 
00260     
00261     if (!(Attrib->CanBeAppliedToObject()))
00262         return TRUE;  // Just pretend they ever tried.
00263 
00264     Node* OriginalCurrentNode = CurrentNode;
00265 
00266     if (CurrentNode->IsAnObject())
00267     {
00268 
00269         // Obtain the real object to apply the attribute to 
00270         CurrentNode = ((NodeRenderableInk*)CurrentNode)->GetObjectToApplyTo(Attrib->GetAttributeType()); 
00271         if (!CurrentNode)
00272         {
00273             ERROR3("OpApplyAttrib::DoApply, Unable to find object to apply to");
00274             return TRUE;
00275         }
00276     }
00277 
00278     // NOTE! Better to get the value of this flag here, now that GetObjectToApplyTo has been called
00279     BOOL bEffectRootOnly = (CurrentNode->IsAnObject() && ((NodeRenderableInk*)CurrentNode)->IsValidEffectAttr(Attrib));
00280 
00281     // If we are applying attributes to the Caret alone then the caller should not retain an undo record
00282     // for this operation...
00283     if (pbCanDiscardUndo)
00284         *pbCanDiscardUndo = *pbCanDiscardUndo && CurrentNode->DiscardsAttributeChildren();
00285 
00286     // BODGE! ------------------------------------------------------------
00287     // Don't apply stroke transparency as an effect attribute - nothing
00288     // needs it. Test has to be done here because other objects in the
00289     // selection might need it...
00290     if (bEffectRootOnly && AttrType == CC_RUNTIME_CLASS(AttrStrokeTransp))
00291         return TRUE;
00292     // END-BODGE! --------------------------------------------------------
00293 
00294     // Needed to support adding triggers, who can be multiply applied
00295     BOOL AddEvenIfMultiple = FALSE;
00296 
00297     // Do not apply the attribute if CurrentNode does not require it 
00298     if ((((NodeRenderableInk*)CurrentNode)->RequiresAttrib(Attrib)))
00299     {
00300 
00301         if (pbAttribWasRequired)
00302             *pbAttribWasRequired = TRUE;
00303 
00304         if (CurrentNode != OriginalCurrentNode)
00305         {
00306             // BODGE TEXT
00307             // We need to call AllowOp, for the TextStories benefit, 
00308             // so that lines know they have been affected 
00309 
00310             ObjChangeFlags cFlags;
00311             cFlags.Attribute = TRUE;
00312             ObjChangeParam ObjChange(OBJCHANGE_STARTING, cFlags, NULL, pOp);
00313 
00314             if (!CurrentNode->AllowOp(&ObjChange))
00315             {
00316                 // It's already passed the test once.
00317                 ERROR3("Doing op when no nodes will allow it");
00318             }
00319         }
00320             
00321         // -----------------------------------------------------------------------------
00322         // Determine if the current node already has an attribute which is the same 
00323         // runtime class as Attrib. 
00324         Node* n = CurrentNode->FindFirstChild(); 
00325         Node* pLastBoundedNode = NULL;
00326         if (bEffectRootOnly)
00327         {
00328             pLastBoundedNode = CurrentNode->FindLastChild(CC_RUNTIME_CLASS(NodeRenderableBounded));
00329             ERROR3IF(pLastBoundedNode==NULL, "Attempt to apply effect attr to node with no children");
00330 
00331             if (pLastBoundedNode) n = pLastBoundedNode->FindNext();
00332         }
00333 
00334         while (n  && (n->IsOrHidesAnAttribute()))
00335         {
00336             // Now lets see if we can find an attribute of the same type
00337             // as the one we are interested in.
00338             if (n->IsAnAttribute())
00339             {
00340                 NodeAttribute* pNdAttr = (NodeAttribute*)n;
00341                  
00342                 if( pNdAttr->GetAttributeType() == AttrType)
00343                 {
00344                     // Attributes are of the same type (whatever that might mean)
00345 
00346                     // Look for another attribute of the same Type if we're allowed more than
00347                     // one per object
00348                     BOOL LookForMultiple = FALSE;
00349 
00350                     if (Attrib->CanBeMultiplyApplied())
00351                     {
00352                         // If the classID's are the same then we want to replace the old one
00353                         // (unless they're identical)
00354                         if (Attrib->GetAttributeClassID() == pNdAttr->GetAttributeClassID())
00355                         {
00356                             LookForMultiple = FALSE;    // stop looking now
00357                             AddEvenIfMultiple = FALSE;  // replace the old one
00358                         }
00359                         else
00360                         {
00361                             // they're not the same classID's then we'll add a new one
00362                             // but keep looking for a duplicate
00363                             LookForMultiple = TRUE;
00364                             AddEvenIfMultiple = TRUE;
00365                         }
00366                     }
00367     
00368                     // Do the attributes have the same value ?. If they do then there is no point in 
00369                     // applying it a second time
00370 
00371                     // to have the same value they must share the same runtime class
00372                     if (IS_SAME_CLASS(pNdAttr, Attrib))
00373                     {
00374                         // Found an identical attribute, so ignore this apply
00375                         if ((*pNdAttr)==(*Attrib))
00376                             return TRUE;     // We are not failing, just doing nothing.
00377 
00378                         if (Attrib->IsABrush())
00379                             ((AttrBrushType*)Attrib)->OnReplaceAttribute((AttrBrushType*)pNdAttr);
00380                     }
00381                     
00382                     // if applying a brush attribute we must copy some data over
00383                     if (!LookForMultiple)
00384                     {
00385                         AttributeExists = TRUE; 
00386                         break; 
00387                     }
00388                 }
00389             }
00390             n = n->FindNext(); // Find next child of CurrentNode
00391         }   
00392 
00393         // >>>> Temporary BODGE to aid select-inside...
00394         // >>>> ALWAYS clear any attributes of the same type from the subtree (even if the
00395         // >>>> attribute just applied replaced an existing one) so that dubious
00396         // >>>> attribute states (due to this routine not dealing with Select-inside
00397         // >>>> properly yet) can be cleared.
00398         // >>>> NOTE! the current att (n) is passed in to DoRemoveAttTypeFromSubtree so
00399         // >>>> that it won't be deleted along with atts of the same type - that would be
00400         // >>>> (has been) disastrous!
00401         // Triggers can be multiply applied to groups, so we need to check CanBeMultiplyApplied
00402         if (CurrentNode->IsCompound() && !Attrib->CanBeMultiplyApplied() && !bEffectRootOnly)
00403         {
00404             // Remove all instances of attributes of the same type from the subtree.
00405             // This is not neccessary if the AttributeExists flag is TRUE because
00406             // we know in this situation that the subtree cannot contain any other
00407             // instances of the attribute !.
00408             if (pOp && !pOp->DoRemoveAttrTypeFromSubtree(CurrentNode, Attrib->GetAttributeType(), n))
00409             {
00410                 pOp->FailAndExecute(); // Just to make sure
00411                 return FALSE;
00412             }           
00413         }
00414 
00415         // At this point we have Either .....
00416         //      1. Found an attribute of the same type as the new one, and we will
00417         //         now replace it.
00418         //      2. Found the specific attribute we were looking for and will replace
00419         //         that.
00420         //      3. Found an attribute to mutate.
00421         //      4. Found no attribute of the correct type, indicating that it has
00422         //         been factored out, and we need to put a new one in here.
00423         //      5. The attribute had CanBeMultiplyApplied() set, and no exact same
00424         //         Attribute was found.
00425         //
00426         // Have we have got an Attribute to do something with ?
00427         NodeAttribute* AttribClone = NULL;
00428         if ( !AddEvenIfMultiple && n && n->IsAnAttribute())
00429         {
00430             // Yes !
00431             NodeAttribute* pAttr = (NodeAttribute*)n;
00432 
00433             if (Mutate)
00434             {
00435                 // Mutate it into the new type.
00436                 AttribClone = ((AttrFillGeometry*)n)->Mutate((AttrFillGeometry*)Attrib, bOptimise);
00437             }
00438             else
00439             {
00440                 // We're gunna just replace the attribute with the new one.
00441 
00442                 // First make a copy of the new attribute.
00443                 if (pOp)
00444                     ALLOC_WITH_FAIL(AttribClone ,((NodeAttribute*)Attrib->SimpleCopy()), pOp)
00445                 else
00446                     AttribClone = (NodeAttribute*)Attrib->SimpleCopy();
00447                 if (AttribClone == NULL)
00448                     return FALSE; 
00449 
00450                 // Complication !!
00451                 // If we are replacing a Fill Attribute and the attribute we are replacing
00452                 // is already filled, then we need to extract the colour of the existing
00453                 // fill and use them for the new fill.
00454                 if ( KeepExistingCols && ((NodeAttribute*)n)->IsAFillAttr() )
00455                 {
00456                     AttrFillGeometry* NodeReplaced = (AttrFillGeometry*)n;
00457 
00458                     // Copy the old fill characteristics into the new Fill
00459                     if (!OpApplyAttrib::KeepExistingCharacteristics(NodeReplaced, (AttrFillGeometry*)AttribClone))
00460                         return FALSE;
00461                 }
00462 
00463                 if ( Attrib->IsATranspFill() && Attrib->IsAFlatFill())
00464                 {
00465                     ((AttrFillGeometry*)n)->RenderFillBlobs();
00466                 }
00467             }
00468 
00469             // If Mutate returned NULL, which means the Mutate
00470             // did nothing, we should return TRUE, which will move
00471             // onto the next object.
00472             if (AttribClone == NULL)
00473                 return TRUE; 
00474 
00475             // ----------------------------------------------------------
00476             // If we have just set an effect attribute then we can avoid releasing
00477             // cached info of the specified node and all its children...
00478             //
00479             // We RELY on invalidations associated with this function not
00480             // calling ReleaseCached themselves!
00481             //
00482             ReleaseCachedForAttrApply((NodeRenderableBounded*)CurrentNode, bEffectRootOnly);
00483             // ----------------------------------------------------------
00484 
00485             if (InvalidateRegion)
00486             {
00487                 // Invalidate the Object before the attribute is applied
00488                 if (!InvalidateNodeRegion(pOp, (NodeRenderableBounded*)CurrentNode, Attrib, Mutate))    // Doesn't invalidate cached info!
00489                     return FALSE;
00490             }
00491 
00492             // Now we have done with the old attribute, so lets hide it, so
00493             // the changes can be undone
00494             //
00495             // Don't write any undo info if we don't have an op or the node
00496             // discards attributes by itself
00497             if (pOp && !CurrentNode->DiscardsAttributeChildren())
00498             {
00499                 // Note that we wouldn't need this test and we wouldn't need
00500                 // to use the HideNode/ShowNode actions here if solid fill
00501                 // dragging used OpApplyAttrInteractive. Instead it uses an
00502                 // older system which relies on the HideNodeAction recording
00503                 // a pointer to an attribute at the start of the drag which
00504                 // is subsequently updated during the drag.
00505                 if (pOp->IsKindOf(CC_RUNTIME_CLASS(OpApplyAttrInteractive)))
00506                 {
00507                     ApplyAction* pUndoApply; 
00508                     // Create an action to re-apply the attribute when we undo 
00509                     if ( ApplyAction::Init(pOp, 
00510                                             pOp->GetUndoActionList(),
00511                                             CurrentNode,
00512                                             pAttr,
00513                                             TRUE,               // When the attribute gets hidden we
00514                                                                 // must include its size 
00515                                             (Action**)(&pUndoApply))
00516                          == AC_FAIL)
00517                     {
00518                         return FALSE;
00519                     }
00520                     pAttr->CascadeDelete();
00521                     delete pAttr;
00522                     pAttr = NULL;
00523                 }
00524                 else
00525                 {
00526                     if (!pOp->DoHideNode(n, TRUE))          // Include the subtree size 
00527                         return FALSE;
00528                 }
00529             }
00530             else
00531             {
00532                 pAttr->CascadeDelete();
00533                 delete pAttr;
00534                 pAttr = NULL;
00535             }
00536 
00537         }
00538         else  // if (n != NULL)
00539         {
00540             // We've not found an attribute to replace, so we'll have to put
00541             // a new one in
00542             NodeAttribute* TempAttr = NULL;
00543 
00544             BOOL FoundAttr = ((NodeRenderableInk*)CurrentNode)->
00545                                 FindAppliedAttribute(Attrib->GetAttributeType(), &TempAttr);
00546 
00547             if (!FoundAttr || TempAttr == NULL)
00548                 return FALSE;
00549 
00550             if (Mutate)
00551             {
00552                 AttribClone = ((AttrFillGeometry*)TempAttr)->Mutate((AttrFillGeometry*)Attrib, bOptimise);
00553             }
00554             else
00555             {
00556                 // We'll just put a copy of our attribute in the tree.
00557                 if (pOp)
00558                     ALLOC_WITH_FAIL(AttribClone ,((NodeAttribute*)Attrib->SimpleCopy()), pOp)
00559                 else
00560                     AttribClone = (NodeAttribute*)Attrib->SimpleCopy();
00561 
00562                 if ( KeepExistingCols && (TempAttr->IsAFillAttr()) )
00563                 {
00564                     AttrFillGeometry* NodeReplaced = (AttrFillGeometry*)TempAttr;
00565 
00566                     // Copy the old fill characteristics into the new Fill
00567                     if (!OpApplyAttrib::KeepExistingCharacteristics(NodeReplaced, (AttrFillGeometry*)AttribClone))
00568                         return FALSE;
00569                 }
00570 
00571                 if (Attrib->IsATranspFill() && Attrib->IsAFlatFill())
00572                 {
00573                     // If we are mutating into a flat fill, then we need
00574                     // to make sure we remove any existing fill blobs.
00575                     ((AttrFillGeometry*)TempAttr)->RenderFillBlobs();
00576                 }
00577             }
00578 
00579             if (AttribClone)
00580             {
00581                 ReleaseCachedForAttrApply((NodeRenderableBounded*)CurrentNode, bEffectRootOnly);
00582 
00583                 if (InvalidateRegion)
00584                 {
00585                     // Make sure the node is redrawn
00586                     if (!InvalidateNodeRegion(pOp, (NodeRenderableBounded*)CurrentNode, Attrib, Mutate))    // Doesn't invalidate cached info!
00587                         return FALSE;
00588                 }
00589             }
00590         }
00591 
00592         // If the AttribClone has the same value as an applied attribute 
00593         // (A default attr if the attribs have been localised). Then we don't want to apply it !
00594         if (AttribClone && AttribClone->ShouldBeOptimized() && !bEffectRootOnly && bOptimise)
00595         {
00596             NodeAttribute* pAppliedAttr;
00597             if (((NodeRenderableInk*)CurrentNode)->FindAppliedAttribute(AttribClone->GetAttributeType(), 
00598                                                   &pAppliedAttr))
00599             {
00600                 // Do the attributes have the same value ?
00601                 if ((IS_SAME_CLASS(AttribClone, pAppliedAttr)))
00602                 {
00603                     if ((*AttribClone)==(*pAppliedAttr))
00604                     {
00605                         AttribClone->CascadeDelete();       // Delete the attribute
00606 
00607                         // If attribute affect's the bounds of the object (eg. a LineWidth) then invalidate the
00608                         // bounds of the object
00609 
00610                         if (pAppliedAttr->EffectsParentBounds())
00611                         {
00612                             ((NodeRenderableBounded*)CurrentNode)->InvalidateBoundingRect(TRUE);
00613                         }
00614 
00615                         delete AttribClone;
00616                         AttribClone = NULL;
00617                     }
00618                 }
00619             }
00620         }
00621 
00622         // Effect attributes don't optimise so if the value of this is the
00623         // same as the default value we should not add it here
00624         //
00625         if (AttribClone && bEffectRootOnly && AttribClone->HasEquivalentDefaultValue() && bOptimise)
00626         {
00627             // Just allow the attribute to be hidden
00628             AttribClone->CascadeDelete();       // Delete the attribute
00629             delete AttribClone;
00630             AttribClone = NULL;
00631         }
00632 
00633         if (AttribClone)
00634         {
00635             // Finally !! We can add the new attribute node into the tree.
00636             if (bEffectRootOnly)
00637                 AttribClone->AttachNode(pLastBoundedNode, NEXT);
00638             else
00639                 AttribClone->AttachNode(CurrentNode, FIRSTCHILD);
00640 
00641             AttributeManager::pLastNodeAppliedTo = (NodeRenderableInk*)CurrentNode;
00642 
00643             // And now it's in the tree, we need to make sure that any fill control
00644             // points are valid.
00645             if (AttribClone->IsAFillAttr())
00646             {
00647                 ((AttrFillGeometry*)AttribClone)->AttributeChanged();
00648             }
00649 
00650             if (pOp && !CurrentNode->DiscardsAttributeChildren())
00651             {
00652                 // Note that we wouldn't need this test and we wouldn't need
00653                 // to use the HideNode/ShowNode actions here if solid fill
00654                 // dragging used OpApplyAttrInteractive. Instead it uses an
00655                 // older system which relies on the HideNodeAction recording
00656                 // a pointer to an attribute at the start of the drag and the
00657                 // contents of the attribute being updated during the drag.
00658                 if (pOp->IsKindOf(CC_RUNTIME_CLASS(OpApplyAttrInteractive)))
00659                 {
00660                     UnApplyAction* pUndoApply; 
00661                     // Create an action to hide the attribute when we undo 
00662                     if ( UnApplyAction::Init(pOp, 
00663                                               pOp->GetUndoActionList(),
00664                                               CurrentNode,
00665                                               AttribClone,
00666                                               TRUE,             // When the attribute gets hidden we
00667                                                                 // must include its size 
00668                                              (Action**)(&pUndoApply))
00669                          == AC_FAIL)
00670                     {   
00671                         AttribClone->CascadeDelete();       
00672                         delete (AttribClone); 
00673                         return FALSE;  
00674                     }
00675                 }
00676                 else
00677                 {
00678                     HideNodeAction* UndoHideNodeAction; 
00679                     // Create an action to hide the attribute when we undo 
00680                     if ( HideNodeAction::Init(pOp,
00681                                               pOp->GetUndoActionList(),
00682                                               AttribClone,
00683                                               TRUE,             // When the attribute gets hidden we
00684                                                                 // must include its size 
00685                                              (Action**)(&UndoHideNodeAction))
00686                          == AC_FAIL)  
00687                     {   
00688                         AttribClone->CascadeDelete();       
00689                         delete (AttribClone); 
00690                         return FALSE;  
00691                     }
00692                 }
00693             }
00694 
00695         }
00696 
00697         ReleaseCachedForAttrApply((NodeRenderableBounded*)CurrentNode, bEffectRootOnly);
00698 
00699         if (InvalidateRegion)
00700         {
00701             // Invalidate the node rectangle, now the new attr has been applied
00702             if (!InvalidateNodeRegion(pOp, (NodeRenderableBounded*)CurrentNode, Attrib, Mutate))    // Doesn't invalidate cached info!
00703                 return FALSE; 
00704         }
00705     }
00706 
00707     // we may have to apply this to a brushed node
00708     if (pOp)                                                // TODO: Make this work when no undo op specified?
00709     {
00710         if (!pOp->ApplyAttributeToBrush(CurrentNode, Attrib))
00711             return FALSE;
00712     }
00713     return TRUE; // success
00714 }
00715 
00716 
00717 
00718 
00719 /********************************************************************************************
00720 
00721 >   static BOOL SelOperation::ReleaseCachedForAttrApply(NodeRenderableBounded* pNode, BOOL bEffectRootOnly) 
00722 
00723     Author:     Phil_Martin (Xara Group Ltd) <camelotdev@xara.com>
00724     Created:    08/03/2005
00725     Inputs:     pCurrentNode    - the node that the attribute is being applied to 
00726                 bEffectRootOnly - TRUE if
00727     Returns:    TRUE if all ok.
00728     Purpose:    If we have just set an effect attribute then we can avoid releasing
00729                 cached info of the specified node and all its children...
00730                 We RELY on invalidations associated with this function not
00731                 calling ReleaseCached themselves!
00732     Errors:     -
00733     SeeAlso:    -
00734 
00735 ********************************************************************************************/
00736 
00737 BOOL SelOperation::ReleaseCachedForAttrApply(NodeRenderableBounded* pNode, BOOL bEffectRootOnly)
00738 {
00739     if (bEffectRootOnly)
00740         pNode->ReleaseCached(TRUE, FALSE, FALSE, TRUE);                     // Parents and Derived data only
00741     else
00742         pNode->ReleaseCached();                                             // Self, Parents, Children and derived data
00743 
00744     return TRUE;
00745 }
00746 
00747 
00748 
00749 
00750 /********************************************************************************************
00751 
00752 >       BOOL SelOperation::ApplyAttributeToBrush(Node* pCurrentNode, NodeAttribute* pAttr) 
00753                         
00754 
00755     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
00756     Created:    24/2/2000
00757     Inputs:     pCurrentNode - the node that the attribute is being applied to 
00758                 pAttr        - the attribute to apply
00759     Returns:    TRUE if all ok.
00760     Purpose:    Works out if pCurrentNode has an applied brush attribute, and if so then sets
00761                 up an action to tell the brush to use the attribute being applied.
00762     Errors:     -
00763     SeeAlso:    -
00764 
00765     Notes:      This is only currently operational for fill attributes, others may be added
00766                 in future.
00767                 18/4/2000 - Line widths added
00768 ********************************************************************************************/
00769 
00770 BOOL SelOperation::ApplyAttributeToBrush(Node* pCurrentNode, NodeAttribute* pAttr)
00771 {
00772     ERROR2IF(pCurrentNode == NULL, FALSE, "Current node is NULL");
00773     ERROR2IF(pAttr == NULL, FALSE, "Attribute is NULL");
00774     
00775     // currently this only works for colour changes and transparent fills
00776     if (!pAttr->IsKindOf(CC_RUNTIME_CLASS(AttrStrokeColourChange)) && !pAttr->IsATranspFill()
00777         && !pAttr->IsKindOf(CC_RUNTIME_CLASS(AttrLineWidth)) && !pAttr->IsABrush())
00778         return TRUE;
00779     
00780     // first get the existing brush attribute
00781     AttrBrushType* pAttrBrush = NULL;
00782     // for some reason we don't check the return value here??
00783     /*BOOL ok =*/ ((NodeRenderableInk*)pCurrentNode)->FindAppliedAttribute(CC_RUNTIME_CLASS(AttrBrushType),
00784                                                                         (NodeAttribute**)&pAttrBrush);
00785 
00786     // if we're applying a brush attribute then do something else
00787     if (pAttr->IsABrush())
00788     {
00789         AttrBrushType* pNewAttrBrush = (AttrBrushType*)pAttr;
00790         return ApplyBrushAttr(pCurrentNode, pNewAttrBrush);
00791     }
00792 
00793 
00794     // there isn't one - thats ok
00795     if (pAttrBrush == NULL || pAttrBrush->GetBrushHandle() == BrushHandle_NoBrush)
00796         return TRUE;
00797 
00798     // so now we have a brush attribute, so lets determine what to do:
00799     if (pAttr->IsKindOf(CC_RUNTIME_CLASS(AttrStrokeColourChange)))
00800     {
00801         // only do this is the new colour is not transparent
00802         AttrStrokeColourChange* pStrokeCol = static_cast<AttrStrokeColourChange*>(pAttr);
00803         DocColour* pCol = pStrokeCol->GetStartColour();
00804 //PORTNOTE("other","Removed DocColour usage") - AMB Unremoved
00805         if (pCol->IsTransparent())
00806             return TRUE;
00807 
00808         return ApplyStrokeColourToBrush(pAttrBrush, (NodeRenderableInk*)pCurrentNode);
00809     }
00810 
00811 // Is this a porting comment-out? AMB
00812 //  if (pAttr->IsATranspFill())
00813 //      return ApplyTransparencyToBrush(pAttrBrush, (NodeRenderableInk*)pCurrentNode);
00814     if (pAttr->IsKindOf(CC_RUNTIME_CLASS(AttrLineWidth)))
00815         return ApplyLineWidthToBrush(pAttrBrush, (NodeRenderableInk*)pCurrentNode, pAttr);
00816 
00817     // trash the cached bounding rect
00818     pAttrBrush->ClearCachedRect();
00819 
00820     // if it isn't one of the above then we dont have to do anything
00821     return TRUE;
00822 }
00823 
00824 /********************************************************************************************
00825 
00826 >   BOOL SelOperation::ApplyTransparencyToBrush(AttrBrushType* pAttrBrush, NodeAttribute* pAttr) 
00827                         
00828 
00829     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
00830     Created:    24/2/2000
00831     Inputs:     pAttrBrush   - the brush attribute that we wish to update
00832                 pAttr        - the attribute to apply
00833     Returns:    TRUE if all ok.
00834     Purpose:    When a transparency is applied to a brushed object we need to set a flag
00835                 in the brush to tell it to use this new transparency
00836 ********************************************************************************************/
00837 
00838 BOOL SelOperation::ApplyTransparencyToBrush(AttrBrushType* pAttrBrush, NodeRenderableInk* pBrushedNode)
00839 {
00840     ERROR2IF(pAttrBrush == NULL, FALSE, "Attribute pointer is NULL in OnApplyAttrib::ApplyTransparencyToBrush");
00841     ERROR2IF(pBrushedNode == NULL, FALSE,  "Attribute pointer is NULL in OnApplyAttrib::ApplyTransparencyToBrush");
00842 
00843     // make the OpParam
00844     ChangeBrushOpParam Param;
00845 
00846     Param.ChangeType = CHANGEBRUSH_USELOCALTRANSP;
00847     Param.m_bNewUseLocalTransp = TRUE;
00848     ChangeBrushAction* pAction = NULL;
00849     // initialise the action
00850     if (ChangeBrushAction::Init(this, &UndoActions, pBrushedNode, &Param, &pAction) == AC_FAIL)
00851         return FALSE;
00852 
00853     return TRUE;
00854 }
00855 /********************************************************************************************
00856 
00857 >   BOOL SelOperation::ApplyStrokeColourToBrush(AttrBrushType* pAttrBrush,NodeRenderableInk* pBrushedNode) 
00858                         
00859 
00860     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
00861     Created:    24/2/2000
00862     Inputs:     pAttrBrush   - the brush attribute that we wish to update
00863                 pbrushed node   - the node that the brush applies to
00864     Returns:    TRUE if all ok.
00865     Purpose:    When a new stroke colour is applied to a brushed object we wish to set the
00866                 flag that tells the brush to override the named colours in the brush with
00867                 the colour applied
00868 
00869 ********************************************************************************************/
00870 
00871 BOOL SelOperation::ApplyStrokeColourToBrush(AttrBrushType* pAttrBrush, NodeRenderableInk* pBrushedNode)
00872 {
00873     ERROR2IF(pAttrBrush == NULL, FALSE, "Attribute pointer is NULL in OnApplyAttrib::ApplyStrokeColourToBrush");
00874     ERROR2IF(pBrushedNode == NULL, FALSE,  "Attribute pointer is NULL in OnApplyAttrib::ApplyStrokeColourToBrush");
00875 
00876     
00877     
00878 
00879     // if the brush already uses local colours then don't bother doing anything
00880     PathProcessorBrush *pPPB = pAttrBrush->GetPathProcessor();
00881     if (pPPB == NULL)
00882         return TRUE;
00883 
00884     if (pPPB->GetUseLocalFillColour() || !pPPB->GetUseNamedColours())
00885         return TRUE;
00886 
00887     // make the OpParam
00888     ChangeBrushOpParam Param;
00889 
00890     Param.ChangeType = CHANGEBRUSH_USENAMEDCOL;
00891     Param.m_bNewUseNamed = FALSE;
00892     ChangeBrushAction* pAction = NULL;
00893     // initialise the action
00894     if (ChangeBrushAction::Init(this, &UndoActions, pBrushedNode, 
00895                             &Param, &pAction) == AC_FAIL)
00896         return FALSE;
00897 
00898     return TRUE;
00899 }
00900 
00901 
00902 
00903 /********************************************************************************************
00904 
00905 >   BOOL SelOperation::ApplyLineWidthToBrush(AttrBrushType* pAttrBrush, NodeRenderableInk* pBrushedNode,
00906                                 NodeAttribute* pAttr) 
00907                         
00908 
00909     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
00910     Created:    24/2/2000
00911     Inputs:     pAttrBrush   - the brush attribute that we wish to update
00912                 pBrushedNode   -the node that the brush applies to
00913                 pAttr      - the line width that we are applying
00914     Returns:    TRUE if all ok.
00915     Purpose:    If we are applying a line width to a brushed node we need to tell the brush that
00916                 it must scale to line width.
00917                 We must also do an additional invalidate region so that if we are increasing in
00918                 size then the brush will be rerendered properly
00919 ********************************************************************************************/
00920 
00921 BOOL SelOperation::ApplyLineWidthToBrush(AttrBrushType* pAttrBrush, NodeRenderableInk* pBrushedNode,
00922                                           NodeAttribute* pAttr)
00923 {
00924     ERROR2IF(pAttrBrush == NULL, FALSE, "Attribute pointer is NULL in OnApplyAttrib::ApplyLineWidthToBrush");
00925     ERROR2IF(pBrushedNode == NULL, FALSE,  "Node pointer is NULL in OnApplyAttrib::ApplyLineWidthToBrush");
00926     ERROR2IF(pAttr == NULL, FALSE, "Attribute pointer is NULL in OnApplyAttrib::ApplyLineWidthToBrush");
00927 
00928     // if we are on a closed path then ask the PPB to even out the spacing
00929     PathProcessorBrush* pPPB = pAttrBrush->GetPathProcessor();
00930     if (pPPB != NULL)
00931     {
00932         // scale to the new line width
00933         AttrLineWidth* pLineWidth = (AttrLineWidth*)pAttr;
00934         BOOL UsesPressure = pAttrBrush->GetPressureCache() != NULL;
00935         pPPB->ScaleToValue(pLineWidth->Value.LineWidth, !UsesPressure);
00936         pPPB->AdjustSpacingForClosedPath(pBrushedNode);
00937     }
00938 
00939     return TRUE;
00940 }
00941 
00942 
00943 
00944 /********************************************************************************************
00945 
00946 >BOOL SelOperation::ApplyBrushAttr(Node* pCurrentNode, AttrBrushType* pAttrBrush)
00947                         
00948 
00949     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
00950     Created:    24/2/2000
00951     Inputs:     pCurrentNode - the node we are applying the brush to
00952                 pAttrBrush   - the brush attribute we are applying
00953     Returns:    TRUE if all ok.
00954     Purpose:    When we apply a brush attribute to an object we want to check to see if the
00955                 current line width attribute is the same as the default attribute.  If it is then
00956                 we will apply the default line width suggested by the brush itself.
00957                 This is because people keep on applying brush attributes to 500MP lines
00958                 and ending up with thousands of brush objects, which makes everything really slow.
00959 ********************************************************************************************/
00960 
00961 BOOL SelOperation::ApplyBrushAttr(Node* pCurrentNode, AttrBrushType* pAttrBrush)
00962 {
00963     ERROR2IF(pCurrentNode == NULL || pAttrBrush == NULL, FALSE, "Null input pointers to OpApplyAttrib::ApplyBrushAttr");
00964     ERROR2IF(!pCurrentNode->IsAnObject(), FALSE, "Input node is not an ink node in OpApplyAttrib::ApplyBrushAttr");
00965 
00966     // its too complicated when you have compound nodes
00967     if (pCurrentNode->IsCompound())
00968         return TRUE;
00969 
00970     AttrLineWidth* pCurrentLineWidth = NULL;
00971     ((NodeRenderableInk*)pCurrentNode)->FindAppliedAttribute(CC_RUNTIME_CLASS(AttrLineWidth), (NodeAttribute**)&pCurrentLineWidth);
00972     
00973     if (pCurrentLineWidth != NULL)
00974     {
00975         // Get the default line width
00976         AttrLineWidth* pDefault = (AttrLineWidth*)(AttributeManager::GetDefaultAttribute(ATTR_LINEWIDTH));
00977         if (pDefault == NULL)
00978         {
00979             ERROR3("Unable to get default line width, theres no way this can happen Jim!");
00980             // continue anyway
00981         }
00982         else
00983         {
00984             // check to see if default is the same as current, if not then just leave
00985             if (pCurrentLineWidth->Value.LineWidth != pDefault->Value.LineWidth-1)
00986             {
00987                 delete pDefault;
00988                 PathProcessorBrush* pPPB = pAttrBrush->GetPathProcessor();
00989                 if (pPPB)
00990                 {
00991                     BOOL UsesPressure = pAttrBrush->GetPressureCache() != NULL;
00992                     pPPB->ScaleToValue(pCurrentLineWidth->Value.LineWidth, !UsesPressure);
00993                     pPPB->AdjustSpacingForClosedPath((NodeRenderableInk*)pCurrentNode);
00994                 }
00995 
00996                 return TRUE;
00997             }
00998             delete pDefault;
00999         }
01000     }
01001 
01002 
01003     // otherwise we want a new line width attribute
01004     MILLIPOINT LineWidth = pAttrBrush->GetDefaultLineWidth(TRUE);
01005     
01006     //DoApply makes a copy so we only need to make this local
01007     AttrLineWidth NewLineWidth;
01008 
01009     NewLineWidth.Value.LineWidth = LineWidth;
01010 
01011     // now apply it
01012     return DoApply(pCurrentNode, &NewLineWidth, FALSE, FALSE);
01013     
01014 }
01015 
01016 /********************************************************************************************
01017 
01018 >   static BOOL OpApplyAttrib::KeepExistingCharacteristics(AttrFillGeometry* OldAttr, 
01019                                                     AttrFillGeometry* NewAttr)
01020 
01021     Author:     Will_Cowling (Xara Group Ltd) <camelotdev@xara.com>
01022     Created:    6/6/95
01023     Inputs:     -
01024     Returns:    TRUE if all ok.
01025     Purpose:    Retains the previous fill charateristics when applying a new fill.
01026     Errors:     -
01027     SeeAlso:    -
01028 
01029 ********************************************************************************************/
01030 
01031 BOOL OpApplyAttrib::KeepExistingCharacteristics(AttrFillGeometry* OldAttr, 
01032                                                 AttrFillGeometry* NewAttr)
01033 {
01034     NewAttr->SetStartTransp(OldAttr->GetStartTransp());
01035     NewAttr->SetEndTransp(OldAttr->GetEndTransp());
01036 
01037     NewAttr->SetTranspType(OldAttr->GetTranspType());
01038 
01039     if (OldAttr->IsAFlatFill())
01040     {
01041         if (OldAttr->FindParent()->IsNodeDocument())
01042         {
01043             // If the applied attr is the default fill at the top of the tree,
01044             // then we need to 'bodge' it to be black.
01045             DocColour DefFillCol;
01046             AttributeManager::FindDefaultColour(ColourManager::GetCurrentColourList(),
01047                                                 _R(IDS_BLACKNAME), &DefFillCol);
01048             NewAttr->SetEndColour(&DefFillCol);
01049         }
01050         else
01051             NewAttr->SetEndColour(OldAttr->GetStartColour());
01052     }
01053     else
01054     {
01055         NewAttr->SetStartColour(OldAttr->GetStartColour());
01056         NewAttr->SetEndColour(OldAttr->GetEndColour());
01057     }
01058 
01059     NewAttr->SetTesselation(OldAttr->GetTesselation());
01060     NewAttr->AttachBitmap(OldAttr->GetBitmap());
01061 
01062     if (OldAttr->IsAFractalFill())
01063     {
01064         NewAttr->SetSeed(OldAttr->GetSeed());
01065         NewAttr->SetGraininess(OldAttr->GetGraininess());
01066         NewAttr->SetGravity(OldAttr->GetGravity());
01067         NewAttr->SetSquash(OldAttr->GetSquash());
01068         NewAttr->SetTileable(OldAttr->GetTileable());
01069         NewAttr->SetFractalDPI(AttrFillGeometry::FractalDPI);
01070         NewAttr->RecalcFractal();
01071     }
01072 
01073     return TRUE;
01074 }
01075 
01076 /********************************************************************************************
01077 
01078 >   BOOL SelOperation::InvalidateNodeRegion(    SelOperation* pOp,
01079                                                 NodeRenderableBounded* CurrentNode, 
01080                                                 NodeAttribute* Attrib, 
01081                                                 BOOL Mutate)
01082     Author:     Will_Cowling (Xara Group Ltd) <camelotdev@xara.com>
01083     Created:    6/6/95
01084     Inputs:     -
01085     Returns:    TRUE if region invalidated ok.
01086     Purpose:    Invalidates the Bounds of a Node.
01087     Errors:     -
01088     SeeAlso:    -
01089 
01090 ********************************************************************************************/
01091 
01092 BOOL SelOperation::InvalidateNodeRegion(    SelOperation* pOp,
01093                                             NodeRenderableBounded* CurrentNode, 
01094                                             NodeAttribute* Attrib, 
01095                                             BOOL Mutate)
01096 {
01097     // Include the blobs, if the attribute will change the parents bounds.
01098     BOOL IncludeBlobs = Attrib->EffectsParentBounds();
01099 
01100     if ( (Mutate && !Attrib->IsAValueChange()) ||
01101          Attrib->GetRuntimeClass() == CC_RUNTIME_CLASS(AttrBitmapDpiChange) )
01102     {
01103         // Check for special cases of, either a mutate from one fill type to another,
01104         // or a dpi change.  Both of these can change the blob bounds.
01105         IncludeBlobs = TRUE;
01106     }
01107 
01108     // Invalidate the bounds of the node, including the blobs if necessary.
01109     if (pOp)
01110     {
01111         if (!pOp->DoInvalidateNodeRegion(CurrentNode, IncludeBlobs, FALSE, FALSE, FALSE))   // NOTE! We do not release cached info!
01112                                                                                         // Must be used in conjunction with ApplyToSelection
01113         {
01114             // Summit went wrong.
01115             return FALSE;
01116         }
01117     }
01118     else
01119     {
01120         BaseDocument* pBaseDoc = CurrentNode->FindOwnerDoc();
01121         if (pBaseDoc && pBaseDoc->IsKindOf(CC_RUNTIME_CLASS(Document)))
01122         {
01123             Document* pDoc = (Document*)pBaseDoc;
01124             Spread* pSpread = (Spread*)CurrentNode->FindParent(CC_RUNTIME_CLASS(Spread));
01125             DocRect TempRect; 
01126             TempRect = (IncludeBlobs ? 
01127                             (CurrentNode->GetUnionBlobBoundingRect()):
01128                             (CurrentNode->GetBoundingRect())
01129                          );
01130             TempRect = TempRect.Union(CurrentNode->GetEffectStackBounds());
01131             pDoc->ForceRedraw(pSpread, TempRect, TRUE, CurrentNode, FALSE);
01132         }
01133     }
01134 
01135     return TRUE;    // All ok
01136 }
01137 
01138 /********************************************************************************************
01139 
01140 >   BOOL OpApplyAttrib::DoInvalidateRegions(Range* NodeRange, 
01141                                             NodeAttribute* Attrib, 
01142                                             BOOL Mutate, 
01143                                             NodeAttribute* OtherAttr,
01144                                             BOOL OtherMuate)
01145     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
01146     Created:    10/6/95