blndtool.cpp

Go to the documentation of this file.
00001 // $Id: blndtool.cpp 1401 2006-07-03 11:17:52Z 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 // Implementation of the blend tool
00099 
00100 /*
00101 */
00102 
00103 #include "camtypes.h"
00104 //#include "oilfiles.h"
00105 //#include "docview.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00106 //#include "selop.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00107 #include "csrstack.h"
00108 //#include "markn.h"
00109 //#include "spread.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00110 #include "nodepath.h"
00111 #include "progress.h"
00112 #include "nodeblnd.h"
00113 #include "nodebldr.h"
00114 //#include "simon.h"
00115 #include "blobs.h"
00116 //#include "blndres.h"
00117 //#include "bevres.h"
00118 #include "objchge.h"
00119 //#include "resource.h"
00120 //#include "will.h"
00121 #include "filltool.h"
00122 #include "bubbleid.h"
00123 //#include "becomea.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00124 #include "attrmap.h"
00125 #include "ndbldpth.h"
00126 #include "pathedit.h"
00127 #include "keypress.h"
00128 #include "vkextra.h"
00129 
00130 #include "blndtool.h"
00131 //#include "ezmodule.h"
00132 #include "opbevel.h"
00133 
00134 //#include "will2.h"
00135 #include "biasgdgt.h"
00136 #include "opcntr.h"
00137 //#include "cntrtool.h"
00138 #include "shapeops.h"
00139 #include "biasdlg.h"
00140 
00141 //#include "will.h"
00142 
00143 
00144 // for bevels & shadows
00145 #include "nbevcont.h"
00146 #include "nodecont.h"
00147 
00148 #include "nodecntr.h"
00149 #include "ncntrcnt.h"
00150 #include "opbevel.h"
00151 #include "layer.h"
00152 #include "ophist.h"
00153 
00154 DECLARE_SOURCE( "$Revision: 1401 $" );
00155                                                 
00156 CC_IMPLEMENT_MEMDUMP(BlendTool,Tool_v1)
00157 CC_IMPLEMENT_DYNCREATE(BlendInfoBarOp,InformationBarOp)
00158 CC_IMPLEMENT_MEMDUMP(BlendToolRef,CC_CLASS_MEMDUMP)
00159 CC_IMPLEMENT_DYNCREATE(OpBlendNodes,SelOperation)
00160 CC_IMPLEMENT_DYNCREATE(OpRemoveBlend,SelOperation)
00161 CC_IMPLEMENT_DYNCREATE(OpAddBlendPath,SelOperation)
00162 CC_IMPLEMENT_DYNCREATE(OpDetachBlendPath,SelOperation)
00163 CC_IMPLEMENT_DYNCREATE(OpChangeBlend,SelOperation)
00164 CC_IMPLEMENT_DYNCREATE(OpBlendOneToOne,OpChangeBlend)
00165 CC_IMPLEMENT_DYNCREATE(OpBlendAntialias,OpChangeBlend)
00166 CC_IMPLEMENT_DYNCREATE(OpBlendTangential,OpChangeBlend)
00167 CC_IMPLEMENT_DYNCREATE(OpChangeBlendSteps,SelOperation)
00168 CC_IMPLEMENT_DYNCREATE(ChangeBlendAction,Action)
00169 CC_IMPLEMENT_DYNCREATE(ChangeBlenderAction,Action)
00170 CC_IMPLEMENT_DYNCREATE(ChangeBlendStepsAction,Action)
00171 CC_IMPLEMENT_DYNCREATE(RemapBlendAction,Action)
00172 CC_IMPLEMENT_DYNCREATE(InitBlendersAction,Action)
00173 CC_IMPLEMENT_MEMDUMP(ChangeBlendOpParam,OpParam)
00174 CC_IMPLEMENT_MEMDUMP(ChangeBlenderOpParam,OpParam)
00175 CC_IMPLEMENT_DYNCREATE(InvalidateBoundsAction,Action)
00176 CC_IMPLEMENT_DYNCREATE(OpChangeBlendDistance, SelOperation)
00177 CC_IMPLEMENT_DYNCREATE(OpEditBlendEndObject, SelOperation)
00178 CC_IMPLEMENT_MEMDUMP(BlenderInfoItem,ListItem);
00179 
00180 
00181 
00182 // Must come after the last CC_IMPLEMENT.. macro
00183 #define new CAM_DEBUG_NEW     
00184 
00185 // These are still char* while we wait for resource technology to be developed for modules
00186 TCHAR* BlendTool::FamilyName    = _T("Blend Tools");
00187 TCHAR* BlendTool::ToolName      = _T("Blend Tool");
00188 TCHAR* BlendTool::Purpose       = _T("Blend manipulation");
00189 TCHAR* BlendTool::Author        = _T("Mark Neves");
00190 
00191 // Init those other useful static vars
00192 BlendInfoBarOp* BlendTool::pBlendInfoBarOp          = NULL;
00193 BlendToolRef*   BlendTool::pRefStart                = NULL;
00194 BlendToolRef*   BlendTool::pRefEnd                  = NULL;
00195 Cursor*         BlendTool::pcNormalCursor           = NULL;
00196 Cursor*         BlendTool::pcBlendableCursor        = NULL;
00197 Cursor*         BlendTool::pcBlendableBlobCursor    = NULL;
00198 Cursor*         BlendTool::pcBlendableRemapCursor   = NULL;
00199 Cursor*         BlendTool::pcCurrentCursor          = NULL;
00200 INT32           BlendTool::CurrentCursorID          = 0;
00201 UINT32          BlendTool::StatusID                 = _R(IDS_BLENDSTATUS_FINDSTART);
00202 
00203 //#define Swap(a,b)       { (a)^=(b), (b)^=(a), (a)^=(b); }
00204 
00205 #define SWAP(type,a,b) { type x=a; a=b; b=x; }
00206 
00207 /********************************************************************************************
00208 
00209 >   BlendTool::BlendTool()
00210 
00211     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
00212     Created:    3/10/94
00213     Purpose:    Default Constructor.
00214                 Other initialisation is done in BlendTool::Init which is called by the Tool Manager
00215     SeeAlso:    BlendTool::Init
00216 
00217 ********************************************************************************************/
00218 
00219 BlendTool::BlendTool()
00220 {
00221     pcCurrentCursor = NULL;
00222 }
00223 
00224 /********************************************************************************************
00225 
00226 >   BlendTool::~BlendTool()
00227 
00228     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
00229     Created:    3/10/94
00230     Purpose:    Destructor.
00231 
00232 ********************************************************************************************/
00233 
00234 BlendTool::~BlendTool()
00235 {
00236     if (pRefStart != NULL)
00237     {
00238         delete pRefStart;
00239         pRefStart = NULL;
00240     }
00241 
00242     if (pRefEnd != NULL)
00243     {
00244         delete pRefEnd;
00245         pRefEnd = NULL;
00246     }
00247 }
00248 
00249 
00250 /********************************************************************************************
00251 
00252 >   BOOL BlendTool::Init( INT32 Pass )
00253 
00254     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
00255     Created:    3/10/94
00256     Returns:    FALSE if it does not want to be created, TRUE otherwise
00257     Purpose:    Used to check if the Tool was properly constructed
00258     SeeAlso:    BlendTool::BlendTool
00259 
00260 ********************************************************************************************/
00261 
00262 BOOL BlendTool::Init()
00263 {
00264     // Declare all your ops here and only succeed if all declarations succeed
00265 
00266     BOOL ok = ( OpBlendNodes::Declare()         && 
00267                 OpRemoveBlend::Declare()        && 
00268                 OpAddBlendPath::Declare()       && 
00269                 OpDetachBlendPath::Declare()    && 
00270                 OpChangeBlend::Declare()        && 
00271                 OpBlendOneToOne::Declare()      &&
00272                 OpBlendAntialias::Declare()     &&
00273                 OpChangeBlendSteps::Declare());
00274 
00275     if (!ok) return FALSE;
00276 
00277     // We need two BlendToolRef objects
00278     BlendTool::pRefStart = new BlendToolRef;
00279     BlendTool::pRefEnd   = new BlendToolRef;
00280 
00281     ok = (BlendTool::pRefStart != NULL && BlendTool::pRefEnd != NULL);
00282 
00283     // This section reads in the infobar definition and creates an instance of
00284     // BlendInfoBarOp.  Also pBlendInfoBarOp, the ptr to the tool's infobar, is set up
00285     // after the infobar is successfully read and created.
00286     if (ok)
00287     {
00288         pBlendInfoBarOp = new BlendInfoBarOp();
00289         ok = (pBlendInfoBarOp != NULL);
00290 #if 0
00291         CCResTextFile       file;               // Resource File
00292         BlendInfoBarOpCreate BarCreate;         // Object that creates BlendInfoBarOp objects
00293 
00294                 ok = file.open(_R(IDM_BLEND_BAR), _R(IDT_INFO_BAR_RES));        // Open resource
00295         if (ok) ok = DialogBarOp::ReadBarsFromFile(file,BarCreate); // Read and create info bar
00296         if (ok) file.close();                                       // Close resource
00297 
00298         ENSURE(ok,"Unable to load blendbar.ini from resource\n"); 
00299 
00300         if (ok)
00301         {
00302             // Info bar now exists.  Now get a pointer to it
00303             String_32 str = String_32(_R(IDS_BLNDTOOL_INFOBARNAME));
00304             DialogBarOp* pDialogBarOp = DialogBarOp::FindDialogBarOp(str);
00305 
00306                     ok = (pDialogBarOp != NULL);
00307             if (ok) ok = pDialogBarOp->IsKindOf(CC_RUNTIME_CLASS(BlendInfoBarOp));
00308             if (ok) pBlendInfoBarOp = (BlendInfoBarOp*)pDialogBarOp;
00309 
00310             ENSURE(ok,"Error finding the blend tool info bar");
00311         }
00312 #endif
00313     }
00314 
00315     return (ok);
00316 }
00317 
00318 
00319 /********************************************************************************************
00320 
00321 >   void BlendTool::Describe(void *InfoPtr)
00322 
00323     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
00324     Created:    3/10/94
00325     Inputs:     InfoPtr -   A pointer to a tool info block. It is passed cast to void* as
00326                             the version of the tool is unknown at this point. Later versions 
00327                             of the Tool class may have more items in this block, that this 
00328                             tool will not use
00329     Outputs:    InfoPtr -   The structure pointed to by InfoPtr will have had all the info
00330                             that this version of the Tool knows about
00331     Purpose:    Allows the tool manager to extract information about the tool
00332 
00333 ********************************************************************************************/
00334 
00335 void BlendTool::Describe(void *InfoPtr)
00336 {
00337     // Cast structure into the latest one we understand.
00338     ToolInfo_v1 *Info = (ToolInfo_v1 *) InfoPtr;
00339 
00340     Info->InfoVersion = 1;
00341     
00342     Info->InterfaceVersion = GetToolInterfaceVersion();  // You should always have this line.
00343         
00344     // These are all arbitrary at present.
00345     Info->Version = 1;
00346     Info->ID      = GetID();
00347     Info->TextID  = _R(IDS_BLEND_TOOL);
00348 
00349     Info->Family  = FamilyName;
00350     Info->Name    = ToolName;
00351     Info->Purpose = Purpose;
00352     Info->Author  = Author;
00353 
00354     Info->BubbleID = _R(IDBBL_BLEND_TOOLBOX);
00355 }
00356 
00357 /********************************************************************************************
00358 
00359 >   virtual void BlendTool::SelectChange(BOOL isSelected)
00360 
00361     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
00362     Created:    3/10/94
00363     Inputs:     isSelected  - TRUE  = tool has been selected
00364                             - FALSE = tool has been deselected
00365     Outputs:    -
00366     Returns:    -
00367     Purpose:    Starts up and closes down the blend tool
00368     Errors:     Debug warning if creating the cursor fails.
00369     SeeAlso:    -
00370 
00371 ********************************************************************************************/
00372 
00373 void BlendTool::SelectChange(BOOL isSelected)
00374 {
00375     if (isSelected)
00376     {
00377         if (!CreateCursors()) return;
00378         CurrentCursorID = CursorStack::GPush(pcNormalCursor, FALSE);        // Push cursor but don't display now
00379         pcCurrentCursor = pcNormalCursor;
00380 
00381         // Create and display the tool's info bar
00382         pBlendInfoBarOp->Create();
00383         m_EditEndObject = FALSE;
00384         // Which blobs do I want displayed
00385         BlobManager* BlobMgr = GetApplication()->GetBlobManager();
00386         if (BlobMgr != NULL)
00387         {
00388             // Decide which blobs we will display
00389             BlobStyle MyBlobs;
00390             
00391             MyBlobs.Object = TRUE;
00392             MyBlobs.Tiny = FALSE;
00393 
00394             BlobMgr->ToolInterest(MyBlobs);
00395         }
00396 
00397         Document* pDoc = Document::GetCurrent();
00398         if (pDoc != NULL)
00399             BlobMgr->RenderToolBlobsOn(this, pDoc->GetSelectedSpread(),NULL);
00400     }
00401     else
00402     {
00403         // Deselection - destroy the tool's cursors, if they exist.
00404         if (pcCurrentCursor != NULL)
00405         {
00406             CursorStack::GPop(CurrentCursorID);
00407             pcCurrentCursor = NULL;
00408             CurrentCursorID = 0;
00409         }
00410         DestroyCursors();
00411 
00412         // we need to close down any profile dialogs that are currently open ....
00413         pBlendInfoBarOp->CloseProfileDialog (pBlendInfoBarOp->m_BiasGainGadgetPosition);
00414         pBlendInfoBarOp->CloseProfileDialog (pBlendInfoBarOp->m_BiasGainGadgetAttribute);
00415         
00416         // BEFORE we do the next call !!!! Cause otherwise pBlendInfoBarOp will
00417         // have been "deleted", and the above will access violate!
00418 
00419         // ensure any tool object blobs are removed.
00420         BlobManager* BlobMgr = GetApplication()->GetBlobManager();
00421         if (BlobMgr != NULL)
00422         {
00423             BlobStyle bsRemoves;
00424             bsRemoves.ToolObject = TRUE;
00425             BlobMgr->RemoveInterest(bsRemoves);
00426         }
00427 
00428         // Remove the info bar from view by deleting the actual underlying window
00429         pBlendInfoBarOp->Delete();
00430 
00431         Document* pDoc = Document::GetCurrent();
00432         if (pDoc != NULL)
00433             BlobMgr->RenderToolBlobsOff(this, pDoc->GetSelectedSpread(),NULL);
00434     }
00435 }
00436 
00437 /********************************************************************************************
00438 
00439 >   BOOL BlendTool::CreateCursors()
00440 
00441     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
00442     Created:    3/10/94
00443     Inputs:     -
00444     Outputs:    -
00445     Returns:    TRUE if all the blend tool cursors have been successfully created
00446     Purpose:    Creates all the blend tool cursors
00447     SeeAlso:    -
00448 
00449 ********************************************************************************************/
00450 
00451 BOOL BlendTool::CreateCursors()
00452 {
00453     // This tool has just been selected.  Create the cursors.
00454     pcNormalCursor          = new Cursor(this, _R(IDC_BLENDNORMALCURSOR));
00455     pcBlendableCursor       = new Cursor(this, _R(IDC_BLENDABLECURSOR));
00456     pcBlendableBlobCursor   = new Cursor(this, _R(IDC_BLENDABLEBLOBCURSOR));
00457     pcBlendableRemapCursor  = new Cursor(this, _R(IDC_BLENDABLEREMAPCURSOR));
00458 
00459     if ( pcNormalCursor         ==NULL || !pcNormalCursor->IsValid()            ||
00460          pcBlendableCursor      ==NULL || !pcBlendableCursor->IsValid()         ||
00461          pcBlendableBlobCursor  ==NULL || !pcBlendableBlobCursor->IsValid()     ||
00462          pcBlendableRemapCursor ==NULL || !pcBlendableRemapCursor->IsValid()
00463        )
00464     {
00465         DestroyCursors();
00466         return FALSE;
00467     }
00468     else
00469         return TRUE;
00470 }
00471 
00472 /********************************************************************************************
00473 
00474 >   void BlendTool::DestroyCursors()
00475 
00476     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
00477     Created:    3/10/94
00478     Inputs:     -
00479     Outputs:    -
00480     Returns:    -
00481     Purpose:    Destroys all the blend tool cursors
00482     SeeAlso:    -
00483 
00484 ********************************************************************************************/
00485 
00486 void BlendTool::DestroyCursors()
00487 {
00488     if (pcNormalCursor          != NULL) delete pcNormalCursor;
00489     if (pcBlendableCursor       != NULL) delete pcBlendableCursor;
00490     if (pcBlendableBlobCursor   != NULL) delete pcBlendableBlobCursor;
00491     if (pcBlendableRemapCursor  != NULL) delete pcBlendableRemapCursor;
00492 }
00493 
00494 /********************************************************************************************
00495 
00496 >   BOOL BlendTool::OnKeyPress(KeyPress* pKeyPress)
00497 
00498     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
00499     Created:    1/6/99
00500     Inputs:     pKeyPress - pointer to a keypress object
00501     Returns:    TRUE if it handled the keypress, FALSE otherwise
00502     Purpose:    To handle keypress events for the Blend Tool.
00503 
00504 ********************************************************************************************/
00505 
00506 BOOL BlendTool::OnKeyPress(KeyPress* pKeyPress)
00507 {
00508 #if defined(_DEBUG) && 0
00509     if (pKeyPress == NULL)
00510         return FALSE;
00511 
00512     if (pKeyPress->IsRepeat())
00513         return FALSE;
00514 
00515     if (pKeyPress->IsRelease())
00516         return FALSE;
00517 
00518     AFp BiasDelta = 0.0;
00519     AFp GainDelta = 0.0;
00520     BOOL Reset = FALSE;
00521     if (*pKeyPress == KeyPress(CAMKEY(Z)))  { BiasDelta = -0.1; TRACEUSER( "Markn", _T("Decrease Bias by 0.1\n"));}
00522     if (*pKeyPress == KeyPress(CAMKEY(X)))  { BiasDelta =  0.1; TRACEUSER( "Markn", _T("Increase Bias by 0.1\n"));}
00523     if (*pKeyPress == KeyPress(CAMKEY(N)))  { GainDelta = -0.1; TRACEUSER( "Markn", _T("Decrease Gain by 0.1\n"));}
00524     if (*pKeyPress == KeyPress(CAMKEY(M)))  { GainDelta =  0.1; TRACEUSER( "Markn", _T("Increase Gain by 0.1\n"));}
00525 
00526     if (*pKeyPress == KeyPress(CAMKEY(R)))  { Reset = TRUE;     TRACEUSER( "Markn", _T("Resetting Bias and Gain\n"));}
00527 
00528     SelRange* pSelRange = GetApplication()->FindSelection();
00529     Node* pNode = pSelRange->FindFirst();
00530     while (pNode)
00531     {
00532         if (IS_A(pNode,NodeBlend))
00533         {
00534             NodeBlend* pNodeBlend = (NodeBlend*)pNode;
00535 
00536             // This alters the Attribute profile, but can easily be modified to alter the Object profile if necessary
00537             CProfileBiasGain* pProfile = pNodeBlend->GetAttrProfile();
00538             if (pProfile)
00539             {
00540                 AFp Bias = pProfile->GetBias() + BiasDelta;
00541                 AFp Gain = pProfile->GetGain() + GainDelta;
00542                 if (Reset)
00543                     Bias = Gain = 0.0;
00544 
00545                 if (Bias < -0.9)    Bias = -0.9;
00546                 if (Bias >  0.9)    Bias =  0.9;
00547                 if (Gain < -0.9)    Gain = -0.9;
00548                 if (Gain >  0.9)    Gain =  0.9;
00549 
00550                 pProfile->SetBiasGain(Bias,Gain);
00551             }
00552         }
00553         // Now find the next selected node
00554         pNode = pSelRange->FindNext(pNode);
00555     }
00556 #endif // _DEBUG
00557 
00558     return FALSE;
00559 }
00560 
00561 /********************************************************************************************
00562 
00563 >   void BlendTool::OnClick( DocCoord PointerPos, ClickType Click, ClickModifiers ClickMods,
00564                         Spread* pSpread )
00565 
00566     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
00567     Created:    3/10/94
00568     Inputs:     PointerPos  -   The DocCoord of the point where the mouse button was clicked
00569                 Click       -   Describes the type of click that was detected. 
00570                 ClickMods   -   Indicates which buttons caused the click and which modifers were
00571                                 pressed at the same time
00572                 pSpread     -   The spread in which the click happened
00573     Returns:    -
00574     Purpose:    To handle a Mouse Click event for the Blend Tool.  If the click is over the 
00575                 central blob of a blend on a path then start a EditEnd operation, otherwise
00576                 start a createblend operation.
00577     SeeAlso:    Tool::MouseClick; ClickType; ClickModifiers
00578 
00579 ********************************************************************************************/
00580 
00581 void BlendTool::OnClick( DocCoord PointerPos, ClickType Click, ClickModifiers ClickMods,
00582                         Spread* pSpread )
00583 {
00584     if (ClickMods.Menu) return;                         // Don't do anything if the user clicked the Menu button
00585 
00586     ERROR3IF_PF(pSpread==NULL,("pSpread is NULL"));
00587 
00588     if (Click == CLICKTYPE_DRAG)
00589     {
00590         // are we over the middle blob of a blend on a curve?
00591         BOOL MoveEndObject = EditBlendEnd(pSpread, PointerPos);
00592 
00593         // Diccon  9/99, do we wish to create a blend or move an object?
00594         if (!MoveEndObject)  // create a blend
00595         {
00596             UpdateRef(pRefStart,pSpread,PointerPos);
00597             UpdateRef(pRefEnd  ,pSpread,PointerPos);
00598             CheckNodeRemapping(pRefStart,pRefEnd);
00599             UpdateCursorAndStatus();
00600 
00601             if (pRefStart->pNode != NULL)
00602             {
00603                 // Start a drag
00604                 OpBlendNodes* pOpBlendNodes = new OpBlendNodes;
00605                 if (pOpBlendNodes != NULL)
00606                 {
00607                     // Start the drag operation and pass in the Anchor Point to the push operation
00608                     if  (!pOpBlendNodes->DoDrag(this))
00609                         delete pOpBlendNodes;
00610                 }
00611             }
00612         }
00613         else  // drag end object
00614         {   
00615             OpEditBlendEndObject* pEditEnd = new OpEditBlendEndObject(this);
00616 
00617             if (pEditEnd != NULL)
00618             {
00619                 StatusID = _R(IDS_BLENDSTATUS_MOVEEND);
00620                 DisplayStatusBarHelp(StatusID);
00621                 pEditEnd->DoDrag(PointerPos, pSpread);
00622             }
00623         }
00624     }
00625     
00626     // call the base class ....
00627     
00628     DragTool::OnClick (PointerPos, Click, ClickMods, pSpread);
00629 }
00630 
00631 /********************************************************************************************
00632 
00633 >   void BlendTool::OnMouseMove( DocCoord PointerPos,Spread* pSpread, ClickModifiers ClickMods)
00634 
00635     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
00636     Created:    3/10/94
00637     Inputs:     PointerPos  -   The DocCoord of the point where the mouse has moved to
00638                 pSpread     -   The spread in which the move occurred
00639     Returns:    TRUE if it handled the Click, FALSE otherwise
00640     Purpose:    To handle a Mouse Move event for the Blend Tool.
00641     SeeAlso:    Tool::MouseClick; ClickType; ClickModifiers
00642 
00643 ********************************************************************************************/
00644 
00645 void BlendTool::OnMouseMove(DocCoord PointerPos,Spread* pSpread,ClickModifiers ClickMods)
00646 {
00647     ERROR3IF_PF(pSpread==NULL,("pSpread is NULL"));
00648 
00649     pRefEnd->Reset();
00650     UpdateRef(pRefStart,pSpread,PointerPos,FALSE);
00651     // Diccon added - check first to see if there is a hit on an edit end blob
00652     m_EditEndObject = EditBlendEndAndUpdateCursor(pSpread, PointerPos);
00653     // if not then check for new blend/remapping etc.
00654     if (m_EditEndObject == FALSE)
00655         UpdateCursorAndStatus();
00656 }
00657 
00658 
00659 /********************************************************************************************
00660 
00661 >   BOOL BlendTool::EditBlendEndAndUpdateCursor(Spread* pSpread, DocCoord PointerPos)
00662 
00663     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
00664     Created:    10/9/99
00665     Inputs:     PointerPos  -   The DocCoord of the point where the mouse has moved to
00666                 pSpread     -   The spread in which the move occurred
00667     Returns:    TRUE if we can edit and end object, FALSE otherwise
00668     Purpose:    To determine whether or not the pointer is the central blob of an end object
00669                 of a blend on a curve. If so then we change the cursor and set a flag that 
00670                 allows us to begin an OpEditBlendEndobject if the user clicks and drags.
00671     SeeAlso:    OnMouseMove, OnClick
00672 
00673 ********************************************************************************************/
00674 
00675 BOOL BlendTool::EditBlendEndAndUpdateCursor(Spread* pSpread, DocCoord PointerPos)
00676 {
00677     Cursor* pcNewCursor = pcNormalCursor;
00678 
00679     List BlendList;
00680     // make a list of selected nodes
00681     BOOL ok = BevelTools::BuildListOfSelectedNodes(&BlendList, CC_RUNTIME_CLASS(NodeBlend), FALSE);
00682     if (ok)
00683     {
00684         ok = FALSE;
00685         NodeListItem* pListItem = (NodeListItem*)BlendList.GetHead();
00686         while (pListItem != NULL)
00687         {
00688             
00689             NodeBlend* pNodeBlend = (NodeBlend*)pListItem->pNode;
00690             Node* pNode = NULL;
00691             // check to see if there is a hit
00692             ok = pNodeBlend->HitOnEndDragBlob(PointerPos, &pNode);
00693             if (ok)
00694             {
00695                 pcNewCursor = pcBlendableBlobCursor;
00696                 StatusID = _R(IDS_BLENDSTATUS_EDITENDS);                        
00697                 break;
00698             }
00699             pListItem = (NodeListItem*)BlendList.GetNext(pListItem);
00700         }
00701     }
00702     BlendList.DeleteAll();
00703 
00704     if (pcCurrentCursor != pcNewCursor)
00705     {
00706         // Current cursor has changed
00707         CursorStack::GSetTop(pcNewCursor, CurrentCursorID);
00708         pcCurrentCursor = pcNewCursor;
00709     }
00710     
00711     return ok;
00712 }
00713 
00714 
00715 /********************************************************************************************
00716 
00717 >   BOOL BlendTool::EditBlendEnd(Spread* pSpread, DocCoord PointerPos)
00718 
00719     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
00720     Created:    10/9/99
00721     Inputs:     PointerPos  -   The DocCoord of the point where the mouse has moved to
00722                 pSpread     -   The spread in which the move occurred
00723     Returns:    TRUE if we can edit and end object, FALSE otherwise
00724     Purpose:    To determine whether or not the pointer is the central blob of an end object
00725                 of a blend on a curve. If so then we change the cursor and set a flag that 
00726                 allows us to begin an OpEditBlendEndobject if the user clicks and drags.
00727     SeeAlso:    OnMouseMove, OnClick
00728 
00729 ********************************************************************************************/
00730 
00731 BOOL BlendTool::EditBlendEnd(Spread* pSpread, DocCoord PointerPos)
00732 {
00733     List BlendList;
00734     // make a list of selected nodes
00735     BOOL ok = BevelTools::BuildListOfSelectedNodes(&BlendList, CC_RUNTIME_CLASS(NodeBlend), FALSE);
00736     if (ok)
00737     {
00738         ok = FALSE;
00739         NodeListItem* pListItem = (NodeListItem*)BlendList.GetHead();
00740         while (pListItem != NULL)
00741         {
00742             
00743             NodeBlend* pNodeBlend = (NodeBlend*)pListItem->pNode;
00744             Node* pNode = NULL;
00745             // check to see if there is a hit
00746             ok = pNodeBlend->HitOnEndDragBlob(PointerPos, &pNode);
00747             if (ok)
00748                 break;
00749             pListItem = (NodeListItem*)BlendList.GetNext(pListItem);
00750         }
00751     }
00752     BlendList.DeleteAll();
00753 
00754     return ok;
00755 }
00756 
00757 
00758 
00759 /********************************************************************************************
00760 >   void BlendTool::UpdateRef(  BlendToolRef* pRef,
00761                                 Spread* pSpread, 
00762                                 DocCoord PointerPos,
00763                                 BOOL CheckNodeUnderPoint = TRUE)
00764 
00765     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
00766     Created:    10/10/94
00767     Inputs:     pRef                = ptr to a reference struct to update
00768                 pSpread             = ptr to spread click occurred
00769                 PointerPos          = the DocCoord of the click
00770                 CheckNodeUnderPoint = TRUE to revert to click-detection search for underlying node
00771     Outputs:    The members of pRef are updated.
00772     Returns:    -
00773     Purpose:    This will update the blend tool ref depending on the current pointer pos.
00774                 If CheckNodeUnderPoint is TRUE, then the routine will revert to a click-detection search
00775                 to find out which node lies under the given point.  This is potentially very time-consuming.
00776     Errors:     -
00777     SeeAlso:    -
00778 ********************************************************************************************/
00779 
00780 void BlendTool::UpdateRef(BlendToolRef* pRef,Spread* pSpread, DocCoord PointerPos,BOOL CheckNodeUnderPoint)
00781 {
00782     ERROR3IF_PF(pRef   ==NULL,("pRef is NULL"));
00783     ERROR3IF_PF(pSpread==NULL,("pSpread is NULL"));
00784 
00785     // Reset the reference
00786     pRef->Reset();
00787 
00788     // See if point is over a blob of a selected path
00789     // if IsPointOverPathBlob() fails, it inits pNode to NULL and Index to -1
00790     if (!IsPointOverPathBlob(&PointerPos,pRef))
00791     {
00792         // Reset the reference in case above call altered it
00793         pRef->Reset();
00794 
00795         // See if point is over a blob is a selected blend
00796         // if IsPointOverBlendBlob() fails, it inits pNode & pNodeBlend to NULL and Index to -1
00797         if (!IsPointOverBlendBlob(&PointerPos,pRef))
00798         {
00799             // Reset the reference in case above call altered it
00800             pRef->Reset();
00801 
00802             if (CheckNodeUnderPoint)
00803             {
00804                 // See if we are over an object
00805                 NodeRenderableInk* pNodeUnderPoint = FindObject(pSpread,PointerPos);
00806 
00807                 if (pNodeUnderPoint != NULL)
00808                 {
00809                     BecomeA TestBecomeA(BECOMEA_TEST, CC_RUNTIME_CLASS(NodePath));
00810                     // We are over an object. Can we blend it?
00811                     if (pNodeUnderPoint->CanBecomeA(&TestBecomeA))
00812                     {
00813                         // We are over a NodePath or a node that can become a NodePath
00814                         pRef->pNode = pNodeUnderPoint;
00815                     }
00816                 }
00817             }
00818         }
00819     }
00820 
00821     // Set the spread and pointer pos members
00822     pRef->pSpread    = pSpread;
00823     pRef->PointerPos = PointerPos;
00824 }
00825 
00826 /********************************************************************************************
00827 >   void BlendTool::UpdateCursorAndStatus()
00828 
00829     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
00830     Created:    10/10/94
00831     Inputs:     -
00832     Outputs:    -
00833     Returns:    -
00834     Purpose:    This will update the cursor and status line text depending on the data in the 
00835                 two blend tool references within BlendTool.
00836 
00837                 Copes with the following scenarios:
00838                     Pointer over an object
00839                     Dragging to another object
00840                     Pointer over a selected path's blob
00841                     Dragging to another blob in a selected path
00842                     Pointer over a blend blob
00843                     Dragging to a corresponding blend blob for remapping
00844 
00845     Errors:     -
00846     SeeAlso:    -
00847 ********************************************************************************************/
00848 
00849 void BlendTool::UpdateCursorAndStatus()
00850 {
00851     ERROR3IF_PF(pRefStart==NULL,("pRefStart is NULL"));
00852     ERROR3IF_PF(pRefEnd  ==NULL,("pRefEnd   is NULL"));
00853 
00854     // Default to standard pointer and status line text
00855     Cursor* pcNewCursor = pcNormalCursor;
00856     StatusID = _R(IDS_BLENDSTATUS_FINDSTART);   // StatusID is a member var
00857 
00858     if (pRefStart->RemapRef > 0)
00859     {
00860         // The pointer is either over a blend blob, or user started a drag over a blend blob
00861         pcNewCursor = pcBlendableBlobCursor;
00862         StatusID = _R(IDS_BLENDSTATUS_REMAPSTART);
00863 
00864         if (pRefStart->RemapRef == pRefEnd->RemapRef && pRefStart->AStartNode != pRefEnd->AStartNode)
00865         {
00866             // Dragged to a corresponding blob in the other blend
00867             StatusID = _R(IDS_BLENDSTATUS_REMAPEND);
00868             pcNewCursor = pcBlendableRemapCursor;
00869         }
00870     }
00871     else if (pRefStart->pNode != NULL)
00872     {
00873         if (pRefEnd->pSpread == NULL)
00874         {
00875             if (pRefStart->Index >= 0)
00876             {
00877                 // We are over a blob of a selected path
00878                 pcNewCursor = pcBlendableBlobCursor;
00879                 StatusID    = _R(IDS_BLENDSTATUS_OVERBLOB);
00880             }
00881             else
00882             {
00883                 // We are over a blendable node
00884                 pcNewCursor = pcBlendableCursor;
00885                 StatusID    = _R(IDS_BLENDSTATUS_FINDEND);
00886             }
00887         }
00888         else if (pRefStart->pSpread == pRefEnd->pSpread)
00889         {
00890             // Dragging, and start and end are in the same spread
00891             if (pRefStart->pNode != pRefEnd->pNode && pRefEnd->pNode != NULL)
00892             {
00893                 StatusID = _R(IDS_BLENDSTATUS_OVEREND);
00894 
00895                 if (pRefEnd->Index >= 0 && pRefStart->Index >= 0)
00896                     // We are over a blob of a selected path, for both start & end objects
00897                     // so display the remap cursor
00898                     pcNewCursor = pcBlendableRemapCursor;
00899                 else
00900                     // We are over a blendable node
00901                     pcNewCursor = pcBlendableCursor;
00902             }
00903             else
00904                 StatusID = _R(IDS_BLENDSTATUS_FINDEND);
00905         }
00906     }
00907 
00908     if (pcCurrentCursor != pcNewCursor)
00909     {
00910         // Current cursor has changed
00911         CursorStack::GSetTop(pcNewCursor, CurrentCursorID);
00912         pcCurrentCursor = pcNewCursor;
00913     }
00914 
00915     // Always update the status bar text
00916     DisplayStatusBarHelp(StatusID);
00917 }
00918 
00919 /********************************************************************************************
00920 
00921 >   virtual BOOL BlendTool::GetStatusLineText(  String_256* ptext, 
00922                                                 Spread* pSpread,
00923                                                 DocCoord DocPos, 
00924                                                 ClickModifiers ClickMods)
00925     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
00926     Created:    19/12/94
00927     Inputs:     pStr        - ptr to string to place text in
00928                 pSpread     - ptr to the spread in question
00929                 DocPos      - position of mouse in doc (in spread coords)
00930                 ClickMods   - mouse click modifiers
00931     Outputs:    *pStr - text for status line
00932     Returns:    TRUE if outputting valid text
00933     Purpose:    generate up-to-date text for the status line (called on idles)
00934 
00935 ********************************************************************************************/
00936 
00937 BOOL BlendTool::GetStatusLineText(String_256* pStr,Spread* pSpread,DocCoord DocPos,ClickModifiers ClickMods)
00938 {
00939     *pStr = String_256(StatusID);
00940     return TRUE;
00941 }
00942 
00943 
00944 /********************************************************************************************
00945 
00946 >   void BlendTool::UpdateInfobar()
00947 
00948     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
00949     Created:    16/11/99
00950     Inputs:     -
00951     Outputs:    -
00952     Returns:    -
00953     Purpose:    Asks the infobar to update itself
00954     SeeAlso:    -
00955 
00956 ********************************************************************************************/
00957 
00958 void BlendTool::UpdateInfobar()
00959 {
00960     pBlendInfoBarOp->UpdateInfoBarState();
00961 }
00962 
00963 
00964 
00965 
00966 /********************************************************************************************
00967 
00968 >   BOOL BlendTool::IsPointOverPathBlob(DocCoord* pPointerPos,BlendToolRef* pRef)
00969 
00970     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
00971     Created:    10/10/94
00972     Inputs:     pPointerPos = ptr to position of mouse in DocCoords
00973                 pRef        = ptr to blend tool ref to update
00974     Outputs:    Updates pRef->pNode and pRef->pIndex if a path blob was found.
00975                 Also, if blob found, *pPointerPos is snapped to coord of blob
00976     Returns:    TRUE if the coord is over a blob of a selected path
00977     Purpose:    Scans for selected paths.  If the coord is over a selected path's blob,
00978                 it returns TRUE and pRef->pNode points to the path, and pRef->Index contains
00979                 the element index of the blob.
00980                 Also, *pPointerPos is snapped to the coord of the centre of the blob, if found.
00981 
00982 ********************************************************************************************/
00983 
00984 BOOL BlendTool::IsPointOverPathBlob(DocCoord* pPointerPos,BlendToolRef* pRef)
00985 {
00986     if (pRef == NULL) return FALSE;
00987 
00988     // Find the selected range of objects
00989     SelRange* pSelRange = GetApplication()->FindSelection();
00990     Node* pNode = pSelRange->FindFirst();
00991 
00992     BOOL BlobFound = FALSE;
00993 
00994     // Scan the selection for NodePath objects
00995     while (!BlobFound && pNode != NULL && pNode->FindParent() != NULL)
00996     {
00997         // Only look at selected NodePaths that are NOT selected inside another node.
00998         if (IS_A(pNode,NodePath) && IS_A(pNode->FindParent(),Layer))
00999         {
01000             NodePath* pNodePath = (NodePath*)pNode;
01001 
01002             if (pNodePath->GetUnionBlobBoundingRect().ContainsCoord(*pPointerPos))
01003             {
01004                 // Get a pointer to the Path object within the NodePath
01005                 Path* pPath = &(pNodePath->InkPath);
01006 
01007                 // Is it over a blob? (Only check end points. Forget about control points)
01008                 BlobFound = pPath->FindNearestPoint(*pPointerPos,POINTFLAG_ENDPOINTS,&(pRef->Index));
01009 
01010                 // If a blob is found, store ptr to the node
01011                 if (BlobFound)
01012                 {
01013                     pRef->pNode = pNodePath;
01014                     pPath->SetPathPosition(pRef->Index);
01015                     *pPointerPos = pPath->GetCoord();
01016                 }
01017             }
01018         }
01019 
01020         // Now find the next selected node
01021         pNode = pSelRange->FindNext(pNode);
01022     }
01023 
01024     return BlobFound;
01025 }
01026 
01027 /********************************************************************************************
01028 
01029 >   BOOL BlendTool::IsPointOverBlendBlob(DocCoord* pPointerPos,NodeRenderableInk** ppNodePath,INT32* pIndex)
01030 
01031     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
01032     Created:    10/11/94
01033     Inputs:     pPointerPos = ptr to position of mouse in DocCoords
01034                 pRef        = ptr to blend tool ref to update
01035     Outputs:    Updates pRef->pNode pRef->pNodeBlend and pRef->Index if a blend blob was found.
01036                 Also, if found, *pPointerPos = centre of blob
01037     Returns:    TRUE if the coord is over a blob of a selected blend
01038     Purpose:    Scans for selected blends.  If the coord is over a selected blend's blob,
01039                 it returns TRUE and pRef->pNode points to the path, pRef->pNodeBlend points to the blend
01040                 containing the path, and *pIndex contains the element index of the blob.
01041                 Also, if found, *pPointerPos = centre of blob.
01042 
01043 ********************************************************************************************/
01044 
01045 BOOL BlendTool::IsPointOverBlendBlob(DocCoord* pPointerPos,BlendToolRef* pRef)
01046 {
01047     if (pRef == NULL) return FALSE;
01048 
01049     // Find the selected range of objects
01050     SelRange* pSelRange = GetApplication()->FindSelection();
01051     Node* pNode = pSelRange->FindFirst();
01052 
01053     BOOL BlobFound = FALSE;
01054 
01055     // Scan the selection for NodePath objects
01056     while (pNode != NULL && !BlobFound)
01057     {
01058         if (pNode->GetRuntimeClass() == CC_RUNTIME_CLASS(NodeBlend))
01059         {
01060             NodeBlend* pNodeBlend = (NodeBlend*)pNode;
01061 
01062             if (pNodeBlend->GetUnionBlobBoundingRect().ContainsCoord(*pPointerPos))
01063             {
01064                 BlobFound = pNodeBlend->IsPointOverBlob(pPointerPos,
01065                                                         &(pRef->pBlendPath),
01066                                                         &(pRef->Index),
01067                                                         &(pRef->AStartNode),
01068                                                         &(pRef->RemapRef));
01069 
01070                 if (BlobFound)
01071                 {
01072                     pRef->pNode      = pNodeBlend;
01073                     pRef->pNodeBlend = pNodeBlend;
01074 //                  *pPointerPos = pRef->pBlendPath->GetPathCoord(pRef->Index);
01075                 }
01076             }
01077         }
01078 
01079         // Now find the next selected node
01080         pNode = pSelRange->FindNext(pNode);
01081     }
01082 
01083     return BlobFound;
01084 }
01085 
01086 /********************************************************************************************
01087 
01088 >   void BlendTool::CheckNodeRemapping(BlendToolRef* pRefStart, BlendToolRef* pRefEnd)
01089 
01090     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
01091     Created:    6/12/94
01092     Inputs:     pRefStart = ptr to start ref
01093                 pRefEnd   = ptr to end ref
01094     Outputs:    Potentailly updates pRefStart and pRefEnd, so that if they represent a remapping of nodes,
01095                 they have the same remap reference
01096     Returns:    -
01097     Purpose:    This checks to see if the two references actually represent a node remapping within a blend.
01098                 In order to cope with multi-stage blends, both references have to be looked at the same time.
01099 
01100                 This is not the neatest way of doing it, but it's the quickest and safest method, given that
01101                 it has to work for the gamma release it a few days time.
01102 
01103 ********************************************************************************************/
01104 
01105 void BlendTool::CheckNodeRemapping(BlendToolRef* pRefStart, BlendToolRef* pRefEnd)
01106 {
01107     ERROR3IF(pRefStart == NULL,"pRefStart == NULL");
01108     ERROR3IF(pRefEnd   == NULL,"pRefEnd == NULL");
01109     if (pRefStart == NULL || pRefEnd == NULL)
01110         return;
01111 
01112     NodeBlend* pNodeBlend = pRefStart->pNodeBlend;
01113 
01114     if (pNodeBlend == NULL || pNodeBlend != pRefEnd->pNodeBlend)
01115         return;
01116 
01117     Node* pNode = pNodeBlend->FindFirstChild();
01118     while (pNode != NULL)
01119     {
01120         if (IS_A(pNode,NodeBlender))
01121         {
01122             NodeBlender* pNodeBlender = (NodeBlender*)pNode;
01123 
01124             BOOL StartFound = pNodeBlender->IsPointOverBlob(&(pRefStart->PointerPos),
01125                                                             &(pRefStart->pBlendPath),
01126                                                             &(pRefStart->Index),
01127                                                             &(pRefStart->AStartNode));
01128 
01129             BOOL EndFound   = pNodeBlender->IsPointOverBlob(&(pRefEnd  ->PointerPos),
01130                                                             &(pRefEnd  ->pBlendPath),
01131                                                             &(pRefEnd  ->Index),
01132                                                             &(pRefEnd  ->AStartNode));
01133 
01134             if (StartFound && EndFound && (pRefStart->AStartNode != pRefEnd->AStartNode))
01135             {
01136                 pRefStart->RemapRef = pNodeBlender->GetTag();
01137                 pRefEnd  ->RemapRef = pNodeBlender->GetTag();
01138                 return;
01139             }
01140         }
01141         pNode = pNode->FindNext();
01142     }
01143 }
01144 
01145 /********************************************************************************************
01146 >   NodeRenderableInk* BlendTool::FindObject(Spread* pSpread, DocCoord PointerPos)
01147 
01148     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
01149     Created:    7/10/94
01150     Inputs:     pSpread  = ptr to spread click occurred
01151                 PointerPos = the DocCoord of the click
01152     Outputs:    -
01153     Returns:    -
01154     Purpose:    Looks for a (possibly grouped) node(s) in the given spread at the
01155                 given mouse-click position.  Convenient shorthand for the hit-testing
01156                 functions.
01157     Errors:     -
01158     SeeAlso:    -
01159 ********************************************************************************************/
01160 
01161 NodeRenderableInk* BlendTool::FindObject(Spread* pSpread, DocCoord PointerPos)
01162 {
01163     return NodeRenderableInk::FindCompoundAtPoint(pSpread,PointerPos,NULL);
01164 }
01165 
01166 
01167 /********************************************************************************************
01168 
01169 >   void BlendTool::RenderToolBlobs(Spread* pSpread,DocRect* pDocRect)
01170 
01171     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
01172     Created:    3/10/94
01173     Inputs:     pSpread  = ptr to a spread
01174                 pDocRect = ptr to DocRect of spread to render in
01175     Returns:    -
01176     Purpose:    Handles the RenderToolBlobs method.
01177                 Renders the tool's blobs into the current doc view.
01178     SeeAlso:    
01179 
01180 ********************************************************************************************/
01181 
01182 void BlendTool::RenderToolBlobs(Spread* pSpread,DocRect* pDocRect)
01183 {
01184     // Render into the current doc view
01185     DocView* pDocView = DocView::GetCurrent();
01186         
01187     if (pDocView != NULL && pSpread != NULL)
01188     {
01189         SelRange* pSel = GetApplication()->FindSelection();
01190         if (pSel == NULL) return;
01191     
01192         List BlendList;
01193         BOOL ok = BevelTools::BuildListOfSelectedNodes(&BlendList, CC_RUNTIME_CLASS(NodeBlend), FALSE);
01194         if (ok)
01195         {   
01196             // Get a render region on the spread.  We need to render EORd stuff on top of the current view
01197             RenderRegion* pRender = pDocView->RenderOnTop(pDocRect,pSpread,UnclippedEOR);
01198         
01199             // two possibilities here: either we have a selected blend in which case we can 
01200             // collect it in the list that we compile, or we have a selected inside object which is
01201             // a member of a blend. 
01202             while (pRender != NULL)
01203             {   
01204                 NodeListItem* pListItem = (NodeListItem*)BlendList.GetHead();
01205         
01206                 while (pListItem != NULL)
01207                 {       
01208                     NodeBlend* pNodeBlend = (NodeBlend*)pListItem->pNode;
01209                     pNodeBlend->RenderBlendBlobs(pRender);
01210                     pListItem = (NodeListItem*)BlendList.GetNext(pListItem);
01211                 }
01212             
01213                 Node *pNode = pSel->FindFirst();
01214                 while (pNode != NULL)
01215                 {
01216                     Node* pParent = pNode->FindParentOfSelected();
01217 
01218                     if ((pParent != NULL) && (IS_A(pParent, NodeBlend)))
01219                     {
01220                         RenderSelectInsideBlobs(pRender, (NodeRenderableInk*)pNode);
01221                     }
01222                     // get next in selection
01223                     pNode = pSel->FindNext(pNode);
01224                 }
01225                     // get the next render region
01226                 pRender = pDocView->GetNextOnTop(pDocRect);
01227             }
01228         }
01229         BlendList.DeleteAll();
01230     }
01231 }
01232 
01233 
01234 
01235 /********************************************************************************************
01236 
01237 >   BOOL BlendTool::RenderSelectInsideBlobs(RenderRegion* pRender, NodeRenderableInk* pNode)
01238 
01239     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
01240     Created:    21/9/99
01241     Inputs:     pRender - region to render into
01242                 pNode - node to render the blobs on
01243     Outputs:    -
01244     Returns:    TRUE if all went well, otherwise FALSE
01245     Purpose:    To render blobs on selected-inside nodes whilst in the blend tool.
01246                 The exact blob depends on whether or not the node is a text story.
01247     SeeAlso:    Blendtool::RenderToolBlobs()
01248 
01249 ********************************************************************************************/
01250 
01251 BOOL BlendTool::RenderSelectInsideBlobs(RenderRegion* pRender, NodeRenderableInk* pNode)
01252 {
01253     // some checks
01254     if (pRender == NULL)
01255     {
01256         ERROR3("No render region");
01257         return FALSE;
01258     }
01259 
01260     if (pNode == NULL)
01261     {
01262         ERROR3("node is NULL");
01263         return FALSE;
01264     }
01265 
01266 
01267     // if the node is a text node we want a red blob, otherwise a black cross
01268     if (pNode->IsABaseTextClass())
01269     {
01270         pRender->SetLineColour(COLOUR_BEZIERBLOB);
01271         pRender->SetFillColour(COLOUR_BEZIERBLOB);
01272     }
01273     else
01274     {
01275         pRender->SetLineColour(COLOUR_UNSELECTEDBLOB);
01276         pRender->SetFillColour(COLOUR_UNSELECTEDBLOB);
01277     }
01278                         
01279     // Draw a blob at the centre point
01280     DocRect BlobSize;
01281     BlobManager* pBlobMgr = GetApplication()->GetBlobManager();
01282     if (pBlobMgr != NULL)
01283     {
01284         DocRect BoundingRect = ((NodeRenderableBounded*)pNode)->GetBoundingRect();
01285         DocCoord Point = BoundingRect.Centre();
01286         pBlobMgr->GetBlobRect(Point, &BlobSize);
01287         
01288         if (pNode->IsABaseTextClass())
01289         {
01290             pRender->DrawBlob(Point, BT_UNSELECTED);
01291         }
01292         else
01293         {
01294             pRender->DrawLine(DocCoord(BlobSize.hi.x, BlobSize.hi.y), DocCoord(BlobSize.lo.x, BlobSize.lo.y));
01295             pRender->DrawLine(DocCoord(BlobSize.lo.x, BlobSize.hi.y), DocCoord(BlobSize.hi.x, BlobSize.lo.y));
01296             pRender->DrawPixel(DocCoord(BlobSize.hi.x, BlobSize.lo.y));
01297             pRender->DrawPixel(DocCoord(BlobSize.lo.x, BlobSize.lo.y));
01298         }
01299         return TRUE;
01300     }
01301 
01302     ERROR3("Couldn't get BlobManager");
01303     return FALSE;
01304 }
01305 
01306 
01307 
01308 
01309 /********************************************************************************************
01310 
01311 >   static void BlendTool::DisplayStatusBarHelp(UINT32 StatusID)
01312 
01313     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
01314     Created:    3/10/94
01315     Inputs:     StatusID = ID of status help string
01316     Outputs:    -
01317     Returns:    -
01318     Purpose:    Displays the given status help string in the status bar
01319     SeeAlso:    -
01320 
01321 ********************************************************************************************/
01322 
01323 void BlendTool::DisplayStatusBarHelp(UINT32 StatusID)
01324 {
01325     String_256 StatusMsg("");
01326     StatusMsg.Load(StatusID);
01327     GetApplication()->UpdateStatusBarText(&StatusMsg);
01328 }
01329 
01330 /********************************************************************************************
01331 
01332 >   static BOOL BlendTool::IsCurrent()
01333 
01334     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
01335     Created:    4/11/94
01336     Inputs:     -
01337     Outputs:    -
01338     Returns:    TRUE if the blend tool is the current tool
01339     Purpose:    Is the blend tool the current one? Call this to find out.
01340     SeeAlso:    -
01341 
01342 ********************************************************************************************/
01343 
01344 BOOL BlendTool::IsCurrent()
01345 {
01346     return (Tool::GetCurrentID() == TOOLID_BLEND);
01347 }
01348 
01349 
01350 
01351 /********************************************************************************************
01352 
01353 >   BOOL BlendTool::SelectedBlendIsOnCurve()
01354 
01355     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
01356     Created:    13/9/99
01357     Inputs:     -
01358     Outputs:    -
01359     Returns:    TRUE if there is a blend in the selection that is on a curve
01360     Purpose:    To find out if any selected blends are on a curve
01361     SeeAlso:    Used in BlendTool::SelectChange to determine which blobs to render
01362 
01363 ********************************************************************************************/
01364 
01365 BOOL BlendTool::SelectedBlendIsOnCurve()
01366 {
01367     List BlendList;
01368 
01369     BOOL ok = BevelTools::BuildListOfSelectedNodes(&BlendList, CC_RUNTIME_CLASS(NodeBlend), FALSE);
01370     if (ok)
01371     {
01372         NodeListItem* pListItem = (NodeListItem*)BlendList.GetHead();
01373         while (pListItem != NULL)
01374         {
01375             NodeBlend* pNodeBlend = (NodeBlend*)pListItem->pNode;
01376 //          Node* pNode = NULL;
01377             if (pNodeBlend->IsOnACurve())
01378             {
01379                 BlendList.DeleteAll();
01380                 return TRUE;
01381             }
01382 
01383             pListItem = (NodeListItem*)BlendList.GetNext(pListItem);
01384         }
01385     }
01386     BlendList.DeleteAll();
01387     return FALSE;
01388 }
01389 
01390 
01391 
01392 //----------------------------------------------
01393 //----------------------------------------------
01394 //----------------------------------------------
01395 //----------------------------------------------
01396 
01397 /********************************************************************************************
01398 
01399 >   MsgResult BlendInfoBarOp::Message(Msg* Message) 
01400 
01401     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
01402     Created:    3/10/94  changed 13/9/99 Diccon Yamanaka
01403     Inputs:     Message = The message to handle
01404     Outputs:    -
01405     Returns:    -
01406     Purpose:    Blend info bar dialog message handler
01407     Errors:     -
01408     SeeAlso:    -
01409 
01410 ********************************************************************************************/
01411 
01412 MsgResult BlendInfoBarOp::Message(Msg* Message) 
01413 {
01414     if (IS_OUR_DIALOG_MSG(Message))
01415     {
01416         DialogMsg* Msg = (DialogMsg*)Message;
01417 //      BOOL SetProfile = FALSE;   // these must be declared here even though they are only used in WM_CREATE
01418 //      INT32 ProfileIndex = 1;
01419         switch (Msg->DlgMsg)
01420         {   
01421             
01422 
01423             case DIM_CANCEL:
01424                 // Check if the message is a CANCEL
01425                 Close(); // Close the dialog 
01426                 break;
01427 
01428             case DIM_CREATE:
01429             {
01430 
01431                 // code added Diccon Yamanaka 9/99
01432                 // init profile gadgets
01433 //              m_BiasGainGadgetPosition.LinkControlButton ( this, _R(IDC_BLENDOBJECTBIASGAIN), _R(IDBBL_BLENDOBJECTBIASGAIN), _R(IDS_BLENDOBJECTBIASGAIN)  );
01434 //              m_BiasGainGadgetAttribute.LinkControlButton ( this, _R(IDC_BLENDATTRBIASGAIN),  _R(IDBBL_BLENDATTRBIASGAIN),   _R(IDS_BLENDATTRBIASGAIN)    );
01435                 m_BiasGainGadgetPosition.Init(this, _R(IDC_BLENDOBJECTBIASGAIN), _R(IDBBL_BLENDOBJECTBIASGAIN), _R(IDS_BLENDOBJECTBIASGAIN));
01436                 m_BiasGainGadgetAttribute.Init(this, _R(IDC_BLENDATTRBIASGAIN), _R(IDBBL_BLENDATTRBIASGAIN), _R(IDS_BLENDATTRBIASGAIN));
01437                 m_BiasGainGadgetAttribute.ToggleFillProfile ();
01438                 
01439                 SetGadgetHelp(_R(IDC_BTN_BLENDSTEPS), _R(IDBBL_BLENDSTEPSEDIT), _R(IDS_BLENDSTEPSEDIT));
01440 
01441                 // these two buttons need different bitmaps for their selected and 
01442                 // unselected states, set them here.
01443                 SetBitmapButtonIndexes(_R(IDC_ADDREMOVEBLENDPATH), 79, 80);
01444                 SetBitmapButtonIndexes(_R(IDC_TANGENTIAL), 81, 84);
01445                 
01446                 // if we were editing a blend on a path then make the blend selected
01447                 CheckSelectionAndSet();  
01448 
01449                 // solve the sticky button problem
01450                 if (GetBlendOnCurve() != NULL)
01451                     m_BlendedOnCurve = TRUE;
01452                 else
01453                     m_BlendedOnCurve = FALSE;
01454                 
01455                 // ensure we have the correct edit button depressed 
01456                 NodeBlend* pNodeBlend = GetNodeBlend();
01457                 if (pNodeBlend != NULL)
01458                 {
01459                     if (pNodeBlend->GetEditState() == EDIT_STEPS)
01460                     {
01461                         m_EditBlendSteps = TRUE;
01462                         SetLongGadgetValue(_R(IDC_BTN_BLENDDISTANCE), FALSE);
01463                     }
01464                     else
01465                     {
01466                         m_EditBlendSteps = FALSE;
01467                         SetLongGadgetValue(_R(IDC_BTN_BLENDDISTANCE), FALSE);
01468                     }
01469                 }
01470 
01471                 UpdateInfoBarState();
01472             }
01473             break;
01474 
01475             case DIM_LFT_BN_CLICKED:
01476             {
01477                 if (FALSE) {}
01478                 else if (Msg->GadgetID == _R(IDC_ADDREMOVEBLENDPATH))
01479                 {
01480                     // DY 13/9/99 if button is down then pop it up 
01481                     BOOL OnPath = (GetBlendOnCurve() != NULL);
01482                     m_BlendedOnCurve = OnPath;
01483                     ChangeBitmapButtonState(_R(IDC_ADDREMOVEBLENDPATH), &m_BlendedOnCurve);
01484 
01485                     OpDescriptor* pOpDesc = NULL;
01486 
01487                     if (!m_BlendedOnCurve)
01488                         pOpDesc = OpDescriptor::FindOpDescriptor(OPTOKEN_DETACHBLENDPATH);
01489                     else
01490                         pOpDesc = OpDescriptor::FindOpDescriptor(OPTOKEN_ADDBLENDPATH);
01491                 
01492                     if (pOpDesc != NULL) 
01493                         pOpDesc->Invoke();          
01494                 }
01495                 else if (Msg->GadgetID ==_R(IDC_REMOVEBLEND))
01496                 {
01497                     OpDescriptor* pOpDesc = OpDescriptor::FindOpDescriptor(OPTOKEN_REMOVEBLEND);
01498                     if (pOpDesc != NULL) 
01499                         pOpDesc->Invoke();
01500                 
01501                     m_BlendedOnCurve = FALSE;
01502 
01503                 }
01504                 else if (Msg->GadgetID == _R(IDC_BLENDOBJECTBIASGAIN))
01505                     HandleProfileButtonClick (m_BiasGainGadgetPosition, _R(IDC_BLENDOBJECTBIASGAIN));
01506                 else if (Msg->GadgetID == _R(IDC_BLENDATTRBIASGAIN))
01507                     HandleProfileButtonClick (m_BiasGainGadgetAttribute, _R(IDC_BLENDATTRBIASGAIN));
01508                 else if (Msg->GadgetID == _R(IDC_BTN_ONETOONE))
01509                 {
01510                     // DY 13/9/99 if button is down then pop it up 
01511                     ChangeBitmapButtonState(_R(IDC_BTN_ONETOONE), &m_OneToOne); 
01512 
01513                     OpDescriptor* pOpDesc = OpDescriptor::FindOpDescriptor(OPTOKEN_CHANGEBLEND);
01514                     if (pOpDesc != NULL)
01515                     {
01516                         ChangeBlendOpParam Param;
01517                         Param.ChangeType  = CHANGEBLEND_ONETOONE;
01518                         Param.NewOneToOne = GetBoolGadgetSelected(_R(IDC_BTN_ONETOONE));
01519                         pOpDesc->Invoke(&Param);
01520                     }
01521                 }
01522                 else if (Msg->GadgetID == _R(IDC_BTN_BLENDANTIALIAS))
01523                 {
01524                     // DY 13/9/99 if button is down then pop it up 
01525                     ChangeBitmapButtonState(_R(IDC_BTN_BLENDANTIALIAS), &m_BlendAntiAlias);
01526                         
01527                     OpDescriptor* pOpDesc = OpDescriptor::FindOpDescriptor(OPTOKEN_CHANGEBLEND);
01528                     if (pOpDesc != NULL)
01529                     {
01530                         ChangeBlendOpParam Param;
01531                         Param.ChangeType   = CHANGEBLEND_ANTIALIAS;
01532                         Param.NewAntiAlias = GetBoolGadgetSelected(_R(IDC_BTN_BLENDANTIALIAS));
01533                         pOpDesc->Invoke(&Param);
01534                     }
01535                 }
01536                 else if (Msg->GadgetID == _R(IDC_TANGENTIAL))
01537                 {
01538                     // DY 13/9/99 if button is down then pop it up 
01539                     ChangeBitmapButtonState(_R(IDC_TANGENTIAL), &m_Tangential);             
01540                             
01541                     OpDescriptor* pOpDesc = OpDescriptor::FindOpDescriptor(OPTOKEN_CHANGEBLEND);
01542                     if (pOpDesc != NULL)
01543                     {
01544                         ChangeBlendOpParam Param;
01545                         Param.ChangeType  = CHANGEBLEND_TANGENTIAL;
01546                         Param.NewTangential = GetBoolGadgetSelected(_R(IDC_TANGENTIAL));
01547                         pOpDesc->Invoke(&Param);
01548                     }
01549                 }
01550                 // DY 13/9/99 buttons to select whether blend steps or blend distance
01551                 // is to be edited in the edit box
01552                 else if (Msg->GadgetID == _R(IDC_BTN_BLENDDISTANCE))
01553                 {
01554                     // set the steps control    
01555                     SetLongGadgetValue(_R(IDC_BTN_BLENDSTEPS), FALSE);
01556                     m_EditBlendSteps = FALSE;
01557                     SetBlendEditState(EDIT_DISTANCE);  // let the blend know we are editing distance
01558                     UpdateInfoBarState();      // tell the infobar something has changed
01559                     
01560                 }
01561                 else if (Msg->GadgetID == _R(IDC_BTN_BLENDSTEPS))
01562                 {   
01563                     // set the distance control
01564                     SetLongGadgetValue(_R(IDC_BTN_BLENDDISTANCE), FALSE);
01565                     m_EditBlendSteps = TRUE;
01566                     SetBlendEditState(EDIT_STEPS);   // let the blend know we are editing state
01567                     UpdateInfoBarState();                    // update toolbar
01568                 }
01569 
01570             }
01571             break;
01572 
01573             case DIM_SELECTION_CHANGED:
01574             {
01575                 if (FALSE) {}
01576                 else if (Msg->GadgetID == _R(IDC_BLENDSTEPS))
01577                 {
01578                     BOOL Valid = FALSE;
01579                     INT32 NumSteps = 0;
01580                     
01581                     // either change the number of blend steps or
01582                     // the distance between steps
01583                     if (m_EditBlendSteps)
01584                     {
01585                         Valid = GetNumSteps(1, 999, &NumSteps);
01586                         if (Valid)
01587                         {
01588                             OpDescriptor* pOpDesc = OpDescriptor::FindOpDescriptor(OPTOKEN_CHANGEBLENDSTEPS);
01589                             if (pOpDesc != NULL)
01590                             {
01591                                 OpParam Param(NumSteps,0);
01592                                 pOpDesc->Invoke(&Param);
01593                             }
01594                         }
01595                         UpdateInfoBarState();
01596                     }
01597                     else
01598                     {
01599                         double Distance = 0.0;
01600                         Valid = GetDistanceEntered( &Distance);
01601                         if (Valid)
01602                         {
01603                             
01604                             Valid = IsStepDistanceValid(1, 999, Distance);
01605                             if (Valid)
01606                             {
01607                                 OpDescriptor* pOpDesc = OpDescriptor::FindOpDescriptor(OPTOKEN_CHANGEBLENDDISTANCE);
01608                                 if (pOpDesc != NULL)
01609                                 {
01610                                     OpParam Param((INT32)Distance, 0);
01611                                     pOpDesc->Invoke(&Param);
01612                                 }
01613                             }
01614                         }
01615                         UpdateInfoBarState();
01616                     }
01617                     
01618                 }
01619                 else if (Msg->GadgetID == _R(IDC_EFFECT))
01620                 {
01621                     WORD Index;
01622                     GetValueIndex(_R(IDC_EFFECT),&Index); 
01623                     ColourBlendType ColBlendType = (ColourBlendType)Index;
01624 
01625                     if (ColBlendType <= COLOURBLEND_ALTRAINBOW)
01626                     {
01627                         OpDescriptor* pOpDesc = OpDescriptor::FindOpDescriptor(OPTOKEN_CHANGEBLEND);
01628                         if (pOpDesc != NULL)
01629                         {
01630                             ChangeBlendOpParam Param;
01631                             Param.ChangeType      = CHANGEBLEND_COLOURBLENDTYPE;
01632                             Param.NewColBlendType = ColBlendType;
01633                             pOpDesc->Invoke(&Param);
01634                         }
01635                     }
01636                 }
01637             }
01638             break;
01639 
01640             default:
01641                 break;
01642         }
01643 
01644 
01645         if (( Msg->GadgetID == _R(IDC_BLENDOBJECTBIASGAIN)) || (Msg->GadgetID == _R(IDC_BLENDATTRBIASGAIN)))
01646         {  
01647             ProfileSelectionChange( Msg, Msg->GadgetID );
01648         }
01649 
01650     }
01651     // do we have a selection change message? 
01652     if (MESSAGE_IS_A(Message, SelChangingMsg))
01653     {
01654         UpdateInfoBarState();
01655         
01656         if (this != NULL && this->HasWindow())
01657         {
01658             if (BlendTool::IsCurrent())
01659             {
01660                 HandleProfileSelChangingMsg (m_BiasGainGadgetPosition, _R(IDC_BLENDOBJECTBIASGAIN));
01661                 HandleProfileSelChangingMsg (m_BiasGainGadgetAttribute, _R(IDC_BLENDATTRBIASGAIN));
01662             }
01663         }
01664     }
01665 
01666     // Pass the message on to the immediate base class
01667     return (InformationBarOp::Message(Message));
01668 }    
01669         
01670 /********************************************************************************************
01671 
01672 >   void BlendInfoBarOp::ChangeProfile(CProfileBiasGain* Profile, CGadgetID GadgetID)
01673 
01674     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com> Snook
01675     Created:    17/9/99
01676     Purpose:    See InformationBarOp::ChangeProfile () for an explanation of this function.
01677     See Also:   InformationBarOp::ChangeProfile ()
01678 
01679 *********************************************************************************************/
01680 
01681 void BlendInfoBarOp::ChangeProfile(CProfileBiasGain* Profile, CGadgetID GadgetID)
01682 {
01683                 
01684     OpDescriptor* pOpDesc = OpDescriptor::FindOpDescriptor(OPTOKEN_CHANGEBLEND);
01685     if (pOpDesc != NULL)
01686     {
01687         BOOL FireOp = TRUE;
01688 
01689         // we only want to generate one bit of undo information - so decided whether
01690         // we have to fire the above op, or whether we just 'pump' the values into
01691         // our nodes (thereby nolonger generating infinite undo information) ....
01692 
01693         Operation* pLastOp = NULL;
01694 
01695         if (Profile->GetGeneratesInfiniteUndo ())   // only do if they didn't select a preset profile
01696         {
01697             pLastOp = Document::GetSelected()->GetOpHistory().FindLastOp();
01698         }
01699         
01700         ChangeBlendOpParam Param;
01701         
01702         if (GadgetID == _R(IDC_BLENDOBJECTBIASGAIN))
01703         {
01704             if (pLastOp)
01705             {
01706                 if (pLastOp->GetRuntimeClass() == CC_RUNTIME_CLASS(OpChangeBlend))
01707                 {
01708                     OpChangeBlend* pLastBlendOp = (OpChangeBlend*) pLastOp;
01709                     
01710                     if (pLastBlendOp->GetChangeType () == CHANGEBLEND_OBJECTPROFILE)
01711                     {
01712                         if (Profile->GetGeneratesInfiniteUndo ())
01713                         {
01714                             FireOp = FALSE;
01715                         }
01716                     }
01717                 }
01718             }
01719             
01720             if (FireOp == TRUE)
01721             {
01722                 Param.ChangeType  = CHANGEBLEND_OBJECTPROFILE;
01723                 Param.NewObjectProfile = *Profile;
01724             }
01725             else
01726             {   
01727                 // we don't need/want any undo information - so just change the value ....
01728 
01729                 ChangeBlendAction Action;
01730                 Action.ChangeObjectProfileWithNoUndo (*Profile);
01731             }
01732         }
01733         else if (GadgetID == _R(IDC_BLENDATTRBIASGAIN))
01734         {
01735             if (pLastOp)
01736             {
01737                 if (pLastOp->GetRuntimeClass() == CC_RUNTIME_CLASS(OpChangeBlend))
01738                 {
01739                     OpChangeBlend* pLastBlendOp = (OpChangeBlend*) pLastOp;
01740                     
01741                     if (pLastBlendOp->GetChangeType () == CHANGEBLEND_ATTRPROFILE)
01742                     {
01743                         FireOp = FALSE;
01744                     }
01745                 }
01746             }
01747             
01748             if (FireOp == TRUE)
01749             {
01750                 Param.ChangeType  = CHANGEBLEND_ATTRPROFILE;
01751                 Param.NewAttrProfile = *Profile;
01752             }
01753             else
01754             {   
01755                 // we don't need/want any undo information - so just change the value ....
01756             
01757                 ChangeBlendAction Action;
01758                 Action.ChangeAttributeProfileWithNoUndo (*Profile);
01759             }
01760         }
01761         else
01762             return;  // shome mishtake shurely, lets go
01763         
01764         if (FireOp == TRUE)
01765         {
01766             pOpDesc->Invoke(&Param);
01767         }
01768     }               
01769 }
01770 
01771 /********************************************************************************************
01772 
01773 >   void BlendInfoBarOp::ChangeProfileOnIdle(CProfileBiasGain* Profile, CGadgetID GadgetID)
01774 
01775     Author:     Chris_Snook (Xara Group Ltd) <camelotdev@xara.com>
01776     Created:    8/9/2000
01777     Purpose:    See InformationBarOp::ChangeProfile () for an explanation of this function.
01778     See Also:   InformationBarOp::ChangeProfile ()
01779 
01780 *********************************************************************************************/
01781 
01782 void BlendInfoBarOp::ChangeProfileOnIdle(CProfileBiasGain* Profile, CGadgetID GadgetID)
01783 {
01784     if (GadgetID == _R(IDC_BLENDOBJECTBIASGAIN))
01785     {   
01786         // we don't need/want any undo information - so just change the value ....
01787 
01788         ChangeBlendAction Action;
01789         Action.ChangeObjectProfileWithNoUndo (*Profile, TRUE);
01790     }
01791 
01792     // NOTE: not doing anything for the attribute profiles here, since it does not appear as
01793     // though we have to ....
01794 }
01795 
01796         
01797 /********************************************************************************************
01798 
01799 >   BOOL BlendInfoBarOp::GetNumSteps( UINT32 MinValue, UINT32 MaxValue, INT32* LNumSteps)
01800 
01801     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
01802     Created:    24/9/99
01803     Inputs:     pointer to INT32 to store the result, minimum and maximum valid values
01804     Outputs:    returns TRUE if value can be retrieved and is within the valid range.
01805                 This value will be assigned to LNumSteps, if a number cannot be retreived
01806                 or it is invalid then FALSE.
01807     Purpose:    to retrieve an INT32 value for the number of steps and 
01808                 check its validity. Strips out any chars following the numerical value
01809 
01810 *********************************************************************************************/
01811 
01812 BOOL BlendInfoBarOp::GetNumSteps( UINT32 MinValue, UINT32 MaxValue, INT32 *NumSteps)
01813 {
01814     BOOL Valid = TRUE;  // the return value
01815     // first get the string from the gadget
01816     String_256 StrNumSteps = GetStringGadgetValue(_R(IDC_BLENDSTEPS), &Valid);
01817     
01818     
01819     if (Valid)
01820     {   
01821         INT32 Position = 0;
01822         double DNumSteps = 0.0;
01823 
01824         // get the numerical value from the string
01825         Valid = Convert::ReadNumber(StrNumSteps, &Position, &DNumSteps);
01826 
01827         if (Valid)
01828         {
01829             // check to see if it is in bounds, if not send a warning
01830             if (DNumSteps < MinValue || DNumSteps > MaxValue)
01831             {
01832                 InformWarning(_R(IDS_BLENDSTEPS_INVALID));
01833                 Valid = FALSE;
01834             }
01835             else
01836             {
01837                 *NumSteps = (INT32)DNumSteps;  // success
01838                 return Valid;
01839             }
01840         }
01841     }
01842 
01843     // if we reach here we have failed
01844     NumSteps = 0;
01845     return Valid;                       
01846         
01847 }
01848         
01849 /********************************************************************************************
01850 
01851 >   BOOL BlendInfoBarOp::GetDistanceEntered( INT32* LNumSteps)
01852 
01853     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
01854     Created:    7/9/99
01855     Inputs:     -
01856     Outputs:    the distance entered in millipoints 
01857     returns:    TRUE if a distance value can be retrieved 
01858                 
01859     Purpose:    retrieve what the user entered in the edit field
01860 
01861 *********************************************************************************************/
01862 
01863 BOOL BlendInfoBarOp::GetDistanceEntered(double* Distance)
01864 
01865 {
01866 
01867     SelRange* pSel = GetApplication()->FindSelection();
01868     BOOL Valid = FALSE;
01869     if (pSel != NULL)
01870     {
01871         Node* pNode = pSel->FindFirst();  // only used to pass to the gadget function
01872         
01873         double UnitValue = 0.0;
01874         UnitType TheUnit;
01875         Valid = GetDoubleAndUnitGadgetValue(Distance, &UnitValue, &TheUnit, _R(IDC_BLENDSTEPS), pNode);
01876         if (!Valid)
01877             InformWarning(_R(IDS_INVALIDDISTANCE));
01878     }
01879     return Valid;
01880 }
01881 
01882         
01883 /********************************************************************************************
01884 
01885 >   BOOL BlendInfoBarOp::GetNumStepsFromDistance( UINT32 MinValue, UINT32 MaxValue, INT32* LNumSteps)
01886 
01887     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
01888     Created:    26/9/99
01889     Inputs:     pointer to INT32 to store the result, minimum and maximum valid values
01890     Outputs:    returns TRUE if value can be retrieved and is within the valid range.
01891                 This value will be assigned to NumSteps, if a number cannot be retreived
01892                 or it is invalid then FALSE.
01893     Purpose:    When the Distance between steps in a blend is edited we need to recalculate
01894                 the number of steps.  This function retrieves the distance value from the edit
01895                 box, converts to Mps and works out the number of steps needed to get closest
01896                 to that distance value.
01897                 If the specified distance leads to an invalid number of steps then the user
01898                 is warned.
01899 
01900 *********************************************************************************************/
01901 
01902 BOOL BlendInfoBarOp::GetNumStepsFromDistance(UINT32 MinValue, UINT32 MaxValue, INT32* NumSteps)
01903 {
01904     
01905     SelRange* pSel = GetApplication()->FindSelection();
01906     BOOL Valid = FALSE;
01907     if (pSel != NULL)
01908     {
01909         Node* pNode = pSel->FindFirst();  // only used to pass to the gadget function
01910         double MPValue = 0.0;
01911         double UnitValue = 0.0;
01912         UnitType TheUnit;
01913         Valid = GetDoubleAndUnitGadgetValue(&MPValue, &UnitValue, &TheUnit, _R(IDC_BLENDSTEPS), pNode);
01914     
01915         if (Valid)
01916         {
01917         
01918                 if (pNode->IS_KIND_OF(NodeBlend))
01919                 {
01920                     double Distance = 0.0;
01921                     Valid = ((NodeBlend*)pNode)->GetBlendDistance(FALSE, &Distance);
01922                     if (Valid)
01923                     {
01924                         UINT32 TempNumSteps = (UINT32)(Distance/MPValue);
01925                         if (TempNumSteps < MinValue)
01926                         {
01927                             InformWarning(_R(IDS_BLENDDISTANCE_TOOBIG));
01928                             Valid = FALSE;
01929                         }
01930                         else if (TempNumSteps > MaxValue)
01931                         {
01932                             InformWarning(_R(IDS_BLENDDISTANCE_TOOSMALL));
01933                             Valid = FALSE;
01934                         }
01935                         else
01936                         {
01937                             *NumSteps = TempNumSteps;
01938                         }
01939                     }
01940                     else
01941                         InformWarning(_R(IDS_BLENDDISTANCE_INVALID));
01942                 }
01943                 else
01944                     InformWarning(_R(IDS_BLENDDISTANCE_INVALID));
01945             }
01946         }
01947         return Valid;
01948 }
01949 
01950 /********************************************************************************************
01951 
01952 >   void BlendInfoBarOp::ApplyOneToOne(BOOL State)
01953 
01954     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
01955     Created:    1/12/94
01956     Inputs:     State = state of one to one flag (TRUE for set, FALSE for clear)
01957     Purpose:    This scans the selected blends setting the one-to-one flag state to "State"
01958 
01959 *********************************************************************************************/
01960 /*
01961 void BlendInfoBarOp::ApplyOneToOne(BOOL State)
01962 {
01963     DocView* pDocView = DocView::GetCurrent();          // Get ptr to current doc view
01964     Spread*  pSpread  = Document::GetSelectedSpread();  // Get ptr to selected spread
01965 
01966     SelRange* pSel = GetApplication()->FindSelection();
01967     if (pSel != NULL)
01968     {
01969         Node* pNode = pSel->FindFirst();
01970         while (pNode != NULL)
01971         {
01972             if (pNode->IS_KIND_OF(NodeBlend))
01973             {
01974                 NodeBlend* pNodeBlend = ((NodeBlend*)pNode);
01975 
01976                 pNodeBlend->SetOneToOne(State);
01977 
01978                 if (pDocView != NULL && pSpread != NULL)
01979                     pDocView->ForceRedraw(pSpread,pNodeBlend->GetBlobBoundingRect());
01980             }
01981 
01982             pNode = pSel->FindNext(pNode);
01983         }
01984     }
01985 }
01986 */
01987 /********************************************************************************************
01988 
01989 >   void BlendInfoBarOp::ApplyAntialias(BOOL State)
01990 
01991     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
01992     Created:    1/12/94
01993     Inputs:     State = state of anitalias flag (TRUE for set, FALSE for clear)
01994     Purpose:    This scans the selected blends setting the antialias flag state to "State"
01995 
01996 *********************************************************************************************/
01997 /*
01998 void BlendInfoBarOp::ApplyAntialias(BOOL State)
01999 {
02000     DocView* pDocView = DocView::GetCurrent();          // Get ptr to current doc view
02001     Spread*  pSpread  = Document::GetSelectedSpread();  // Get ptr to selected spread
02002 
02003     SelRange* pSel = GetApplication()->FindSelection();
02004     if (pSel != NULL)
02005     {
02006         Node* pNode = pSel->FindFirst();
02007         while (pNode != NULL)
02008         {
02009             if (pNode->IS_KIND_OF(NodeBlend))
02010             {
02011                 NodeBlend* pNodeBlend = ((NodeBlend*)pNode);
02012 
02013                 pNodeBlend->SetNotAntialiased(!State);
02014 
02015                 if (pDocView != NULL && pSpread != NULL)
02016                     pDocView->ForceRedraw(pSpread,pNodeBlend->GetBlobBoundingRect());
02017             }
02018 
02019             pNode = pSel->FindNext(pNode);
02020         }
02021     }
02022 }
02023 */
02024 
02025 
02026 /********************************************************************************************
02027 
02028 >   void BlendInfoBarOp::UpdateState()
02029 
02030     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
02031     Created:    4/11/94, changed 13/9/99 Diccon Yamanaka
02032     Purpose:    Overrides the empty UpdateState function provided by InformationBarOp
02033                 making a call to the function in DialogBarOp.
02034 
02035 *********************************************************************************************/
02036 
02037 void BlendInfoBarOp::UpdateState()
02038 {
02039     // NOTE:  all of the functionality of updatestate has been moved to 
02040     // UpdateInfoBarState.  This is because we were getting flickering whenver
02041     // you used a scroll bar.
02042 }
02043 
02044 /********************************************************************************************
02045 
02046 >   void BlendInfoBarOp::UpdateInfoBarState()
02047 
02048     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
02049     Created:    4/11/94, changed 13/9/99 Diccon Yamanaka
02050     Purpose:    Overrides the empty UpdateState function provided by InformationBarOp
02051                 making a call to the function in DialogBarOp.
02052 
02053 *********************************************************************************************/
02054 
02055 void BlendInfoBarOp::UpdateInfoBarState()
02056 {
02057     if(this== NULL)
02058         return;
02059     if(!this->HasWindow())
02060         return ;
02061 
02062     if (!BlendTool::IsCurrent()) return;
02063 
02064     DialogBarOp::UpdateState();     // This updates all controls that have OpDescs attached
02065 
02066     BOOL OneToOne = FALSE;
02067     BOOL Antialias = NodeBlend::GetDefaultAntialias();
02068     INT32 NumSteps = 0;
02069     UINT32 NumSelBlends = 0;
02070     ColourBlendType ColBlendType = COLOURBLEND_FADE;
02071 
02072     BOOL OnlyBlendsSelected         = TRUE;
02073     BOOL AllSelectedBlendsAreOnCurve= TRUE;
02074 //  BOOL AtLeastOneBlendIsOnCurve   = FALSE;
02075     BOOL Tangential                 = FALSE;
02076     BOOL EditSteps                  = TRUE;
02077     UINT32 NumBlendsOnCurve         = 0;
02078     BOOL NonLinearProfile           = FALSE;
02079     BOOL MultiStageBlendOnCurve     = FALSE;
02080     List BlendList;
02081 
02082     BOOL ok = BevelTools::BuildListOfSelectedNodes(&BlendList, CC_RUNTIME_CLASS(NodeBlend), FALSE);
02083     if (!ok)
02084     {
02085         OnlyBlendsSelected = FALSE;
02086         AllSelectedBlendsAreOnCurve = FALSE;
02087     }
02088     else
02089     {
02090         NodeListItem* pListItem = (NodeListItem*)BlendList.GetHead();
02091         
02092         while (pListItem != NULL)
02093         {
02094             NodeBlend* pNodeBlend = (NodeBlend*)pListItem->pNode;
02095             // exclude blends that are part of a group as we don't want to put 
02096             // them on a path
02097             if (!pNodeBlend->IsChildOfGroup())
02098             {
02099                 NumSelBlends++; 
02100 
02101                 if (pNodeBlend->GetEditState() == EDIT_DISTANCE)
02102                     EditSteps = FALSE;
02103                 
02104                 UINT32          ThisNumSteps    = pNodeBlend->GetNumBlendSteps();
02105                 ColourBlendType ThisColBlendType= pNodeBlend->GetColourBlendType();
02106 
02107                 if (NumSelBlends == 1)
02108                 {
02109                     NumSteps     = ThisNumSteps;
02110                     ColBlendType = ThisColBlendType;
02111                 }
02112                 else
02113                 {
02114                     if (NumSteps >= 0 && NumSteps != INT32(ThisNumSteps))
02115                         NumSteps = -1;
02116                     if (ColBlendType != ThisColBlendType)
02117                             ColBlendType = COLOURBLEND_NONE;
02118                 }
02119 
02120                 if (!OneToOne && pNodeBlend->IsOneToOne())
02121                     OneToOne = TRUE;
02122 
02123                 if (Antialias && pNodeBlend->IsNotAntialiased())
02124                     Antialias = FALSE;
02125 
02126                 if (!Tangential && pNodeBlend->IsTangential())
02127                     Tangential = TRUE;
02128 
02129                 if (pNodeBlend->GetNodeBlendPath(0) != NULL)
02130                     NumBlendsOnCurve++;
02131                 else
02132                     AllSelectedBlendsAreOnCurve = FALSE;    
02133 
02134                 if (pNodeBlend->NonLinearObjectProfile())
02135                     NonLinearProfile = TRUE;
02136                 
02137                 if (pNodeBlend->GetNumNodeBlendPaths() > 1)
02138                     MultiStageBlendOnCurve = TRUE;
02139             }
02140 
02141             pListItem = (NodeListItem*)BlendList.GetNext(pListItem);
02142             
02143         }
02144     }
02145     BlendList.DeleteAll();
02146 
02147     OnlyBlendsSelected = !NonBlendsSelected();
02148     
02149     EnableBlendSelectedGadgets(NumSelBlends > 0);
02150 
02151     // if we have a non-linear profile it makes no sense to enable distance editing so 
02152     // send a message to switch it off
02153     // also as we cannot currently deal with distance editing for blends with multiple 
02154     // curves then do the same thing
02155     // Likewise if we are not on a curve, and our operations are not allowed to move the end
02156     // objects then it makes no sense to allow this
02157     if (NonLinearProfile == TRUE || MultiStageBlendOnCurve == TRUE || NumBlendsOnCurve == 0)
02158     {
02159         EnableGadget(_R(IDC_BTN_BLENDDISTANCE), FALSE);
02160         // only send the message once
02161         if (m_EditBlendSteps == FALSE)
02162             BROADCAST_TO_CLASS( DialogMsg(NULL, DIM_LFT_BN_CLICKED, _R(IDC_BTN_BLENDSTEPS), 0, 0 ), DialogOp);
02163     }
02164     else
02165         EnableGadget(_R(IDC_BTN_BLENDDISTANCE), TRUE);
02166 
02167     EnableGadget(_R(IDC_BTN_BLENDSTEPS),    TRUE);
02168     EnableGadget(_R(IDC_ADDREMOVEBLENDPATH), !(NumSelBlends == 0 || (OnlyBlendsSelected && NumBlendsOnCurve==0)));
02169     EnableGadget(_R(IDC_TANGENTIAL),    (NumSelBlends > 0) && (NumBlendsOnCurve > 0));
02170 
02171     SetBoolGadgetSelected(_R(IDC_BTN_ONETOONE),OneToOne);
02172     SetBoolGadgetSelected(_R(IDC_BTN_BLENDANTIALIAS),Antialias);
02173     SetBoolGadgetSelected(_R(IDC_TANGENTIAL), Tangential); 
02174 
02175     LoadStringsIntoEffectCombo();
02176 
02177     if (m_EditBlendSteps != EditSteps)
02178     {
02179         if (EditSteps)
02180         {
02181             SetLongGadgetValue(_R(IDC_BTN_BLENDDISTANCE), FALSE);   
02182         }
02183         else
02184         {
02185             SetLongGadgetValue(_R(IDC_BTN_BLENDSTEPS), FALSE);
02186         }
02187     }
02188 
02189     if (NumSelBlends != GetApplication()->FindSelection()->Count())
02190     {
02191         EnableGadget (_R(IDC_BLENDOBJECTBIASGAIN), FALSE);
02192         EnableGadget (_R(IDC_BLENDATTRBIASGAIN), FALSE);
02193     }
02194 
02195     if (NumSelBlends == 0)
02196     {
02197         // make the effect combo and the blendsteps text blank
02198         String_64 Str;
02199         Str = _T("");
02200         SetStringGadgetValue(_R(IDC_BLENDSTEPS),Str);
02201         SetStringGadgetValue(_R(IDC_EFFECT),Str, FALSE, -1);
02202     }
02203     else
02204     {
02205         ShowEffectComboString(ColBlendType);
02206         // determine whether to display number of steps or distance
02207         // in the edit box and display appropriate value
02208         if (NumSelBlends == 1)
02209         {   
02210             if (m_EditBlendSteps)
02211                 SetBlendStepsEditText((INT32)NumSteps);
02212             else
02213                 SetBlendDistanceEditText((INT32)NumSteps);
02214         }
02215 
02216         else if (NumSelBlends > 1)
02217         {
02218             if (m_EditBlendSteps)
02219             {
02220                 if (AllBlendsHaveSameNumSteps())
02221                     SetBlendStepsEditText((INT32)NumSteps);
02222                 else
02223                     SetStringGadgetValue(_R(IDC_BLENDSTEPS),_R(IDS_MANY));
02224             }
02225             else 
02226             { 
02227                 if (AllBlendsHaveSameDistance())
02228                     SetBlendDistanceEditText((INT32)NumSteps);
02229                 else
02230                     SetStringGadgetValue(_R(IDC_BLENDSTEPS),_R(IDS_MANY));
02231             }
02232         }
02233     }
02234 
02235 
02236     // For some reason the add blend to curve button doesn't like
02237     // to stay depressed, so force it.
02238     if (NumSelBlends > 0)
02239     {
02240         if (NumBlendsOnCurve > 0)
02241         {
02242             SetLongGadgetValue(_R(IDC_ADDREMOVEBLENDPATH), TRUE);
02243             if (Tangential)
02244             {
02245                 SetLongGadgetValue(_R(IDC_TANGENTIAL), TRUE);
02246             }
02247         }
02248     
02249     }
02250 
02251     // depress either the edit steps or edit distance button and
02252     // change the bubble strings displayed in the edit box according
02253     // to which button is currently selected
02254     if (m_EditBlendSteps == TRUE)
02255     {
02256         SetLongGadgetValue(_R(IDC_BTN_BLENDSTEPS), TRUE);
02257         SetGadgetHelp(_R(IDC_BLENDSTEPS), _R(IDBBL_BLENDSTEPSEDITVALUE), _R(IDS_BLENDSTEPSEDITVALUE));
02258     }
02259     else
02260     {
02261         SetLongGadgetValue(_R(IDC_BTN_BLENDDISTANCE), TRUE);
02262         SetGadgetHelp(_R(IDC_BLENDSTEPS), _R(IDBBL_BLENDDISTANCEEDITVALUE), _R(IDS_BLENDDISTANCEEDITVALUE));
02263     }
02264 }
02265 
02266 /********************************************************************************************
02267 
02268 >   void BlendInfoBarOp::ShowEffectComboString(ColourBlendType Type)
02269     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
02270     Created:    13/9/99
02271     Inputs:     Type - determines which to show
02272     Outputs:    -
02273     Returns:    -
02274     Purpose:    Determines which string to show in the combo
02275 *********************************************************************************************/
02276 
02277 void BlendInfoBarOp::ShowEffectComboString(ColourBlendType Type)
02278 {
02279     String_64 Str;
02280     switch (Type)
02281     {
02282         case COLOURBLEND_FADE:          Str.Load(_R(IDS_FILLTOOL_FADE));        break;
02283         case COLOURBLEND_RAINBOW:       Str.Load(_R(IDS_FILLTOOL_RAINBOW));     break;
02284         case COLOURBLEND_ALTRAINBOW:    Str.Load(_R(IDS_FILLTOOL_ALTRAINBOW));  break;
02285         case COLOURBLEND_NONE:          Str.Load(_R(IDS_MANY));                 break;
02286 
02287         default:ERROR3("Unknown colour blend type"); Str.Load(_R(IDS_FILLTOOL_FADE)); break;
02288     }
02289     SetStringGadgetValue(_R(IDC_EFFECT),Str, FALSE, -1);
02290 }
02291 
02292 /********************************************************************************************
02293 
02294 >   void BlendInfoBarOp::LoadStringsIntoEffectCombo()
02295     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
02296     Created:    13/9/99
02297     Inputs:     -
02298     Outputs:    -
02299     Returns:    -
02300     Purpose:    Loads the correct strings into the combo box
02301 *********************************************************************************************/
02302 
02303 void BlendInfoBarOp::LoadStringsIntoEffectCombo()
02304 {
02305     String_64 Str;
02306     DeleteAllValues(_R(IDC_EFFECT));
02307     Str.Load(_R(IDS_FILLTOOL_FADE));
02308     SetStringGadgetValue(_R(IDC_EFFECT),Str,FALSE, FEMENU_FADE);
02309     Str.Load(_R(IDS_FILLTOOL_RAINBOW));
02310     SetStringGadgetValue(_R(IDC_EFFECT),Str,FALSE, FEMENU_RAINBOW);
02311     Str.Load(_R(IDS_FILLTOOL_ALTRAINBOW));
02312     SetStringGadgetValue(_R(IDC_EFFECT),Str,TRUE, FEMENU_ALTRAINBOW);
02313 
02314     SetComboListLength(_R(IDC_EFFECT));
02315 }
02316 
02317 /********************************************************************************************
02318 
02319 >   void BlendInfoBarOp::EnableBlendSelectedGadgets(BOOL Enable)
02320     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
02321     Created:    13/9/99
02322     Inputs:     Enable, TRUE or FALSE
02323     Outputs:    -
02324     Returns:    -
02325     Purpose:    All of these controls require at least one blend to be in the current selection
02326                 if they are to be enabled. 
02327 *********************************************************************************************/
02328 
02329 void BlendInfoBarOp::EnableBlendSelectedGadgets(BOOL Enable)
02330 {
02331     EnableGadget(_R(IDC_BLENDOBJECTBIASGAIN), Enable);
02332     EnableGadget(_R(IDC_BLENDATTRBIASGAIN), Enable);
02333     EnableGadget(_R(IDC_BLENDSTEPS),    Enable);
02334     EnableGadget(_R(IDC_BTN_ONETOONE),      Enable);
02335     EnableGadget(_R(IDC_BTN_BLENDANTIALIAS),Enable);
02336     EnableGadget(_R(IDC_EFFECT),        Enable);
02337 }
02338 
02339 /********************************************************************************************
02340 
02341 >   void BlendInfoBarOp::ChangeBitmapButtonState(CGadget GadgetID)
02342 
02343     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
02344     Created:    13/9/99
02345     Purpose:     utility used by BlendInfoBarOp::Message, if a button is depressed 
02346                 and clicked again then un-depress it  For some reason the bitmap button custom
02347                 control does not do this by itself..
02348 
02349 *********************************************************************************************/
02350 
02351 void BlendInfoBarOp::ChangeBitmapButtonState(CGadgetID GadgetID, BOOL* CurrentState)
02352 {
02353     /*BOOL test =*/  GetBoolGadgetSelected(GadgetID);
02354     if (*CurrentState == FALSE)
02355     {
02356         *CurrentState = TRUE;
02357     }
02358     else
02359     {
02360         *CurrentState = FALSE;
02361         SetLongGadgetValue(GadgetID, FALSE);
02362     }
02363     
02364 }
02365 
02366 /********************************************************************************************
02367 
02368 >   void BlendInfoBarOp::SetBlendStepsEditText(INT32 NumSteps)
02369 
02370     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
02371     Created:    25/9/99
02372     inputs :    number of steps in the currently selected blend
02373     Purpose:    converts the number of steps into a string and concatenates it with 
02374                 the string "steps" before writing it into the control
02375 *********************************************************************************************/
02376 
02377 void BlendInfoBarOp::SetBlendStepsEditText(INT32 NumSteps)
02378 {
02379         String_256 StepString(_R(IDS_BLENDSTEPS_POSTFIX));  // = " Steps";
02380         String_256 StrNumSteps = _T("");
02381         Convert::LongToString(NumSteps, &StrNumSteps);
02382         StrNumSteps += StepString;
02383 
02384         /*BOOL ok =*/ SetStringGadgetValue(_R(IDC_BLENDSTEPS), StrNumSteps);
02385         //  error code to go here
02386 }
02387 
02388 /********************************************************************************************
02389 
02390 >   void BlendInfoBarOp::SetBlendDistanceEditText(INT32 NumSteps, UINT32 OnCurve)
02391 
02392     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
02393     Created:    25/9/99
02394     inputs :    NumSteps - number of steps in the currently selected blend 
02395     Purpose:    works out the distance between each blend step in the selected blend
02396                 concatenates with the current units used in the spread and writes it
02397                 to the edit box
02398 *********************************************************************************************/
02399 
02400 void BlendInfoBarOp::SetBlendDistanceEditText(INT32 NumSteps)
02401 {   
02402     double Distance = 0.0;
02403     
02404     BOOL Valid = GetMeanBlendDistance(&Distance);
02405 
02406     if (!Valid)
02407         return;
02408 
02409     double DistanceBetweenSteps = Distance/NumSteps;
02410 
02411     // get the current spread 
02412     SelRange* pSel = GetApplication()->FindSelection();
02413     if (pSel == NULL)
02414         return;
02415     Spread* pCurrentSpread = pSel->FindFirst()->FindParentSpread();
02416 
02417     if (pCurrentSpread != NULL)
02418     {
02419         // get the unit type
02420         DimScale* pDimScale = DimScale::GetPtrDimScale((Node*)pCurrentSpread);
02421         if (pDimScale == NULL)
02422             return;
02423 
02424         String_256 Str;
02425         pDimScale->ConvertToUnits(DistanceBetweenSteps, &Str, TRUE, 4);
02426         
02427         SetStringGadgetValue(_R(IDC_BLENDSTEPS), Str);// set it
02428     }
02429     
02430     return;
02431 }
02432 
02433 /********************************************************************************************
02434 
02435 >   BOOL BlendInfoBarOp::GetMeanBlendDistance(double* Distance)
02436 
02437     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
02438     Created:    26/9/99
02439     inputs :    
02440     outputs:    double value of length of blend, zero if fails
02441     returns:    TRUE if successful, FALSE otherwise 
02442     Purpose:    To find the mean length of the currently selected blend.  That is to say if
02443                 this is a multistage blend then it returns the mean blend stage length.
02444 
02445 *********************************************************************************************/
02446 
02447 BOOL BlendInfoBarOp::GetMeanBlendDistance(double* Distance)
02448 {
02449     BOOL Valid = FALSE; // the return value
02450     *Distance = 0.0;
02451     NodeBlend* pNodeBlend = GetNodeBlend();
02452 
02453     if (pNodeBlend == NULL)
02454         return FALSE;
02455 
02456     UINT32 NumBlenders = pNodeBlend->GetNumBlenders();
02457     if (NumBlenders == 0)
02458     {
02459         ERROR3("This blend has zero blenders");
02460         return FALSE;
02461     }
02462     double TempDistance = 0.0;
02463 
02464     Valid = pNodeBlend->GetBlendDistance(FALSE, &TempDistance);
02465     if (Valid)
02466     {
02467         TempDistance = TempDistance / NumBlenders;
02468         *Distance = TempDistance;
02469     }
02470 
02471     return Valid;
02472 }
02473 
02474 /********************************************************************************************
02475 
02476 >   BOOL BlendInfoBarOp::AllBlendsHaveSameNumSteps()
02477 
02478     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
02479     Created:    26/9/99
02480     inputs :    -
02481     outputs:    
02482     returns:    TRUE if all blends in selection have the same number of steps,
02483                 FALSE otherwise
02484     Purpose:    find out if all selected blends have the same number of steps
02485 
02486 *********************************************************************************************/
02487 
02488 BOOL BlendInfoBarOp::AllBlendsHaveSameNumSteps()
02489 {
02490     UINT32 NumSteps = 0;
02491     BOOL FoundFirst = FALSE;
02492     BOOL SameNumber = FALSE;
02493     SelRange* pSel = GetApplication()->FindSelection();
02494     if (pSel != NULL)
02495     {
02496         Node* pNode = pSel->FindFirst();
02497         while (pNode != NULL)
02498         {
02499             if (pNode->IS_KIND_OF(NodeBlend))   
02500                 if (!FoundFirst)
02501                 {
02502                     NumSteps = ((NodeBlend*)pNode)->GetNumBlendSteps();
02503                     FoundFirst = TRUE;
02504                     SameNumber = TRUE;
02505                 }
02506                 else
02507                 {
02508                     if (((NodeBlend*)pNode)->GetNumBlendSteps() != NumSteps)
02509                         return FALSE;
02510                 }
02511                 pNode = pSel->FindNext(pNode);
02512         }
02513     }
02514     return SameNumber;
02515 }
02516 
02517 /********************************************************************************************
02518 
02519 >   BOOL BlendInfoBarOp::AllBlendsHaveSameDistance()
02520 
02521     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
02522     Created:    26/9/99
02523     inputs :    -
02524     outputs:    
02525     returns:    TRUE if all blends in selection have the same distance between steps
02526                 FALSE otherwise
02527     Purpose:    find out if all selected blends have the same distance between steps
02528 
02529 *********************************************************************************************/
02530 
02531 BOOL BlendInfoBarOp::AllBlendsHaveSameDistance()
02532 {
02533     double FirstDistance = 0.0;
02534     double FirstStepDistance = 0.0;
02535     
02536     BOOL FoundFirst = FALSE;
02537     BOOL SameNumber = FALSE;
02538     SelRange* pSel = GetApplication()->FindSelection();
02539     if (pSel != NULL)
02540     {
02541         Node* pNode = pSel->FindFirst();
02542         while (pNode != NULL)
02543         {
02544             if (pNode->IS_KIND_OF(NodeBlend))   
02545                 if (!FoundFirst)
02546                 {
02547                     if (!((NodeBlend*)pNode)->GetBlendDistance(FALSE, &FirstDistance))
02548                         return FALSE;
02549                     UINT32 NumSteps = ((NodeBlend*)pNode)->GetNumBlendSteps();
02550                     FirstStepDistance = FirstDistance/NumSteps;
02551                     FoundFirst = TRUE;
02552                     SameNumber = TRUE;
02553                 }
02554                 else
02555                 {
02556                     double NextDistance;
02557                     if (!((NodeBlend*)pNode)->GetBlendDistance(FALSE, &NextDistance))
02558                         return FALSE;
02559                     
02560                     UINT32 NumSteps = ((NodeBlend*)pNode)->GetNumBlendSteps();
02561                     double ThisStepDistance = FirstDistance/NumSteps;
02562 
02563                     if (ThisStepDistance != FirstStepDistance)
02564                         return FALSE;
02565                 }
02566                 pNode = pSel->FindNext(pNode);
02567         }
02568     }
02569     return SameNumber;
02570 }
02571 
02572 /********************************************************************************************
02573 
02574 >   NodeBlender* BlendInfoBarOp::GetBlender()
02575 
02576     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
02577     Created:    26/9/99
02578     inputs :    -
02579     outputs:    pointer to the first nodeblender node in the current selection
02580                 or NULL if there isn't one
02581     Purpose:    To find the first nodeblender node in a given blend
02582 
02583 *********************************************************************************************/
02584 
02585 NodeBlender* BlendInfoBarOp::GetBlender()
02586 {
02587     // first get the selection
02588     SelRange Sel(*( GetApplication()->FindSelection()));
02589     NodeBlend* pNodeBlend = NULL;
02590     
02591     // get the node blend
02592     if (!Sel.IsEmpty())
02593     {
02594         Node* pNode = Sel.FindFirst();
02595         while (pNode != NULL)
02596         {
02597             if (pNode->IS_KIND_OF(NodeBlend))
02598             {
02599                  pNodeBlend = (NodeBlend*)pNode;
02600                 break;
02601             }
02602             pNode = Sel.FindNext(pNode);
02603         }
02604     }
02605 
02606     if (pNodeBlend == NULL)  // if no blend node then there won't be a blender
02607         return NULL;
02608 
02609     //  now get the blender
02610     NodeBlender* pBlender = NULL;
02611 
02612     Node* pNode = pNodeBlend->FindFirstChild();
02613     while (pNode != NULL)
02614     {
02615         if (pNode->IS_KIND_OF(NodeBlender))
02616         {
02617             pBlender = (NodeBlender*)pNode;
02618             break;
02619         }
02620         pNode = pNode->FindNext();
02621     }
02622     
02623     return pBlender;
02624 }
02625 
02626 /********************************************************************************************
02627 
02628 >   NodeBlend* BlendInfoBarOp::GetNodeBlend()
02629 
02630     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
02631     Created:    9/9/99
02632     inputs :    -
02633     outputs:    pointer to the first nodeblend node in the current selection
02634                 or NULL if there isn't one
02635     Purpose:    To find the first nodeblend node in the selection
02636 
02637 *********************************************************************************************/
02638 
02639 NodeBlend* BlendInfoBarOp::GetNodeBlend()
02640 {
02641     SelRange* pSel = GetApplication()->FindSelection();
02642     NodeBlend* pNodeBlend = NULL;
02643     
02644     if (pSel != NULL)
02645     {
02646         Node* pNode = pSel->FindFirst();
02647 
02648         while (pNode != NULL)
02649         {
02650             if (pNode->IS_KIND_OF(NodeBlend))
02651             {
02652                 pNodeBlend = (NodeBlend*)pNode;
02653                 return pNodeBlend;
02654             }
02655             pNode = pSel->FindNext(pNode);
02656         }
02657     }
02658     return pNodeBlend;
02659 }
02660 
02661 /********************************************************************************************
02662 
02663 >   NodeBlend* BlendInfoBarOp::GetBlendOnCurve()
02664 
02665     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
02666     Created:    26/9/99
02667     inputs :    -
02668     outputs:    
02669     returns:    pointer to the first nodeblend in the selection if successful, 
02670                 NULL otherwise
02671     Purpose:    To determine if the current selection is a blend on a curve
02672 
02673 *********************************************************************************************/
02674 
02675 NodeBlend* BlendInfoBarOp::GetBlendOnCurve()
02676 {
02677     // first get the selection
02678     SelRange Sel(*( GetApplication()->FindSelection()));
02679     NodeBlend* pNodeBlend = NULL;
02680     
02681     // get the node blend
02682     if (!Sel.IsEmpty())
02683     {
02684         Node* pNode = Sel.FindFirst();
02685         while (pNode != NULL)
02686         {
02687             if (pNode->IS_KIND_OF(NodeBlend))
02688             {
02689                  pNodeBlend = (NodeBlend*)pNode;
02690                 break;
02691             }
02692             pNode = Sel.FindNext(pNode);
02693         }
02694     }
02695 
02696     if (pNodeBlend == NULL)  // if no blend node then there won't be a blender
02697         return NULL;
02698     
02699     if (pNodeBlend->IsOnACurve())
02700         return pNodeBlend;
02701     else 
02702         return NULL;
02703 
02704 }
02705 
02706 /********************************************************************************************
02707 
02708 >   void BlendInfoBarOp::SetBlendEditState(EditState State)
02709 
02710     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
02711     Created:    26/9/99
02712     inputs :    edit state to set (EDIT_STEPS or EDIT_DISTANCE)
02713     outputs:    -
02714     Purpose:    to set the edit state of all selected blends to the given value
02715 
02716 *********************************************************************************************/
02717 
02718 void BlendInfoBarOp::SetBlendEditState(EditState State)
02719 {
02720     // first get the selection
02721     SelRange Sel(*( GetApplication()->FindSelection()));
02722 //  NodeBlend* pNodeBlend = NULL;
02723     
02724     // get the node blend
02725     if (!Sel.IsEmpty())
02726     {
02727         Node* pNode = Sel.FindFirst();
02728         while (pNode != NULL)
02729         {
02730             if (pNode->IS_KIND_OF(NodeBlend))
02731             {
02732                 ((NodeBlend*)pNode)->SetEditState(State);
02733             }
02734             pNode = Sel.FindNext(pNode);
02735         }
02736     }
02737 }
02738 
02739 /********************************************************************************************
02740 
02741 >   void BlendInfoBarOp::IsBlendDistanceValid(UINT32 MinValue, UINT32 Maxvalue, double Distance)
02742 
02743     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
02744     Created:    26/9/99
02745     inputs :    the step distance to set, values for the minimum and maximum valid number of 
02746                 steps that are allowed
02747     outputs:    -
02748     returns:    TRUE if this distance can be set, FALSE if the distance will cause an invalid number of
02749                 blend steps 
02750     Purpose:    to see if this step distance will give us a valid number of blend steps
02751 
02752 *********************************************************************************************/
02753 
02754 BOOL BlendInfoBarOp::IsStepDistanceValid(UINT32 MinValue, UINT32 MaxValue, double Distance)
02755 {
02756     // first get the selection
02757     SelRange Sel(*( GetApplication()->FindSelection()));
02758 //  NodeBlend* pNodeBlend = NULL;
02759     
02760     if (Distance <= 0)
02761     {
02762         InformWarning(_R(IDS_BLENDDISTANCE_TOOSMALL));
02763         return FALSE;
02764     }
02765     // get the node blend
02766     if (!Sel.IsEmpty())
02767     {
02768         Node* pNode = Sel.FindFirst();
02769         while (pNode != NULL)
02770         {
02771             if (pNode->IS_KIND_OF(NodeBlend))
02772             {
02773                 // check to make sure this distance will not cause
02774                 // an illegal number of steps
02775                 UINT32 NumSteps = 0;
02776                 BOOL Valid = ((NodeBlend*)pNode)->GetNumStepsFromDistance(Distance, &NumSteps);
02777                 if (Valid)
02778                 {
02779                     if (NumSteps < MinValue)
02780                     {
02781                         InformWarning(_R(IDS_BLENDDISTANCE_TOOBIG));
02782                         return FALSE;
02783                     }
02784                     else if (NumSteps > MaxValue)
02785                     {
02786                         InformWarning(_R(IDS_BLENDDISTANCE_TOOSMALL));
02787                         return FALSE;
02788                     }
02789                 }
02790                 else
02791                     return FALSE;  
02792             }
02793             pNode = Sel.FindNext(pNode);
02794         }
02795     }
02796     return TRUE;
02797 }
02798 
02799 /********************************************************************************************
02800 
02801 >   virtual CProfileBiasGain* GetProfileFromSelection(CGadgetID GadgetID, INT32* Index, BOOL* bAllSameType)
02802 
02803     Author:     Chris_Snook (Xara Group Ltd) <camelotdev@xara.com>
02804     Created:    19/1/2000
02805     Inputs:     The GadgetID of the CBiasGainGadget that we are dealing with.
02806     Outputs:    bMany - returned as TRUE if we have MANY profiles selected.
02807                 bAllSameType - returned as TRUE if objects within selection are all of the
02808                 same type.
02809     returns     Ptr to common CProfileBiasGain, or NULL if there is NOT one.
02810     Purpose:    See InformationBarOp::GetProfileFromSelection () for a description of this
02811                 function.
02812 
02813 *********************************************************************************************/
02814 
02815 CProfileBiasGain* BlendInfoBarOp::GetProfileFromSelection(CGadgetID GadgetID, BOOL* bMany, BOOL* bAllSameType)
02816 {
02817     BOOL ok = ((GadgetID == _R(IDC_BLENDOBJECTBIASGAIN)) || (GadgetID == _R(IDC_BLENDATTRBIASGAIN)));
02818 
02819     ERROR2IF(ok==FALSE, FALSE, "Invalid gadgetID passed");
02820 
02821     //BOOL ok = BevelTools::BuildListOfSelectedNodes(&BlendList, CC_RUNTIME_CLASS(NodeBlend), FALSE);
02822     // NOTE:  we could make use of the above function (and base the code around that); BUT since
02823     // this code works fine - why bother changing it?  If you wan't to change it, then take a look
02824     // at SoftShadowInfoBarOp::GetProfileFromSelection () ....
02825 
02826     SelRange Sel(*( GetApplication()->FindSelection()));
02827     
02828     NodeBlend* pFirstNodeBlend = NULL;
02829     CProfileBiasGain* pFirstProfile = NULL;
02830     
02831     // get the node blend
02832     if (!Sel.IsEmpty())
02833     {
02834         Node* pNode = Sel.FindFirst();
02835         while (pNode != NULL)                       // code for comment 1) ....
02836         {
02837             if (pNode->IS_KIND_OF(NodeBlend))       // part of condition 3 check
02838             {
02839                 if (pFirstNodeBlend == NULL)        // code for comment 2) ....
02840                 {
02841                     pFirstNodeBlend = (NodeBlend*) pNode;
02842 
02843                     if (GadgetID == _R(IDC_BLENDOBJECTBIASGAIN))
02844                     {
02845                         pFirstProfile = pFirstNodeBlend->GetObjectProfile();
02846                     }
02847                     else if (GadgetID == _R(IDC_BLENDATTRBIASGAIN))
02848                     {
02849                         pFirstProfile = pFirstNodeBlend->GetAttrProfile();
02850                     }
02851                 }
02852                 else
02853                 {
02854                     // code for comment 3) ....
02855                     // condition 1/2 check ....
02856                     // taking into account multiple profile controls ....
02857 
02858                     if (GadgetID == _R(IDC_BLENDOBJECTBIASGAIN))
02859                     {
02860                         if (*pFirstProfile == *((NodeBlend*) pNode)->GetObjectProfile())
02861                         {
02862                             // all ok
02863                         }
02864                         else
02865                         {
02866                             *bMany = TRUE;
02867                         }
02868                     }
02869                     else if (GadgetID == _R(IDC_BLENDATTRBIASGAIN))
02870                     {
02871                         if (*pFirstProfile == *((NodeBlend*) pNode)->GetAttrProfile())
02872                         {
02873                             // all ok
02874                         }
02875                         else
02876                         {
02877                             *bMany = TRUE;
02878                         }
02879                     }
02880                 }
02881             }
02882             else    // part of condition 3 check
02883             {
02884                 *bAllSameType = FALSE;
02885                 return (NULL);
02886             }
02887             pNode = Sel.FindNext(pNode);
02888         }
02889     }
02890 
02891     if (*bMany == TRUE)
02892     {
02893         return (NULL);
02894     }
02895     else
02896     {
02897         return (pFirstProfile);
02898     }
02899 }
02900 
02901 // a big hack: make sure this is fixed..
02902 
02903 INT32 BlendInfoBarOp::AttributeIndexModifier(INT32 CurrentIndex)
02904 {
02905     INT32 retval = 1;
02906 
02907     switch (CurrentIndex)
02908     {
02909     case 1:
02910         retval = 1;
02911         break;
02912     case 2:
02913         retval=  3;
02914         break;
02915     case 3:
02916         retval = 2;
02917         break;
02918     case 4:
02919         retval=  5;
02920         break;
02921     case 5:
02922         retval = 4; 
02923         break;
02924     case 6:
02925         retval = 6;
02926         break;
02927     }
02928     return retval;
02929 }
02930 
02931 /********************************************************************************************
02932 
02933 >   NodeBlend* BlendInfoBarOp::GetCurrentNodeBlend()
02934 
02935     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
02936     Created:    1/9/99
02937     inputs :    -
02938     outputs:    -
02939     returns:    the first nodeblend in the current selection, or null if there isn't one
02940     Purpose:    utility function to retrieve the first nodeblend in the current selection
02941 
02942 *********************************************************************************************/
02943 
02944 NodeBlend* BlendInfoBarOp::GetCurrentNodeBlend()
02945 {
02946     // first get the selection
02947     SelRange Sel(*( GetApplication()->FindSelection()));
02948     NodeBlend* pNodeBlend = NULL;
02949     
02950     // get the node blend
02951     if (!Sel.IsEmpty())
02952     {
02953         Node* pNode = Sel.FindFirst();
02954         while (pNode != NULL)
02955         {
02956             if (pNode->IS_KIND_OF(NodeBlend))
02957             {
02958                  pNodeBlend = (NodeBlend*)pNode;
02959                 break;
02960             }
02961             pNode = Sel.FindNext(pNode);
02962         }
02963     }
02964 
02965     return pNodeBlend;
02966 }
02967 
02968 /********************************************************************************************
02969 
02970 >   BOOL BlendInfoBarOp::CheckSelectionAndSet()
02971 
02972     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
02973     Created:    8/9/99
02974     inputs :    -
02975     outputs:    -
02976     returns:    TRUE if the selection is changed, FALSE otherwise
02977     Purpose:    If we have a situation where there is a blend on a path and the user
02978                 changes to the Bezier or FreeHand tool in order to edit the path, 
02979                 if they return to the Blend we wish to have that blend reselected. 
02980                 This is accomplished by checking to see whether the currently selected 
02981                 node is a NodeBlendPath, if it is then we reset the selection to its parent.
02982                 A bit hacky but what can you do...
02983 
02984 *********************************************************************************************/
02985 
02986 BOOL BlendInfoBarOp::CheckSelectionAndSet()
02987 {
02988     SelRange* pSel = GetApplication()->FindSelection();
02989 
02990     if (pSel == NULL)
02991         return FALSE;
02992 
02993     Node* pNode = pSel->FindFirst();
02994     
02995     if (pNode != NULL)
02996     {
02997         if (pNode->IS_KIND_OF(NodeBlendPath))
02998         {
02999             Node* pParent = pNode->FindParent();
03000 
03001             if (pParent->IS_KIND_OF(NodeBlend))
03002             {   
03003                 NodeRenderableInk::DeselectAll();
03004                 ((NodeRenderable*)pParent)->Select(TRUE);
03005                 pSel->Update();
03006             
03007                 return TRUE;
03008             }
03009         }
03010     }
03011     return FALSE;
03012 
03013 }
03014 
03015 /********************************************************************************************
03016 
03017 >   BOOL BlendInfoBarOp::NonBlendsSelected()
03018 
03019     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
03020     Created:    26/9/99
03021     inputs :    
03022     Returns:    TRUE if there are not only blends in the current selection, otherwise FALSE
03023     Purpose:    To find out if there is anything in the selection apart from blends
03024 *********************************************************************************************/
03025 
03026 BOOL BlendInfoBarOp::NonBlendsSelected()
03027 {
03028     SelRange* pSel = GetApplication()->FindSelection();
03029 
03030     if (pSel == NULL)
03031         return FALSE;
03032 
03033     Node* pNode = pSel->FindFirst();
03034     while (pNode != NULL)
03035     {
03036         if (pNode->IsAnObject())
03037         {
03038             
03039             if (pNode->IsCompound())
03040             {
03041                 if (!pNode->IS_KIND_OF(NodeBlend))
03042                 {
03043                     // look inside bevels, contours, etc.
03044                     if (pNode->IS_KIND_OF(NodeBevelController) || 
03045                         pNode->IS_KIND_OF(NodeShadowController) ||
03046                         pNode->IS_KIND_OF(NodeContourController))
03047                     {
03048                         Node* pChild = pNode->FindFirstChild(CC_RUNTIME_CLASS(NodeBlend));
03049                         if (pChild == NULL)
03050                             return TRUE;
03051                     }
03052                 }
03053             }
03054             else 
03055                 return TRUE;
03056         }
03057         pNode = pSel->FindNext(pNode);
03058     }
03059     return FALSE;
03060 
03061 }
03062 
03063 
03065 //  OpBlendNodes
03066 //
03067 // This operation is responsible for creating and editing
03068 
03069 
03070 
03071 /********************************************************************************************
03072 
03073 >   OpBlendNodes::OpBlendNodes()
03074 
03075     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
03076     Created:    11/10/94
03077     Purpose:    Constructor. 
03078 
03079 ********************************************************************************************/
03080 
03081 OpBlendNodes::OpBlendNodes()
03082 {
03083     pRefStart = NULL;
03084     pRefEnd   = NULL;
03085     OpType    = BLENDOPTYPE_NONE;
03086 
03087     pNodeBlend      = NULL;
03088     pNodeBlendStart = NULL;
03089     pNodeBlendEnd   = NULL;
03090 }
03091 
03092 /********************************************************************************************
03093 
03094 >   OpBlendNodes::~OpBlendNodes()
03095 
03096     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
03097     Created:    11/10/94
03098     Purpose:    Destructor.
03099 
03100 ********************************************************************************************/
03101 
03102 OpBlendNodes::~OpBlendNodes()
03103 {
03104 }
03105 
03106 
03107 /********************************************************************************************
03108 
03109 >   BOOL OpBlendNodes::DoDrag()
03110     
03111     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
03112     Created:    11/10/94
03113     Inputs:     -
03114     Outputs:    -
03115     Returns:    -
03116     Purpose:    This starts a drag that may lead to a blend.
03117                 The DragFinished() method will do the hard work of blending if it can be done.
03118 
03119 ********************************************************************************************/
03120 
03121 BOOL OpBlendNodes::DoDrag(BlendTool* pBlendTool)
03122 {
03123     // DMc
03124     // find out the active tool for the drag
03125     if (Tool::GetCurrent()->GetID() == TOOLID_BLEND)
03126     {
03127         pRefStart = BlendTool::GetPtrRefStart();
03128         pRefEnd   = BlendTool::GetPtrRefEnd();
03129     }
03130         
03131     ERROR2IF_PF(pRefStart == NULL || pRefEnd == NULL,FALSE,("Blend tool refs are NULL"));
03132 
03133     pRefEnd->PointerPos = pRefStart->PointerPos;
03134     RenderMyDragBlobs();
03135     m_pBlendTool = pBlendTool;
03136 
03137     // Tell the Dragging system that we need drags to happen
03138     StartDrag( DRAGTYPE_AUTOSCROLL );
03139 
03140     return TRUE;
03141 }
03142 
03143 
03144 
03145 
03146 /********************************************************************************************
03147 
03148 >   void OpBlendNodes::DragPointerMove( DocCoord PointerPos, ClickModifiers ClickMods, 
03149                                    Spread* pSpread, BOOL bSolidDrag)
03150     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
03151     Created:    11/10/94
03152     Inputs:     PointerPos - The current position of the mouse in Doc Coords
03153                 ClickMods  - Which key modifiers are being pressed
03154                 pSpread    - The spread that the mouse pointer is over
03155     Purpose:    Takes the pointer position and calculates the new dragged outline of the EORd
03156                 bounding box
03157     SeeAlso:    ClickModifiers
03158 
03159 ********************************************************************************************/
03160 
03161 void OpBlendNodes::DragPointerMove( DocCoord PointerPos, ClickModifiers ClickMods, 
03162                                    Spread* pSpread, BOOL bSolidDrag)
03163 {
03164     // First Rub out the old box
03165     RenderMyDragBlobs();
03166 
03167     // Make sure that start and end refer to the same spread
03168     if (pSpread != pRefStart->pSpread)
03169         PointerPos = MakeRelativeToSpread(pRefStart->pSpread, pSpread, PointerPos);
03170 
03171     // DMc
03172     // find out the active tool for the drag
03173     if (Tool::GetCurrent()->GetID() == TOOLID_BLEND)
03174     {
03175         BlendTool::UpdateRef(pRefEnd,pRefStart->pSpread,PointerPos,FALSE);
03176         BlendTool::CheckNodeRemapping(pRefStart,pRefEnd);
03177         BlendTool::UpdateCursorAndStatus();
03178     }
03179     
03180     // Render the new drag box
03181     RenderMyDragBlobs();
03182 }
03183 
03184 
03185 
03186 /********************************************************************************************
03187 
03188 >   void OpBlendNodes::DragFinished( DocCoord PointerPos, ClickModifiers ClickMods, 
03189                                 Spread* pSpread, BOOL Success, BOOL bSolidDrag)
03190 
03191     
03192     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
03193     Created:    11/10/94
03194     Inputs:     PointerPos - The position of the mouse at the end of the drag
03195                 ClickMods - the key modifiers being pressed
03196                 pSpread - The spread that the drag finished on
03197                 Success - TRUE if the drag was terminated properly, FALSE if it
03198                 was ended with the escape key being pressed
03199     Purpose:    Ends the drag.
03200                 Either creates a new grid or resizes GridClicked depending on the state of affairs
03201                 when the drag started
03202     SeeAlso:    ClickModifiers
03203 
03204 ********************************************************************************************/
03205 
03206 void OpBlendNodes::DragFinished( DocCoord PointerPos, ClickModifiers ClickMods, 
03207                                 Spread* pSpread, BOOL Success, BOOL bSolidDrag)
03208 {
03209     ERROR3IF(pRefStart == NULL,"pRefStart is NULL");
03210     ERROR3IF(pRefEnd   == NULL,"pRefEnd is NULL");
03211     if ((pRefStart == NULL) || (pRefEnd == NULL)) return;
03212 
03213     // First Rub out the old box
03214     RenderMyDragBlobs();
03215 
03216     if (Success)
03217     {
03218         // Make sure that start and end refer to the same spread
03219         if (pSpread != pRefStart->pSpread)
03220             PointerPos = MakeRelativeToSpread(pRefStart->pSpread, pSpread, PointerPos);
03221 
03222         if (Tool::GetCurrent()->GetID() == TOOLID_BLEND)
03223         {
03224             BlendTool::UpdateRef(pRefEnd,pRefStart->pSpread,PointerPos);
03225             BlendTool::CheckNodeRemapping(pRefStart,pRefEnd);
03226         }
03227         else
03228         {
03229             FailAndExecute();
03230             EndDrag();
03231             delete this;
03232             return;
03233         }
03234     
03235         String_32 ProgString = _T("Calculating blend, please wait..");
03236         Progress Hourglass(&ProgString, -1, FALSE);
03237         Node* pNodeStart = pRefStart->pNode;
03238         Node* pNodeEnd   = pRefEnd  ->pNode;
03239 
03240         // make the start & end nodes point to any 'needs parent' compounds
03241         while (pNodeStart)
03242         {
03243             if (!pNodeStart->PromoteHitTestOnChildrenToMe() && pNodeStart->IsAnObject())
03244             {
03245                 pRefStart->pNode = (NodeRenderableInk *)pNodeStart;
03246             }
03247 
03248             pNodeStart = pNodeStart->FindParent();
03249         }
03250 
03251         while (pNodeEnd)
03252         {
03253             if (!pNodeEnd->PromoteHitTestOnChildrenToMe() && pNodeEnd->IsAnObject())
03254             {
03255                 pRefEnd->pNode = (NodeRenderableInk *)pNodeEnd;
03256             }
03257 
03258             pNodeEnd = pNodeEnd->FindParent();
03259         }
03260 
03261 
03262         BOOL ok = FALSE;
03263 
03264         if (pRefStart->pNode == NULL || pRefEnd->pNode == NULL)
03265         {
03266             // can't blend so exit
03267             FailAndExecute();
03268             EndDrag();
03269             delete this;
03270             return;
03271         }
03272         else if (pRefStart->pNode == pRefEnd->pNode)
03273         {
03274             if (pRefStart->RemapRef == pRefEnd->RemapRef && pRefStart->RemapRef > 0)
03275             {
03276                 // User's tried to remap two blend blobs
03277 
03278                 if (pRefStart->AStartNode != pRefEnd->AStartNode)
03279                     // The blobs lie on opposite ends of a blend
03280                     ok = DoRemapBlend();
03281             }
03282             else
03283             {
03284                 // can't blend so exit
03285                 FailAndExecute();
03286                 EndDrag();
03287                 delete this;
03288                 return;
03289             }
03290         }
03291         else if (IS_A(pRefStart->pNode,NodeBlend) &&
03292                  IS_A(pRefEnd  ->pNode,NodeBlend))
03293         {
03294             // Blending a blend to a blend
03295             ok = DoBlendBlendAndBlend();
03296         }
03297         else if (IS_A(pRefStart->pNode,NodeBlend) ||
03298                  IS_A(pRefEnd  ->pNode,NodeBlend))
03299         {
03300             // Blending an object to a blend, or a blend to an object
03301             ok = DoBlendBlendAndObject();
03302         }
03303         else
03304         {
03305             ok = DoBlendObjects();
03306         }
03307 
03308         if (!ok) 
03309             FailAndExecute();
03310 
03311         // Do this at the end so that the status bar text doesn't get wiped before the op begins
03312         if (Tool::GetCurrent()->GetID() == TOOLID_BLEND)
03313         {
03314             BlendTool::UpdateCursorAndStatus();
03315         }
03316         else
03317         {
03318             
03319         }
03320     }
03321     else
03322         FailAndExecute();
03323 
03324 
03325     // End the drag and the op
03326     EndDrag();
03327     End();
03328 }
03329 
03330 /********************************************************************************************
03331 
03332 >   BOOL OpBlendNodes::DoContourNode(Node * pNode, DocCoord PointerPos, UINT32 Steps )
03333 
03334     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
03335     Created:    18/8/99
03336     Inputs:     Node to contour
03337     Outputs:    -
03338     Returns:    TRUE if all when well, FALSE otherwise
03339     Purpose:    Applies a contour to the given node
03340 
03341 ********************************************************************************************/
03342 BOOL OpBlendNodes::DoContourNode(Node * pNode, DocCoord PointerPos, UINT32 Steps)
03343 {
03344     /*
03345     List NodeList;
03346 
03347     // get the bounding rect of the node to be contoured
03348     DocRect br = ((NodeRenderableBounded *)pNode)->GetBoundingRect();
03349 
03350     UINT32 Width = 0;
03351 
03352     INT32 OffsetX = 0;
03353     INT32 OffsetY = 0;
03354 
03355     // try to work out the width of the contour
03356     if (PointerPos.x < br.lo.x && PointerPos.y >= br.lo.y &&
03357         PointerPos.y <= br.hi.y)
03358     {
03359         Width = br.lo.x - PointerPos.x;
03360     }
03361     else if (PointerPos.x > br.hi.x && PointerPos.y >= br.lo.y &&
03362         PointerPos.y <= br.hi.y)
03363     {
03364         Width = PointerPos.x - br.hi.x;
03365     }
03366     else if (PointerPos.x >= br.lo.x && PointerPos.x <= br.hi.x &&
03367         PointerPos.y < br.lo.y)
03368     {
03369         Width = br.lo.y - PointerPos.y;
03370     }
03371     else if (PointerPos.x >= br.lo.x && PointerPos.x <= br.hi.x &&
03372         PointerPos.y > br.hi.y)
03373     {
03374         Width = PointerPos.y - br.hi.y;
03375     }
03376     else if (PointerPos.x < br.lo.x && PointerPos.y < br.lo.y)
03377     {
03378         OffsetX = br.lo.x - PointerPos.x;
03379         OffsetY = br.lo.y - PointerPos.y;
03380 
03381         if (OffsetX > OffsetY)
03382         {
03383             Width = OffsetX;
03384         }
03385         else
03386         {
03387             Width = OffsetY;
03388         }
03389     }
03390     else if (PointerPos.x > br.hi.x && PointerPos.y < br.lo.y)
03391     {
03392         OffsetX = PointerPos.x - br.hi.x ;
03393         OffsetY = br.lo.y - PointerPos.y;
03394 
03395         if (OffsetX > OffsetY)
03396         {
03397             Width = OffsetX;
03398         }
03399         else
03400         {
03401             Width = OffsetY;
03402         }
03403     }
03404     else if (PointerPos.x > br.hi.x && PointerPos.y > br.hi.y)
03405     {
03406         OffsetX = PointerPos.x - br.hi.x ;
03407         OffsetY = PointerPos.y - br.hi.y;
03408 
03409         if (OffsetX > OffsetY)
03410         {
03411             Width = OffsetX;
03412         }
03413         else
03414         {
03415             Width = OffsetY;
03416         }
03417     }
03418     else if (PointerPos.x < br.lo.x && PointerPos.y > br.hi.y)
03419     {
03420         OffsetX = br.lo.x - PointerPos.x ;
03421         OffsetY = PointerPos.y - br.hi.y;
03422 
03423         if (OffsetX > OffsetY)
03424         {
03425             Width = OffsetX;
03426         }
03427         else
03428         {
03429             Width = OffsetY;
03430         }
03431     }
03432 
03433     // find top level of the node to contour
03434     Node * pParent = pNode->FindParent();
03435 
03436     BOOL bContourExists = FALSE;
03437     
03438     while (pParent)
03439     {
03440         if (pParent->IsKindOf(CC_RUNTIME_CLASS(NodeContourController)))
03441         {
03442             pNode = pParent;
03443             bContourExists = TRUE;
03444         }
03445 
03446         pParent = pParent->FindParent();
03447     }
03448 
03449     NodeListItem * pItem = new NodeListItem(pNode);
03450 
03451     NodeList.AddTail(pItem);
03452 
03453     // if a contour exists then change its width
03454     if (!bContourExists)
03455     {
03456         CreateContourParam Param(&NodeList, Steps, Width);
03457 
03458         OpDescriptor * pOpDesc = OpDescriptor::FindOpDescriptor(CC_RUNTIME_CLASS(OpCreateContour));
03459 
03460         if (pOpDesc && Width > 0)
03461         {
03462             pOpDesc->Invoke(&Param);
03463         }
03464     }
03465     else
03466     {
03467         ChangeContourWidthParam Param(&NodeList, Width, FALSE);
03468 
03469         OpDescriptor * pOpDesc = OpDescriptor::FindOpDescriptor(CC_RUNTIME_CLASS(OpChangeContourWidth));
03470 
03471         if (pOpDesc && Width > 0)
03472         {
03473             pOpDesc->Invoke(&Param);
03474         }
03475     }
03476 
03477     NodeList.DeleteAll();
03478     */
03479     
03480     return TRUE;
03481 
03482 }
03483 
03484 /********************************************************************************************
03485 
03486 >   BOOL OpBlendNodes::DoBlendBlendAndBlend()
03487 
03488     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
03489     Created:    18/11/94
03490     Inputs:     - (data is taken from pRefStart and pRefEnd member vars)
03491     Outputs:    -
03492     Returns:    TRUE if all when well, FALSE otherwise
03493     Purpose:    This merges to blend nodes together, so that the last object in the first blend
03494                 is blended with the first of the second blend.
03495 
03496 ********************************************************************************************/
03497 
03498 BOOL OpBlendNodes::DoBlendBlendAndBlend()
03499 {
03500     // Get ptr to the NodeBlends in question, and put them in the member vars pNodeBlendStart & pNodeBlendEnd
03501     pNodeBlendStart = (NodeBlend*)pRefStart->pNode;
03502     pNodeBlendEnd   = (NodeBlend*)pRefEnd  ->pNode;
03503 
03504     // DY changed to allow for new blending to and from a blend on a path
03505     if (pNodeBlendStart->IsOnACurve() || pNodeBlendEnd->IsOnACurve())
03506     {
03507         OpType = BLENDOPTYPE_BLENDBLENDONPATH;
03508         pNodeBlendStart->SetBlendedOnCurve(TRUE);
03509     }
03510     else
03511         OpType = BLENDOPTYPE_BLENDANDBLEND;
03512 
03513 
03514     if (pNodeBlendStart == NULL || pNodeBlendEnd == NULL || pNodeBlendStart == pNodeBlendEnd) return FALSE;
03515 
03516     ERROR3IF(!IS_A(pNodeBlendStart,NodeBlend) || !IS_A(pNodeBlendEnd,NodeBlend),"Either start or end is not a NodeBlend");
03517     if (!IS_A(pNodeBlendStart,NodeBlend) || !IS_A(pNodeBlendEnd,NodeBlend)) return FALSE;
03518 
03519     // Get a record of all the blenders in the blend nodes
03520     List BlenderListStart,BlenderListEnd;
03521     if (!RecordBlenderInfo(BlenderListStart,pNodeBlendStart) || !RecordBlenderInfo(BlenderListEnd,pNodeBlendEnd))
03522     {
03523         // Tidy up if we fail to get blender info on the two blend nodes
03524         BlenderListStart.DeleteAll();
03525         BlenderListEnd  .DeleteAll();
03526         return FALSE;
03527     }
03528 
03529     //BlenderInfoItem*   pItemStart = (BlenderInfoItem*)BlenderListStart.GetTail();
03530     //BlenderInfoItem*   pItemEnd   = (BlenderInfoItem*)BlenderListEnd  .GetHead();
03531     NodeRenderableInk* pNodeStart = NULL;  //pItemStart->pNodeEnd;
03532     NodeRenderableInk* pNodeEnd   = NULL;  //pItemEnd  ->pNodeStart;
03533 
03534     BOOL ok = GetNodeClosestToPoint(&pNodeStart, TRUE);
03535     if (ok) ok = GetNodeClosestToPoint(&pNodeEnd, FALSE);
03536     ERROR3IF(pNodeStart == NULL,"The start node ptr is NULL");
03537     ERROR3IF(pNodeEnd   == NULL,"The end node ptr is NULL");
03538     if (pNodeStart == NULL || pNodeEnd == NULL) return FALSE;
03539 
03540     BlobManager* pBlobManager = GetApplication()->GetBlobManager();
03541     ENSURE(pBlobManager, "Can't get BlobManager");
03542 
03543     ok = DeterminBlendObjectsProcessorHit ();
03544 
03545     // Now we are ready to do the actual blend
03546 
03547     // Firstly, record the selection state
03548     if (ok) ok = DoStartSelOp(TRUE,TRUE);
03549 
03550     if (ok) ok = (Tool::GetCurrent() != NULL);
03551     if (ok) pBlobManager->RenderToolBlobsOff(Tool::GetCurrent(), pRefStart->pSpread,NULL);
03552     if (ok) NodeRenderableInk::DeselectAll(FALSE);
03553 
03554     // Select the start blend node
03555     if (ok) pNodeBlendStart->SetSelected(TRUE);
03556 
03557     // Deinit all the blenders in the end blend.
03558     if (ok) ok = DoDeinitBlenders(BlenderListEnd);
03559 
03560     // Localise the attributes in the start blend
03561     if (ok) ok = DoLocaliseCommonAttributes(pNodeBlendStart);
03562 
03563     // Localise the attributes in the end blend
03564     if (ok) ok = DoLocaliseCommonAttributes(pNodeBlendEnd);
03565 
03566     // Hide the end blend node
03567     NodeHidden* pNodeHidden;
03568     if (ok) ok = DoHideNode(pNodeBlendEnd,TRUE,&pNodeHidden);
03569 
03570     if (ok)
03571     {
03572         // DY update blendpath indexes in existing blenders before we
03573         // move them
03574         INT32 FirstBlendNumPaths = pNodeBlendStart->GetNumNodeBlendPaths();
03575         NodeBlender* pBlender = pNodeBlendEnd->FindFirstBlender();
03576         while (pBlender != NULL)
03577         {
03578             INT32 CurrentIndex = pBlender->GetNodeBlendPathIndex();
03579             if (CurrentIndex > -1)
03580             {
03581                 ChangeBlenderOpParam Param;
03582                 Param.m_NewNodeBlendPathIndex = (CurrentIndex + FirstBlendNumPaths);
03583                 Param.m_ChangeType = CHANGEBLENDER_NBPINDEX;
03584                 ok = ChangeBlenderAction::Init(this, &UndoActions, pBlender, Param);
03585             }
03586             pBlender =  pNodeBlendEnd->FindNextBlender(pBlender);
03587         }
03588     }
03589 
03590     // Now move all the end blend's children (except hidden nodes) into the start blend.
03591     if (ok)
03592     {
03593         Node* pNode = pNodeBlendEnd->FindFirstChild();
03594         while (pNode != NULL && ok)
03595         {
03596             Node* pNext = pNode->FindNext();
03597             if (!pNode->IS_KIND_OF(NodeHidden))
03598                 ok = DoMoveNode(pNode,pNodeBlendStart,LASTCHILD);
03599             
03600             // DY keep track of which nodeblendpath this is
03601             //if (pNode->IS_KIND_OF(NodeBlendPath))
03602             //  ((NodeBlendPath*)pNode)->SetPathIndex();
03603             pNode = pNext;
03604         }
03605     }
03606 
03607     // Create a blender node to blend the two nodes
03608     if (ok) ok = DoCreateBlender(pNodeStart,-1,pNodeEnd,-1,pNodeStart,NEXT);
03609 
03610     // Reinit the end blenders using the same start and end pointers they were using before the blend
03611     if (ok) ok = DoReinitBlenders(BlenderListEnd);
03612 
03613     // Factor out any common attrs that might have been copied during the blend
03614     if (ok) ok = DoFactorOutCommonChildAttributes(pNodeBlendStart);
03615 
03616     // Invalid the whole of the start blend
03617     if (ok) ok = DoInvalidateNodeRegion(pNodeBlendStart,TRUE);
03618 
03619     // Clear out the lists (we don't want memory leaks, do we).
03620     BlenderListStart.DeleteAll();
03621     BlenderListEnd  .DeleteAll();
03622 
03623     // Throw away the selection cache
03624     GetApplication()->UpdateSelection();
03625 
03626     return (ok);
03627 }
03628 
03629 /********************************************************************************************
03630 
03631 >   BOOL OpBlendNodes::DoBlendBlendAndObject()
03632 
03633     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
03634     Created:    14/11/94
03635     Inputs:     - (data is taken from pRefStart and pRefEnd member vars)
03636     Outputs:    -
03637     Returns:    TRUE if all when well, FALSE otherwise
03638     Purpose:    This blends a blend object with another non-blend object.
03639                 This is like a concatination of the the two nodes, resulting a single blend with an extra
03640                 blend stage. E.g. blending a blend containing two paths (i.e. a single blend of two objects) with
03641                 another path.  The result will be a single blend object, blending the first path to the second,
03642                 and the second to the third.
03643 
03644                 The order in which the two objects are concatinated depends on which is is the start
03645                 of the blend and which is the end.
03646 
03647 ********************************************************************************************/
03648 
03649 BOOL OpBlendNodes::DoBlendBlendAndObject()
03650 {
03651     ERROR3IF(!IS_A(pRefStart->pNode,NodeBlend) && !IS_A(pRefEnd->pNode,NodeBlend),"Neither start or end are NodeBlends");
03652     if (!IS_A(pRefStart->pNode,NodeBlend) && !IS_A(pRefEnd->pNode,NodeBlend)) return FALSE;
03653 
03654     OpType = BLENDOPTYPE_BLENDANDOBJECT;
03655     // BlendIsStart == TRUE if the blend node was the start of the blend and not the end
03656     BOOL BlendIsStart = IS_A(pRefStart->pNode,NodeBlend);
03657 
03658     // Get ptr to the NodeBlend in question, and put it in the member var pNodeBlend
03659     pNodeBlend = NULL;
03660     if (BlendIsStart) 
03661         pNodeBlend = (NodeBlend*)pRefStart->pNode; 
03662     else
03663         pNodeBlend = (NodeBlend*)pRefEnd->pNode; 
03664 
03665     if (pNodeBlend == NULL) return FALSE;
03666 
03667     // Get a record of all the blenders in the blend node
03668     List BlenderList;
03669     if (!RecordBlenderInfo(BlenderList,pNodeBlend))
03670         return FALSE;
03671 
03672     NodeRenderableInk* pNodeStart = NULL;
03673     NodeRenderableInk* pNodeEnd = NULL;
03674     Node* pContextNode;
03675     AttachNodeDirection AttachDir;
03676     BOOL ok = FALSE;
03677     if (BlendIsStart)
03678     {
03679 //      BlenderInfoItem* pItem = (BlenderInfoItem*)BlenderList.GetTail();
03680         pNodeBlendStart = pNodeBlend;
03681         ok              = GetNodeClosestToPoint(&pNodeStart, TRUE);
03682         pNodeEnd        = pRefEnd->pNode;
03683         pContextNode    = pNodeEnd;
03684         AttachDir       = PREV;
03685     }
03686     else
03687     {
03688 //      BlenderInfoItem* pItem = (BlenderInfoItem*)BlenderList.GetHead();
03689         pNodeStart      = pRefStart->pNode;
03690         pNodeBlendEnd   = pNodeBlend;
03691         ok              = GetNodeClosestToPoint(&pNodeEnd, FALSE);
03692         pContextNode    = pNodeStart;
03693         AttachDir       = NEXT;
03694     }
03695 
03696     ERROR3IF(pNodeBlend == NULL,"Neither start or end are NodeBlends");
03697     ERROR3IF(pNodeStart == NULL,"The start node ptr is NULL");
03698     ERROR3IF(pNodeEnd   == NULL,"The end node ptr is NULL");
03699     if (pNodeBlend == NULL || pNodeStart == NULL || pNodeEnd == NULL)
03700         return FALSE;
03701 
03702     BlobManager* pBlobManager = GetApplication()->GetBlobManager();
03703     ENSURE(pBlobManager, "Can't get BlobManager");
03704 
03705     ok = DeterminBlendObjectsProcessorHit ();
03706 
03707     // Firstly, record the selection state
03708     if (ok) ok = DoStartSelOp(TRUE,TRUE);
03709 
03710     if (ok) ok = (Tool::GetCurrent() != NULL);
03711     if (ok) pBlobManager->RenderToolBlobsOff(Tool::GetCurrent(), pRefStart->pSpread,NULL);
03712     if (ok) NodeRenderableInk::DeselectAll(FALSE);
03713 
03714     // Select th blend node
03715     if (ok) pNodeBlend->SetSelected(TRUE);
03716 
03717     // Deinit all the blenders in the blend.
03718     if (ok) ok = DoDeinitBlenders(BlenderList);
03719 
03720     // Localise the attributes in the blend before we start moving node around
03721     if (ok) ok = DoLocaliseCommonAttributes(pNodeBlend);
03722 
03723     // If blending to a blend, move the start node, and make sure it's deselected
03724     // Also find the number of paths that will be passed back (for the progress display)
03725     if (ok && !BlendIsStart)    ok = DoMoveNode(pNodeStart,pNodeBlend,FIRSTCHILD);
03726     if (ok && !BlendIsStart)    ok = DoDeselectNode(pNodeStart);
03727 
03728     // If blending from a blend, move the end node, and make sure it's deselected
03729     // Also find the number of paths that will be passed back (for the progress display)
03730     if (ok &&  BlendIsStart)    ok = DoMoveNode(pNodeEnd,pNodeBlend,LASTCHILD);
03731     if (ok &&  BlendIsStart)    ok = DoDeselectNode(pNodeEnd);
03732 
03733     // Create a blender node to blend the two nodes
03734     if (ok) ok = DoCreateBlender(pNodeStart,-1,pNodeEnd,-1,pContextNode,AttachDir);
03735 
03736     // Factor out any common attrs that might have been copied during the blend
03737     if (ok) ok = DoFactorOutCommonChildAttributes(pNodeBlend);
03738 
03739     // Invalidate the whole blend
03740     if (ok) ok = DoInvalidateNodeRegion(pNodeBlend,TRUE);
03741 
03742     // Reinit the blenders using the same start and end pointers they were using before the blend
03743     if (ok) ok = DoReinitBlenders(BlenderList);
03744 
03745     // Clear out the list (we don't want memory leaks, do we).
03746     BlenderList.DeleteAll();
03747 
03748     // Throw away the selection cache
03749     GetApplication()->UpdateSelection();
03750 
03751     return (ok);
03752 }
03753 
03754 /********************************************************************************************
03755 
03756 >   BOOL OpBlendNodes::DoDeinitBlenders(List& BlenderList)
03757 
03758     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
03759     Created:    21/11/94
03760     Inputs:     BlenderList = list of blenders to deinit.
03761     Outputs:    -
03762     Returns:    -
03763     Purpose:    This calls DeinitBlenders() on the given list, and creates an action that
03764                 will call ReinitBlenders() on undo, and DeinitBlenders() on redo
03765 
03766 ********************************************************************************************/
03767 
03768 BOOL OpBlendNodes::DoDeinitBlenders(List& BlenderList)
03769 {
03770     InitBlendersAction* pAction;
03771     BOOL ok = (InitBlendersAction::Init(this,&UndoActions,&BlenderList,TRUE,&pAction) != AC_FAIL);
03772 
03773     if (ok) DeinitBlenders(BlenderList);
03774 
03775     return (ok);
03776 }
03777 
03778 /********************************************************************************************
03779 
03780 >   BOOL OpBlendNodes::DoReinitBlenders(NodeBlend* pNodeBlend)
03781 
03782     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
03783     Created:    21/11/94
03784     Inputs:     BlenderList = list of blenders to deinit.
03785     Outputs:    -
03786     Returns:    -
03787     Purpose:    This calls ReinitBlenders() on the given list, and creates an action that
03788                 will call DeinitBlenders() on undo, and ReinitBlenders() on redo
03789 
03790 ********************************************************************************************/
03791 
03792 BOOL OpBlendNodes::DoReinitBlenders(List& BlenderList)
03793 {
03794     InitBlendersAction* pAction;
03795     BOOL ok = (InitBlendersAction::Init(this,&UndoActions,&BlenderList,FALSE,&pAction) != AC_FAIL);
03796 
03797     if (ok)
03798     {
03799         ok = ReinitBlenders(BlenderList);
03800         if (!ok) FailAndExecuteAllButLast();
03801     }
03802 
03803     return ok;
03804 }
03805 
03806 /********************************************************************************************
03807 
03808 >   void OpBlendNodes::DeinitBlenders(List& BlenderList)
03809 
03810     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
03811     Created:    14/11/94
03812     Inputs:     BlenderList = reference to a list of blender info items
03813     Outputs:    -
03814     Returns:    -
03815     Purpose:    This scans the blenders, calling the Deinit() method for each of them.
03816 
03817 ********************************************************************************************/
03818 
03819 void OpBlendNodes::DeinitBlenders(List& BlenderList)
03820 {
03821     BlenderInfoItem* pItem = (BlenderInfoItem*) BlenderList.GetHead();
03822 
03823     while (pItem != NULL)
03824     {
03825         pItem->pNodeBlender->Deinit();
03826         pItem = (BlenderInfoItem*) BlenderList.GetNext(pItem);
03827     }
03828 }
03829 
03830 /********************************************************************************************
03831 
03832 >   BOOL OpBlendNodes::ReinitBlenders(List& BlenderList)
03833 
03834     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
03835     Created:    14/11/94
03836     Inputs:     BlenderList = reference to a list of blender info items
03837     Outputs:    -
03838     Returns:    TRUE if all when well, FALSE otherwise
03839     Purpose:    This scans the blenders, calling the Reinit() method for each of them.
03840                 It uses the start and end node pointers gathered by RecordBlenderInfo() when
03841                 calling Reinit().
03842 
03843 ********************************************************************************************/
03844 
03845 BOOL OpBlendNodes::ReinitBlenders(List& BlenderList)
03846 {
03847     BOOL ok = TRUE;
03848     BlenderInfoItem* pItem = (BlenderInfoItem*) BlenderList.GetHead();
03849 
03850     while (pItem != NULL && ok)
03851     {
03852         ok = pItem->pNodeBlender->Reinit(pItem->pNodeStart,pItem->pNodeEnd);
03853         pItem = (BlenderInfoItem*) BlenderList.GetNext(pItem);
03854     }
03855 
03856     return (ok);
03857 }
03858 
03859 /********************************************************************************************
03860 
03861 >   BOOL OpBlendNodes::RecordBlenderInfo(List& BlenderList,NodeBlend* pNodeBlend)
03862 
03863     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
03864     Created:    14/11/94
03865     Inputs:     BlenderList = reference to a list to put all the blender info items on
03866                 pNodeBlend  = the blend containing the blenders of interested
03867     Outputs:    -
03868     Returns:    TRUE if all when well, FALSE otherwise
03869     Purpose:    This scans the blenders in the given blend node, creating a BlenderInfoItem for each
03870                 NodeBlender.  Each BlenderInfoItem is placed on BlenderList
03871 
03872 ********************************************************************************************/
03873 
03874 BOOL OpBlendNodes::RecordBlenderInfo(List& BlenderList,NodeBlend* pNodeBlend)
03875 {
03876     ERROR3IF(pNodeBlend == NULL,"pNodeBlend == NULL");
03877     if (pNodeBlend == NULL) return FALSE;
03878 
03879     BOOL ok = TRUE;
03880 
03881     Node* pNode = pNodeBlend->FindFirstChild();
03882     while (pNode != NULL && ok)
03883     {
03884         if (IS_A(pNode,NodeBlender))
03885         {
03886             NodeBlender* pNodeBlender = (NodeBlender*)pNode;
03887             BlenderInfoItem* pItem = new BlenderInfoItem;
03888             ok = (pItem != NULL);
03889 
03890             if (ok)
03891             {
03892                 pItem->pNodeBlender = pNodeBlender;
03893                 pItem->pNodeStart   = pNodeBlender->GetNodeStart();
03894                 pItem->pNodeEnd     = pNodeBlender->GetNodeEnd();
03895 
03896                 ok = (pItem->pNodeStart != NULL && pItem->pNodeEnd != NULL);
03897             }
03898 
03899             if (ok) BlenderList.AddTail(pItem);
03900         }
03901         pNode = pNode->FindNext();
03902     }
03903 
03904     return ok;
03905 }
03906 
03907 /********************************************************************************************
03908 
03909 >   BOOL OpBlendNodes::DoRemapBlend()
03910 
03911     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
03912     Created:    10/11/94
03913     Inputs:     - (data is taken from pRefStart and pRefEnd member vars)
03914     Outputs:    -
03915     Returns:    TRUE if all when well, FALSE otherwise
03916     Purpose:    This tries to remap a blend using the data held in pRefStart && pRefEnd.
03917                 It asks the pNodeBlend object to try and remap the two paths by rotating
03918                 the path elements so that the element at Index is the first element in the path.
03919 
03920 ********************************************************************************************/
03921 
03922 BOOL OpBlendNodes::DoRemapBlend()
03923 {
03924     // What type of blend op are we doing?
03925     OpType = BLENDOPTYPE_REMAP;
03926 
03927     // Are we going mad? Better check our onions...
03928     ERROR3IF(pRefStart              == NULL,"pRefStart == NULL");
03929     ERROR3IF(pRefEnd                == NULL,"pRefEnd == NULL");
03930     ERROR3IF(pRefStart->pNode       == NULL,"pRefStart->pNode == NULL");
03931     ERROR3IF(pRefEnd  ->pNode       == NULL,"pRefEnd->pNode == NULL");
03932     ERROR3IF(pRefStart->pNodeBlend  == NULL,"pRefStart->pNodeBlend == NULL");
03933     ERROR3IF(pRefEnd  ->pNodeBlend  == NULL,"pRefEnd->pNodeBlend == NULL");
03934     ERROR3IF(pRefStart->pNodeBlend  != pRefEnd->pNodeBlend,"Start and end blend nodes not equal");
03935     ERROR3IF(pRefStart->RemapRef    != pRefEnd->RemapRef,"Start and end remap refs not equal");
03936     ERROR3IF(pRefStart->AStartNode  == pRefEnd->AStartNode,"Start and end AStartNode vals equal");
03937 
03938     BOOL ok = TRUE;
03939     RemapBlendAction* pAction;
03940     NodeBlend*  pNodeBlend  = pRefStart->pNodeBlend;
03941     UINT32      RemapRef    = pRefStart->RemapRef;
03942 
03943     DocCoord    PosStart    = pRefStart->PointerPos;
03944     DocCoord    PosEnd      = pRefEnd  ->PointerPos;
03945 
03946     if (!pRefStart->AStartNode)
03947     {
03948         // Swap the coords around
03949         DocCoord Temp = PosStart;
03950         PosStart = PosEnd;
03951         PosEnd = Temp;
03952     }
03953 
03954     ObjChangeFlags cFlags;
03955     ObjChangeParam ObjChange(OBJCHANGE_STARTING,cFlags,pNodeBlend,this);
03956     ok = pNodeBlend->AllowOp(&ObjChange);
03957 
03958     if (ok) ok = DoInvalidateNodeRegion(pRefStart->pNodeBlend,TRUE);
03959 
03960     if (ok) ok = RemapBlendAction::Init(this,&UndoActions,
03961                                         pNodeBlend,RemapRef,PosStart,PosEnd,
03962                                         &pAction) != AC_FAIL;
03963 
03964     if (ok)
03965     {
03966         ObjChange.Define(OBJCHANGE_FINISHED,cFlags,pNodeBlend,this);
03967         UpdateChangedNodes(&ObjChange);
03968     }
03969 
03970     return (ok);
03971 }
03972 
03973 /********************************************************************************************
03974 
03975 >   BOOL OpBlendNodes::RemoveCompoundNodes(CompoundNodeTreeFactoryList * pList)
03976 
03977     Author:     David_McClarnon (Xara Group Ltd) <camelotdev@xara.com>
03978     Created:    4/8/99
03979     Inputs:     The list to return the class factories necessary to regenerate any
03980                 compound nodes which have been removed
03981     Outputs:    TRUE if all when well, FALSE otherwise
03982     Purpose:    Removes all compound nodes
03983 
03984 ********************************************************************************************/
03985 BOOL OpBlendNodes::RemoveCompoundNodes(CompoundNodeTreeFactoryList * pList)
03986 {
03987 /*  Node * pNewStartNode = NULL;
03988     Node * pNewEndNode = NULL;
03989     
03990     if (!RemoveCompoundNodesFromNode(pRefStart->pNode, &pNewStartNode, NULL))
03991     {
03992     ERROR3("OpBlendNodes::RemoveCompoundNodes - Removal of compound nodes\n\
03993         from start node has failed");
03994     }
03995     
03996     if (!RemoveCompoundNodesFromNode(pRefEnd->pNode, &pNewEndNode, pList))
03997     {
03998     ERROR3("OpBlendNodes::RemoveCompoundNodes - Removal of compound nodes\n\
03999         from end node has failed");
04000     }
04001 
04002     pRefStart->pNode = (NodeRenderableInk *)pNewStartNode;
04003     pRefEnd->pNode   = (NodeRenderableInk *)pNewEndNode;
04004 
04005     GetApplication()->UpdateSelection();*/
04006     
04007     
04008     return TRUE;
04009 }
04010 
04011 /********************************************************************************************
04012 
04013 >   BOOL OpBlendNodes::RemoveCompoundNodesFromNode(Node * pNode, Node ** pRetnNode,
04014                                 CompoundNodeTreeFactoryList * pList)
04015 
04016     Author:     David_McClarnon (Xara Group Ltd) <camelotdev@xara.com>
04017     Created:    4/8/99
04018     Inputs:     The node to remove all compound nodes from, and the return node to be
04019                 used in the blend as well as the list of compound node factories needed
04020                 to regenerate the compounds nodes removed
04021     Outputs:    The return node to use in the blend
04022     Returns:    TRUE if all when well, FALSE otherwise
04023     Purpose:    Removes all compound nodes from the given node (i.e. unshadows and unbevels 
04024                 nodes)
04025 
04026 ********************************************************************************************/
04027 BOOL OpBlendNodes::RemoveCompoundNodesFromNode(Node * pNode, Node ** pRetnNode,
04028                                                CompoundNodeTreeFactoryList * pList)
04029 {
04030     /*if (!pNode)
04031         return FALSE;
04032 
04033     if (!pRetnNode)
04034         return FALSE;
04035 
04036     // first, get the first parent in the hierarchy which has a PromoteHitTestOnChildrenToMe() set
04037     Node * pParent = pNode->FindParent();
04038 
04039     Node * pPromoteParent = NULL;
04040 
04041     *pRetnNode = pNode;
04042 
04043     while (pParent)
04044     {
04045         if (!pParent->PromoteHitTestOnChildrenToMe())
04046         {
04047             pPromoteParent = pParent;
04048         }
04049 
04050         pParent = pParent->FindParent();
04051     }
04052 
04053     // if we haven't found one - this means that there is one under me
04054     if (!pPromoteParent)
04055     {
04056         pPromoteParent = pNode;
04057     }
04058 
04059     List CompoundNodeList;
04060 
04061     // get the compound node list for the whole subtree
04062     BevelTools::GetAllNodesUnderNode(pPromoteParent, &CompoundNodeList, CC_RUNTIME_CLASS(NodeCompound));
04063 
04064     NodeListItem * pNodeToGroupItem = (NodeListItem *)CompoundNodeList.GetHead();
04065     Node         * pChildNode       = NULL;
04066     Node         * pNextChildNode   = NULL;
04067     NodeHidden   * pHidden          = NULL;
04068     Node * pChildNode2 = NULL;
04069 
04070     BOOL ok = TRUE;
04071 
04072     // first, invalidate all the compound node's regions
04073     while (pNodeToGroupItem && ok)
04074     {
04075         if (ok)
04076             ok = DoInvalidateNodeRegion((NodeRenderableBounded *)pNodeToGroupItem->pNode, TRUE);                
04077 
04078         pNodeToGroupItem = (NodeListItem *)CompoundNodeList.GetNext(pNodeToGroupItem);
04079     }           
04080 
04081     // now, run through all the compound nodes turning them into groups
04082     pNodeToGroupItem = (NodeListItem *)CompoundNodeList.GetHead();
04083 
04084     while (pNodeToGroupItem && ok)
04085     {
04086         if (pNodeToGroupItem->pNode->ShouldITransformWithChildren())
04087         {
04088             
04089             // find out how many children we have -
04090             // if there are more than 1 that don't need their parents then we need to group them
04091             // otherwise, just promote them
04092             INT32 NumChildren = 0;
04093 
04094             pChildNode2 = pNodeToGroupItem->pNode->FindFirstChild();
04095 
04096             while (pChildNode2)
04097             {
04098                 if (!pChildNode2->NeedsParent(pNodeToGroupItem->pNode) &&
04099                     !pChildNode2->IsAnAttribute()                      &&
04100                     !pChildNode2->IsNodeHidden()                       &&
04101                     pChildNode2->IsAnObject())
04102                 {
04103                     NumChildren ++;
04104                 }
04105 
04106                 pChildNode2 = pChildNode2->FindNext();
04107             }
04108 
04109             // localise the common attributes underneath the compound node
04110             if (ok)
04111                 ok = DoLocaliseCommonAttributes((NodeRenderableInk *)pNodeToGroupItem->pNode,
04112                 TRUE);              
04113             
04114             if (NumChildren > 1)
04115             {
04116                 // make a new group node and transfer all child nodes which don't need their
04117                 // parents into this group node
04118                 NodeGroup * pGroup = NULL;
04119                 ALLOC_WITH_FAIL(pGroup, new NodeGroup, this);
04120                 
04121                 *pRetnNode = pGroup;
04122                 
04123                 // get the first child
04124                 pChildNode = pNodeToGroupItem->pNode->FindFirstChild();
04125                 
04126                 while (pChildNode && ok)
04127                 {
04128                     pNextChildNode = pChildNode->FindNext();
04129                     
04130                     if (!pChildNode->NeedsParent(pNodeToGroupItem->pNode) &&
04131                         pChildNode->IsAnObject() && !pChildNode->IsNodeHidden())
04132                     {
04133                         if (ok) 
04134                             ok = DoMoveNode(pChildNode, pGroup, LASTCHILD);
04135                     }
04136                     // continuity check
04137                     else if (pChildNode->ShouldITransformWithChildren())
04138                     {
04139                         ERROR3("Found a child node which is a compound node !");
04140                     }
04141                     
04142                     pChildNode = pNextChildNode;
04143                 }
04144                 
04145                 // insert the group node into the tree, and hide the original node
04146                 
04147                 if (ok)
04148                 {
04149                     ok = DoInsertNewNode(   pGroup,
04150                         pNodeToGroupItem->pNode,
04151                         NEXT,
04152                         FALSE);
04153                 }
04154             }
04155             else
04156             {
04157                 // if there's only one node, then move it
04158                 // find the first node that NeedParent returns FALSE
04159                 pChildNode = pNodeToGroupItem->pNode->FindFirstChild();
04160 
04161                 while (pChildNode->NeedsParent(pNodeToGroupItem->pNode)  ||
04162                        pChildNode->IsAnAttribute()                       ||
04163                        pChildNode->IsNodeHidden()                        ||
04164                        !pChildNode->IsAnObject()
04165                        )
04166                 {
04167                     pChildNode = pChildNode->FindNext();
04168                 }
04169 
04170                 // ok, move the node & set the passback variable
04171                 if (ok)
04172                 {
04173                     ok = DoMoveNode(pChildNode, pNodeToGroupItem->pNode, NEXT);
04174 
04175                     *pRetnNode = pChildNode;
04176                 }
04177             }
04178 
04179             // before we hide the original, add its class factory to the list
04180             if (pList)
04181             {
04182                 CompoundNodeTreeFactory * pFactory = NULL;
04183                 
04184                 ((NodeCompound *)pNodeToGroupItem->pNode)->CreateCompoundNodeTreeFactory(&pFactory);
04185                 
04186                 if (pFactory)
04187                 {
04188                     pList->AddItem(pFactory);
04189                 }
04190             }
04191                 
04192             // hide the original
04193             if (ok)
04194             {
04195                 ok = DoHideNode ( pNodeToGroupItem->pNode,
04196                                   TRUE,
04197                                   &pHidden,
04198                                   TRUE);
04199             }
04200         }
04201 
04202         pNodeToGroupItem = (NodeListItem *)CompoundNodeList.GetNext(pNodeToGroupItem);
04203     }
04204 
04205     CompoundNodeList.DeleteAll();
04206     */
04207 
04208     return TRUE;
04209 }
04210 
04211 
04212 
04213 /********************************************************************************************
04214 
04215 >   BOOL OpBlendNodes::DoBlendObjects()
04216 
04217     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
04218     Created:    12/10/94
04219     Inputs:     (data is taken from pRefStart and pRefEnd member vars)
04220     Outputs:    -
04221     Returns:    TRUE if all when well, FALSE otherwise
04222     Purpose:    This blends two objects together to form a new blend object in the tree.
04223                 All the data that's required is stored in pRefStart (for the start object)
04224                 and pRefEnd (for the end object).
04225                 It assumes that neither the start or end objects are blends, so no clever
04226                 merging of blend objects will take place here.              
04227 
04228 ********************************************************************************************/
04229 
04230 BOOL OpBlendNodes::DoBlendObjects()
04231 {
04232     // DMc - are the nodes to be blended shadowed or bevelled ?
04233 //  BOOL bIsShadowed = FALSE;
04234 //  BOOL bIsBevelled = FALSE;
04235 
04236     // What type of blend op are we doing?
04237     OpType = BLENDOPTYPE_NEW;
04238 
04239     // Are we going mad? Better check our onions...
04240     ERROR3IF_PF(pRefStart        == NULL,("pRefStart == NULL"));
04241     ERROR3IF_PF(pRefEnd          == NULL,("pRefEnd == NULL"));
04242     ERROR3IF_PF(pRefStart->pNode == NULL,("pRefStart->pNode == NULL"));
04243     ERROR3IF_PF(pRefEnd  ->pNode == NULL,("pRefEnd->pNode == NULL"));
04244 
04245     BOOL ok = DeterminBlendObjectsProcessorHit ();
04246 
04247     if (!ok)
04248     {
04249         return (FALSE);
04250     }
04251 
04252     ok = DoStartSelOp(TRUE,TRUE);
04253 
04254     CompoundNodeTreeFactoryList CFList;
04255 
04256     RemoveCompoundNodes(&CFList);
04257 
04258     NodeBlend* pNodeBlend;
04259 
04260     // DMc
04261     // move the end nodes upwards to include compound nodes
04262     Node * pParent = pRefStart->pNode;
04263 
04264     while (pParent)
04265     {
04266         if (!pParent->PromoteHitTestOnChildrenToMe() && pParent->IsAnObject())
04267         {
04268             pRefStart->pNode = (NodeRenderableInk *)pParent;
04269         }
04270 
04271         pParent = pParent->FindParent();
04272     }
04273 
04274     pParent = pRefEnd->pNode;
04275 
04276     while (pParent)
04277     {
04278         if (!pParent->PromoteHitTestOnChildrenToMe() && pParent->IsAnObject())
04279         {
04280             pRefEnd->pNode = (NodeRenderableInk *)pParent;
04281         }
04282 
04283         pParent = pParent->FindParent();
04284     }
04285 
04286     BlobManager* pBlobManager = GetApplication()->GetBlobManager();
04287     ENSURE(pBlobManager, "Can't get BlobManager");
04288 
04289     if (ok) ok = (Tool::GetCurrent() != NULL);
04290     if (ok) pBlobManager->RenderToolBlobsOff(Tool::GetCurrent(), pRefStart->pSpread,NULL);
04291     if (ok) NodeRenderableInk::DeselectAll(FALSE,FALSE);
04292 
04293     if (ok) ALLOC_WITH_FAIL(pNodeBlend,new NodeBlend,this);
04294     if (ok) ok = (pNodeBlend != NULL);
04295 
04296     if (ok) pRefStart->pNode->SetSelected(FALSE);
04297     if (ok) pRefEnd  ->pNode->SetSelected(FALSE);
04298 
04299     if (ok) ok = DoInsertNewNode(pNodeBlend,pRefEnd->pNode,NEXT,FALSE,FALSE,FALSE,FALSE);
04300     if (ok) ok = DoMoveNode(pRefStart->pNode,pNodeBlend,LASTCHILD);
04301     if (ok) ok = DoMoveNode(pRefEnd  ->pNode,pNodeBlend,LASTCHILD);
04302     if (ok) ok = DoCreateBlender(pRefStart->pNode,pRefStart->Index,pRefEnd->pNode,pRefEnd->Index,pRefStart->pNode,NEXT);
04303     if (ok) ok = DoInvalidateNodeRegion(pNodeBlend,TRUE);
04304 
04305     // Simon - Factor out the blend's common attributes
04306     if (ok) ok = DoFactorOutCommonChildAttributes(pNodeBlend);
04307 
04308 
04309     if (ok)
04310         pNodeBlend->SetSelected(TRUE);      // Select the new node
04311 
04312     // Throw away the selection cache
04313     GetApplication()->UpdateSelection();
04314 
04315     return (ok);
04316 }
04317 
04318 
04319 
04320 /********************************************************************************************
04321 
04322 >   BOOL OpBlendNodes::DeterminBlendObjectsProcessorHit ()
04323 
04324     Author:     Chris_Snook (Xara Group Ltd) <camelotdev@xara.com>
04325     Created:    6/9/2000
04326     Inputs:     -
04327     Outputs:    -
04328     Returns:    TRUE if should continue, FALSE otherwise
04329     Purpose:    This function provides a quick (and rough) estimate on the amount of work
04330                 that will have to be done to create the blend.  This estimate is based upon
04331                 the total length of all paths that exist within the start and end nodes.
04332                 An attempt is also made at altering the 'threshold' based upon the CPU type.
04333 
04334                 This system could probably be enhanced - but for now it seems to work fine.
04335 
04336 ********************************************************************************************/
04337 
04338 BOOL OpBlendNodes::DeterminBlendObjectsProcessorHit ()
04339 {
04340     Node* pStart = pRefStart->pNode;
04341     Node* pEnd = pRefEnd->pNode;
04342 
04343     INT32 complexityEstimate1 = pStart->EstimateNodeComplexity (NULL);
04344     INT32 complexityEstimate2 = pEnd->EstimateNodeComplexity (NULL);
04345 
04346     INT32 generatedPathsEstimate = 3*complexityEstimate1 + 2*complexityEstimate2;
04347 
04348     static BOOL foundCPU = FALSE;
04349     static INT32 pathsBeforeAsk = 0;
04350 
04351     if (!foundCPU)
04352     {
04353 /*      SYSTEM_INFO systemInfo;
04354         GetSystemInfo (&systemInfo);
04355 
04356         if (systemInfo.dwProcessorType == PROCESSOR_INTEL_386)
04357         {
04358             pathsBeforeAsk = 6000;
04359         }
04360         else if (systemInfo.dwProcessorType == PROCESSOR_INTEL_486)
04361         {
04362             pathsBeforeAsk = 12000;
04363         }
04364         else if (systemInfo.dwProcessorType == PROCESSOR_INTEL_PENTIUM)
04365         {
04366             pathsBeforeAsk = 20000;
04367         }
04368         else */
04369         {
04370             // assume its faster than a pentium .... (in 2005 this will alwys be the case!)
04371             pathsBeforeAsk = 24000;
04372         }
04373         foundCPU = TRUE;
04374     }
04375 
04376     if (generatedPathsEstimate > pathsBeforeAsk)
04377     {
04378         // Load and build the question text.
04379         String_256 QueryString(_R(IDS_ASKLONGJOB));
04380                         
04381         // The only way of bringing up a box with a string in it
04382         Error::SetError(0, QueryString, 0);
04383         INT32 DlgResult = InformMessage(0, _R(IDS_YES), _R(IDS_NO));
04384         Error::ClearError();
04385 
04386         switch (DlgResult)
04387         {
04388             case 1:     // YES
04389                 return (TRUE);
04390             break;
04391             case 2:     // NO
04392                 return (FALSE);         // break out of this stuff!
04393         }
04394     }
04395 
04396     return (TRUE);
04397 }
04398 
04399 /********************************************************************************************
04400 
04401 >   BOOL OpBlendNodes::DoCreateBlender( NodeRenderableInk* pNodeStart,INT32 PathIndexStart,
04402                                         NodeRenderableInk* pNodeEnd,  INT32 PathIndexEnd,
04403                                         Node* pContextNode, AttachNodeDirection AttachDir)
04404 
04405     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
04406     Created:    12/10/94 
04407     Inputs:     pNodeStart      = the start node to blend
04408                 PathIndexStart  = index into start path, if applicable (-1 means not applicable)
04409                 pNodeEnd        = the end node to blend
04410                 PathIndexEnd    = index into end   path, if applicable (-1 means not applicable)
04411                 pContextNode    = ptr to context for insertion of the blender
04412                 AttachDir       = how to attach the blender to the tree in relation to pContextNode
04413                 
04414     Outputs:    -
04415     Returns:    TRUE if all when well, FALSE otherwise
04416     Purpose:    This forms a blender object in the tree that can blend pRefStart->pNode
04417                 to pRefEnd->pNode.
04418 
04419 ********************************************************************************************/
04420 
04421 BOOL OpBlendNodes::DoCreateBlender( NodeRenderableInk* pNodeStart,INT32 PathIndexStart,
04422                                     NodeRenderableInk* pNodeEnd,  INT32 PathIndexEnd,
04423                                     Node* pContextNode, AttachNodeDirection AttachDir)
04424 {
04425     ERROR2IF(pNodeStart == NULL,FALSE,"pNodeStart is NULL");
04426     ERROR2IF(pNodeEnd   == NULL,FALSE,"pNodeEnd   is NULL");
04427 
04428     // Work out how many paths will be passed back
04429     // The sum will be used to determine the final count for the progress bar
04430     BecomeA TestStart(BECOMEA_TEST, CC_RUNTIME_CLASS(NodePath));
04431     BecomeA TestEnd(BECOMEA_TEST, CC_RUNTIME_CLASS(NodePath));
04432     TestStart.ResetCount();
04433     TestEnd.ResetCount();
04434     pNodeStart->CanBecomeA(&TestStart);
04435     pNodeEnd  ->CanBecomeA(&TestEnd);
04436 
04437     // Create the hourglass with the correct final count.
04438     Progress Hourglass(_R(IDS_BLENDING), INT32(TestStart.GetCount() + TestEnd.GetCount()) );
04439 
04440     BOOL ok = FALSE;
04441     NodeBlender* pNodeBlender;
04442 
04443     ALLOC_WITH_FAIL(pNodeBlender,new NodeBlender,this);
04444     ok = (pNodeBlender != NULL);
04445 
04446     // If either of the indexes are -ve, make sure both are -ve
04447     if (PathIndexStart < 0 || PathIndexEnd < 0)
04448         PathIndexStart = PathIndexEnd = -1;
04449 
04450     if (ok) ok = DoInsertNewNode(pNodeBlender,pContextNode,AttachDir,FALSE,FALSE,FALSE,FALSE);
04451     if (ok) ok = pNodeBlender->Initialise(pNodeStart,pNodeEnd,PathIndexStart,PathIndexEnd,this,&Hourglass,FALSE);
04452     
04453     pRefStart->pNode = pNodeStart;
04454     pRefEnd->pNode = pNodeEnd;
04455     return (ok);
04456 }
04457 
04458 //----------------------------------------------------------------------------
04459 //----------------------------------------------------------------------------
04460 //----------------------------------------------------------------------------
04461 
04462 /********************************************************************************************
04463 
04464 >   virtual void OpBlendNodes::RenderMyDragBlobs()
04465 
04466     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
04467     Created:    11/10/94
04468     Inputs:     Rect    - The region that needs the blobs to be drawn
04469                 pSpread - The spread that the drawing will happen on
04470     Purpose:    Draws an EORed rectangle defined by AnchorPoint and DragPoint
04471 
04472 ********************************************************************************************/
04473 
04474 void OpBlendNodes::RenderMyDragBlobs()
04475 {
04476     INT32 x0 = pRefStart->PointerPos.x;
04477     INT32 y0 = pRefStart->PointerPos.y;
04478     INT32 x1 = pRefEnd  ->PointerPos.x;
04479     INT32 y1 = pRefEnd  ->PointerPos.y;
04480 
04481     DocRect Rect = DocRect( min(x0,x1),min(y0,y1),max(x0,x1),max(y0,y1));
04482 
04483     RenderDragBlobs(Rect, pRefStart->pSpread, FALSE);
04484 }
04485 
04486 
04487 
04488 /********************************************************************************************
04489 
04490 >   void OpBlendNodes::RenderDragBlobs(DocRect Rect,Spread* pSpread, BOOL bSolidDrag)
04491 
04492     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
04493     Created:    11/10/94
04494     Inputs:     Rect    - The region that needs the blobs to be drawn
04495                 pSpread - The spread that the drawing will happen on
04496     Purpose:    Draws an EORed rectangle defined by AnchorPoint and DragPoint
04497 
04498 ********************************************************************************************/
04499 
04500 void OpBlendNodes::RenderDragBlobs(DocRect Rect,Spread* pSpread, BOOL bSolidDrag)
04501 {
04502     if (pRefStart->PointerPos == pRefEnd->PointerPos)
04503         return;
04504 
04505     // Get the scaled pixel size for the view.
04506     FIXED16 ScaledPixelWidth,
04507             ScaledPixelHeight;
04508     GetWorkingView()->GetScaledPixelSize(&ScaledPixelWidth, &ScaledPixelHeight);
04509 
04510     MILLIPOINT LineWidth = ScaledPixelWidth.MakeLong()*2;
04511     StockColour Colour = COLOUR_BLACK;
04512 
04513     if (pRefStart->RemapRef == pRefEnd->RemapRef && pRefStart->RemapRef > 0 &&
04514         pRefStart->AStartNode != pRefEnd->AStartNode)
04515     {
04516         Colour = COLOUR_RED;
04517         LineWidth += LineWidth;
04518     }
04519     else if ((pRefStart->pNode != NULL) &&
04520              (pRefEnd  ->pNode != NULL) &&
04521              (pRefStart->pNode != pRefEnd->pNode) &&
04522              (pRefStart->Index >= 0) &&
04523              (pRefEnd  ->Index >= 0))
04524     {
04525         Colour = COLOUR_RED;
04526         LineWidth += LineWidth;
04527     }
04528 
04529     // Inflate for the width of the line
04530     Rect.Inflate(LineWidth);
04531 
04532     RenderRegion* pRegion = DocView::RenderOnTop( &Rect, pSpread, UnclippedEOR );
04533 
04534     while ( pRegion != NULL )
04535     {
04536         // Set the line colour and Draw the rect
04537         pRegion->SetLineColour(Colour);
04538 
04539         // Draw the line
04540         pRegion->SetLineWidth(LineWidth);
04541         pRegion->DrawLine(pRefStart->PointerPos,pRefEnd->PointerPos);
04542 
04543         // Get the Next render region
04544         pRegion = DocView::GetNextOnTop( &Rect );
04545     }
04546 }
04547 
04548 
04549 
04550 /********************************************************************************************
04551 
04552 >   BOOL OpBlendNodes::Declare()
04553 
04554     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
04555     Created:    11/10/94
04556     Returns:    TRUE if all went OK, FALSE otherwise
04557     Purpose:    Adds the operation to the list of all known operations
04558 
04559 ********************************************************************************************/
04560 
04561 BOOL OpBlendNodes::Declare()
04562 {
04563     return (RegisterOpDescriptor(
04564                                 0, 
04565                                 _R(IDS_BLEND_TOOL),
04566                                 CC_RUNTIME_CLASS(OpBlendNodes), 
04567                                 OPTOKEN_BLENDNODES,
04568                                 OpBlendNodes::GetState,
04569                                 0,          /* help ID */
04570                                 _R(IDBBL_NOOP), /* bubble ID */
04571                                 0           /* bitmap ID */
04572                                 ));
04573 }
04574 
04575 
04576 /********************************************************************************************
04577 
04578 >   static OpState OpBlendNodes::GetState(String_256* Description, OpDescriptor*)
04579 
04580     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
04581     Created:    11/10/94
04582     Outputs:    Description - GetState fills this string with an approriate description
04583                 of the current state of the push tool
04584     Returns:    The state of the operation, so that menu items (ticks and greying can be
04585                 done properly
04586     Purpose:    Find out the state of the operation at the specific time
04587 
04588 ********************************************************************************************/
04589 
04590 OpState OpBlendNodes::GetState(String_256* Description, OpDescriptor*)
04591 {
04592     OpState State;
04593     
04594     return State;
04595 }
04596 
04597 /********************************************************************************************
04598 
04599 >   virtual void OpBlendNodes::GetOpName(String_256* OpName) 
04600 
04601     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
04602     Created:    11/10/94
04603     Inputs:     OpName = ptr to str to place op name in
04604     Outputs:    The undo string for the operation
04605     Returns:    
04606     Purpose:    The GetOpName fn is overridden so that we return back a description 
04607                 appropriate to the type of attribute that the operation applies. 
04608     Errors:     -
04609     SeeAlso:    -
04610 
04611 ********************************************************************************************/
04612 
04613 void OpBlendNodes::GetOpName(String_256* OpName) 
04614 { 
04615     switch (OpType)
04616     {
04617         case BLENDOPTYPE_NEW:
04618         case BLENDOPTYPE_BLENDANDOBJECT:
04619         case BLENDOPTYPE_BLENDANDBLEND:
04620             *OpName = String_256(_R(IDS_BLEND_UNDO));
04621             break;
04622 
04623         case BLENDOPTYPE_REMAP:
04624             *OpName = String_256(_R(IDS_REMAP_UNDO));
04625             break;
04626 
04627         default:
04628             break;
04629     }
04630 }  
04631 
04632 /********************************************************************************************
04633 
04634 >   virtual BOOL OpBlendNodes::Undo()
04635 
04636     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
04637     Created:    15/11/94
04638     Inputs:     -
04639     Outputs:    -
04640     Returns:    TRUE if all OK, FALSE otherwise
04641     Purpose:    Gets called whenever this operation is undone.
04642     Errors:     -
04643     SeeAlso:    -
04644 
04645 ********************************************************************************************/
04646 
04647 BOOL OpBlendNodes::Undo()
04648 {
04649     // Pre-undo stuff
04650     switch (OpType)
04651     {
04652         case BLENDOPTYPE_NEW:
04653         case BLENDOPTYPE_BLENDANDBLEND:
04654         case BLENDOPTYPE_REMAP:
04655         case BLENDOPTYPE_BLENDANDOBJECT:
04656         case BLENDOPTYPE_BLENDBLENDONPATH:
04657             break;
04658         default:
04659             ERROR3_PF(("Unknown blend OpType (%d)",OpType));
04660             break;
04661     }
04662 
04663     // Do the Undo
04664     SelOperation::Undo();
04665 
04666     // Post-undo stuff
04667     switch (OpType)
04668     {
04669         case BLENDOPTYPE_NEW:
04670         case BLENDOPTYPE_REMAP:
04671             break;
04672 
04673         case BLENDOPTYPE_BLENDANDBLEND:
04674         /*
04675             // Check that we have a blend object
04676             ERROR3IF(pNodeBlendStart==NULL,"pNodeBlendStart == NULL");
04677             ERROR3IF(pNodeBlendEnd  ==NULL,"pNodeBlendEnd == NULL");
04678             if (pNodeBlendStart == NULL || pNodeBlendEnd == NULL) return TRUE;
04679             DeinitAndReinitBlend(pNodeBlendStart);
04680             DeinitAndReinitBlend(pNodeBlendEnd);
04681         */
04682             break;
04683 
04684         case BLENDOPTYPE_BLENDANDOBJECT:
04685         /*
04686             // Check that we have a blend object
04687             ERROR3IF(pNodeBlend==NULL,"pNodeBlend == NULL");
04688             if (pNodeBlend == NULL) return TRUE;
04689             DeinitAndReinitBlend(pNodeBlend);
04690         */
04691             break;
04692 
04693         default:
04694             break;
04695     }
04696 
04697     return TRUE;
04698 }
04699 
04700 /********************************************************************************************
04701 
04702 >   virtual BOOL OpBlendNodes::Redo()
04703 
04704     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
04705     Created:    15/11/94
04706     Inputs:     -
04707     Outputs:    -
04708     Returns:    TRUE if all OK, FALSE otherwise
04709     Purpose:    Gets called whenever this operation is Redone.
04710     Errors:     -
04711     SeeAlso:    -
04712 
04713 ********************************************************************************************/
04714 
04715 BOOL OpBlendNodes::Redo()
04716 {
04717     // Pre-Redo stuff
04718     switch (OpType)
04719     {
04720         case BLENDOPTYPE_NEW:
04721         case BLENDOPTYPE_BLENDANDBLEND:
04722         case BLENDOPTYPE_REMAP:
04723         case BLENDOPTYPE_BLENDANDOBJECT:
04724         case BLENDOPTYPE_BLENDBLENDONPATH:
04725             break;
04726         default:
04727             ERROR3_PF(("Unknown blend OpType (%d)",OpType));
04728             break;
04729     }
04730     SelRange* pSel = GetApplication()->FindSelection();
04731     /*Node* pNode =*/ pSel->FindFirst();
04732 
04733     // Do the Redo
04734     SelOperation::Redo();
04735 
04736     // Post-Redo stuff
04737     switch (OpType)
04738     {
04739         case BLENDOPTYPE_NEW:
04740         case BLENDOPTYPE_REMAP:
04741             break;
04742 
04743         case BLENDOPTYPE_BLENDANDBLEND:
04744         /*
04745             // Check that we have a blend object
04746             ERROR3IF(pNodeBlendStart==NULL,"pNodeBlendStart == NULL");
04747             if (pNodeBlendStart == NULL) return TRUE;
04748             DeinitAndReinitBlend(pNodeBlendStart);
04749         */
04750             break;
04751 
04752         case BLENDOPTYPE_BLENDANDOBJECT:
04753         /*
04754             // Check that we have a blend object
04755             ERROR3IF(pNodeBlend==NULL,"pNodeBlend == NULL");
04756             if (pNodeBlend == NULL) return TRUE;
04757             DeinitAndReinitBlend(pNodeBlend);
04758         */
04759             break;
04760 
04761         default:
04762             break;
04763     }
04764 
04765     return TRUE;
04766 }
04767 
04768 
04769 /********************************************************************************************
04770 
04771 >   void OpBlendNodes::GetStartAndEndNodes(NodeRenderableink** pInk,BOOL StartBlend)
04772 
04773     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
04774     Created:    27/9/99
04775     Inputs:     -
04776     Outputs;    ppInk, the starting node of the blend
04777                 StartBlend - do we wish to find the start or end 
04778     Returns:    TRUE if successful , FALSE otherwise, or if we are not blending two blends. 
04779     Purpose:    when blending from two blends, this retrieves the node closest to the actual 
04780                 point where the blend began or ended
04781 ********************************************************************************************/
04782 
04783 BOOL OpBlendNodes::GetNodeClosestToPoint(NodeRenderableInk** ppInk, BOOL StartBlend)
04784 {
04785 //  ERROR3IF_PF(pNodeBlendStart == NULL; "Nodeblend start is NULL");
04786 //  ERROR3IF_PF(pNodeBlendEnd == NULL; "Nodeblend end is NULL");
04787 
04788     DocCoord Point;
04789 //  Node* pStart = NULL;
04790 //  Node* pEnd = NULL;
04791 //  BOOL ok = FALSE;
04792     NodeBlend* pBlend = NULL;
04793     
04794     if (StartBlend)
04795     {
04796         Point = pRefStart->PointerPos;
04797         //ok = pNodeBlendStart->GetStartAndEndNodes(&pStart, &pEnd);
04798         pBlend = pNodeBlendStart;
04799     }
04800     else
04801     {
04802         Point = pRefEnd->PointerPos;
04803         //ok = pNodeBlendEnd->GetStartAndEndNodes(&pStart, &pEnd);
04804         pBlend = pNodeBlendEnd;
04805     }
04806     
04807     
04808     double ClosestDistance = 9999999999999.9;
04809     Node* pClosestNode = NULL;
04810     Node* pNode = pBlend->FindFirstChild();
04811     while (pNode != NULL)
04812     {
04813         // we are looking for inks but don't want blenders or nodeblendpaths
04814         // could do with some virtual functions here
04815         if (pNode->IS_KIND_OF(NodeRenderableInk) && 
04816             (!pNode->IS_KIND_OF(NodeBlender)) && 
04817             (!pNode->IS_KIND_OF(NodeBlendPath)))
04818         {
04819             DocRect Rect = ((NodeRenderableInk*)pNode)->GetBoundingRect();
04820             Coord Centre = (Coord)(Rect.Centre());
04821             double Distance = Point.Distance(Centre);
04822         
04823             if (Distance < ClosestDistance)
04824             {
04825                 pClosestNode = pNode;
04826                 ClosestDistance = Distance;
04827             }
04828         }
04829         pNode = pNode->FindNext();
04830     }
04831     if (pClosestNode != NULL)
04832     {
04833         *ppInk = (NodeRenderableInk*)pClosestNode;
04834         return TRUE;
04835     }
04836     else
04837     {
04838         ERROR3("Couldn't find a node close to point");
04839         return FALSE;
04840     }
04841 }
04842 
04843 
04844 
04845 /********************************************************************************************
04846 
04847 >   BOOL OpBlendNodes::DeinitAndReintBlend(NodeBlend* pThisNodeBlend)
04848 
04849     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
04850     Created:    15/11/94
04851     Inputs:     pThisNodeBlend = ptr to a blend node
04852     Outputs:    -
04853     Returns:    TRUE if all OK, FALSE otherwise
04854     Purpose:    This gets a list of all the blenders in pNodeBlend.
04855                 The it calls Deinit() followed by Reinit() on the blenders.
04856                 The Reinit() is sent the actual object pointers to the objects they blended.                
04857     Errors:     -
04858     SeeAlso:    -
04859 
04860 ********************************************************************************************/
04861 
04862 /*BOOL OpBlendNodes::DeinitAndReinitBlend(NodeBlend* pThisNodeBlend)
04863 {
04864     BOOL ok = FALSE;
04865     List BlenderList;
04866     if (RecordBlenderInfo(BlenderList,pThisNodeBlend))
04867     {
04868         DeinitBlenders(BlenderList);
04869         ok = ReinitBlenders(BlenderList);
04870     }
04871     BlenderList.DeleteAll();
04872 
04873     return ok;
04874 }
04875 */
04876 
04877 
04878 class BlendPathBecomeA : public BecomeA
04879 {
04880 public:
04881     BlendPathBecomeA(UndoableOperation* pOp) : BecomeA(BECOMEA_PASSBACK,CC_RUNTIME_CLASS(NodePath),pOp)
04882     {
04883         m_pNodePath         = NULL;
04884         m_pAttrMap          = NULL;
04885     }
04886 
04887     ~BlendPathBecomeA()
04888     {
04889         if (m_pNodePath != NULL)
04890             delete m_pNodePath;
04891 
04892         if (m_pAttrMap != NULL)
04893         {
04894             m_pAttrMap->DeleteAttributes();
04895             delete m_pAttrMap;
04896         }
04897     }
04898 
04899     virtual BOOL PassBack(NodeRenderableInk* pNewNode,NodeRenderableInk* pCreatedByNode,CCAttrMap* pAttrMap=NULL)
04900     {
04901         ERROR2IF(pCreatedByNode == NULL,FALSE,"pCreatedByNode == NULL");
04902         ERROR2IF(pNewNode == NULL,FALSE,"pNewNode == NULL");
04903         ERROR2IF(!pNewNode->IsKindOf(CC_RUNTIME_CLASS(NodePath)),FALSE,"pNewNode not a kind of NodePath");
04904 
04905         BOOL ok = FALSE;
04906         if (m_pNodePath == NULL)
04907         {
04908             m_pNodePath = new NodePath;
04909             if (m_pNodePath)
04910             {
04911                 if (!m_pNodePath->SetUpPath())
04912                 {
04913                     delete m_pNodePath;
04914                     m_pNodePath = NULL;
04915                 }
04916 
04917             }
04918         }
04919 
04920         if (m_pNodePath)
04921         {
04922             Path& PassedPath = ((NodePath*)pNewNode)->InkPath;
04923             INT32 Len = PassedPath.GetNumCoords();
04924 
04925                     ok = m_pNodePath->InkPath.MakeSpaceInPath(Len);
04926             if (ok) ok = m_pNodePath->InkPath.MergeTwoPaths(PassedPath);
04927         }
04928 
04929         if (m_pAttrMap == NULL)
04930         {
04931             if (pAttrMap == NULL)
04932             {
04933                 CCAttrMap* pTempAttrMap = CCAttrMap::MakeAppliedAttrMap(pCreatedByNode);
04934                 if (pTempAttrMap)
04935                 {
04936                     m_pAttrMap = pTempAttrMap->Copy();
04937                     delete pTempAttrMap;
04938                 }
04939             }
04940             else
04941                 m_pAttrMap = pAttrMap;
04942         }
04943         else
04944         {
04945             if (pAttrMap != NULL)
04946             {
04947                 pAttrMap->DeleteAttributes();
04948                 delete pAttrMap;
04949                 pAttrMap = NULL;
04950             }
04951         }
04952 
04953         if (pNewNode)
04954         {
04955             pNewNode->CascadeDelete();
04956             delete pNewNode;
04957             pNewNode = NULL;
04958         }
04959 
04960         return ok;
04961     }
04962 
04963     NodePath*   GetNodePath()   { return m_pNodePath; }
04964     CCAttrMap*  GetAttrMap()    { return m_pAttrMap; }
04965 
04966 private:
04967     NodePath*   m_pNodePath;
04968     CCAttrMap*  m_pAttrMap;
04969 };
04970 
04971 
04973 //  OpAddBlendPath
04974 //
04975 // Creates a single path out of all the selected objects that aren't blends
04976 // and applies them to all the selected blends.
04977 
04978 /********************************************************************************************
04979 
04980 >   void OpAddBlendPath::Do(OpDescriptor*)
04981 
04982     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
04983     Created:    28/4/99
04984     Returns:    -
04985     Purpose:    This removes all the selected blend objects, leaving the original objects in the 
04986                 tree.
04987 
04988 ********************************************************************************************/
04989 
04990 
04991 void OpAddBlendPath::Do(OpDescriptor*)
04992 {
04993     SelRange* pSel = GetApplication()->FindSelection();
04994 
04995     BOOL ok = (pSel != NULL);
04996     
04997 
04998     if (ok) ok = DoStartSelOp(FALSE,FALSE);
04999     
05000     /* DY 16/9 in order to render the BlendPath either on
05001     top of or below the rest of the blend we need to know where it was in the 
05002     tree before we began */
05003     BOOL BlendPathOnTop = FALSE;
05004     if (ok)
05005          BlendPathOnTop = IsBlendPathOnTopOfBlend(pSel);
05006 
05007     if (ok)
05008     {
05009         // Get an ObjChangeParam ready so we can tell the selected objects what we hope to do 
05010         // to the them (i.e. effectively delete them and use their paths).
05011         ObjChangeFlags cFlags;
05012         cFlags.DeleteNode = TRUE;
05013         ObjChangeParam ObjChange(OBJCHANGE_STARTING,cFlags,NULL,this);
05014 
05015         BlendPathBecomeA BPBecomeA(this);
05016         BecomeA TestPathBecomeA(BECOMEA_TEST, CC_RUNTIME_CLASS(NodePath));
05017 
05018         Node* pSelNode = pSel->FindFirst();
05019         while (pSelNode != NULL && ok)
05020         {
05021             Node* pNode = pSelNode;
05022             pSelNode = pSel->FindNext(pSelNode);
05023 
05024             if (!pNode->IS_KIND_OF(NodeBlend) && pNode->IsAnObject() && pNode->CanBecomeA(&TestPathBecomeA))
05025             {
05026                 // Will the blend node allow us to do the op to it?
05027                         ok = pNode->AllowOp(&ObjChange);
05028                 if (ok) ok = pNode->DoBecomeA(&BPBecomeA);
05029                 if (ok) ok = DoInvalidateNodeRegion((NodeRenderableInk*)pNode,TRUE,FALSE);
05030                 if (ok) ok = DoHideNode(pNode,TRUE);
05031             }
05032         }
05033 
05034         // Get an ObjChangeParam ready so we can tell the selected blends what we hope to do 
05035         // to the them (i.e. effectively add or change the blend path "attribute").
05036         ObjChangeFlags cBlendFlags;
05037         cBlendFlags.Attribute = TRUE;
05038         ObjChangeParam BlendChange(OBJCHANGE_STARTING,cBlendFlags,NULL,this);
05039 
05040         NodePath*  pBlendPath = BPBecomeA.GetNodePath();
05041         CCAttrMap* pAttrMap   = BPBecomeA.GetAttrMap();
05042 
05043         if (ok) ok = (pBlendPath != NULL);
05044 
05045         pSelNode = pSel->FindFirst();
05046         while (pSelNode != NULL && ok)
05047         {
05048             Node* pNode = pSelNode;
05049             pSelNode = pSel->FindNext(pSelNode);
05050 
05051             if (pNode->IS_KIND_OF(NodeBlend))
05052             {
05053                 NodeBlend* pNodeBlend = (NodeBlend*)pNode;
05054                 
05055                 
05056                         ok = pNodeBlend->AllowOp(&BlendChange);
05057                 if (ok) ok = pNodeBlend->BlendAlongThisPath(pBlendPath,pAttrMap,this, BlendPathOnTop);
05058                 if (ok) ok = DoInvalidateNodeRegion(pNodeBlend,TRUE);
05059                 if (ok) 
05060                 {
05061                     // Diccon 9/99 added changeblend action
05062                     ChangeBlendAction* pAction;
05063                     ChangeBlendOpParam ChangeParam;
05064                     ChangeParam.ChangeType = CHANGEBLEND_BLENDONPATH;
05065                     ChangeParam.NewBlendedOnCurve = TRUE;
05066                     ok = ChangeBlendAction::Init(this,&UndoActions,pNodeBlend,&ChangeParam,&pAction) != AC_FAIL;
05067                 }
05068                                      
05069                     pNodeBlend->UpdateStepDistance();
05070             }
05071         }
05072 
05073         if (ok)
05074         {
05075             // update the effected parents after the change
05076             ObjChange.Define(OBJCHANGE_FINISHED,cFlags,NULL,this);
05077             UpdateChangedNodes(&ObjChange);
05078         }
05079     }
05080 
05081     if (ok) 
05082     {
05083         pSel->Update();
05084 
05085     }
05086     else
05087         FailAndExecute();
05088 
05089     End();
05090 }
05091 
05092 /********************************************************************************************
05093 
05094 >   BOOL OpAddBlendPath::Declare()
05095 
05096     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
05097     Created:    28/4/99
05098     Returns:    TRUE if all went OK, FALSE otherwise
05099     Purpose:    Adds the operation to the list of all known operations
05100 
05101 ********************************************************************************************/
05102 
05103 BOOL OpAddBlendPath::Declare()
05104 {
05105     return (RegisterOpDescriptor(
05106                                 0, 
05107                                 _R(IDS_ADDBLENDPATH),
05108                                 CC_RUNTIME_CLASS(OpAddBlendPath), 
05109                                 OPTOKEN_ADDBLENDPATH,
05110                                 OpAddBlendPath::GetState,
05111                                 0,          /* help ID */
05112                                 _R(IDBBL_ADDBLENDPATH), /* bubble ID */
05113                                 0           /* bitmap ID */
05114                                 ));
05115 }
05116 
05117 
05118 /********************************************************************************************
05119 
05120 >   static OpState OpAddBlendPath::GetState(String_256* Description, OpDescriptor*)
05121 
05122     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
05123     Created:    28/4/99
05124     Outputs:    Description - GetState fills this string with an approriate description
05125                 of the current state of the operation 
05126     Returns:    The state of the operation, so that menu items (ticks and greying) can be
05127                 done properly
05128     Purpose:    Find out the state of the operation at the specific time
05129 
05130 ********************************************************************************************/
05131 
05132 OpState OpAddBlendPath::GetState(String_256* Description, OpDescriptor*)
05133 {
05134     OpState State(FALSE,TRUE); // It's not ticked, but it is greyed by default
05135     
05136     // Get an ObjChangeParam ready so we can tell the selected blend's parents what we hope to do 
05137     // to the blend (i.e. replace it with other nodes).
05138     ObjChangeFlags cFlags;
05139     cFlags.MultiReplaceNode = TRUE;
05140     ObjChangeParam ObjChange(OBJCHANGE_STARTING,cFlags,NULL,NULL);
05141 
05142     BOOL Denied = FALSE;
05143     SelRange* pSel = GetApplication()->FindSelection();
05144 
05145     if (pSel != NULL)
05146     {
05147         Node* pNode = pSel->FindFirst();
05148         while (pNode != NULL && State.Greyed)
05149         {
05150             if (IS_A(pNode,NodeBlend))
05151             {
05152                 // will the node allow the op to happen? (Don't set op permissions - we're only asking)
05153                 if (pNode->AllowOp(&ObjChange,FALSE))
05154                     State.Greyed = FALSE;       // Yes! we can ungrey
05155                 else
05156                     Denied = TRUE;              // Oooh! we've been denied by at least one selected blend
05157             }
05158             pNode = pSel->FindNext(pNode);
05159         }
05160     }
05161 
05162     UINT32 IDS = 0;
05163     if (State.Greyed)
05164     {
05165         // If we are greyed because we've been denied, get the reason for the denial
05166         if (Denied)
05167             IDS = ObjChange.GetReasonForDenial();
05168         else
05169             IDS = _R(IDS_ADDBLENDPATH_GREYED);
05170     }
05171 
05172 
05173     if (IDS == 0)
05174         IDS = _R(IDS_ADDBLENDPATH);
05175 
05176     *Description = String_256(IDS);
05177 
05178     return State;
05179 }
05180 
05181 /********************************************************************************************
05182 
05183 >   virtual void OpAddBlendPath::GetOpName(String_256* OpName) 
05184 
05185     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
05186     Created:    29/4/99
05187     Inputs:     -
05188     Outputs:    The undo string for the operation
05189     Returns:    
05190     Purpose:    The GetOpName fn is overridden so that we return back a description 
05191                 appropriate to the type of attribute that the operation applies. 
05192     Errors:     -
05193     SeeAlso:    -
05194 
05195 ********************************************************************************************/
05196 
05197 void OpAddBlendPath::GetOpName(String_256* OpName) 
05198 { 
05199     *OpName = String_256(_R(IDS_ADDBLENDPATH_UNDO));
05200 }  
05201 
05202 //-----------------------------------------------------------------------------------------------------
05203 //-----------------------------------------------------------------------------------------------------
05204 //-----------------------------------------------------------------------------------------------------
05205 //-----------------------------------------------------------------------------------------------------
05206 //-----------------------------------------------------------------------------------------------------
05207 // OpDetachBlendPath
05208 
05209 void OpDetachBlendPath::Do(OpDescriptor*)
05210 {
05211     SelRange* pSel = GetApplication()->FindSelection();
05212 
05213     BOOL ok = (pSel != NULL);
05214 
05215     if (ok) ok = DoStartSelOp(FALSE,FALSE);
05216 
05217     if (ok)
05218     {
05219         // Get an ObjChangeParam ready so we can tell the selected blends what we hope to do 
05220         // to the them (i.e. effectively remove the blend path "attribute").
05221         ObjChangeFlags cBlendFlags;
05222         cBlendFlags.Attribute = TRUE;
05223         ObjChangeParam BlendChange(OBJCHANGE_STARTING,cBlendFlags,NULL,this);
05224 
05225         Node* pSelNode = pSel->FindFirst();
05226         while (pSelNode != NULL && ok)
05227         {
05228             if (pSelNode->IS_KIND_OF(NodeBlend))
05229             {
05230                 NodeBlend* pNodeBlend = (NodeBlend*)pSelNode;
05231 
05232                         ok = pNodeBlend->AllowOp(&BlendChange);
05233                 if (ok) ok = pNodeBlend->DetachNodeBlendPath(pNodeBlend,NEXT,this);
05234                 if (ok) ok = DoInvalidateNodeRegion(pNodeBlend,TRUE);
05235                 if (ok) ok = pNodeBlend->RotateBlendEndObjectsBack(this);
05236                 if (ok) 
05237                 {   
05238                     // Diccon 9/99 added changeblend action to solve sticky button prob.
05239                     ChangeBlendAction* pAction;
05240                     ChangeBlendOpParam ChangeParam;
05241                     ChangeParam.ChangeType = CHANGEBLEND_BLENDONPATH;
05242                     ChangeParam.NewBlendedOnCurve = FALSE;
05243                     ok = ChangeBlendAction::Init(this,&UndoActions,pNodeBlend,&ChangeParam,&pAction) != AC_FAIL;
05244                 }
05245                 if (ok) ok = DoInvalidateNodeRegion(pNodeBlend,TRUE);
05246             }
05247             pSelNode = pSel->FindNext(pSelNode);
05248         }
05249 
05250         if (ok)
05251         {
05252             // update the effected parents after the change
05253             BlendChange.Define(OBJCHANGE_FINISHED,cBlendFlags,NULL,this);
05254             UpdateChangedNodes(&BlendChange);
05255         }
05256     }
05257 
05258     if (ok)
05259     {
05260         pSel->Update();
05261         // re-render the blobs on the path
05262         /*BlobManager* BlobMgr =*/ GetApplication()->GetBlobManager(); // AB - why does GetBlobManager rerender the paths?
05263     }
05264     else
05265         FailAndExecute();
05266 
05267     End();
05268 }
05269 
05270 /********************************************************************************************
05271 
05272 >   BOOL OpAddBlendPath::Declare()
05273 
05274     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
05275     Created:    16/9/99
05276     Returns:    TRUE if path to blend to is an older sibling to the blend, FALSE otherwise
05277     Purpose:    Little utility called by OpAddBlendPath::Do, does what the name suggests
05278 
05279 ********************************************************************************************/
05280 BOOL OpAddBlendPath::IsBlendPathOnTopOfBlend(SelRange* pSel)
05281 {
05282     Node* pNode = pSel->FindFirst();
05283     
05284     while (pNode != NULL)
05285     {
05286         if (pNode->IS_KIND_OF(NodeBlend))
05287             return TRUE;
05288         else if (pNode->IS_KIND_OF(NodePath))
05289             return FALSE;
05290 
05291         pNode = pSel->FindNext(pNode);
05292     }
05293 
05294     /* no way we should reach this point as there must be
05295     either one or the other in the selection for the op to be
05296     allowed, but just in case.. */
05297     return TRUE;
05298 }
05299 
05300 
05301 /********************************************************************************************
05302 
05303 >   BOOL OpDetachBlendPath::Declare()
05304 
05305     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
05306     Created:    11/5/99
05307     Returns:    TRUE if all went OK, FALSE otherwise
05308     Purpose:    Adds the operation to the list of all known operations
05309 
05310 ********************************************************************************************/
05311 
05312 BOOL OpDetachBlendPath::Declare()
05313 {
05314     return (RegisterOpDescriptor(
05315                                 0, 
05316                                 _R(IDS_DETACHBLENDPATH),
05317                                 CC_RUNTIME_CLASS(OpDetachBlendPath), 
05318                                 OPTOKEN_DETACHBLENDPATH,
05319                                 OpDetachBlendPath::GetState,
05320                                 0,          /* help ID */
05321                                 _R(IDBBL_DETACHBLENDPATH),  /* bubble ID */
05322                                 0           /* bitmap ID */
05323                                 ));
05324 }
05325 
05326 
05327 /********************************************************************************************
05328 
05329 >   static OpState OpDetachBlendPath::GetState(String_256* Description, OpDescriptor*)
05330 
05331     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
05332     Created:    11/5/99
05333     Outputs:    Description - GetState fills this string with an approriate description
05334                 of the current state of the operation 
05335     Returns:    The state of the operation, so that menu items (ticks and greying) can be
05336                 done properly
05337     Purpose:    Find out the state of the operation at the specific time
05338 
05339 ********************************************************************************************/
05340 
05341 OpState OpDetachBlendPath::GetState(String_256* Description, OpDescriptor*)
05342 {
05343     OpState State(FALSE,TRUE); // It's not ticked, but it is greyed by default
05344     
05345     // Get an ObjChangeParam ready so we can tell the selected blend's parents what we hope to do 
05346     // to the blend (i.e. change it's "blend path" attribute")
05347     ObjChangeFlags cFlags;
05348     cFlags.Attribute = TRUE;
05349     ObjChangeParam ObjChange(OBJCHANGE_STARTING,cFlags,NULL,NULL);
05350 
05351     BOOL Denied = FALSE;
05352     SelRange* pSel = GetApplication()->FindSelection();
05353 
05354     if (pSel != NULL)
05355     {
05356         Node* pNode = pSel->FindFirst();
05357         while (pNode != NULL && State.Greyed)
05358         {
05359             if (IS_A(pNode,NodeBlend))
05360             {
05361                 // will the node allow the op to happen? (Don't set op permissions - we're only asking)
05362                 if (pNode->AllowOp(&ObjChange,FALSE))
05363                     State.Greyed = FALSE;       // Yes! we can ungrey
05364                 else
05365                     Denied = TRUE;              // Oooh! we've been denied by at least one selected blend
05366             }
05367             pNode = pSel->FindNext(pNode);
05368         }
05369     }
05370 
05371     UINT32 IDS = 0;
05372     if (State.Greyed)
05373     {
05374         // If we are greyed because we've been denied, get the reason for the denial
05375         if (Denied)
05376             IDS = ObjChange.GetReasonForDenial();
05377         else
05378             IDS = _R(IDS_DETACHBLENDPATH_GREYED);
05379     }
05380 
05381 
05382     if (IDS == 0)
05383         IDS = _R(IDS_DETACHBLENDPATH);
05384 
05385     *Description = String_256(IDS);
05386 
05387     return State;
05388 }
05389 
05390 /********************************************************************************************
05391 
05392 >   virtual void OpDetachBlendPath::GetOpName(String_256* OpName) 
05393 
05394     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
05395     Created:    11/5/99
05396     Inputs:     -
05397     Outputs:    The undo string for the operation
05398     Returns:    
05399     Purpose:    The GetOpName fn is overridden so that we return back a description 
05400                 appropriate to the type of attribute that the operation applies. 
05401     Errors:     -
05402     SeeAlso:    -
05403 
05404 ********************************************************************************************/
05405 
05406 void OpDetachBlendPath::GetOpName(String_256* OpName) 
05407 { 
05408     *OpName = String_256(_R(IDS_DETACHBLENDPATH_UNDO));
05409 }  
05410 
05411 
05413 //  OpRemoveBlend
05414 //
05415 // Removes the selected blend objects, leaving the original objects in the tree
05416 
05417 /********************************************************************************************
05418 
05419 >   void OpRemoveBlend::Do(OpDescriptor*)
05420 
05421     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
05422     Created:    4/11/94
05423     Returns:    -
05424     Purpose:    This removes all the selected blend objects, leaving the original objects in the 
05425                 tree.
05426     Notes:      Phil 12/09/2005
05427                 This routine is somewhat perverse. It makes efforts to find all the blends
05428                 in the selection surface and below by calling BuildListOfSelectedNodes but then
05429                 all of the blends below the selection surface don't allow themselves to be
05430                 "removed" during the AllowOp phase.
05431                 Note that after AllowOp has been called on inner blends the subtree is marked
05432                 as OPPERMISSION_DENIED and is no longer found by Range::SmartFindNext!
05433                 What a mess! (Too dangerous to change it now, though)
05434 
05435 ********************************************************************************************/
05436 
05437 void OpRemoveBlend::Do(OpDescriptor*)
05438 {
05439     SelRange* pSel = GetApplication()->FindSelection();
05440 
05441     BOOL ok = (pSel != NULL);
05442 
05443     if (ok) ok = DoStartSelOp(FALSE,FALSE);
05444     List BlendList;
05445     if (ok) ok = BevelTools::BuildListOfSelectedNodes(&BlendList, CC_RUNTIME_CLASS(NodeBlend), FALSE);
05446 
05447 
05448     if (ok)
05449     {
05450         // Get an ObjChangeParam ready so we can tell the selected blend's parents what we hope to do 
05451         // to the blend (i.e. replace it with other nodes).
05452         ObjChangeFlags cFlags;
05453         cFlags.MultiReplaceNode = TRUE;
05454         ObjChangeParam ObjChange(OBJCHANGE_STARTING,cFlags,NULL,this);
05455 
05456         NodeListItem* pListItem = (NodeListItem*)BlendList.GetHead();
05457 
05458         
05459         while (pListItem != NULL)
05460         {
05461             
05462             NodeBlend* pNodeBlend = (NodeBlend*)pListItem->pNode;
05463 
05464             // We now have a selected NodeBlend node. Here's what we do:
05465             //      Invalidate the region
05466             //      Localise common attributes
05467             //      Hide the NodeBlend
05468             //      Move all other nodes next to the place the NodeBlend node used to be (select them too)
05469 
05470             NodeHidden* pNodeHidden;
05471 
05472             // Bodge to overcome strange behaviour of this function when blends
05473             // are found inside selected blend
05474             // (After AllowOp, OPPERMISSION_DENIED is set on the blend and then
05475             // DoInvalidateRegion does nothing)
05476             pNodeBlend->ReleaseCached(TRUE, FALSE, FALSE, TRUE);
05477 
05478             // Will the blend node allow us to do the op to it?
05479             ok = pNodeBlend->AllowOp(&ObjChange);
05480 
05481             if(!ok)
05482             {
05483                 UINT32 IDS = ObjChange.GetReasonForDenial();
05484 
05485                 if (IDS == _R(IDS_CANT_REMOVE_BLEND_WHEN_BEVELLED) ||
05486                     IDS == _R(IDS_CANT_REMOVE_BLEND_WHEN_CONTOURED) )
05487                     InformWarning(IDS);
05488             }
05489 
05490             DocRect BRect = pNodeBlend->GetBoundingRect();
05491             Spread* pSpread = Document::GetSelectedSpread();
05492             
05493             if (ok) ok = DoInvalidateRegion(pSpread, BRect);
05494             // Invalidate the whole blend region                
05495             if (ok) ok = DoInvalidateNodeRegion(pNodeBlend,TRUE,FALSE);
05496 
05497             if (ok) ok = pNodeBlend->RotateBlendEndObjectsBack(this);
05498 
05499             // Invalidate the whole blend region again
05500             BRect = pNodeBlend->GetBoundingRect();
05501             if (ok) ok = DoInvalidateRegion(pSpread, BRect);
05502             // Localise the attributes 
05503             if (ok) ok = DoLocaliseCommonAttributes(pNodeBlend);
05504 
05505             // Firstly, hide the blend node, and deselect it
05506             if (ok) ok = DoHideNode(pNodeBlend,TRUE,&pNodeHidden,FALSE);
05507             if (ok) pNodeBlend->SetSelected(FALSE);
05508 
05509             if (ok)
05510             {
05511                 // Diccon 9/99 add an action so we can restore multiple blends on curves
05512                 NodeBlender* pBlender = pNodeBlend->FindFirstBlender();
05513                 while (pBlender != NULL)
05514                 {
05515                     // set the nodeblendpathindex in an undoable way
05516                     ChangeBlenderOpParam Param;
05517                     Param.m_NewNodeBlendPathIndex = (-1);
05518                     Param.m_ChangeType = CHANGEBLENDER_NBPINDEX;
05519                     ok = ChangeBlenderAction::Init(this, &UndoActions, pBlender, Param);
05520                     //pBlender = pNodeBlendFindNextBlender(pBlender);
05521                     pBlender = pNodeBlend->FindNextBlender(pBlender);
05522                 }
05523             }
05524 
05525             // Move all the child ink nodes up (leave blender nodes safe under the hidden blend parent node)
05526             Node* pNode = pNodeBlend->FindFirstChild();
05527             while (pNode != NULL && ok)
05528             {
05529                 Node* pNext = pNode->FindNext();
05530 
05531                 if (pNode->IsAnObject())
05532                 {
05533                     NodeRenderableInk* pInkNode = (NodeRenderableInk*)pNode;
05534                     if (pInkNode->IS_KIND_OF(NodeBlendPath))
05535                         ok = pNodeBlend->DetachNodeBlendPath(pNodeHidden,PREV,this);                        
05536                     else if (!pInkNode->IS_KIND_OF(NodeBlender))
05537                     {
05538                         ok = DoMoveNode(pInkNode,pNodeHidden,PREV); // Move next to hidden node (where blend node used to be)
05539                         if (ok) DoSelectNode(pInkNode);             // Select it, because it's nicer that way
05540                     }
05541                 }
05542                 pNode = pNext;
05543             }
05544             
05545             pListItem = (NodeListItem*)BlendList.GetNext(pListItem);
05546         }
05547         BlendList.DeleteAll();
05548     
05549 
05550         if (ok)
05551         {
05552             // update the effected parents after the change
05553             ObjChange.Define(OBJCHANGE_FINISHED,cFlags,NULL,this);
05554             UpdateChangedNodes(&ObjChange);
05555         }
05556 
05557 
05558         if (ok) 
05559             pSel->Update();
05560         else
05561             FailAndExecute();
05562 
05563         End();
05564     }
05565 }
05566 
05567 /********************************************************************************************
05568 
05569 >   BOOL OpRemoveBlend::Declare()
05570 
05571     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
05572     Created:    4/11/94
05573     Returns:    TRUE if all went OK, FALSE otherwise
05574     Purpose:    Adds the operation to the list of all known operations
05575 
05576 ********************************************************************************************/
05577 
05578 BOOL OpRemoveBlend::Declare()
05579 {
05580     return (RegisterOpDescriptor(
05581                                 0, 
05582                                 _R(IDS_REMOVEBLEND),
05583                                 CC_RUNTIME_CLASS(OpRemoveBlend), 
05584                                 OPTOKEN_REMOVEBLEND,
05585                                 OpRemoveBlend::GetState,
05586                                 0,          /* help ID */
05587                                 _R(IDBBL_REMOVEBLEND),  /* bubble ID */
05588                                 0           /* bitmap ID */
05589                                 ));
05590 }
05591 
05592 
05593 /********************************************************************************************
05594 
05595 >   static OpState OpRemoveBlend::GetState(String_256* Description, OpDescriptor*)
05596 
05597     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
05598     Created:    4/11/94
05599     Outputs:    Description - GetState fills this string with an approriate description
05600                 of the current state of the operation 
05601     Returns:    The state of the operation, so that menu items (ticks and greying) can be
05602                 done properly
05603     Purpose:    Find out the state of the operation at the specific time
05604 
05605 ********************************************************************************************/
05606 
05607 OpState OpRemoveBlend::GetState(String_256* Description, OpDescriptor*)
05608 {
05609     OpState State(FALSE,TRUE); // It's not ticked, but it is greyed by default
05610     
05611     // Get an ObjChangeParam ready so we can tell the selected blend's parents what we hope to do 
05612     // to the blend (i.e. replace it with other nodes).
05613     ObjChangeFlags cFlags;
05614     cFlags.MultiReplaceNode = TRUE;
05615     ObjChangeParam ObjChange(OBJCHANGE_STARTING,cFlags,NULL,NULL);
05616 
05617     BOOL Denied = FALSE;
05618     List BlendList;
05619     BOOL ok = BevelTools::BuildListOfSelectedNodes(&BlendList, CC_RUNTIME_CLASS(NodeBlend), FALSE);
05620     if (ok)
05621     {
05622         NodeListItem* pListItem = (NodeListItem*)BlendList.GetHead();
05623         
05624         while (pListItem != NULL)
05625         {
05626             
05627             NodeBlend* pNodeBlend = (NodeBlend*)pListItem->pNode;
05628                 // will the node allow the op to happen? (Don't set op permissions - we're only asking)
05629             if (pNodeBlend->AllowOp(&ObjChange,FALSE))
05630                 State.Greyed = FALSE;       // Yes! we can ungrey
05631             else
05632                 Denied = TRUE;              // Oooh! we've been denied by at least one selected blend
05633 
05634             pListItem = (NodeListItem*)BlendList.GetNext(pListItem);
05635         }
05636     }
05637 
05638     BlendList.DeleteAll();
05639 
05640 
05641 
05642     /*
05643     SelRange* pSel = GetApplication()->FindSelection();
05644 
05645     if (pSel != NULL)
05646     {
05647         RangeControl rg = pSel->GetRangeControlFlags();
05648         rg.PromoteToParent = TRUE;
05649         pSel->Range::SetRangeControl(rg);
05650     
05651         Node* pNode = pSel->FindFirst();
05652         while (pNode != NULL && State.Greyed)
05653         {
05654             if (IS_A(pNode,NodeBlend))
05655             {
05656                 // will the node allow the op to happen? (Don't set op permissions - we're only asking)
05657                 if (pNode->AllowOp(&ObjChange,FALSE))
05658                     State.Greyed = FALSE;       // Yes! we can ungrey
05659                 else
05660                     Denied = TRUE;              // Oooh! we've been denied by at least one selected blend
05661             }
05662             pNode = pSel->FindNext(pNode);
05663         }
05664 
05665         rg.PromoteToParent = FALSE;
05666         pSel->Range::SetRangeControl(rg);   
05667     }
05668     */
05669     UINT32 IDS = 0;
05670     if (State.Greyed)
05671     {
05672         // If we are greyed because we've been denied, get the reason for the denial
05673         if (Denied)
05674             IDS = ObjChange.GetReasonForDenial();
05675         else
05676             IDS = _R(IDS_REMOVEBLEND_GREYED);
05677     }
05678 
05679 
05680     if (IDS == 0)
05681         IDS = _R(IDS_REMOVEBLEND);
05682 
05683     *Description = String_256(IDS);
05684 
05685     return State;
05686 }
05687 
05688 /********************************************************************************************
05689 
05690 >   virtual void OpRemoveBlend::GetOpName(String_256* OpName) 
05691 
05692     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
05693     Created:    4/11/94
05694     Inputs:     -
05695     Outputs:    The undo string for the operation
05696     Returns:    
05697     Purpose:    The GetOpName fn is overridden so that we return back a description 
05698                 appropriate to the type of attribute that the operation applies. 
05699     Errors:     -
05700     SeeAlso:    -
05701 
05702 ********************************************************************************************/
05703 
05704 void OpRemoveBlend::GetOpName(String_256* OpName) 
05705 { 
05706     *OpName = String_256(_R(IDS_REMOVEBLEND_UNDO));
05707 }  
05708 
05709 
05710 
05712 //  OpChangeBlendSteps
05713 //
05714 // Changes the number of steps of all the selected blends
05715 
05716 
05717 /********************************************************************************************
05718 
05719 >   void OpChangeBlendSteps::DoWithParam(OpDescriptor*,OpParam* pOpParam)
05720 
05721     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
05722     Created:    7/11/94
05723     Returns:    -
05724     Purpose:    This changes all the selected blend objects to have pOpParam->Param1 number of steps
05725 
05726 ********************************************************************************************/
05727 
05728 void OpChangeBlendSteps::DoWithParam(OpDescriptor*,OpParam* pOpParam)
05729 {
05730     ERROR3IF(pOpParam == NULL,"NULL OpParam ptr");
05731     if (pOpParam == NULL) return;
05732 
05733     List NodeList;
05734     BevelTools::BuildListOfSelectedNodes(&NodeList, CC_RUNTIME_CLASS(NodeRenderableInk));
05735 
05736     NodeListItem *pItem = NULL;
05737 
05738     BOOL ok = !NodeList.IsEmpty();
05739 
05740     ok = DeterminBlendObjectsProcessorHit (pOpParam, &NodeList);
05741 
05742     if (ok) ok = DoStartSelOp(FALSE,FALSE);
05743 
05744     if (ok)
05745     {
05746         // The new number of steps is in pOpParam->Param1 of the 
05747         UINT32 NewNumSteps = UINT32(pOpParam->Param1);
05748         pItem = (NodeListItem *)NodeList.GetHead();
05749 
05750         Node* pSelNode = NULL;
05751 
05752         if (pItem)
05753         {
05754             pSelNode = pItem->pNode;
05755         }
05756 
05757         while (pSelNode != NULL && ok)
05758         {
05759             Node* pNode = pSelNode;
05760 
05761             pItem = (NodeListItem *)NodeList.GetNext(pItem);
05762 
05763             if (pItem)
05764             {
05765                 pSelNode = pItem->pNode;
05766             }
05767             else
05768             {
05769                 pSelNode = NULL;
05770             }
05771 
05772             if (pNode->IS_KIND_OF(NodeBlend))
05773             {
05774                 // We now have a selected blend node so:
05775                 //  Invalidate the node's region
05776                 //  Store the current number of blend steps in an undo actiom
05777                 //  Change the number of steps to NewNumSteps
05778             
05779                 // Altered by DMC to account for contoured nodes
05780                 BOOL bBlend = FALSE;
05781 
05782                 if (pNode->IS_KIND_OF(NodeBlend))
05783                 {
05784                     bBlend = TRUE;
05785                 }
05786 
05787 
05788 
05789                 NodeRenderableInk * pInk = (NodeRenderableInk *)pNode;
05790 
05791                 UINT32 NumSteps = 0;
05792                 double DistanceEntered = 0.0;
05793                 if (bBlend)
05794                 {               
05795                     NumSteps = ((NodeBlend *)pNode)->GetNumBlendSteps();
05796                     DistanceEntered = ((NodeBlend *)pNode)->GetDistanceEntered(); 
05797                 }
05798                 
05799                 ChangeBlendStepsAction* pAction;
05800 
05801                 // Ask the node if it's ok to do the op
05802                 ObjChangeFlags cFlags;
05803                 ObjChangeParam ObjChange(OBJCHANGE_STARTING,cFlags,NULL,this);
05804                 ok = pInk->AllowOp(&ObjChange);
05805 
05806                 if (ok) ok = DoInvalidateNodeRegion(pInk,TRUE,FALSE);
05807                 if (ok) ok = (InvalidateBoundsAction::Init(this,&UndoActions,pInk,TRUE) != AC_FAIL);
05808                 if (ok) ok = ChangeBlendStepsAction::Init(this,&UndoActions,pInk,NumSteps, DistanceEntered, &pAction) != AC_FAIL;
05809                 
05810         
05811                 if (ok)
05812                 {
05813                     if (bBlend)
05814                     {               
05815                         ((NodeBlend *)pNode)->SetNumBlendSteps(NewNumSteps);
05816                         
05817                         // DY update the distance between steps variable
05818                         ((NodeBlend *)pNode)->UpdateStepDistance();
05819                     }
05820                     
05821                 }
05822                 
05823                 if (ok) ok = DoInvalidateNodeRegion(pInk,TRUE,FALSE);
05824                 if (ok) ok = (InvalidateBoundsAction::Init(this,&UndoActions,pInk,TRUE) != AC_FAIL);
05825             }
05826         }
05827     }
05828 
05829     NodeList.DeleteAll();
05830 
05831     if (ok) 
05832     {
05833         // Inform the effected parents of the change
05834         ObjChangeFlags cFlags;
05835         ObjChangeParam ObjChange(OBJCHANGE_FINISHED,cFlags,NULL,this);
05836         UpdateChangedNodes(&ObjChange);
05837     }
05838     else
05839         FailAndExecute();
05840 
05841     End();
05842 }
05843 
05844 BOOL OpChangeBlendSteps::DeterminBlendObjectsProcessorHit (OpParam* pOpParam, List* NodeList)
05845 {
05846     NodeListItem *pItem = NULL;
05847     INT32 total = 0;
05848     
05849     // The new number of steps is in pOpParam->Param1 of the 
05850     INT32               NewNumSteps = UINT32(pOpParam->Param1);
05851     pItem = (NodeListItem *)NodeList->GetHead();
05852 
05853     OpParam             pParam( NewNumSteps, INT32(0) );
05854 
05855     Node* pSelNode = NULL;
05856 
05857     if (pItem)
05858     {
05859         pSelNode = pItem->pNode;
05860     }
05861 
05862     while (pSelNode != NULL)
05863     {
05864         Node* pNode = pSelNode;
05865 
05866         pItem = (NodeListItem *)NodeList->GetNext(pItem);
05867 
05868         if (pItem)
05869         {
05870             pSelNode = pItem->pNode;
05871         }
05872         else
05873         {
05874             pSelNode = NULL;
05875         }
05876 
05877         if (pNode->IS_KIND_OF(NodeBlend))
05878         {
05879             // We now have a selected blend node so:
05880             // add in its paths
05881 
05882             NodeBlend* pBlend = (NodeBlend*) pNode;
05883             Node* start = NULL, *end = NULL;
05884 
05885             pBlend->GetStartAndEndNodes (&start, &end);
05886 
05887             if ((start != NULL) && (end != NULL))
05888             {
05889                 total += pNode->EstimateNodeComplexity ( &pParam );
05890             }
05891             else
05892             {
05893                 return (FALSE);
05894             }
05895         }
05896     }
05897 
05898     static BOOL foundCPU = FALSE;
05899     static INT32 pathsBeforeAsk = 0;
05900 
05901     if (!foundCPU)
05902     {
05903 /*      SYSTEM_INFO systemInfo;
05904         GetSystemInfo (&systemInfo);
05905 
05906         if (systemInfo.dwProcessorType == PROCESSOR_INTEL_386)
05907         {
05908             pathsBeforeAsk = 6000;
05909         }
05910         else if (systemInfo.dwProcessorType == PROCESSOR_INTEL_486)
05911         {
05912             pathsBeforeAsk = 12000;
05913         }
05914         else if (systemInfo.dwProcessorType == PROCESSOR_INTEL_PENTIUM)
05915         {
05916             pathsBeforeAsk = 20000;
05917         }
05918         else */
05919         {
05920             // assume its faster than a pentium .... (always true now in 2005!)
05921             pathsBeforeAsk = 24000;
05922         }
05923         foundCPU = TRUE;
05924     }
05925 
05926     if (total > pathsBeforeAsk)
05927     {
05928         // Load and build the question text.
05929         String_256 QueryString(_R(IDS_ASKLONGJOB));
05930                         
05931         // The only way of bringing up a box with a string in it
05932         Error::SetError(0, QueryString, 0);
05933         INT32 DlgResult = InformMessage(0, _R(IDS_YES), _R(IDS_NO));
05934         Error::ClearError();
05935 
05936         switch (DlgResult)
05937         {
05938             case 1:     // YES
05939                 return (TRUE);
05940             break;
05941             case 2:     // NO
05942                 return (FALSE);         // break out of this stuff!
05943         }
05944     }
05945     
05946     return (TRUE);
05947 }
05948 
05949 /********************************************************************************************
05950 
05951 >   BOOL OpChangeBlendSteps::Declare()
05952 
05953     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
05954     Created:    7/11/94
05955     Returns:    TRUE if all went OK, FALSE otherwise
05956     Purpose:    Adds the operation to the list of all known operations
05957 
05958 ********************************************************************************************/
05959 
05960 BOOL OpChangeBlendSteps::Declare()
05961 {
05962     return (RegisterOpDescriptor(
05963                                 0, 
05964                                 0,
05965                                 CC_RUNTIME_CLASS(OpChangeBlendSteps), 
05966                                 OPTOKEN_CHANGEBLENDSTEPS,
05967                                 OpChangeBlendSteps::GetState,
05968                                 0,  /* help ID */
05969                                 0,  /* bubble ID */
05970                                 0   /* bitmap ID */
05971                                 ));
05972 }
05973 
05974 
05975 /********************************************************************************************
05976 
05977 >   static OpState OpChangeBlendSteps::GetState(String_256* Description, OpDescriptor*)
05978 
05979     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
05980     Created:    7/11/94
05981     Outputs:    Description - GetState fills this string with an approriate description
05982                 of the current state of the operation 
05983     Returns:    The state of the operation, so that menu items (ticks and greying) can be
05984                 done properly
05985     Purpose:    Find out the state of the operation at the specific time
05986 
05987 ********************************************************************************************/
05988 
05989 OpState OpChangeBlendSteps::GetState(String_256* Description, OpDescriptor*)
05990 {
05991     OpState State(FALSE,TRUE); // It's not ticked, but it is greyed by default
05992     
05993     // DMc - to test for bevels & contours
05994     // are there any contour nodes in the selection
05995     List NodeList;
05996     BevelTools::BuildListOfSelectedNodes(&NodeList, CC_RUNTIME_CLASS(NodeBlend));
05997 
05998     if (!NodeList.IsEmpty())
05999     {
06000         State.Greyed = FALSE;
06001     }
06002 
06003     NodeList.DeleteAll();   
06004 
06005     // DY awful hack to allow us to call this op from the bezier tool 
06006     // when we wish to edit the path of a blend on a path.
06007     Range * pSel = GetApplication()->FindSelection();
06008 
06009     if (pSel)
06010     {
06011         Node* pNode = pSel->FindFirst();
06012         if (pNode->IS_KIND_OF(NodeBlendPath))
06013         {
06014             State.Greyed = FALSE;
06015         }
06016     }
06017     
06018     if (State.Greyed)
06019         *Description = String_256(_R(IDS_REMOVEBLEND_GREYED));
06020     else
06021         *Description = String_256(_R(IDS_BLENDSTEPS));
06022 
06023     return State;
06024 }
06025 
06026 /********************************************************************************************
06027 
06028 >   virtual void OpChangeBlendSteps::GetOpName(String_256* OpName) 
06029 
06030     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
06031     Created:    7/11/94
06032     Inputs:     -
06033     Outputs:    The undo string for the operation
06034     Returns:    
06035     Purpose:    The GetOpName fn is overridden so that we return back a description 
06036                 appropriate to the type of attribute that the operation applies. 
06037     Errors:     -
06038     SeeAlso:    -
06039 
06040 ********************************************************************************************/
06041 
06042 void OpChangeBlendSteps::GetOpName(String_256* OpName) 
06043 { 
06044     *OpName = String_256(_R(IDS_BLENDSTEPS_UNDO));
06045 }  
06046 
06047 
06048 
06049 //------------------------------------------------------------------------------------------------
06050 //------------------------------------------------------------------------------------------------
06051 //------------------------------------------------------------------------------------------------
06052 // The ChangeBlendStepsAction class
06053 
06054 /********************************************************************************************
06055 
06056 >   ChangeBlendStepsAction::ChangeBlendStepsAction()
06057 
06058     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
06059     Created:    7/11/94
06060     Inputs:     -
06061     Outputs:    -
06062     Returns:    -
06063     Purpose:    Constructor for the action
06064     Errors:     -
06065     SeeAlso:    -
06066 
06067 ********************************************************************************************/
06068 
06069 ChangeBlendStepsAction::ChangeBlendStepsAction()
06070 {
06071     pNodeBlend  = NULL;
06072     OldNumSteps = 0;
06073 }
06074 
06075 
06076 /********************************************************************************************
06077 
06078 >   ActionCode ChangeBlendStepsAction::Init(    Operation*  pOp,
06079                                                 ActionList* pActionList,
06080                                                 NodeBlend*  pThisNodeBlend,
06081                                                 UINT32      NumSteps,
06082                                                 ChangeBlendStepsAction**    ppNewAction);
06083 
06084     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
06085     Created:    7/11/94
06086     Inputs:     pOp             = ptr to the operation to which this action belongs
06087                 pActionList     =  ptr to action list to which this action should be added
06088                 pThisNodeBlend  = ptr to NodeBlend to change 
06089                 NumSteps        = Num steps to applied to pThisNodeBlend
06090     Outputs:    ppNewAction     = ptr to a ptr to an action, allowing the function to return
06091                                   a pointer to the created action
06092     Returns:    ActionCode, one of AC_OK, AC_NO_RECORD or AC_FAIL
06093     Purpose:    This is the function which creates an instance of this action. If there is no room 
06094                 in the undo buffer (which is determined by the base class Init function called within)
06095                 the function will either return AC_NO_RECORD which means the operation can continue, 
06096                 but no undo information needs to be stored, or AC_OK which means the operation should
06097                 continue AND record undo information. If the function returns AC_FAIL, there was not 
06098                 enough memory to record the undo information, and the user has decided not to continue
06099                 with the operation.
06100     Errors:     -
06101     SeeAlso:    Action::Init()
06102 
06103 ********************************************************************************************/
06104 
06105 
06106 
06107 ActionCode ChangeBlendStepsAction::Init(Operation* pOp,
06108                                         ActionList* pActionList,
06109                                         Node* pThisNodeBlend,
06110                                         UINT32 NumSteps,
06111                                         double DistanceEntered,
06112                                         ChangeBlendStepsAction** ppNewAction)
06113 {
06114     UINT32 ActSize = sizeof(ChangeBlendStepsAction);
06115 
06116     ActionCode Ac = Action::Init(pOp,pActionList,ActSize,CC_RUNTIME_CLASS(ChangeBlendStepsAction),(Action**)ppNewAction);
06117 
06118     if (Ac != AC_FAIL)
06119     {
06120         (*ppNewAction)->pNodeBlend  = pThisNodeBlend;
06121         (*ppNewAction)->OldNumSteps = NumSteps;
06122         (*ppNewAction)->OldDistanceEntered = DistanceEntered;
06123     }
06124 
06125     return Ac;
06126 }
06127 
06128 /********************************************************************************************
06129 
06130 >   ActionCode ChangeBlendStepsAction::Execute();
06131 
06132     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
06133     Created:    7/11/94
06134     Inputs:     -
06135     Outputs:    -
06136     Returns:    ActionCode, one of AC_OK, AC_NO_RECORD or AC_FAIL
06137     Purpose:    Executes the action.  This will reset the num blend steps in pThisNodeBlend to OldNumSteps,
06138                 after creating another action to record the current num steps of pThisNodeBlend
06139     Errors:     -
06140     SeeAlso:    Action::Init()
06141 
06142 ********************************************************************************************/
06143 
06144 ActionCode ChangeBlendStepsAction::Execute()
06145 {
06146     ActionCode Act;
06147     ChangeBlendStepsAction* pAction;
06148 
06149     UINT32 NumSteps = 0;
06150     double DistanceEntered = 0.0;
06151 
06152     if (pNodeBlend->IsKindOf(CC_RUNTIME_CLASS(NodeBlend)))
06153     {
06154         NumSteps = ((NodeBlend *)pNodeBlend)->GetNumBlendSteps();
06155         DistanceEntered = ((NodeBlend *)pNodeBlend)->GetDistanceEntered();
06156     }
06157     Act = ChangeBlendStepsAction::Init( pOperation, 
06158                                         pOppositeActLst,
06159                                         pNodeBlend,
06160                                         NumSteps,
06161                                         DistanceEntered,
06162                                         &pAction);
06163     if (Act != AC_FAIL)
06164     {
06165         if (pNodeBlend->IsKindOf(CC_RUNTIME_CLASS(NodeBlend)))
06166         {
06167             ((NodeBlend *)pNodeBlend)->SetNumBlendSteps(OldNumSteps);
06168             ((NodeBlend *)pNodeBlend)->SetDistanceEntered(OldDistanceEntered);
06169         }
06170         
06171     }
06172 
06173     return Act;
06174 }
06175 
06176 ChangeBlendStepsAction::~ChangeBlendStepsAction()
06177 {
06178 }
06179 
06180 
06182 //  OpChangeBlendDistance
06183 //
06184 // Changes the distance between steps of all the selected blends
06185 
06186 
06187 /********************************************************************************************
06188 
06189 >   void OpChangeBlendDistance::DoWithParam(OpDescriptor*,OpParam* pOpParam)
06190 
06191     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
06192     Created:    7/9/99
06193     Returns:    -
06194     Purpose:    To change the distance between steps in the selected blends to that 
06195                 given in OpParam->Param1.  The method differs depending on whether or
06196                 not the blend is on a curve.  
06197                 If the blend is on a curve then it is necessary to recalculate the number of steps 
06198                 that can fit on the curve with the new length. If there is not an exact fit then 
06199                 the last object must be moved along the curve to ensure correct distance is maintained.
06200                 If the blend is not on a curve the it is necessary to calculate the direction of
06201                 the blend and move the last object forwards or backwards to maintain the correct 
06202                 distance.               
06203 ********************************************************************************************/
06204 
06205 void OpChangeBlendDistance::DoWithParam(OpDescriptor* pOpDesc, OpParam* pOpParam)
06206 {
06207     ERROR3IF(pOpParam == NULL,"NULL OpParam ptr");
06208     if (pOpParam == NULL) return;
06209 
06210     SelRange* pSel = GetApplication()->FindSelection();
06211 
06212     RangeControl rg;    
06213 
06214     if (pSel)
06215     {
06216         rg = pSel->GetRangeControlFlags();
06217         rg.PromoteToParent = TRUE;
06218         pSel->Range::SetRangeControl(rg);
06219     }
06220 
06221     BOOL ok = (pSel != NULL);
06222 
06223     if (ok) ok = DoStartSelOp(FALSE,FALSE);
06224 
06225     if (ok)
06226     {
06227         List BlendList;
06228         // The new distance is in pOpParam->Param1 
06229         double NewStepDistance = double(pOpParam->Param1);
06230         ok = BevelTools::BuildListOfSelectedNodes(&BlendList, CC_RUNTIME_CLASS(NodeBlend), FALSE);
06231         if (ok)
06232         {
06233             NodeListItem* pListItem = (NodeListItem*)BlendList.GetHead();
06234             while (pListItem != NULL)
06235             {
06236                 NodeBlend* pNodeBlend = (NodeBlend*)pListItem->pNode;
06237     
06238                 // We now have a selected blend node so:
06239                 //   - Invalidate the node's region
06240                 //   - Store the current number of blend steps in an undo actiom
06241                 //   - Calculate the new number of blend steps and apply
06242                 //   - Get the nodeblender child of the blend
06243                 //   - Calculate the proportion along the path we have to set in order to
06244                 //     achieve the correct number of steps at the exact distance entered.
06245                 
06246                 NodeRenderableInk * pInk = (NodeRenderableInk *)pNodeBlend;
06247 
06248                 // Ask the node if it's ok to do the op
06249                 ObjChangeFlags cFlags;
06250                 ObjChangeParam ObjChange(OBJCHANGE_STARTING,cFlags,NULL,this);
06251                 ok = pInk->AllowOp(&ObjChange);
06252         
06253                 if (ok) ok = DoInvalidateNodeRegion(pInk,TRUE,FALSE);
06254                 if (ok) ok = (InvalidateBoundsAction::Init(this,&UndoActions,pInk,TRUE) != AC_FAIL);
06255                 
06256                 if (ok)
06257                 {
06258                     
06259 //                  NodeBlend* pCopyBlend = pNodeBlend;
06260                     BOOL OnCurve = pNodeBlend->IsOnACurve();
06261 
06262                     if (OnCurve)
06263                     {
06264                         // NewNumSteps calculated in InsertChangeStepsAction and passed back
06265                         UINT32 NewNumSteps = 0;
06266                         // NewDistances is used to store the new distances along the path for the start and end objects
06267                         double NewDistances[2] = { 1.0, 0.0 };
06268 
06269                         // calculate and perform the actions
06270                                 ok = InsertChangeStepsAction(pNodeBlend, NewStepDistance, &NewNumSteps, NewDistances);
06271                         if (ok) ok = InsertTransformNodesAction(pNodeBlend, NewDistances[0], NewDistances[1]);
06272                         if (ok) ok = InsertChangePathProportion(pNodeBlend, NewDistances[0], NewDistances[1]);
06273                         if (ok)     
06274                         {
06275                             // set the member variables in the nodeblend
06276                             pNodeBlend->SetNumBlendSteps(NewNumSteps);
06277                             pNodeBlend->UpdateStepDistance();
06278                             pNodeBlend->SetDistanceEntered(NewStepDistance);
06279                         }
06280                     }
06281                     else
06282                     {
06283                         ok = InsertChangeLinearBlendActions(pNodeBlend,this, NewStepDistance);
06284                     }
06285                     pListItem = (NodeListItem*)BlendList.GetNext(pListItem);
06286                 
06287                 } // end if ok
06288                 
06289                 if (ok) ok = DoInvalidateNodeRegion(pInk,TRUE,FALSE);
06290                 if (ok) ok = (InvalidateBoundsAction::Init(this,&UndoActions,pInk,TRUE) != AC_FAIL);
06291             }
06292         }
06293         BlendList.DeleteAll();
06294     }
06295 
06296     rg.PromoteToParent = FALSE;
06297     pSel->Range::SetRangeControl(rg);
06298 
06299     if (ok) 
06300     {
06301         pSel->Update();
06302 
06303         // Inform the effected parents of the change
06304         ObjChangeFlags cFlags;
06305         ObjChangeParam ObjChange(OBJCHANGE_FINISHED,cFlags,NULL,this);
06306         UpdateChangedNodes(&ObjChange);
06307     }
06308     else
06309         FailAndExecute();
06310 
06311     End();
06312 
06313 }
06314 
06315 
06316 /********************************************************************************************
06317 
06318 >   BOOL OpChangeBlendDistance::InsertChangeStepsAction(NodeBlend* pNodeBlend,
06319                                                         Operation* pOp,
06320                                                         double StepDistance,
06321                                                         UINT32* NewNumSteps)
06322 
06323     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
06324     Created:    9/9/99
06325     Inputs:     pNodeBlend   - the nodeblend which needs to have its number of steps changed
06326                 pOp          - the Op which wants to use this action
06327                 StepDistance - the new distance between steps of the blend
06328     Outputs:    NewNumSteps  - the number of steps calculated
06329     Returns:    TRUE if all went OK, FALSE otherwise
06330     Purpose:    When the distance between blend steps is changed and the blend is on a curve it 
06331                 is necessary to recalculate how many blend steps we can now fit on the curve.
06332                 This function performs that operation and inserts a new action.
06333 
06334 ********************************************************************************************/
06335 
06336 BOOL OpChangeBlendDistance::InsertChangeStepsAction(NodeBlend* pNodeBlend,double StepDistance, 
06337                                                     UINT32* NewNumSteps, double* NewDistances)
06338 {
06339     UINT32 OldNumSteps = 0;
06340     double BlendDistance = 0.0;
06341     
06342     OldNumSteps = pNodeBlend->GetNumBlendSteps();
06343     UINT32 TempNumSteps = OldNumSteps;
06344     BOOL ok = pNodeBlend->GetBlendDistance(TRUE, &BlendDistance);
06345     
06346     
06347     double StartProportion = 0.0;
06348     double EndProportion = 0.0;
06349     double StartDistance = 0.0;
06350     double EndDistance = 0.0; 
06351 
06352     if (ok)
06353     {
06354         // find out if the user has edited the end position
06355         switch (pNodeBlend->GetLastEdited())
06356         {
06357             case NONE:
06358                 TempNumSteps = (UINT32)(BlendDistance / StepDistance);  
06359                 EndDistance = BlendDistance - (TempNumSteps * StepDistance);
06360                 StartDistance = 0.0;
06361             
06362             break;
06363             case FIRST:
06364                 ok = pNodeBlend->GetStartAndEndProportions(&StartProportion, &EndProportion);
06365                 if (ok)
06366                 {
06367                     StartDistance = StartProportion * BlendDistance;
06368                     EndDistance = BlendDistance - (EndProportion * BlendDistance);
06369                     ok = CalculateNewNumStepsAndPosition(OldNumSteps, BlendDistance, StepDistance,
06370                                                          &StartDistance, &EndDistance, &TempNumSteps);
06371                 }
06372             
06373             break;
06374             case LAST:
06375             
06376                 StartProportion = 0.0;
06377                 EndProportion = 0.0;
06378 
06379                 ok = pNodeBlend->GetStartAndEndProportions(&StartProportion, &EndProportion);
06380                 if (ok)
06381                 {
06382                     StartDistance = StartProportion * BlendDistance;
06383                     EndDistance = BlendDistance - (EndProportion * BlendDistance);
06384                     ok = CalculateNewNumStepsAndPosition(OldNumSteps, BlendDistance, StepDistance,
06385                                                          &EndDistance, &StartDistance, &TempNumSteps);
06386                 }
06387             break;
06388             default:
06389                 break;
06390             
06391         
06392         }
06393         if ((TempNumSteps != OldNumSteps) && ok)
06394         {
06395             double DistanceEntered = pNodeBlend->GetDistanceEntered();
06396             ChangeBlendStepsAction* pStepAction;
06397             NodeRenderableInk * pInk = (NodeRenderableInk *)pNodeBlend;     
06398             ok = ChangeBlendStepsAction::Init(this,&UndoActions,pInk,OldNumSteps,DistanceEntered, &pStepAction) != AC_FAIL;
06399         }
06400     }
06401 
06402     if (ok)
06403     {
06404         *NewNumSteps = TempNumSteps;
06405         NewDistances[0] = StartDistance;
06406         NewDistances[1] = EndDistance;
06407     }
06408     else
06409         *NewNumSteps = OldNumSteps;   // restore the original number
06410                                       // if something went wrong.
06411 
06412     return ok;
06413 }
06414 
06415 
06416 
06417 /********************************************************************************************
06418 
06419 >   BOOL OpChangeBlendDistance::InsertChangePathProportion(NodeBlend* pNodeBlend,
06420                                                         Operation* pOp,
06421                                                         UINT32* NewNumSteps)
06422 
06423     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
06424     Created:    9/9/99
06425     Inputs:     pNodeBlend   - the nodeblend which needs to have its number of steps changed
06426                 pOp          - the Op which wants to use this action
06427                 StepDistance - the distance between steps of the blend
06428                 NewNumSteps  - the number of steps calculated
06429     outputs:    -
06430     Returns:    TRUE if all went OK, FALSE otherwise
06431     Purpose:    To work out the proportion of the nodeblendpath that we need to use to set
06432                 the given number of steps and step distance.  Once this is known we find the 
06433                 point on the path corresponding to that proportion and transform the end node
06434                 to that point
06435 ********************************************************************************************/
06436 
06437 BOOL OpChangeBlendDistance::InsertChangePathProportion(NodeBlend* pNodeBlend, 
06438                                                        double StartDistance, double EndDistance)
06439 
06440 {
06441     if (pNodeBlend == NULL)
06442     {
06443         ERROR3("NodeBlend is NULL");
06444         return FALSE;
06445     }
06446 
06447     NodeBlender* pNodeBlender = pNodeBlend->FindFirstBlender();
06448 
06449     if (pNodeBlender == NULL)
06450     {
06451         ERROR3("This blend has no nodeblenders");
06452         return FALSE;
06453     }
06454 
06455     // this will require changing when we implement blending from a 
06456     // blend on a curve to other objects
06457     double BlendDistance = 0.0;
06458     BOOL ok = pNodeBlend->GetBlendDistance(TRUE, &BlendDistance);
06459 
06460     if (ok)
06461     {
06462         double OldStartProp = 0.0;
06463         double OldEndProp   = 0.0;
06464 
06465         ok = pNodeBlend->GetStartAndEndProportions(&OldStartProp, &OldEndProp);
06466         if (ok)
06467         {
06468             // check to see if the proportions have changed
06469             double OldStartDist = BlendDistance * OldStartProp;
06470             if (OldStartDist != StartDistance)
06471             {
06472                 double NewPathProportion = StartDistance / BlendDistance;
06473                 ChangeBlenderOpParam BlenderParam;
06474                 BlenderParam.m_ChangeType = CHANGEBLENDER_PATHSTART;
06475                 BlenderParam.m_NewPathStart = NewPathProportion;
06476                 ok = ChangeBlenderAction::Init(this, &UndoActions, pNodeBlender, BlenderParam);
06477             }
06478 
06479             double OldEndDist = BlendDistance - (BlendDistance * OldEndProp);
06480             
06481             if (OldEndDist != EndDistance)
06482             {
06483                 double NewPathProportion = 1 - (EndDistance / BlendDistance);
06484                 ChangeBlenderOpParam BlenderParam;
06485                 BlenderParam.m_ChangeType = CHANGEBLENDER_PATHEND;
06486                 BlenderParam.m_NewPathEnd = NewPathProportion;
06487                 ok = ChangeBlenderAction::Init(this, &UndoActions, pNodeBlender, BlenderParam);
06488             }
06489         }
06490     }
06491     return ok;
06492 }
06493 
06494 /*
06495     double BlendDistance = 0.0;
06496     BOOL ValidDistance = pNodeBlend->GetBlendDistance(TRUE, &BlendDistance);
06497     if (ValidDistance = FALSE)
06498         return FALSE;
06499     // work out the proportion of the path in use
06500     double PathDistanceUsed = NumSteps * StepDistance;
06501     double PathProportion = PathDistanceUsed / BlendDistance;
06502     double PathDistanceUnused = BlendDistance - PathDistanceUsed;
06503 
06504     BOOL ok = FALSE;
06505     if (PathProportion != 1.0)
06506     {       
06507         //  We may have to deal with multiple blenders in which case
06508         // we only want to transform the last object of the final blender
06509         
06510         NodeBlender* pNodeBlender = pNodeBlend->FindFirstBlender();
06511         INT32 NumBlenders = pNodeBlend->GetNumBlenders();
06512                         
06513         while (pNodeBlender != NULL)
06514         {
06515             NumBlenders--;
06516             if (NumBlenders ==0)
06517             {   
06518                 ChangeBlenderOpParam BlenderParam;
06519                 BlenderParam.m_ChangeType = CHANGEBLENDER_PATHEND;
06520                 BlenderParam.m_NewPathEnd = PathProportion;
06521                 ok = ChangeBlenderAction::Init(this, &UndoActions, pNodeBlender, BlenderParam);
06522                 if (ok)
06523                 {
06524                     DocCoord NewPoint;
06525                     double ExtraParam = 0.0;  //passed to the function but not used afterwards
06526                     ok = pNodeBlender->GetPointOnNodeBlendPath(1.0,&NewPoint,&ExtraParam);
06527 
06528                     if (ok)
06529                     {
06530                         NodeRenderableInk* pEnd = pNodeBlender->GetNodeEnd();
06531                         ok = ((pEnd != NULL) && (pNodeBlend != NULL));
06532                         if (ok) 
06533                             ok = pNodeBlend->TransformNodeToPoint(pEnd,&NewPoint,this,ExtraParam);
06534                     }
06535                 }
06536             }   
06537             pNodeBlender = pNodeBlend->FindNextBlender(pNodeBlender);
06538         }
06539 
06540     } // end if (pathproportion
06541 
06542     return ok;
06543 
06544 */
06545 
06546 
06547 /********************************************************************************************
06548 
06549 >   BOOL OpChangeBlendDistance::InsertTransformNodesAction(NodeBlend* pNodeBlend, double StartDistance
06550                                                                double EndDistance)
06551 
06552     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
06553     Created:    10/9/99
06554     Inputs:     NodeBlend to perform the actions on
06555                 StartDistance - distance along the path for the start object
06556                 EndDistance - distance along the path for the end object
06557     Returns:    TRUE if all went OK, FALSE otherwise
06558     Purpose:    Determines the point along the nodeblendpath to which the end objects must be
06559                 moved, then creates the actions
06560 
06561 ********************************************************************************************/
06562 
06563 BOOL OpChangeBlendDistance::InsertTransformNodesAction(NodeBlend* pNodeBlend, double StartDistance,
06564                                                        double EndDistance)
06565 {
06566     if (pNodeBlend == NULL)
06567     {
06568         ERROR3("NodeBlend is NULL");
06569         return FALSE;
06570     }
06571 
06572     NodeBlender* pNodeBlender = pNodeBlend->FindFirstBlender();
06573 
06574     if (pNodeBlender == NULL)
06575     {
06576         ERROR3("This blend has no nodeblenders");
06577         return FALSE;
06578     }
06579 
06580     // this will require changing when we implement blending from a 
06581     // blend on a curve to other objects
06582     double BlendDistance = 0.0;
06583     BOOL ok = pNodeBlend->GetBlendDistance(TRUE, &BlendDistance);
06584 
06585     if (ok)
06586     {
06587         double OldStartProp = 0.0;
06588         double OldEndProp   = 0.0;
06589 
06590         ok = pNodeBlend->GetStartAndEndProportions(&OldStartProp, &OldEndProp);
06591         if (ok)
06592         {
06593             // check to see if the proportions have changed
06594             double OldStartDist = BlendDistance * OldStartProp;
06595             if (OldStartDist != StartDistance)
06596             {
06597                 // locate the point on the line corresponding to the new distance
06598                 DocCoord NewPoint;
06599                 double ExtraParam = 0.0;  //passed to the function but not used afterwards
06600                 
06601                 ok = pNodeBlender->GetPointFromDistance(StartDistance, &NewPoint, &ExtraParam);
06602                 if (ok)
06603                 {
06604                     NodeRenderableInk* pEnd = pNodeBlender->GetNodeStart();
06605                     ok = ((pEnd != NULL) && (pNodeBlend != NULL));
06606                     if (ok) 
06607                         ok = pNodeBlend->TransformNodeToPoint(pEnd,&NewPoint,this,ExtraParam);
06608                 }
06609             }
06610 
06611             double OldEndDist = BlendDistance - (BlendDistance * OldEndProp);
06612             
06613             if (OldEndDist != EndDistance)
06614             {
06615                 // locate the point on the line corresponding to the new distance
06616                 DocCoord NewPoint;
06617                 double ExtraParam = 0.0;  //passed to the function but not used afterwards
06618                 double DistanceFromStart = BlendDistance - EndDistance;
06619                 pNodeBlender = pNodeBlend->FindLastBlender();
06620                 if (pNodeBlender != NULL)
06621                 {
06622                     ok = pNodeBlender->GetPointFromDistance(DistanceFromStart, &NewPoint, &ExtraParam);
06623 
06624                     if (ok)
06625                     {
06626                         NodeRenderableInk* pEnd = pNodeBlender->GetNodeEnd();
06627                         ok = ((pEnd != NULL) && (pNodeBlend != NULL));
06628                         if (ok) 
06629                             ok = pNodeBlend->TransformNodeToPoint(pEnd,&NewPoint,this,ExtraParam);
06630                     }
06631                 }
06632             }
06633         }
06634     }
06635     return ok;
06636 }   
06637 
06638 
06639 
06640 
06641 
06642 /********************************************************************************************
06643 
06644 >   BOOL OpChangeBlendDistance::InsertChangeLinearBlendActions(NodeBlend* pNodeBlend, Operation* pOp
06645                                                                double StepDistance)
06646 
06647     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
06648     Created:    10/9/99
06649     Inputs:     NodeBlend to perform the actions on
06650                 Operation performing the actions
06651     Returns:    TRUE if all went OK, FALSE otherwise
06652     Purpose:    Calculates the actions necessary to change a linear blend when the 
06653                 distance between blend steps is edited.  This involves calculating the
06654                 new length of the blend, transforming the last blend object accordingly
06655                 and telling each blender to regenerate itself.
06656 
06657 ********************************************************************************************/
06658 
06659 BOOL OpChangeBlendDistance::InsertChangeLinearBlendActions(NodeBlend* pNodeBlend, 
06660                                                            Operation* pOp,
06661                                                            double StepDistance)
06662 {
06663     if (pNodeBlend->IsOnACurve())
06664     {
06665         ERROR3("Trying to perform linear blend actions to blend on a curve");
06666         return FALSE;
06667     }
06668 
06669     NodeBlender* pBlender = pNodeBlend->FindFirstBlender();
06670     const UINT32 NumSteps = pNodeBlend->GetNumBlendSteps();
06671     BOOL ok = TRUE;
06672     while (pBlender != NULL)
06673     {
06674         double OldDistance = pBlender->GetLinearDistance();
06675         double NewDistance = StepDistance * NumSteps;
06676         double DistanceRatio = NewDistance / OldDistance;
06677         DocCoord Start;
06678         DocCoord End;
06679     
06680          ok = pBlender->GetBlendObjectCentres(&Start, &End);
06681         if (ok)
06682         {
06683             // bit of a hack this, when it comes to undoing we need to have the blender
06684             // regenerate itself AFTER it transforms the end node. So when first performing
06685             // this action it doesn't really do anything, but it has to be there for the Undo
06686 
06687             ChangeBlenderOpParam BlenderParam;
06688             BlenderParam.m_ChangeType = CHANGEBLENDER_REGEN;
06689             ok = ChangeBlenderAction::Init(pOp, &UndoActions, pBlender, BlenderParam);
06690             
06691             DocCoord NewPosition = DocCoord::PositionPointFromRatio(Start, End, DistanceRatio);
06692             
06693             // we need to cast to noderenderable ink in order to use in the
06694             // transform function
06695             NodeRenderableInk* pEnd = (NodeRenderableInk*)(pBlender->GetNodeEnd());
06696 
06697             if (pEnd != NULL)
06698             {
06699                 UndoableOperation* pUndoOp = (UndoableOperation*)pOp;
06700                 ok = pNodeBlend->TransformNodeToPoint(pEnd, &NewPosition, pUndoOp, 0.0);
06701                 
06702                 // tell the blender that it needs to recalculate itself
06703                 pBlender->SetUninitialised();  
06704                                             
06705             }
06706         }
06707         pBlender = pNodeBlend->FindNextBlender(pBlender);
06708     }
06709     
06710     return ok;
06711 
06712 }
06713         
06714 
06715 /********************************************************************************************
06716 
06717 >   BOOL OpChangeBlendDistance::CalculateNewNumStepsAndPosition(UINT32 OldNumSteps, 
06718                                                                 double BlendDistance,
06719                                                                 double StepDistance
06720                                                                 double* FixedPosition
06721                                                                 double* MoveablePosition
06722                                                                 UINT32* NewNumSteps)
06723     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com> 
06724     Created:    10/9/99
06725     Inputs:     OldNumSteps - the number of steps in the blend before any changes were applied
06726                 BlendDistance - the length of the NodeBlendPath
06727                 StepDistance  - the distance between steps that we wish to apply.
06728                 FixedDistance - the distance from the end of the path of the object that we would
06729                 like to keep fixed.  Note that it may not always remain fixed.
06730                 MoveableDistance - the distance from the end of the path of the object that we are
06731                 allowing to move.
06732                 NOTE: FixedDistance and MoveableDistance should be measured from the end that they 
06733                 represent.  That is to say if you wish to keep the first blend object fixed then FixedDistance
06734                 should be the distance from the start of the path to the first blend object, MoveableDistance will
06735                 then be the distance FROM THE OTHER END to the end object.
06736 
06737     Outputs:    NewNumSteps - the new number of steps required
06738                 MoveableDistance - the new position of the end that we don't care about
06739                 FixedDistance - in some rare instances we may have adjust the fixed position
06740                 in order to accomodate the desired step distance
06741                 is the last object.         
06742     returns:    TRUE if all went well, FALSE if the step distance gives and invalid number of steps.
06743     Purpose:    Works out the number of steps needed to accomodate the new step distance, as well as 
06744                 working out the new positions of the end objects.
06745                 
06746     See Also:   
06747 ***********************************************************************************************/
06748     
06749 BOOL OpChangeBlendDistance::CalculateNewNumStepsAndPosition(UINT32 OldNumSteps, double BlendDistance,
06750                                                            double StepDistance, double* FixedDistance,
06751                                                            double* MoveableDistance, UINT32* NewNumSteps)
06752 {
06753     // First check parameters
06754     if (OldNumSteps < 0 || StepDistance < 0)
06755     {
06756         ERROR3("Invalid parameter");
06757         return FALSE;
06758     }
06759     if (BlendDistance < (*FixedDistance + *MoveableDistance))
06760     {
06761         ERROR3("Invalid distance parameter");
06762         return FALSE;
06763     }
06764 
06765     //initialise locals to zero 
06766     *NewNumSteps = 0;
06767 
06768     // get the distance currently occupied by the blend
06769     double DistanceUsed = BlendDistance - (*FixedDistance + *MoveableDistance);
06770 
06771     // ideally we'd like to blend distance to be as close as possible to 
06772     // what it was before editing
06773     UINT32 TempNumSteps = (UINT32)(DistanceUsed / StepDistance);
06774     
06775     // if this gives us a positive number of steps then we'll take it
06776     if (TempNumSteps > 0)
06777     {
06778         double NewDistance = TempNumSteps * StepDistance;
06779         *MoveableDistance = BlendDistance - (*FixedDistance + NewDistance);
06780         *NewNumSteps = TempNumSteps;
06781         //NewFixedDistance = *FixedDistance;
06782     }
06783     // if not then we'll try to use the whole length of the blend
06784     else
06785     {
06786         TempNumSteps = (UINT32)(BlendDistance / StepDistance);
06787         if (TempNumSteps > 0)
06788         {
06789             double NewDistance = TempNumSteps * StepDistance;
06790             *MoveableDistance = BlendDistance -  NewDistance;
06791             *FixedDistance = 0.0;
06792             *NewNumSteps = TempNumSteps;
06793         }
06794         else
06795         {
06796             // we've been passed a step distance that is too long, this should never happen due to the
06797             // checks in the UI
06798             ERROR3("Step distance is too long");
06799             return FALSE;
06800         }
06801     }
06802 
06803 
06804 
06805     // test to see that what we have is ok.
06806     if (*NewNumSteps != 0)
06807         return TRUE;
06808     else
06809         return FALSE;
06810 }
06811 
06812 
06813 
06814 /********************************************************************************************
06815 
06816 >   BOOL OpChangeBlendDistance::Declare()
06817 
06818     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
06819     Created:    7/9/99
06820     Returns:    TRUE if all went OK, FALSE otherwise
06821     Purpose:    Adds the operation to the list of all known operations
06822 
06823 ********************************************************************************************/
06824 
06825 BOOL OpChangeBlendDistance::Declare()
06826 {
06827     return (RegisterOpDescriptor(
06828                                 0, 
06829                                 0,
06830                                 CC_RUNTIME_CLASS(OpChangeBlendDistance), 
06831                                 OPTOKEN_CHANGEBLENDDISTANCE,
06832                                 OpChangeBlendDistance::GetState,
06833                                 0,  /* help ID */
06834                                 0,  /* bubble ID */
06835                                 0   /* bitmap ID */
06836                                 ));
06837 }
06838 
06839 
06840 /********************************************************************************************
06841 
06842 >   static OpState OpChangeBlendDistance::GetState(String_256* Description, OpDescriptor*)
06843 
06844     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
06845     Created:    7/9/99
06846     Outputs:    Description - GetState fills this string with an approriate description
06847                 of the current state of the operation 
06848     Returns:    The state of the operation, so that menu items (ticks and greying) can be
06849                 done properly
06850     Purpose:    Find out the state of the operation at the specific time
06851 
06852 ********************************************************************************************/
06853 
06854 OpState OpChangeBlendDistance::GetState(String_256* Description, OpDescriptor*)
06855 {
06856     OpState State(FALSE,TRUE); // It's not ticked, but it is greyed by default
06857     
06858     SelRange* pSel = GetApplication()->FindSelection();
06859 
06860     RangeControl rg = pSel->GetRangeControlFlags();
06861     rg.PromoteToParent = TRUE;
06862     pSel->Range::SetRangeControl(rg);
06863     if (pSel != NULL)
06864     {
06865         Node* pNode = pSel->FindFirst();
06866         while (pNode != NULL && State.Greyed)
06867         {
06868             State.Greyed = !(pNode->IS_KIND_OF(NodeBlend));
06869             pNode = pSel->FindNext(pNode);
06870         }
06871     }
06872     // if that fails then try this, which will catch any blends that are
06873     // inside bevels or contours - DY
06874     if (State.Greyed)
06875     {
06876         List BlendList;
06877         BOOL ok = BevelTools::BuildListOfSelectedNodes(&BlendList, CC_RUNTIME_CLASS(NodeBlend), FALSE);
06878         State.Greyed = !ok;
06879         BlendList.DeleteAll();
06880     }
06881 
06882     // DY awful hack to allow us to call this op from the bezier tool 
06883     // when we wish to edit the path of a blend on a path.
06884     Node* pNode = pSel->FindFirst();
06885     if (pNode->IS_KIND_OF(NodeBlendPath))
06886     {
06887         State.Greyed = FALSE;
06888     }
06889 
06890     rg.PromoteToParent = FALSE;
06891     pSel->Range::SetRangeControl(rg);
06892 
06893 
06894     if (State.Greyed)
06895         *Description = String_256(_R(IDS_REMOVEBLEND_GREYED));
06896     else
06897         *Description = String_256(_R(IDS_BLENDDISTANCEEDITVALUE));
06898     return State;
06899 }
06900 
06901 /********************************************************************************************
06902 
06903 >   virtual void OpChangeBlendDistance::GetOpName(String_256* OpName) 
06904 
06905     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
06906     Created:    7/9/99
06907     Inputs:     -
06908     Outputs:    The undo string for the operation
06909     Returns:    
06910     Purpose:    The GetOpName fn is overridden so that we return back a description 
06911                 appropriate to the type of attribute that the operation applies. 
06912     Errors:     -
06913     SeeAlso:    -
06914 
06915 ********************************************************************************************/
06916 
06917 void OpChangeBlendDistance::GetOpName(String_256* OpName) 
06918 { 
06919     *OpName = String_256(_R(IDS_STEPDISTANCEUNDO));
06920 }  
06921 
06922 
06923 
06924 
06925 //------------------------------------------------------------------------------------------------
06926 //------------------------------------------------------------------------------------------------
06927 //------------------------------------------------------------------------------------------------
06928 // The RemapBlendAction class
06929 
06930 /********************************************************************************************
06931 
06932 >   RemapBlendAction::RemapBlendAction()
06933 
06934     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
06935     Created:    11/11/94
06936     Inputs:     -
06937     Outputs:    -
06938     Returns:    -
06939     Purpose:    Constructor for the action
06940     Errors:     -
06941     SeeAlso:    -
06942 
06943 ********************************************************************************************/
06944 
06945 RemapBlendAction::RemapBlendAction()
06946 {
06947     pNodeBlend      = NULL;
06948     RemapRef        = 0;
06949 }
06950 
06951 
06952 /********************************************************************************************
06953 
06954 >   ActionCode RemapBlendAction::Init(  Operation*  pOp,
06955                                         ActionList* pActionList,
06956                                         NodeBlend* pNodeBlend,
06957                                         UINT32 RemapRef,
06958                                         INT32 IndexStart,
06959                                         INT32 IndexEnd,
06960                                         RemapBlendAction**  ppNewAction);
06961 
06962     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
06963     Created:    11/11/94
06964     Inputs:     pOp         = ptr to the operation to which this action belongs
06965                 pActionList = ptr to action list to which this action should be added
06966                 pNodeBlend  = ptr to NodeBlend to change 
06967                 RemapRef    = remap ref value to pass to pNodeBlend->Remap()
06968                 IndexStart  = mapping to apply to the start node
06969                 IndexEnd    = mapping to apply to the end  node
06970     Outputs:    ppNewAction = ptr to a ptr to an action, allowing the function to return
06971                               a pointer to the created action
06972     Returns:    ActionCode, one of AC_OK, AC_NO_RECORD or AC_FAIL
06973     Purpose:    This is the function which creates an instance of this action. If there is no room 
06974                 in the undo buffer (which is determined by the base class Init function called within)
06975                 the function will either return AC_NO_RECORD which means the operation can continue, 
06976                 but no undo information needs to be stored, or AC_OK which means the operation should
06977                 continue AND record undo information. If the function returns AC_FAIL, there was not 
06978                 enough memory to record the undo information, and the user has decided not to continue
06979                 with the operation.
06980 
06981                 This function actually performs the remapping of pNodeBlend, using pNodeBlend->Remap().
06982                 This is so that it will only function if it successfully creates the action.
06983     Errors:     -
06984     SeeAlso:    Action::Init()
06985 
06986 ********************************************************************************************/
06987 
06988 
06989 
06990 ActionCode RemapBlendAction::Init(  Operation* pOp,
06991                                     ActionList* pActionList,
06992                                     NodeBlend*  pNodeBlend,
06993                                     UINT32 RemapRef,
06994                                     DocCoord PosStart,
06995                                     DocCoord PosEnd,
06996                                     RemapBlendAction** ppNewAction)
06997 {
06998     UINT32 ActSize = sizeof(RemapBlendAction);
06999 
07000     ActionCode Ac = Action::Init(pOp,pActionList,ActSize,CC_RUNTIME_CLASS(RemapBlendAction),(Action**)ppNewAction);
07001 
07002     if (Ac != AC_FAIL)
07003     {
07004         DocCoord InvPosStart,InvPosEnd;
07005 
07006         if (pNodeBlend->Remap(RemapRef,PosStart,PosEnd,&InvPosStart,&InvPosEnd))
07007         {
07008             (*ppNewAction)->pNodeBlend      = pNodeBlend;
07009             (*ppNewAction)->RemapRef        = RemapRef;
07010             (*ppNewAction)->InvPosStart     = InvPosStart;
07011             (*ppNewAction)->InvPosEnd       = InvPosEnd;
07012         }
07013         else
07014         {
07015             ERROR3("pNodeBlend->Remap() failed");
07016             Ac = AC_FAIL;
07017         }
07018     }
07019 
07020     return Ac;
07021 }
07022 
07023 /********************************************************************************************
07024 
07025 >   ActionCode RemapBlendAction::Execute();
07026 
07027     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
07028     Created:    11/11/94
07029     Inputs:     -
07030     Outputs:    -
07031     Returns:    ActionCode, one of AC_OK, AC_NO_RECORD or AC_FAIL
07032     Purpose:    Executes the action.  
07033                 This remaps the blend node using the inverse mapping indexes it was initialised with,
07034                 creating another RemapBlendAction to invert this mapping
07035     Errors:     -
07036     SeeAlso:    Action::Init()
07037 
07038 ********************************************************************************************/
07039 
07040 ActionCode RemapBlendAction::Execute()
07041 {
07042     ActionCode Act;
07043     RemapBlendAction* pAction;
07044     Act = RemapBlendAction::Init(   pOperation, 
07045                                     pOppositeActLst,
07046                                     pNodeBlend,
07047                                     RemapRef,
07048                                     InvPosStart,
07049                                     InvPosEnd,
07050                                     &pAction);
07051 
07052     return Act;
07053 }
07054 
07055 RemapBlendAction::~RemapBlendAction()
07056 {
07057 }
07058 
07059 //------------------------------------------------------------------------------------------------
07060 //------------------------------------------------------------------------------------------------
07061 //------------------------------------------------------------------------------------------------
07062 // The InitBlendersAction class
07063 
07064 /********************************************************************************************
07065 
07066 >   InitBlendersAction::InitBlendersAction()
07067 
07068     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
07069     Created:    21/11/94
07070     Inputs:     -
07071     Outputs:    -
07072     Returns:    -
07073     Purpose:    Constructor for the action
07074     Errors:     -
07075     SeeAlso:    -
07076 
07077 ********************************************************************************************/
07078 
07079 InitBlendersAction::InitBlendersAction()
07080 {
07081 }
07082 
07083 
07084 /********************************************************************************************
07085 
07086 >   ActionCode InitBlendersAction::Init(    Operation*  pOp,
07087                                         ActionList* pActionList,
07088                                         List* pBlenderInfoList,
07089                                         BOOL Deinit,
07090                                         InitBlendersAction**    ppNewAction);
07091 
07092     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
07093     Created:    21/11/94
07094     Inputs:     pOp             = ptr to the operation to which this action belongs
07095                 pActionList     = ptr to action list to which this action should be added
07096                 pBlenderInfoList= ptr to a NodeBlend
07097                 Deinit          = TRUE if deinit blend, else reinit
07098     Outputs:    ppNewAction     = ptr to a ptr to an action, allowing the function to return
07099                                 a pointer to the created action
07100     Returns:    ActionCode, one of AC_OK, AC_NO_RECORD or AC_FAIL
07101     Purpose:    This is the function which creates an instance of this action. If there is no room 
07102                 in the undo buffer (which is determined by the base class Init function called within)
07103                 the function will either return AC_NO_RECORD which means the operation can continue, 
07104                 but no undo information needs to be stored, or AC_OK which means the operation should
07105                 continue AND record undo information. If the function returns AC_FAIL, there was not 
07106                 enough memory to record the undo information, and the user has decided not to continue
07107                 with the operation.
07108 
07109                 If Deinit == TRUE, then undo will reinit the blenders and redo will deinit them again.
07110                 The reverse happens if Deinit == FALSE;
07111     Errors:     -
07112     SeeAlso:    Action::Init()
07113 
07114 ********************************************************************************************/
07115 
07116 
07117 
07118 ActionCode InitBlendersAction::Init(    OpBlendNodes* pOp,
07119                                         ActionList* pActionList,
07120                                         List* pBlenderInfoList,
07121                                         BOOL DeinitState,
07122                                         InitBlendersAction** ppNewAction)
07123 {
07124     UINT32 ActSize = sizeof(InitBlendersAction)+(pBlenderInfoList->GetCount()*sizeof(BlenderInfoItem));
07125 
07126     ActionCode Ac = Action::Init(pOp,pActionList,ActSize,CC_RUNTIME_CLASS(InitBlendersAction),(Action**)ppNewAction);
07127     InitBlendersAction* pAction = *ppNewAction;
07128 
07129     if (Ac != AC_FAIL)
07130     {
07131         pAction->Deinit = DeinitState;
07132 
07133         // Copy items from the given list to the new action's list
07134         BlenderInfoItem *pItem = (BlenderInfoItem*)pBlenderInfoList->GetHead();
07135         while (pItem != NULL && Ac != AC_FAIL)
07136         {
07137             BlenderInfoItem* pNewItem = pItem->SimpleCopy();
07138             if (pNewItem != NULL)
07139             {
07140                 pAction->BlenderInfoList.AddTail(pNewItem);
07141                 pItem = (BlenderInfoItem*)pBlenderInfoList->GetNext(pItem);
07142             }
07143             else
07144                 Ac = AC_FAIL;
07145         }
07146     }
07147 
07148     return Ac;
07149 }
07150 
07151 /********************************************************************************************
07152 
07153 >   ActionCode InitBlendersAction::Execute();
07154 
07155     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
07156     Created:    21/11/94
07157     Inputs:     -
07158     Outputs:    -
07159     Returns:    ActionCode, one of AC_OK, AC_NO_RECORD or AC_FAIL
07160     Purpose:    Executes the action.  
07161                 This remaps the blend node using the inverse mapping indexes it was initialised with,
07162                 creating another InitBlendersAction to invert this mapping
07163     Errors:     -
07164     SeeAlso:    Action::Init()
07165 
07166 ********************************************************************************************/
07167 
07168 ActionCode InitBlendersAction::Execute()
07169 {
07170     ActionCode Ac;
07171     InitBlendersAction* pAction;
07172     OpBlendNodes* pOp = (OpBlendNodes*)pOperation;
07173 
07174     // Negate the type of blender initialisation
07175     Deinit = !Deinit;
07176 
07177     Ac = InitBlendersAction::Init(  pOp,
07178                                     pOppositeActLst,
07179                                     &BlenderInfoList,
07180                                     Deinit,
07181                                     &pAction);
07182 
07183     if (Ac != AC_FAIL)
07184     {
07185         if (Deinit)
07186             pOp->DeinitBlenders(BlenderInfoList);
07187         else
07188         {
07189             if (!(pOp->ReinitBlenders(BlenderInfoList)))
07190                 Ac = AC_FAIL;
07191         }
07192     }
07193 
07194     return Ac;
07195 }
07196 
07197 InitBlendersAction::~InitBlendersAction()
07198 {
07199     BlenderInfoList.DeleteAll();
07200 }
07201 
07203 //  OpChangeBlend
07204 //
07205 // Changes one of the flags of all the selected blends
07206 
07207 
07208 /********************************************************************************************
07209 
07210 >   void OpChangeBlend::DoWithParam(OpDescriptor*,OpParam* pOpParam)
07211 
07212     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
07213     Created:    6/12/94
07214     Returns:    -
07215     Purpose:    This changes the flag specified in FlagType (given in pOpParam->Param1) in all the 
07216                 selected blend objects to have the same as state of pOpParam->Param2.
07217 
07218 ********************************************************************************************/
07219 
07220 void OpChangeBlend::DoWithParam(OpDescriptor*,OpParam* pOpParam)
07221 {
07222     ERROR3IF(pOpParam == NULL,"NULL OpParam ptr");
07223     if (pOpParam == NULL) return;
07224 
07225     ChangeBlendOpParam* pChangeParam = (ChangeBlendOpParam*)pOpParam;
07226 
07227     List NodeList;
07228     BevelTools::BuildListOfSelectedNodes(&NodeList, CC_RUNTIME_CLASS(NodeRenderableInk));
07229 
07230     BOOL ok = !NodeList.IsEmpty();
07231     if (ok) ok = DoStartSelOp(FALSE,FALSE);
07232 
07233     NodeListItem * pItem = NULL;
07234 
07235     if (ok)
07236     {
07237         pItem = (NodeListItem *)NodeList.GetHead();
07238 
07239         Node* pSelNode = NULL;
07240 
07241         if (pItem)
07242         {
07243             pSelNode = pItem->pNode;
07244         }
07245 
07246         while (pSelNode != NULL && ok)
07247         {
07248             Node* pNode = pSelNode;
07249 
07250             pItem = (NodeListItem *)NodeList.GetNext(pItem);
07251 
07252             if (pItem)
07253             {
07254                 pSelNode = pItem->pNode;
07255             }
07256             else
07257             {
07258                 pSelNode = NULL;
07259             }
07260 
07261             if (pNode->IS_KIND_OF(NodeBlend))
07262             {
07263                 // We now have a selected blend node so:
07264                 //  Invalidate the node's region
07265                 //  Store the current state of blend flag in an undo actiom
07266                 //  Change the flag to the setting in Param2
07267 
07268                 ChangeBlendAction* pAction;
07269                 NodeBlend* pNodeBlend = (NodeBlend*)pNode;
07270 
07271                 // Ask the node if it's ok to do the op
07272                 ObjChangeFlags cFlags;
07273                 // Ilan 7/5/00
07274                 // Ensure AllowOp passes messages on to children in compound (so geom linked attrs informed)
07275                 cFlags.TransformNode = TRUE;
07276                 ObjChangeParam ObjChange(OBJCHANGE_STARTING,cFlags,NULL,this);
07277                 ok = pNodeBlend->AllowOp(&ObjChange);
07278 
07279                 // invalidate the blend's region
07280                 if (ok) ok = DoInvalidateNodeRegion(pNodeBlend,TRUE,FALSE);
07281                 if (ok) ok = (InvalidateBoundsAction::Init(this,&UndoActions,pNodeBlend,TRUE) != AC_FAIL);
07282 
07283                 // change the blend in an undoable way
07284                 if (ok) ok = ChangeBlendAction::Init(this,&UndoActions,pNodeBlend,pChangeParam,&pAction) != AC_FAIL;
07285 
07286                 if (ok && pChangeParam->ChangeType == CHANGEBLEND_TANGENTIAL)
07287                 {
07288                     ok = pNodeBlend->TransformBlendEndObjects(this);
07289                     // invalidate the blend's region again after transfrom
07290                     if (ok) ok = DoInvalidateNodeRegion(pNodeBlend,TRUE,FALSE);
07291                     if (ok) ok = (InvalidateBoundsAction::Init(this,&UndoActions,pNodeBlend,TRUE) != AC_FAIL);
07292                 }
07293             }
07294             
07295         }
07296     }
07297 
07298     // de-allocate the contents of NodeList.
07299     NodeList.DeleteAll();
07300 
07301     if (ok) 
07302     {
07303         // Inform the effected parents of the change
07304         ObjChangeFlags cFlags;
07305         ObjChangeParam ObjChange(OBJCHANGE_FINISHED,cFlags,NULL,this);
07306         UpdateChangedNodes(&ObjChange);
07307 
07308         // Note the way the selected blends were changed
07309         ChangeType = pChangeParam->ChangeType;
07310     }
07311     else
07312         FailAndExecute();
07313 
07314     End();
07315 }
07316 
07317 /********************************************************************************************
07318 
07319 >   BOOL OpChangeBlend::Declare()
07320 
07321     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
07322     Created:    6/12/94
07323     Returns:    TRUE if all went OK, FALSE otherwise
07324     Purpose:    Adds the operation to the list of all known operations
07325 
07326 ********************************************************************************************/
07327 
07328 BOOL OpChangeBlend::Declare()
07329 {
07330     return (RegisterOpDescriptor(
07331                                 0, 
07332                                 0,
07333                                 CC_RUNTIME_CLASS(OpChangeBlend), 
07334                                 OPTOKEN_CHANGEBLEND,
07335                                 OpChangeBlend::GetState,
07336                                 0,  /* help ID */
07337                                 0,  /* bubble ID */
07338                                 0   /* bitmap ID */
07339                                 ));
07340 }
07341 
07342 
07343 /********************************************************************************************
07344 
07345 >   static OpState OpChangeBlend::GetState(String_256* Description, OpDescriptor*)
07346 
07347     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
07348     Created:    6/12/94
07349     Outputs:    Description - GetState fills this string with an approriate description
07350                 of the current state of the operation 
07351     Returns:    The state of the operation, so that menu items (ticks and greying) can be
07352                 done properly
07353     Purpose:    Find out the state of the operation at the specific time
07354 
07355 ********************************************************************************************/
07356 
07357 OpState OpChangeBlend::GetState(String_256* Description, OpDescriptor*)
07358 {
07359     OpState State(FALSE,TRUE); // It's not ticked, but it is greyed by default
07360     
07361     // DMc - to test for bevels & contours
07362     // are there any contour nodes in the selection
07363     List NodeList;
07364     BevelTools::BuildListOfSelectedNodes(&NodeList, CC_RUNTIME_CLASS(NodeBlend));
07365 
07366     if (!NodeList.IsEmpty())
07367     {
07368         State.Greyed = FALSE;
07369     }
07370 
07371     NodeList.DeleteAll();   
07372 
07373     // DY awful hack to allow us to call this op from the bezier tool 
07374     // when we wish to edit the path of a blend on a path.
07375     Range * pSel = GetApplication()->FindSelection();
07376 
07377     if (pSel)
07378     {
07379         Node* pNode = pSel->FindFirst();
07380         if (pNode->IS_KIND_OF(NodeBlendPath))
07381         {
07382             State.Greyed = FALSE;
07383         }
07384     }
07385 
07386     if (State.Greyed)
07387         *Description = String_256(_R(IDS_REMOVEBLEND_GREYED));
07388     else
07389         *Description = String_256(_R(IDS_BLENDSTEPS));
07390 
07391     return State;
07392 
07393 }
07394 
07395 /********************************************************************************************
07396 
07397 >   virtual void OpChangeBlend::GetOpName(String_256* OpName) 
07398 
07399     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
07400     Created:    6/12/94
07401     Inputs:     -
07402     Outputs:    The undo string for the operation
07403     Returns:    
07404     Purpose:    The GetOpName fn is overridden so that we return back a description 
07405                 appropriate to the type of attribute that the operation applies. 
07406     Errors:     -
07407     SeeAlso:    -
07408 
07409 ********************************************************************************************/
07410 
07411 void OpChangeBlend::GetOpName(String_256* OpName) 
07412 { 
07413     UINT32 IDS = _R(IDS_MARKN_EMPTY);
07414 
07415     switch (ChangeType)
07416     {
07417         case CHANGEBLEND_ONETOONE:          IDS = _R(IDS_ONETOONE_UNDO);        break;
07418         case CHANGEBLEND_ANTIALIAS:         IDS = _R(IDS_BLENDANTIALIAS_UNDO);  break;
07419         case CHANGEBLEND_COLOURBLENDTYPE:   IDS = _R(IDS_COLOURBLENDTYPE_UNDO); break;
07420         case CHANGEBLEND_TANGENTIAL:        IDS = _R(IDS_TANGENTIAL_UNDO);      break;
07421         case CHANGEBLEND_OBJECTPROFILE:     IDS = _R(IDS_POSITIONPROFILE_UNDO); break;
07422         case CHANGEBLEND_ATTRPROFILE:       IDS = _R(IDS_ATTRPROFILE_UNDO);     break;
07423         default: ERROR3_PF(("Unknown flag type (%d)",ChangeType));  break;
07424     }
07425 
07426     *OpName = String_256(IDS);
07427 }  
07428 
07429 
07430 //-------------------------------------------------------------------------------------
07431 
07433 //  OpBlendOneToOne
07434 //
07435 // Changes the '1 to 1' state of all the selected blends
07436 
07437 
07438 /********************************************************************************************
07439 
07440 >   void OpBlendOneToOne::Do(OpDescriptor*)
07441 
07442     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
07443     Created:    15/5/94
07444     Returns:    -
07445     Purpose:    Changes the 1 to 1 flag of the selected blend by toggling the flag.
07446 
07447 ********************************************************************************************/
07448 
07449 void OpBlendOneToOne::Do(OpDescriptor* pOpDesc)
07450 {
07451     SelRange* pSel = GetApplication()->FindSelection();
07452     if (pSel != NULL)
07453     {
07454         Node* pSelNode = pSel->FindFirst();
07455         while (pSelNode != NULL)
07456         {
07457             if (IS_A(pSelNode,NodeBlend))
07458             {
07459                 NodeBlend* pNodeBlend = (NodeBlend*)pSelNode;
07460 
07461                 ChangeBlendOpParam Param;
07462                 Param.ChangeType  = CHANGEBLEND_ONETOONE;
07463                 Param.NewOneToOne = !pNodeBlend->IsOneToOne();
07464                 DoWithParam(pOpDesc,&Param);
07465 
07466                 // Only call DoWithParam() once as this func acts upon the whole selection.
07467                 // We only need to scan the selection here in order to find the first selected blend
07468                 // node which is used to determine the state of the NewOneToOne flag
07469                 return;
07470             }
07471             pSelNode = pSel->FindNext(pSelNode);
07472         }
07473     }
07474 }
07475 
07476 /********************************************************************************************
07477 
07478 >   BOOL OpBlendOneToOne::Declare()
07479 
07480     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
07481     Created:    15/5/95
07482     Returns:    TRUE if all went OK, FALSE otherwise
07483     Purpose:    Adds the operation to the list of all known operations
07484 
07485 ********************************************************************************************/
07486 
07487 BOOL OpBlendOneToOne::Declare()
07488 {
07489     return (RegisterOpDescriptor(
07490                                 0, 
07491                                 _R(IDBBL_ONETOONE),
07492                                 //_R(IDS_ONETOONE),
07493                                 CC_RUNTIME_CLASS(OpBlendOneToOne), 
07494                                 OPTOKEN_BLENDONETOONE,
07495                                 OpBlendOneToOne::GetState,
07496                                 0,  /* help ID */
07497                                 0,  /* bubble ID */
07498                                 0   /* bitmap ID */
07499                                 ));
07500 }
07501 
07502 
07503 /********************************************************************************************
07504 
07505 >   static OpState OpBlendOneToOne::GetState(String_256* Description, OpDescriptor*)
07506 
07507     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
07508     Created:    15/5/95
07509     Outputs:    Description - GetState fills this string with an approriate description
07510                 of the current state of the operation 
07511     Returns:    The state of the operation, so that menu items (ticks and greying) can be
07512                 done properly
07513     Purpose:    Find out the state of the operation at the specific time
07514 
07515 ********************************************************************************************/
07516 
07517 OpState OpBlendOneToOne::GetState(String_256* Description, OpDescriptor*)
07518 {
07519     OpState State(FALSE,TRUE);  // not ticked & greyed by default
07520 
07521     SelRange* pSel = GetApplication()->FindSelection();
07522     if (pSel != NULL)
07523     {
07524         Node* pSelNode = pSel->FindFirst();
07525         while (pSelNode != NULL && !State.Ticked)
07526         {
07527             if (IS_A(pSelNode,NodeBlend))
07528             {
07529                 State.Ticked = ((NodeBlend*)pSelNode)->IsOneToOne();
07530                 State.Greyed = FALSE;
07531 
07532                 // We only need to scan the selection here in order to find the first selected blend
07533                 // node which is used to determine the state of the Ticked flag
07534                 return State;
07535             }
07536             pSelNode = pSel->FindNext(pSelNode);
07537         }
07538     }
07539 
07540     return State;
07541 }
07542 
07543 //-------------------------------------------------------------------------------------
07544 
07546 //  OpBlendAntialias
07547 //
07548 // Changes the antialias state of all the selected blends
07549 
07550 
07551 /********************************************************************************************
07552 
07553 >   void OpBlendAntialias::Do(OpDescriptor*)
07554 
07555     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
07556     Created:    15/5/94
07557     Returns:    -
07558     Purpose:    Changes the antialias flag of the selected blend by toggling the flag.
07559 
07560 ********************************************************************************************/
07561 
07562 void OpBlendAntialias::Do(OpDescriptor* pOpDesc)
07563 {
07564     SelRange* pSel = GetApplication()->FindSelection();
07565     if (pSel != NULL)
07566     {
07567         Node* pSelNode = pSel->FindFirst();
07568         while (pSelNode != NULL)
07569         {
07570             if (IS_A(pSelNode,NodeBlend))
07571             {
07572                 NodeBlend* pNodeBlend = (NodeBlend*)pSelNode;
07573 
07574                 ChangeBlendOpParam Param;
07575                 Param.ChangeType   = CHANGEBLEND_ANTIALIAS;
07576                 Param.NewAntiAlias = pNodeBlend->IsNotAntialiased();
07577                 DoWithParam(pOpDesc,&Param);
07578 
07579                 // Only call DoWithParam() once as this func acts upon the whole selection.
07580                 // We only need to scan the selection here in order to find the first selected blend
07581                 // node which is used to determine the state of the NewAntialias flag
07582                 return;
07583             }
07584             pSelNode = pSel->FindNext(pSelNode);
07585         }
07586     }
07587 }
07588 
07589 /********************************************************************************************
07590 
07591 >   BOOL OpBlendAntialias::Declare()
07592 
07593     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
07594     Created:    15/5/95
07595     Returns:    TRUE if all went OK, FALSE otherwise
07596     Purpose:    Adds the operation to the list of all known operations
07597 
07598 ********************************************************************************************/
07599 
07600 BOOL OpBlendAntialias::Declare()
07601 {
07602     return (RegisterOpDescriptor(
07603                                 0, 
07604                                 _R(IDBBL_BLENDANTIALIAS),
07605                                 //_R(IDS_BLENDANTIALIAS),
07606                                 CC_RUNTIME_CLASS(OpBlendAntialias), 
07607                                 OPTOKEN_BLENDANTIALIAS,
07608                                 OpBlendAntialias::GetState,
07609                                 0,  /* help ID */
07610                                 0,  /* bubble ID */
07611                                 0   /* bitmap ID */
07612                                 ));
07613 }
07614 
07615 
07616 /********************************************************************************************
07617 
07618 >   static OpState OpBlendAntialias::GetState(String_256* Description, OpDescriptor*)
07619 
07620     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
07621     Created:    15/5/95
07622     Outputs:    Description - GetState fills this string with an approriate description
07623                 of the current state of the operation 
07624     Returns:    The state of the operation, so that menu items (ticks and greying) can be
07625                 done properly
07626     Purpose:    Find out the state of the operation at the specific time
07627 
07628 ********************************************************************************************/
07629 
07630 OpState OpBlendAntialias::GetState(String_256* Description, OpDescriptor*)
07631 {
07632     OpState State(FALSE,TRUE);  // not ticked & greyed by default
07633 
07634     SelRange* pSel = GetApplication()->FindSelection();
07635     if (pSel != NULL)
07636     {
07637         Node* pSelNode = pSel->FindFirst();
07638         while (pSelNode != NULL && !State.Ticked)
07639         {
07640             if (IS_A(pSelNode,NodeBlend))
07641             {
07642                 State.Ticked = !(((NodeBlend*)pSelNode)->IsNotAntialiased());
07643                 State.Greyed = FALSE;
07644 
07645                 // We only need to scan the selection here in order to find the first selected blend
07646                 // node which is used to determine the state of the Ticked flag
07647                 return State;
07648             }
07649             pSelNode = pSel->FindNext(pSelNode);
07650         }
07651     }
07652 
07653     return State;
07654 }
07655 
07656 //-------------------------------------------------------------------------------------
07657 
07659 //  OpBlendTangential
07660 //
07661 // Changes the Tangential state of all the selected blends
07662 
07663 
07664 /********************************************************************************************
07665 
07666 >   void OpBlendTangential::Do(OpDescriptor*)
07667 
07668     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
07669     Created:    21/5/99
07670     Returns:    -
07671     Purpose:    Changes the Tangential flag of the selected blend by toggling the flag.
07672 
07673 ********************************************************************************************/
07674 
07675 void OpBlendTangential::Do(OpDescriptor* pOpDesc)
07676 {
07677     SelRange* pSel = GetApplication()->FindSelection();
07678     if (pSel != NULL)
07679     {
07680         Node* pSelNode = pSel->FindFirst();
07681         while (pSelNode != NULL)
07682         {
07683             if (IS_A(pSelNode,NodeBlend))
07684             {
07685                 NodeBlend* pNodeBlend = (NodeBlend*)pSelNode;
07686 
07687                 ChangeBlendOpParam Param;
07688                 Param.ChangeType   = CHANGEBLEND_TANGENTIAL;
07689                 Param.NewTangential = !pNodeBlend->IsTangential();
07690                 DoWithParam(pOpDesc,&Param);
07691 
07692                 // Only call DoWithParam() once as this func acts upon the whole selection.
07693                 // We only need to scan the selection here in order to find the first selected blend
07694                 // node which is used to determine the state of the NewTangential flag
07695                 return;
07696             }
07697             pSelNode = pSel->FindNext(pSelNode);
07698         }
07699     }
07700 }
07701 
07702 /********************************************************************************************
07703 
07704 >   BOOL OpBlendTangential::Declare()
07705 
07706     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
07707     Created:    21/5/99
07708     Returns:    TRUE if all went OK, FALSE otherwise
07709     Purpose:    Adds the operation to the list of all known operations
07710 
07711 ********************************************************************************************/
07712 
07713 BOOL OpBlendTangential::Declare()
07714 {
07715     return (RegisterOpDescriptor(
07716                                 0, 
07717                                 _R(IDBBL_TANGENTIAL),
07718                                 CC_RUNTIME_CLASS(OpBlendTangential), 
07719                                 OPTOKEN_BLENDTANGENTIAL,
07720                                 OpBlendTangential::GetState,
07721                                 0,  /* help ID */
07722                                 0,  /* bubble ID */
07723                                 0   /* bitmap ID */
07724                                 ));
07725 }
07726 
07727 
07728 /********************************************************************************************
07729 
07730 >   static OpState OpBlendTangential::GetState(String_256* Description, OpDescriptor*)
07731 
07732     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
07733     Created:    21/5/99
07734     Outputs:    Description - GetState fills this string with an approriate description
07735                 of the current state of the operation 
07736     Returns:    The state of the operation, so that menu items (ticks and greying) can be
07737                 done properly
07738     Purpose:    Find out the state of the operation at the specific time
07739 
07740 ********************************************************************************************/
07741 
07742 OpState OpBlendTangential::GetState(String_256* Description, OpDescriptor*)
07743 {
07744     OpState State(FALSE,TRUE);  // not ticked & greyed by default
07745 
07746     SelRange* pSel = GetApplication()->FindSelection();
07747     if (pSel != NULL)
07748     {
07749         Node* pSelNode = pSel->FindFirst();
07750         while (pSelNode != NULL && !State.Ticked)
07751         {
07752             if (IS_A(pSelNode,NodeBlend))
07753             {
07754                 NodeBlend* pNodeBlend = (NodeBlend*)pSelNode;
07755 
07756                 if (!State.Ticked)
07757                     State.Ticked = pNodeBlend->IsTangential();
07758 
07759                 if (State.Greyed)
07760                     State.Greyed = (pNodeBlend->GetNodeBlendPath(0) != NULL);
07761             }
07762             pSelNode = pSel->FindNext(pSelNode);
07763         }
07764     }
07765 
07766     return State;
07767 }
07768 //------------------------------------------------------------------------------------------------
07769 //------------------------------------------------------------------------------------------------
07770 //------------------------------------------------------------------------------------------------
07771 // The ChangeBlendAction class
07772 
07773 /********************************************************************************************
07774 
07775 >   ChangeBlendAction::ChangeBlendAction()
07776 
07777     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
07778     Created:    6/12/94
07779     Inputs:     -
07780     Outputs:    -
07781     Returns:    -
07782     Purpose:    Constructor for the action
07783     Errors:     -
07784     SeeAlso:    -
07785 
07786 ********************************************************************************************/
07787 
07788 ChangeBlendAction::ChangeBlendAction()
07789 {
07790     pNodeBlend  = NULL;
07791 }
07792 
07793 
07794 /********************************************************************************************
07795 
07796 >   ActionCode ChangeBlendAction::Init(     Operation*      pOp,
07797                                             ActionList*     pActionList,
07798                                             NodeRenderableInk * pNodeBlend,
07799                                             ChangeBlendOpParam  *pChangeParam,
07800                                             ChangeBlendAction** ppNewAction);
07801 
07802     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
07803     Created:    6/12/94
07804     Inputs:     pOp             = ptr to the operation to which this action belongs
07805                 pActionList     =  ptr to action list to which this action should be added
07806                 pNodeBlend      = ptr to NodeBlend to change 
07807                 pChangeParam    = ptr to class that details how the blend should be changed.
07808     Outputs:    ppNewAction     = ptr to a ptr to an action, allowing the function to return
07809                                   a pointer to the created action
07810     Returns:    ActionCode, one of AC_OK, AC_NO_RECORD or AC_FAIL
07811     Purpose:    This is the function which creates an instance of this action. If there is no room 
07812                 in the undo buffer (which is determined by the base class Init function called within)
07813                 the function will either return AC_NO_RECORD which means the operation can continue, 
07814                 but no undo information needs to be stored, or AC_OK which means the operation should
07815                 continue AND record undo information. If the function returns AC_FAIL, there was not 
07816                 enough memory to record the undo information, and the user has decided not to continue
07817                 with the operation.
07818 
07819                 This function actually changes the blend node in a way specified in pChangeParam
07820     Errors:     -
07821     SeeAlso:    Action::Init()
07822 
07823 ********************************************************************************************/
07824 
07825 
07826 
07827 ActionCode ChangeBlendAction::Init( Operation*          pOp,
07828                                     ActionList*         pActionList,
07829                                     NodeRenderableInk*  pNodeBlend,
07830                                     ChangeBlendOpParam* pChangeParam,
07831                                     ChangeBlendAction** ppNewAction)
07832 {
07833     ERROR2IF(pNodeBlend   == NULL,AC_FAIL,"pNodeBlend is NULL");
07834     ERROR2IF(pChangeParam == NULL,AC_FAIL,"pChangeParam is NULL");
07835 
07836     UINT32 ActSize = sizeof(ChangeBlendAction);
07837 
07838     ChangeBlendAction* pNewAction;
07839     ActionCode Ac = Action::Init(pOp,pActionList,ActSize,CC_RUNTIME_CLASS(ChangeBlendAction),(Action**)&pNewAction);
07840 
07841     *ppNewAction = pNewAction;
07842 
07843     BOOL bNodeIsContour = FALSE;
07844 
07845     if (Ac != AC_FAIL && pNewAction != NULL)
07846     {
07847         if (!bNodeIsContour)
07848         {
07849             ChangeBlendType ChangeType = pChangeParam->ChangeType;
07850             
07851             pNewAction->pNodeBlend             = pNodeBlend;
07852             pNewAction->ChangeParam.ChangeType = ChangeType;
07853             pNewAction->ChangeParam.SetOldValues(pNodeBlend);
07854             
07855             switch (ChangeType)
07856             {
07857             case CHANGEBLEND_ONETOONE:          ((NodeBlend *)pNodeBlend)->SetOneToOne(pChangeParam->NewOneToOne);          break;
07858             case CHANGEBLEND_ANTIALIAS:         ((NodeBlend *)pNodeBlend)->SetNotAntialiased(!pChangeParam->NewAntiAlias);  break;
07859             case CHANGEBLEND_COLOURBLENDTYPE:   ((NodeBlend *)pNodeBlend)->SetColourBlendType(pChangeParam->NewColBlendType);   break;
07860             case CHANGEBLEND_EDITEND:           ((NodeBlend *)pNodeBlend)->SetLastEdited(pChangeParam->NewEndObject);       break;
07861             case CHANGEBLEND_TANGENTIAL:
07862             {
07863                 // Only change the tangential flag of blends on a curve
07864                 if (((NodeBlend *)pNodeBlend)->GetNodeBlendPath(0) != NULL)
07865                     ((NodeBlend *)pNodeBlend)->SetTangential(pChangeParam->NewTangential);      
07866             }
07867             break;
07868 
07869             // Diccon Yamanaka 9/99 added profile code
07870             case CHANGEBLEND_OBJECTPROFILE:  
07871             {
07872                 AFp BiasValue = (pChangeParam)->NewObjectProfile.GetBias();
07873                 AFp GainValue = (pChangeParam)->NewObjectProfile.GetGain();
07874                 CProfileBiasGain *pProfile = ((NodeBlend *)pNodeBlend)->GetObjectProfile();
07875                 
07876                 if (pProfile != NULL)
07877                 {
07878                     ((NodeBlend*) pNodeBlend)->RequestObjectProfileProcessing (TRUE);
07879                     pProfile->SetBiasGain(BiasValue, GainValue);
07880                 }
07881                 
07882             }
07883             break;
07884             case CHANGEBLEND_ATTRPROFILE:
07885             {
07886                 AFp BiasValue = (pChangeParam)->NewAttrProfile.GetBias();
07887                 AFp GainValue = (pChangeParam)->NewAttrProfile.GetGain();
07888                 CProfileBiasGain *pProfile = ((NodeBlend *)pNodeBlend)->GetAttrProfile();
07889                 
07890                 if (pProfile != NULL)
07891                 {
07892                     // this may seem a little wierd - BUT we still need to do position
07893                     // processing for the attribute ....
07894                     ((NodeBlend*) pNodeBlend)->RequestObjectProfileProcessing (TRUE);
07895                     pProfile->SetBiasGain(BiasValue, GainValue);
07896                 }
07897 
07898             }
07899             break;
07900             
07901             case CHANGEBLEND_BLENDONPATH:
07902             {
07903                 ((NodeBlend*)pNodeBlend)->SetBlendedOnCurve(pChangeParam->NewBlendedOnCurve);
07904     
07905                 // increment/decrement the nodeblendpath counter
07906                 if (pChangeParam->NewBlendedOnCurve)
07907                     ((NodeBlend*)pNodeBlend)->SetNumNodeBlendPaths(TRUE);
07908                 else
07909                     ((NodeBlend*)pNodeBlend)->SetNumNodeBlendPaths(FALSE);
07910 
07911                 NodeBlender* pNodeBlender = ((NodeBlend*)pNodeBlend)->FindFirstBlender();
07912                 ERROR2IF((pNodeBlender == NULL), AC_FAIL, "Couldn't get blender");
07913                 while (pNodeBlender != NULL)
07914                 {
07915                     if (pChangeParam->NewBlendedOnCurve)
07916                         pNodeBlender->SetNodeBlendPathIndex(0);
07917                     else
07918                         pNodeBlender->SetNodeBlendPathIndex(-1);
07919 
07920                     pNodeBlender = ((NodeBlend*)pNodeBlend)->FindNextBlender(pNodeBlender);
07921                 }
07922 
07923                 /*
07924                 Spread* pSpread = pNodeBlend->FindParentSpread();
07925                 if (pSpread != NULL)
07926                 {
07927                     RenderRegion* pOnTopRegion = DocView::RenderOnTop(NULL, pSpread, ClippedEOR);
07928                     while (pOnTopRegion)
07929                     {
07930                         ((NodeBlend*)pNodeBlend)->RenderObjectBlobs(pOnTopRegion);
07931 
07932                         // Go find the next region
07933                         pOnTopRegion = DocView::GetNextOnTop(NULL);
07934                     }
07935                 }
07936                 */
07937             }
07938             break;
07939 
07940             default : ERROR2(AC_FAIL,"Unknown change blend type"); break;
07941             
07942             } // end
07943         } // end if (!bNode..
07944         else
07945         {
07946             ChangeBlendType ChangeType = pChangeParam->ChangeType;
07947             
07948             pNewAction->pNodeBlend             = pNodeBlend;
07949             pNewAction->ChangeParam.ChangeType = ChangeType;
07950             pNewAction->ChangeParam.SetOldValues(pNodeBlend);
07951             
07952             switch (ChangeType)
07953             {
07954             case CHANGEBLEND_COLOURBLENDTYPE:   ((NodeContourController *)pNodeBlend)->SetColourBlendType(pChangeParam->NewColBlendType);   break;
07955             default : ERROR2(AC_FAIL,"Unknown change blend type"); break;
07956             }
07957         } // end else
07958 
07959 
07960         pNewAction->ChangeParam.SetNewValues(pNodeBlend);
07961     } // end if (AC != ..
07962 
07963     return Ac;
07964 }
07965 
07966 
07967 
07968 /********************************************************************************************
07969 
07970 >   void ChangeBlendAction::ChangeObjectProfileWithNoUndo (CProfileBiasGain &Profile, BOOL regenerateParents)
07971 
07972     Author:     Chris_Snook (Xara Group Ltd) <camelotdev@xara.com>
07973     Created:    24/2/2000
07974     Inputs:     Profile - the profile that is to be applied directly (i.e.  applied with no undo)
07975                 regenerateParents - do we need to regenerate the parents of the affected nodes?
07976     Purpose:    When applying blend (object) profiles, we only want to generate one bit of undo information.
07977                 This function allows us to meet this requirement (the one bit of undo information
07978                 is generated via OpChangeBlend::DoWithParam ()).  This function is ONLY
07979                 called from within BlendInfoBarOp::ChangeProfile () - after
07980                 OpChangeBlend::DoWithParam () has been called.
07981     Errors:     -
07982     SeeAlso:    BlendInfoBarOp::ChangeProfile (), OpChangeBlend::DoWithParam ().
07983 
07984 ********************************************************************************************/
07985 
07986 void ChangeBlendAction::ChangeObjectProfileWithNoUndo (CProfileBiasGain &Profile, BOOL regenerateParents)
07987 {
07988     List NodeList;
07989     BevelTools::BuildListOfSelectedNodes(&NodeList, CC_RUNTIME_CLASS(NodeRenderableInk));
07990 
07991     BOOL ok = !NodeList.IsEmpty();
07992 
07993     NodeListItem * pItem = NULL;
07994 
07995     if (ok)
07996     {
07997         pItem = (NodeListItem *)NodeList.GetHead();
07998 
07999         Node* pSelNode = NULL;
08000 
08001         if (pItem)
08002         {
08003             pSelNode = pItem->pNode;
08004         }
08005 
08006         Document * pDoc = Document::GetCurrent();
08007 
08008         while (pSelNode != NULL && ok)
08009         {
08010             Node* pNode = pSelNode;
08011 
08012             pItem = (NodeListItem *)NodeList.GetNext(pItem);
08013 
08014             if (pItem)
08015             {
08016                 pSelNode = pItem->pNode;
08017             }
08018             else
08019             {
08020                 pSelNode = NULL;
08021             }
08022 
08023             if (pNode->IS_KIND_OF(NodeBlend))
08024             {
08025                 if (!regenerateParents)
08026                 {
08027                     // We now have a selected blend node so:
08028                     // Change the profile in a non-undoable fashion
08029                     // Invalidate the node's region (thereby causing a redraw)
08030 
08031                     NodeBlend* pNodeBlend = (NodeBlend*)pNode;
08032 
08033                     AFp BiasValue = Profile.GetBias ();
08034                     AFp GainValue = Profile.GetGain ();
08035                     CProfileBiasGain *pProfile = ((NodeBlend *)pNodeBlend)->GetObjectProfile();
08036                     
08037                     if (pProfile != NULL)
08038                     {
08039                         pNodeBlend->RequestObjectProfileProcessing (TRUE);
08040                         pProfile->SetBiasGain(BiasValue, GainValue);
08041                     }
08042 
08043                     pDoc->ForceRedraw(pNodeBlend->FindParentSpread(), 
08044                     pNodeBlend->GetBoundingRect(), FALSE, pNodeBlend);
08045                 }
08046                 else
08047                 {
08048                     // now the fun begins!  I need to persuade parents nodes to regenerate - BUT
08049                     // I don't have an op to do it with!  Well lets not let a little thing like that
08050                     // deter us ....
08051 
08052                     NodeBlend* pNodeBlend = (NodeBlend*)pNode;
08053                     
08054                     // Ask the node if it's ok to do the op
08055                     ObjChangeFlags cFlags;
08056                     // Ilan 7/5/00
08057                     // Ensure AllowOp passes messages on to children in compound (so geom linked attrs informed)
08058                     cFlags.TransformNode = TRUE;
08059                     ObjChangeParam ObjChange(OBJCHANGE_STARTING,cFlags,NULL,NULL);//this);
08060                     ok = pNodeBlend->AllowOp(&ObjChange);
08061                 }
08062             }
08063         }
08064 
08065         if (pDoc->GetOpHistory ().CanRedo ())
08066         {
08067             // then we need to clear out the redo information - since we are now 'before' it ....
08068             pDoc->GetOpHistory ().DeleteRedoableOps ();
08069 
08070             // and update the state of things ....
08071             DialogBarOp::SetSystemStateChanged();
08072             DialogBarOp::UpdateStateOfAllBars();
08073         }
08074     }
08075 
08076     // de-allocate the contents of NodeList.
08077     NodeList.DeleteAll();
08078 
08079     if (ok && regenerateParents)
08080     {
08081         // Inform the effected parents of the change
08082         ObjChangeFlags cFlags;
08083         cFlags.RegenerateNode = TRUE;
08084         ObjChangeParam ObjChange(OBJCHANGE_FINISHED,cFlags,NULL,NULL);
08085         
08086         UndoableOperation undoOp;
08087         
08088         undoOp.UpdateChangedNodes(&ObjChange);      // get compound nodes to regenerate themselves
08089         undoOp.FailAndDiscard ();                   // Although we will have succeeded, we don't
08090                                                     // want any of the overheads that would result
08091                                                     // from succeeding - so simply fail
08092     }
08093 }
08094 
08095 
08096 
08097 /********************************************************************************************
08098 
08099 >   void ChangeBlendAction::ChangeAttributeProfileWithNoUndo (CProfileBiasGain &Profile)
08100 
08101     Author:     Chris_Snook (Xara Group Ltd) <camelotdev@xara.com>
08102     Created:    24/2/2000
08103     Inputs:     Profile - the profile that is to be applied directly (i.e.  applied with no undo)
08104     Purpose:    When applying blend (attribute) profiles, we only want to generate one bit of undo information.
08105                 This function allows us to meet this requirement (the one bit of undo information
08106                 is generated via OpChangeBlend::DoWithParam ()).  This function is ONLY
08107                 called from within BlendInfoBarOp::ChangeProfile () - after
08108                 OpChangeBlend::DoWithParam () has been called.
08109     Errors:     -
08110     SeeAlso:    BlendInfoBarOp::ChangeProfile (), OpChangeBlend::DoWithParam ().
08111 
08112 ********************************************************************************************/
08113 
08114 void ChangeBlendAction::ChangeAttributeProfileWithNoUndo (CProfileBiasGain &Profile)
08115 {
08116     List NodeList;
08117     BevelTools::BuildListOfSelectedNodes(&NodeList, CC_RUNTIME_CLASS(NodeRenderableInk));
08118 
08119     BOOL ok = !NodeList.IsEmpty();
08120 
08121     NodeListItem * pItem = NULL;
08122 
08123     if (ok)
08124     {
08125         pItem = (NodeListItem *)NodeList.GetHead();
08126 
08127         Node* pSelNode = NULL;
08128 
08129         if (pItem)
08130         {
08131             pSelNode = pItem->pNode;
08132         }
08133 
08134         Document * pDoc = Document::GetCurrent();
08135 
08136         while (pSelNode != NULL && ok)
08137         {
08138             Node* pNode = pSelNode;
08139 
08140             pItem = (NodeListItem *)NodeList.GetNext(pItem);
08141 
08142             if (pItem)
08143             {
08144                 pSelNode = pItem->pNode;
08145             }
08146             else
08147             {
08148                 pSelNode = NULL;
08149             }
08150 
08151             if (pNode->IS_KIND_OF(NodeBlend))
08152             {
08153                 // We now have a selected blend node so:
08154                 // Change the profile in a non-undoable fashion
08155                 // Invalidate the node's region (thereby causing a redraw)
08156 
08157                 NodeBlend* pNodeBlend = (NodeBlend*)pNode;
08158 
08159                 AFp BiasValue = Profile.GetBias ();
08160                 AFp GainValue = Profile.GetGain ();
08161                 CProfileBiasGain *pProfile = ((NodeBlend *)pNodeBlend)->GetAttrProfile();
08162                 
08163                 if (pProfile != NULL)
08164                 {
08165                     // this may seem a little wierd - BUT we still need to do position
08166                     // processing for the attribute ....
08167                     
08168                     pNodeBlend->RequestObjectProfileProcessing (TRUE);
08169                     pProfile->SetBiasGain(BiasValue, GainValue);
08170                 }
08171 
08172                 pDoc->ForceRedraw(pNodeBlend->FindParentSpread(), 
08173                 pNodeBlend->GetBoundingRect(), FALSE, pNodeBlend);
08174             }
08175         }
08176 
08177         if (pDoc->GetOpHistory ().CanRedo ())
08178         {
08179             // then we need to clear out the redo information - since we are now 'before' it ....
08180             pDoc->GetOpHistory ().DeleteRedoableOps ();
08181 
08182             // and update the state of things ....
08183             DialogBarOp::SetSystemStateChanged();
08184             DialogBarOp::UpdateStateOfAllBars();
08185         }
08186     }
08187 
08188     // de-allocate the contents of NodeList.
08189     NodeList.DeleteAll();
08190 }
08191 
08192 
08193 
08194 /********************************************************************************************
08195 
08196 >   ActionCode ChangeBlendAction::Execute();
08197 
08198     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
08199     Created:    6/12/94
08200     Inputs:     -
08201     Outputs:    -
08202     Returns:    ActionCode, one of AC_OK, AC_NO_RECORD or AC_FAIL
08203     Purpose:    Executes the action.  This will reset the num blend steps in pThisNodeBlend to OldNumSteps,
08204                 after creating another action to record the current num steps of pThisNodeBlend
08205     Errors:     -
08206     SeeAlso:    Action::Init()
08207 
08208 ********************************************************************************************/
08209 
08210 ActionCode ChangeBlendAction::Execute()
08211 {
08212     ChangeParam.SwapOldAndNew();
08213 
08214     ActionCode Act;
08215     ChangeBlendAction* pAction;
08216     Act = ChangeBlendAction::Init(pOperation,pOppositeActLst,pNodeBlend,&ChangeParam,&pAction);
08217 
08218     return Act;
08219 }
08220 
08221 ChangeBlendAction::~ChangeBlendAction()
08222 {
08223 }
08224 
08225 //-------------------------------------------------------
08226 //-------------------------------------------------------
08227 //-------------------------------------------------------
08228 
08229 /********************************************************************************************
08230 
08231 >   void ChangeBlendOpParam::SetOldValues(NodeBlend* pNodeBlend)
08232 
08233     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
08234     Created:    20/3/95
08235     Inputs:     pNodeBlend = ptr to a node blend object
08236     Outputs:    -
08237     Returns:    -
08238     Purpose:    Copies the current state of the blend node into this classes Old member vars
08239     Errors:     -
08240 
08241 ********************************************************************************************/
08242 
08243 void ChangeBlendOpParam::SetOldValues(NodeRenderableInk* pNodeBlend)
08244 {
08245     // DMc changed to include contours
08246     
08247     if (pNodeBlend->IsKindOf(CC_RUNTIME_CLASS(NodeBlend)))
08248     {
08249         OldAntiAlias      = !((NodeBlend*)pNodeBlend)->IsNotAntialiased();
08250         OldOneToOne       = ((NodeBlend *)pNodeBlend)->IsOneToOne();
08251         OldColBlendType   = ((NodeBlend *)pNodeBlend)->GetColourBlendType();
08252         OldTangential     = ((NodeBlend *)pNodeBlend)->IsTangential();
08253         OldBlendedOnCurve = ((NodeBlend *)pNodeBlend)->IsOnACurve();
08254         OldEndObject     = ((NodeBlend *)pNodeBlend)->GetLastEdited();
08255 
08256         // get the object profile values
08257         CProfileBiasGain* pProfile = ((NodeBlend *)pNodeBlend)->GetObjectProfile();
08258         OldObjectProfile = *pProfile;
08259         pProfile = NULL;
08260     
08261         pProfile = ((NodeBlend *)pNodeBlend)->GetAttrProfile();
08262         OldAttrProfile = *pProfile;
08263         pProfile = NULL;
08264     }
08265 }
08266 
08267 /********************************************************************************************
08268 
08269 >   void ChangeBlendOpParam::SetNewValues(NodeRenderabeleInk* pNodeBlend)
08270 
08271     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
08272     Created:    20/3/95
08273     Inputs:     pNodeBlend = ptr to a node blend object
08274     Outputs:    -
08275     Returns:    -
08276     Purpose:    Copies the current state of the blend node into this classes New member vars
08277     Errors:     -
08278 
08279 ********************************************************************************************/
08280 
08281 void ChangeBlendOpParam::SetNewValues(NodeRenderableInk* pNodeBlend)
08282 {
08283     if (pNodeBlend->IsKindOf(CC_RUNTIME_CLASS(NodeBlend)))
08284     {
08285         NewAntiAlias      = !((NodeBlend *)pNodeBlend)->IsNotAntialiased();
08286         NewOneToOne       = ((NodeBlend *)pNodeBlend)->IsOneToOne();
08287         NewColBlendType   = ((NodeBlend *)pNodeBlend)->GetColourBlendType();
08288         NewTangential     = ((NodeBlend *)pNodeBlend)->IsTangential();
08289         NewBlendedOnCurve = ((NodeBlend *)pNodeBlend)->IsOnACurve();
08290         NewEndObject     = ((NodeBlend *)pNodeBlend)->GetLastEdited();
08291 
08292         // DY profile code 9/99
08293         CProfileBiasGain* pProfile = ((NodeBlend *)pNodeBlend)->GetObjectProfile();
08294         NewObjectProfile = *pProfile;
08295         pProfile = NULL;
08296         pProfile = ((NodeBlend *)pNodeBlend)->GetAttrProfile();
08297         NewAttrProfile = *pProfile;
08298         pProfile = NULL;
08299     }
08300 }
08301 
08302 /********************************************************************************************
08303 
08304 >   void ChangeBlendOpParam::SwapOldAndNew()
08305 
08306     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
08307     Created:    20/3/95
08308     Inputs:     -
08309     Outputs:    -
08310     Returns:    -
08311     Purpose:    Swaps the old and new values of this class
08312     Errors:     -
08313 
08314 ********************************************************************************************/
08315 
08316 void ChangeBlendOpParam::SwapOldAndNew()
08317 {
08318     SWAP(BOOL,              OldAntiAlias,      NewAntiAlias);
08319     SWAP(BOOL,              OldOneToOne,       NewOneToOne);
08320     SWAP(ColourBlendType,   OldColBlendType,   NewColBlendType);
08321     SWAP(BOOL,              OldTangential,     NewTangential);
08322     SWAP(BOOL,              OldBlendedOnCurve, NewBlendedOnCurve);
08323     SWAP(EndObject,         OldEndObject,      NewEndObject);
08324     // DY 9/99 profile code
08325     SwapProfiles(&OldObjectProfile, &NewObjectProfile);
08326     SwapProfiles(&OldAttrProfile, &NewAttrProfile);
08327 }
08328 
08329 /********************************************************************************************
08330 
08331 >   void ChangeBlendOpParam::SwapProfiles()
08332 
08333     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
08334     Created:    25/9/99
08335     Inputs:     pointers to the old and new CProfileBiasgain objects
08336     Outputs:    the same pointers, swapped
08337     Returns:    -
08338     Purpose:    Swaps the old and new profiles of this class by swapping the pointers
08339     Errors:     -
08340 
08341 ********************************************************************************************/
08342 
08343 void ChangeBlendOpParam::SwapProfiles(CProfileBiasGain* pOldProfile, 
08344                                       CProfileBiasGain* pNewProfile) 
08345 {
08346     CProfileBiasGain TempProfile = *pOldProfile;
08347     *pOldProfile = *pNewProfile;
08348     *pNewProfile = TempProfile;
08349 }
08350 
08351 
08352 
08353 //------------------------------------------------------------------------------------------------
08354 //------------------------------------------------------------------------------------------------
08355 //------------------------------------------------------------------------------------------------
08356 // The ChangeBlenderAction class
08357 
08358 /********************************************************************************************
08359 
08360 >   ChangeBlenderAction::ChangeBlenderAction()
08361 
08362     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
08363     Created:    20/5/99
08364     Purpose:    Constructor for the action
08365 
08366 ********************************************************************************************/
08367 
08368 ChangeBlenderAction::ChangeBlenderAction()
08369 {
08370     m_pNodeBlender = NULL;
08371 }
08372 
08373 
08374 /********************************************************************************************
08375 
08376 >   ActionCode ChangeBlenderAction::Init(   Operation*              pOp,
08377                                             ActionList*             pActionList,
08378                                             NodeBlender*            pNodeBlender,
08379                                             ChangeBlenderOpParam&   ChangeParam)
08380 
08381     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
08382     Created:    20/5/99
08383     Inputs:     pOp             = ptr to the operation to which this action belongs
08384                 pActionList     = ptr to action list to which this action should be added
08385                 pNodeBlender    = ptr to NodeBlender to change 
08386                 ChangeParam     = class that details how the blender should be changed.
08387     Outputs:    -
08388     Returns:    ActionCode, one of AC_OK, AC_NO_RECORD or AC_FAIL
08389     Purpose:    This is the function which creates an instance of this action. If there is no room 
08390                 in the undo buffer (which is determined by the base class Init function called within)
08391                 the function will either return AC_NO_RECORD which means the operation can continue, 
08392                 but no undo information needs to be stored, or AC_OK which means the operation should
08393                 continue AND record undo information. If the function returns AC_FAIL, there was not 
08394                 enough memory to record the undo information, and the user has decided not to continue
08395                 with the operation.
08396 
08397                 This function actually changes the blender node in a way specified in ChangeParam
08398     Errors:     -
08399     SeeAlso:    Action::Init()
08400 
08401 ********************************************************************************************/
08402 
08403 
08404 
08405 ActionCode ChangeBlenderAction::Init(   Operation*              pOp,
08406                                         ActionList*             pActionList,
08407                                         NodeBlender*            pNodeBlender,
08408                                         ChangeBlenderOpParam&   ChangeParam)
08409 {
08410     ERROR2IF(pNodeBlender == NULL,AC_FAIL,"pNodeBlender is NULL");
08411 
08412     UINT32 ActSize = sizeof(ChangeBlenderAction);
08413 
08414     ChangeBlenderAction* pNewAction;
08415     ActionCode Ac = Action::Init(pOp,pActionList,ActSize,CC_RUNTIME_CLASS(ChangeBlenderAction),(Action**)&pNewAction);
08416 
08417     if (Ac != AC_FAIL && pNewAction != NULL)
08418     {
08419         ChangeBlenderType ChangeType = ChangeParam.m_ChangeType;
08420 
08421         pNewAction->m_pNodeBlender             = pNodeBlender;
08422         pNewAction->m_ChangeParam.m_ChangeType = ChangeType;
08423 
08424         pNewAction->m_ChangeParam.SetOldValues(pNodeBlender);
08425         switch (ChangeType)
08426         {
08427             case CHANGEBLENDER_ANGLESTART:          pNodeBlender->SetAngleStart(ChangeParam.m_NewAngleStart); break;
08428             case CHANGEBLENDER_ANGLEEND:            pNodeBlender->SetAngleEnd(  ChangeParam.m_NewAngleEnd); break;
08429             case CHANGEBLENDER_PATHSTART:           
08430             {
08431                 pNodeBlender->SetProportionOfPathDistStart(ChangeParam.m_NewPathStart); 
08432                 pNodeBlender->SetUninitialised();
08433             }
08434             break;
08435             case CHANGEBLENDER_PATHEND:             
08436             {
08437                 pNodeBlender->SetProportionOfPathDistEnd(ChangeParam.m_NewPathEnd); 
08438                 pNodeBlender->SetUninitialised();
08439             }   
08440             break;
08441             case CHANGEBLENDER_REGEN:
08442             {
08443                 pNodeBlender->SetUninitialised();
08444             }
08445             break;
08446             case CHANGEBLENDER_NBPINDEX:
08447             {
08448                 pNodeBlender->SetNodeBlendPathIndex(ChangeParam.m_NewNodeBlendPathIndex);
08449             }
08450             break;
08451             case CHANGEBLENDER_BLENDONCURVE:
08452             {
08453                 pNodeBlender->SetBlendedOnCurve(ChangeParam.m_NewBlendedOnCurve);
08454             }
08455             break;
08456             case CHANGEBLENDER_SWAPENDS:
08457             {
08458                 pNodeBlender->ReverseEnds();
08459                 pNodeBlender->SetUninitialised();
08460             }
08461             break;  
08462             default : ERROR2(AC_FAIL,"Unknown change blend type"); break;
08463         }
08464         pNewAction->m_ChangeParam.SetNewValues(pNodeBlender);
08465     }
08466 
08467     return Ac;
08468 }
08469 
08470 /********************************************************************************************
08471 
08472 >   ActionCode ChangeBlenderAction::Execute();
08473 
08474     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
08475     Created:    20/5/99
08476     Inputs:     -
08477     Outputs:    -
08478     Returns:    ActionCode, one of AC_OK, AC_NO_RECORD or AC_FAIL
08479     Purpose:    Executes the action.  
08480     Errors:     -
08481     SeeAlso:    Action::Init()
08482 
08483 ********************************************************************************************/
08484 
08485 ActionCode ChangeBlenderAction::Execute()
08486 {
08487     m_ChangeParam.SwapOldAndNew();
08488 
08489     ActionCode Act;
08490     Act = ChangeBlenderAction::Init(pOperation,pOppositeActLst,m_pNodeBlender,m_ChangeParam);
08491 
08492     return Act;
08493 }
08494 
08495 ChangeBlenderAction::~ChangeBlenderAction()
08496 {
08497 }
08498 
08499 //-------------------------------------------------------
08500 //-------------------------------------------------------
08501 //-------------------------------------------------------
08502 
08503 /********************************************************************************************
08504 
08505 >   void ChangeBlenderOpParam::SetOldValues(NodeBlender* pNodeBlender)
08506 
08507     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
08508     Created:    20/5/99
08509     Inputs:     pNodeBlender = ptr to a node blend object
08510     Outputs:    -
08511     Returns:    -
08512     Purpose:    Copies the current state of the blender node into this classes Old member vars
08513     Errors:     -
08514 
08515 ********************************************************************************************/
08516 
08517 void ChangeBlenderOpParam::SetOldValues(NodeBlender* pNodeBlender)
08518 {
08519     m_OldAngleStart = pNodeBlender->GetAngleStart();
08520     m_OldAngleEnd   = pNodeBlender->GetAngleEnd();
08521     m_OldPathStart  = pNodeBlender->GetProportionOfPathDistStart();
08522     m_OldPathEnd    = pNodeBlender->GetProportionOfPathDistEnd();
08523     m_OldNodeBlendPathIndex = pNodeBlender->GetNodeBlendPathIndex();
08524     m_OldBlendedOnCurve = pNodeBlender->IsBlendedOnCurve();
08525     m_OldObjIndexEnd = pNodeBlender->GetObjIndexEnd();
08526     m_OldObjIndexStart = pNodeBlender->GetObjIndexStart();
08527 
08528 
08529 }
08530 
08531 /********************************************************************************************
08532 
08533 >   void ChangeBlenderOpParam::SetNewValues(NodeBlender* pNodeBlender)
08534 
08535     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
08536     Created:    20/5/99
08537     Inputs:     pNodeBlender = ptr to a node blend object
08538     Outputs:    -
08539     Returns:    -
08540     Purpose:    Copies the current state of the blender node into this classes New member vars
08541     Errors:     -
08542 
08543 ********************************************************************************************/
08544 
08545 void ChangeBlenderOpParam::SetNewValues(NodeBlender* pNodeBlender)
08546 {
08547     m_NewAngleStart = pNodeBlender->GetAngleStart();
08548     m_NewAngleEnd   = pNodeBlender->GetAngleEnd();
08549     m_NewPathStart  = pNodeBlender->GetProportionOfPathDistStart();
08550     m_NewPathEnd    = pNodeBlender->GetProportionOfPathDistEnd();
08551     m_NewNodeBlendPathIndex = pNodeBlender->GetNodeBlendPathIndex();
08552     m_NewBlendedOnCurve = pNodeBlender->IsBlendedOnCurve();
08553     m_NewObjIndexEnd = pNodeBlender->GetObjIndexEnd();
08554     m_NewObjIndexStart = pNodeBlender->GetObjIndexStart();
08555 }
08556 
08557 /********************************************************************************************
08558 
08559 >   void ChangeBlenderOpParam::SwapOldAndNew()
08560 
08561     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
08562     Created:    20/5/99
08563     Inputs:     -
08564     Outputs:    -
08565     Returns:    -
08566     Purpose:    Swaps the old and new values of this class
08567     Errors:     -
08568 
08569 ********************************************************************************************/
08570 
08571 void ChangeBlenderOpParam::SwapOldAndNew()
08572 {
08573     SWAP(double,m_NewAngleStart,m_OldAngleStart);
08574     SWAP(double,m_NewAngleEnd  ,m_OldAngleEnd);
08575     SWAP(double,m_NewPathStart, m_OldPathStart);
08576     SWAP(double,m_NewPathEnd,   m_OldPathEnd);
08577     SWAP(UINT32, m_NewNodeBlendPathIndex, m_OldNodeBlendPathIndex);
08578     SWAP(BOOL, m_OldBlendedOnCurve, m_NewBlendedOnCurve);
08579     SWAP(INT32, m_OldObjIndexStart, m_NewObjIndexStart);
08580     SWAP(INT32, m_OldObjIndexEnd, m_NewObjIndexEnd);
08581 }
08582 
08583 //------------------------------------------------------------------------------------------------
08584 //------------------------------------------------------------------------------------------------
08585 //------------------------------------------------------------------------------------------------
08586 // The InvalidateBoundsAction class
08587 
08588 /********************************************************************************************
08589 
08590 >   InvalidateBoundsAction::InvalidateBoundsAction()
08591 
08592     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
08593     Created:    24/5/99
08594     Purpose:    Constructor for the action
08595 
08596 ********************************************************************************************/
08597 
08598 InvalidateBoundsAction::InvalidateBoundsAction()
08599 {
08600     m_pNode = NULL;
08601     m_IncludeChildren = FALSE;
08602 }
08603 
08604 
08605 /********************************************************************************************
08606 
08607 >   ActionCode InvalidateBoundsAction::Init(Operation*              pOp,
08608                                             ActionList*             pActionList,
08609                                             NodeRenderableBounded*  pNode,
08610                                             BOOL                    IncludeChildren)
08611 
08612     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
08613     Created:    20/5/99
08614     Inputs:     pOp             = ptr to the operation to which this action belongs
08615                 pActionList     = ptr to action list to which this action should be added
08616                 pNodeBlender    = ptr to Node whos bounds invalidating
08617                 IncludeChildren = invalidate the bounds of the children too
08618     Outputs:    -
08619     Returns:    ActionCode, one of AC_OK, AC_NO_RECORD or AC_FAIL
08620     Purpose:    This is the function which creates an instance of this action. If there is no room 
08621                 in the undo buffer (which is determined by the base class Init function called within)
08622                 the function will either return AC_NO_RECORD which means the operation can continue, 
08623                 but no undo information needs to be stored, or AC_OK which means the operation should
08624                 continue AND record undo information. If the function returns AC_FAIL, there was not 
08625                 enough memory to record the undo information, and the user has decided not to continue
08626                 with the operation.
08627 
08628     Errors:     -
08629     SeeAlso:    Action::Init()
08630 
08631 ********************************************************************************************/
08632 
08633 
08634 
08635 ActionCode InvalidateBoundsAction::Init(Operation*              pOp,
08636                                         ActionList*             pActionList,
08637                                         NodeRenderableBounded*  pNode,
08638                                         BOOL                    IncludeChildren)
08639 {
08640     ERROR2IF(pNode == NULL,AC_FAIL,"pNode is NULL");
08641 
08642     UINT32 ActSize = sizeof(InvalidateBoundsAction);
08643 
08644     InvalidateBoundsAction* pNewAction;
08645     ActionCode Ac = Action::Init(pOp,pActionList,ActSize,CC_RUNTIME_CLASS(InvalidateBoundsAction),(Action**)&pNewAction);
08646 
08647     if (Ac != AC_FAIL && pNewAction != NULL)
08648     {
08649         pNewAction->m_pNode            = pNode;
08650         pNewAction->m_IncludeChildren  = IncludeChildren;
08651 
08652         pNode->InvalidateBoundingRect(IncludeChildren);
08653     }
08654 
08655     return Ac;
08656 }
08657 
08658 /********************************************************************************************
08659 
08660 >   ActionCode InvalidateBoundsAction::Execute();
08661 
08662     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
08663     Created:    22/5/99
08664     Inputs:     -
08665     Outputs:    -
08666     Returns:    ActionCode, one of AC_OK, AC_NO_RECORD or AC_FAIL
08667     Purpose:    Executes the action.  
08668     Errors:     -
08669     SeeAlso:    Action::Init()
08670 
08671 ********************************************************************************************/
08672 
08673 ActionCode InvalidateBoundsAction::Execute()
08674 {
08675     ActionCode Act;
08676     Act = InvalidateBoundsAction::Init(pOperation,pOppositeActLst,m_pNode,m_IncludeChildren);
08677 
08678     return Act;
08679 }
08680 
08681 InvalidateBoundsAction::~InvalidateBoundsAction()
08682 {
08683 }
08684 
08685 //-------------------------------------------
08686 //-------------------------------------------
08687 //-------------------------------------------
08688 // class CompoundNodeTreeFactoryList
08689 
08690 /********************************************************************************************
08691 
08692 >   CompoundNodeTreeFactoryList::~CompoundNodeTreeFactoryList()
08693 
08694     Author:     David_McClarnon (Xara Group Ltd) <camelotdev@xara.com> Mc
08695     Created:    9/8/99
08696     Inputs:     -
08697     Outputs:    -
08698     Returns:    
08699     Purpose:    Deletes the list
08700     Errors:     -
08701     SeeAlso:    Action::Init()
08702 
08703 ********************************************************************************************/
08704 
08705 CompoundNodeTreeFactoryList::~CompoundNodeTreeFactoryList()
08706 {
08707     CompoundNodeTreeFactoryListItem * pItem = (CompoundNodeTreeFactoryListItem *)GetHead();
08708 
08709     while (pItem)
08710     {
08711         delete pItem->pFactory;
08712         pItem->pFactory = NULL;
08713 
08714         pItem = (CompoundNodeTreeFactoryListItem *)GetNext(pItem);
08715     }
08716     
08717     List::DeleteAll();
08718 }
08719 
08720 /********************************************************************************************
08721 
08722 >   void CompoundNodeTreeFactoryList::AddItem(CompoundNodeTreeFactory *pItem)
08723 
08724 
08725     Author:     David_McClarnon (Xara Group Ltd) <camelotdev@xara.com> Mc
08726     Created:    9/8/99
08727     Inputs:     -
08728     Outputs:    -
08729     Returns:    
08730     Purpose:    Adds an item to the list - but won't add 2 items of the same kind !
08731     Errors:     -
08732     SeeAlso:    Action::Init()
08733 
08734 ********************************************************************************************/
08735 void CompoundNodeTreeFactoryList::AddItem(CompoundNodeTreeFactory *pItem)
08736 {
08737     // find out if any element in the list matches this type - if so, don't add it !
08738     BOOL bAdd = TRUE;
08739 
08740     CompoundNodeTreeFactoryListItem * pListItem = (CompoundNodeTreeFactoryListItem *)GetHead();
08741 
08742     while (pListItem && bAdd)
08743     {
08744         if (pListItem->pFactory->GetRuntimeClass() == pItem->GetRuntimeClass())
08745         {
08746             bAdd = FALSE;
08747         }
08748 
08749         pListItem = (CompoundNodeTreeFactoryListItem *)GetNext(pListItem);
08750     }
08751 
08752     if (bAdd)
08753     {
08754         pListItem = new CompoundNodeTreeFactoryListItem;
08755         pListItem->pFactory = pItem;
08756         AddTail(pListItem);
08757     }
08758 }
08759 
08760 /********************************************************************************************
08761 
08762 >   CompoundNodeTreeFactory * CompoundNodeTreeFactoryList::GetItem(INT32 index)
08763 
08764     Author:     David_McClarnon (Xara Group Ltd) <camelotdev@xara.com> Mc
08765     Created:    9/8/99
08766     Inputs:     -
08767     Outputs:    -
08768     Returns:    
08769     Purpose:    Adds an item to the list - but won't add 2 items of the same kind !
08770     Errors:     -
08771     SeeAlso:    Action::Init()
08772 
08773 ********************************************************************************************/
08774 CompoundNodeTreeFactory * CompoundNodeTreeFactoryList::GetItem(INT32 index)
08775 {
08776     if( index >= INT32(GetCount()) )
08777         return NULL;
08778     
08779     CompoundNodeTreeFactoryListItem * pItem = (CompoundNodeTreeFactoryListItem *)GetHead();
08780 
08781     for (INT32 i = 0 ; i < index; i++)
08782     {
08783         pItem = (CompoundNodeTreeFactoryListItem *)GetNext(pItem);
08784     }
08785     
08786     return pItem->pFactory;
08787 }
08788 
08789 
08790 
08791 /********************************************************************************************
08792 
08793 >   OpEditBlendEndObject::OpEditBlendEndObject()
08794 
08795     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
08796     Created:    10/9/99
08797     Returns:    
08798     Purpose:    default constructor
08799 
08800 ********************************************************************************************/
08801 
08802 OpEditBlendEndObject::OpEditBlendEndObject(BlendTool* pBlendTool)
08803 {
08804     if (pBlendTool == NULL)
08805     {
08806         ERROR3("No blend tool");
08807         return;
08808     }
08809     m_pTransMatrix = NULL;
08810     m_pNodeBlend = NULL;
08811     m_pRange = NULL;
08812     m_pNodeBlendPath = NULL;
08813     m_pBlendTool = pBlendTool;
08814 }
08815 
08816 
08817 
08818 /********************************************************************************************
08819 
08820 >   OpEditBlendEndObject::~OpEditBlendEndObject()
08821 
08822     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
08823     Created:    10/9/99
08824     Returns:    
08825     Purpose:    destructor, deletes pointers to dynamically created objects
08826 
08827 ********************************************************************************************/
08828 
08829 OpEditBlendEndObject::~OpEditBlendEndObject()
08830 {
08831     delete m_pTransMatrix;
08832     delete m_pSelState;
08833 }
08834 
08835 
08836 /********************************************************************************************
08837 
08838 >   BOOL OpEditBlendEndObject::Declare()
08839 
08840     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
08841     Created:    10/9/99
08842     Returns:    TRUE if all went OK, FALSE otherwise
08843     Purpose:    Adds the operation to the list of all known operations
08844 
08845 ********************************************************************************************/
08846 
08847 BOOL OpEditBlendEndObject::Declare()
08848 {
08849     return (RegisterOpDescriptor(
08850                                 0, 
08851                                 0,
08852                                 CC_RUNTIME_CLASS(OpEditBlendEndObject), 
08853                                 OPTOKEN_EDITBLENDENDOBJECT,
08854                                 OpEditBlendEndObject::GetState,
08855                                 0,          /* help ID */
08856                                 _R(IDBBL_NOOP), /* bubble ID */
08857                                 0           /* bitmap ID */
08858                                 ));
08859 }
08860 
08861 
08862 /********************************************************************************************
08863 
08864  >  static OpState OpEditBlendEndObject::GetState(String_256* Description, OpDescriptor*)
08865 
08866     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
08867     Created:    10/9/99
08868     Outputs:    Description - GetState fills this string with an approriate description
08869                 of the current state of the operation 
08870     Returns:    The state of the operation, so that menu items (ticks and greying) can be
08871                 done properly
08872     Purpose:    Find out the state of the operation at the specific time
08873 
08874 ********************************************************************************************/
08875 
08876 OpState OpEditBlendEndObject::GetState(String_256* Description, OpDescriptor*)
08877 {
08878     OpState State;
08879 
08880     return State;
08881 }
08882 
08883 
08884 
08885 /********************************************************************************************
08886 
08887 >   BOOL OpEditBlendEndObject::DoDrag(DocCoord PointerPos, Spread* pSpread)
08888     
08889     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
08890     Created:    10/9/99
08891     Inputs:     Position of the pointer to start the drag from.
08892                 The current spread.
08893     Outputs:    -
08894     Returns:    TRUE if successful, FALSE otherwise
08895     Purpose:    This starts a drag to change the position of one of the end objects of a blend
08896                 The DragFinished() method will do the hard work of recalculating the 
08897                 blend etc.
08898 
08899 
08900 ********************************************************************************************/
08901 
08902 /* IMPLEMENTATION NOTE:
08903    This operation does some very bad things, forced on me by failures in the Range code.
08904    Range::RenderXOROutlinesOn() does not work when you generate your own range which you want
08905    to XOR.  However it does work with the selection range.  The problem here is that we wish to
08906    XOR one element of the selection, therefore the method I have used is to temporarily change
08907    the selection for the duration of this drag so that the only object selected is the one we 
08908    are dragging.  The selection is restored in DragFinished().  This is a pretty poor way of 
08909    doing things but I see no alternative.
08910    */
08911 
08912 
08913 BOOL OpEditBlendEndObject::DoDrag(DocCoord PointerPos, Spread* pSpread)
08914 {
08915     GetApplication()->RegisterIdleProcessor(IDLEPRIORITY_HIGH, this);
08916 
08917     List BlendList;
08918     Node* pNodeUnderPoint = NULL;
08919     BOOL ok = BevelTools::BuildListOfSelectedNodes(&BlendList, CC_RUNTIME_CLASS(NodeBlend), FALSE);
08920     if (ok)
08921     {
08922         NodeListItem* pListItem = (NodeListItem*)BlendList.GetHead();
08923         while (pListItem != NULL)
08924         {
08925             
08926             NodeBlend* pNodeBlend = (NodeBlend*)pListItem->pNode;
08927             
08928             ok = pNodeBlend->HitOnEndDragBlob(PointerPos, &pNodeUnderPoint);
08929             if (ok)
08930             {
08931                 ObjChangeFlags cFlags;
08932                 cFlags.TransformNode = TRUE;
08933                 ObjChangeParam ObjChange(OBJCHANGE_STARTING,cFlags,NULL,this);
08934                 if (pNodeBlend->AllowOp(&ObjChange, TRUE))
08935                 {
08936                     m_pNodeBlend = pNodeBlend;                      
08937                     break;
08938                 }
08939             }
08940             pListItem = (NodeListItem*)BlendList.GetNext(pListItem);
08941         }
08942     }
08943     else
08944     {
08945         // that didn't work, but it may be selected inside so try this;
08946         pNodeUnderPoint = NodeRenderableInk::FindSimpleAtPoint(pSpread, PointerPos);
08947         
08948         if (pNodeUnderPoint == NULL)
08949         {
08950             // op shouldn't have been allowed as we are not above a node, lets quit
08951             FailAndExecute();
08952             End();
08953             BlendList.DeleteAll();
08954             return FALSE;
08955         }
08956         
08957         Node* pParent = pNodeUnderPoint->FindParentOfSelected();
08958         if (pParent == NULL || (!pParent->IS_KIND_OF(NodeBlend)))
08959         {
08960             FailAndExecute();
08961             End();
08962             BlendList.DeleteAll();
08963             return FALSE;
08964         }
08965         // its ok, we can continue
08966         m_pNodeBlend = (NodeBlend*)pParent;
08967 
08968     }
08969 
08970     BlendList.DeleteAll();
08971     // see if there is a node between the blend and the simple node, if so it may be a group
08972     NodeRenderableInk* pGroupUnderPoint = NodeRenderableInk::FindInnerCompound(pNodeUnderPoint, m_pNodeBlend);
08973     
08974     Node* pNodeToEdit = NULL;
08975     
08976     if (pGroupUnderPoint != NULL)
08977     {
08978         // if the group is the same as the nodeblend
08979         if (pGroupUnderPoint == m_pNodeBlend)
08980         {
08981             pNodeToEdit = (Node*) pNodeUnderPoint;
08982         }
08983         else
08984         {
08985             pNodeToEdit = (Node*)pGroupUnderPoint;
08986         }
08987     }
08988     else
08989     {
08990         FailAndExecute();
08991         End();
08992         return FALSE;
08993     }
08994     
08995     // save the selection state for restoring at the end of the drag
08996     ALLOC_WITH_FAIL(m_pSelState, new SelectionState, this);
08997     ok = m_pSelState->Record();
08998 
08999     if (!ok)
09000     {
09001         ERROR3("Could not record selection");
09002         FailAndExecute();
09003         End();
09004         return FALSE;
09005     }   
09006 
09007 
09008     m_pRange = GetApplication()->FindSelection(); 
09009 
09010     if (m_pRange == NULL)
09011     {
09012         ERROR3("No Selection");
09013         FailAndExecute();
09014         End();
09015         return FALSE;
09016     }
09017 
09018     // the bodging continues... When the TransformNodeAction gets called as part of
09019     // the undoing of this op it changes the selection so that only the transformed node is
09020     // selected.  Hence we need to insert an action that will occur after TransformNode in the 
09021     // undo list in order to have the selection as it started....nice
09022     Action* pRestoreAction;
09023     SelectionState* pCopySelState =  NULL;
09024     ALLOC_WITH_FAIL(pCopySelState, new SelectionState, this);  // gets destroyed by RestoreSelectionsAction
09025     pCopySelState->Record();  
09026 
09027     ok = RestoreSelectionsAction::Init(this, &UndoActions, pCopySelState,
09028                                         TRUE, TRUE, FALSE, TRUE, FALSE, FALSE,
09029                                         &pRestoreAction);
09030 /*  the function proto
09031 RestoreSelectionsAction::Init(Operation* const pOp, 
09032                                          ActionList* pActionList,   
09033                                          SelectionState* SelState,
09034                                          BOOL Toggle, 
09035                                          BOOL ToggleStatus,
09036                                          BOOL SelStateShared,
09037                                          BOOL RenderStartBlobs, 
09038                                          BOOL RenderEndBlobs, 
09039                                          BOOL StartRestore,  
09040                                          Action** NewAction)       
09041 
09042 */
09043     
09044     // setting the selection so that only the node we wish to be XOR'd during the
09045     // drag is selected - ugh.
09046     if (ok)
09047     {
09048         NodeRenderableInk::DeselectAll();
09049         ((NodeRenderable*)pNodeToEdit)->Select(TRUE);
09050     
09051         // we don't want to see those object blobs whilst dragging
09052         BlobManager* BlobMgr = GetApplication()->GetBlobManager();
09053         if (BlobMgr != NULL)
09054         {
09055                 // Decide which blobs we will display
09056                 BlobStyle MyBlobs;
09057             
09058                 MyBlobs.Object = FALSE;
09059                 MyBlobs.Tiny = FALSE;
09060 
09061                 BlobMgr->ToolInterest(MyBlobs);
09062         }
09063 
09064 
09065         m_pRange->ResetXOROutlineRenderer();
09066 
09067         // work out where we need to translate to and create a matrix for it
09068         DocRect BRect = ((NodeRenderableBounded*)pNodeToEdit)->GetBoundingRect();
09069         m_StartCoord = BRect.Centre();
09070 
09071         ALLOC_WITH_FAIL(m_pTransMatrix, new Matrix, this)
09072         m_pTransMatrix->SetTranslation(PointerPos - m_StartCoord);
09073 
09074         m_pRange->RenderXOROutlinesOn(NULL, pSpread, m_pTransMatrix, pNodeUnderPoint);
09075     
09076         m_LastCoord = m_StartCoord;
09077         RenderSelectedObjectBlobs(pSpread);
09078         // And tell the Dragging system that we need drags to happen
09079         StartDrag( DRAGTYPE_AUTOSCROLL );
09080     }
09081     else
09082     {
09083         FailAndExecute();
09084         End();
09085     }
09086     return ok;
09087 }
09088 
09089 
09090 
09091 
09092 
09093 /********************************************************************************************
09094 
09095 >   void OpEditBlendEndObject::DragPointerMove( DocCoord PointerPos, ClickModifiers ClickMods, 
09096                                    Spread* pSpread, BOOL bSolidDrag)
09097     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com> 
09098     Created:    10/9/99
09099     Inputs:     PointerPos - The current position of the mouse in Doc Coords
09100                 ClickMods  - Which key modifiers are being pressed
09101                 pSpread    - The spread that the mouse pointer is over
09102     Purpose:    Takes the pointer position and calculates the new dragged outline of the EORd
09103                 bounding box.  The closest point on the NodeBlendpath to the actual mouseclick
09104                 is used.
09105     SeeAlso:    ClickModifiers
09106 
09107 ********************************************************************************************/
09108 
09109 void OpEditBlendEndObject::DragPointerMove(DocCoord PointerPos, ClickModifiers ClickMods, Spread* pSpread, BOOL bSolidDrag)
09110 {
09111     // get rid of existing blobs
09112     RenderDragBlobs(pSpread, m_LastCoord, FALSE);
09113     if (m_pBlendTool != NULL)
09114         m_pBlendTool->DisplayStatusBarHelp(_R(IDS_BLENDSTATUS_MOVEEND));
09115     DocCoord PointOnLine;
09116     BOOL ValidPoint = GetClosestPointOnPath(PointerPos, &PointOnLine);
09117     if (!ValidPoint)
09118     {   
09119         EndDrag();  // not sure this is correct, should we make some attempt to 
09120         return;       // render the xor outlines off? 
09121     }
09122     // draw new blobs
09123     RenderDragBlobs(pSpread, PointOnLine, TRUE);
09124     //RenderAllBlobs(pSpread);
09125     
09126     m_LastCoord = PointOnLine;
09127 
09128 
09129 
09130 }
09131 
09132 
09133 /********************************************************************************************
09134 
09135 >   void OpEditBlendEndObject::DragPointerIdle( DocCoord PointerPos,
09136                                                 ClickModifiers ClickMods,
09137                                                 Spread* pSpread, BOOL bSolidDrag)
09138     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com> 
09139     Created:    10/9/99
09140     Inputs:     PointerPos - The current position of the mouse in Doc Coords
09141                 pSpread    - The spread that the mouse pointer is over
09142     Purpose:    If the pointer is idle take the opportunity to render a few more objects
09143 
09144 ********************************************************************************************/
09145 
09146 void OpEditBlendEndObject::DragPointerIdle(DocCoord PointerPos, ClickModifiers Clickmodifiers, Spread* pSpread, BOOL bSolidDrag)
09147 {
09148 
09149     m_pRange->RenderXOROutlinesOn(NULL, pSpread, m_pTransMatrix, NULL);
09150 }
09151 
09152 /********************************************************************************************
09153 
09154 >   void OpEditBlendEndObject::DragFinished( DocCoord PointerPos, ClickModifiers ClickMods, 
09155                                 Spread* pSpread, BOOL Success, BOOL bSolidDrag)
09156 
09157     
09158     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com> 
09159     Created:    10/9/99
09160     Inputs:     PointerPos - The position of the mouse at the end of the drag
09161                 ClickMods - the key modifiers being pressed
09162                 pSpread - The spread that the drag finished on
09163                 Success - TRUE if the drag was terminated properly, FALSE if it
09164                 was ended with the escape key being pressed
09165     Purpose:    Ends the drag and recalculates the blend 
09166     SeeAlso:    ClickModifiers, DoDrag() for an explanation of whats going on with 
09167                 the selection.
09168 
09169 ********************************************************************************************/
09170 
09171 void OpEditBlendEndObject::DragFinished(DocCoord PointerPos, ClickModifiers ClickMods, Spread* pSpread, BOOL Success, BOOL bSolidDrag)
09172 {
09173     GetApplication()->RemoveIdleProcessor(IDLEPRIORITY_HIGH, this);
09174     
09175     DocCoord PointOnLine;
09176     
09177     BOOL ValidPoint = GetClosestPointOnPath(PointerPos, &PointOnLine);
09178     if (!ValidPoint)
09179     {   
09180         EndDrag();                  // not sure this is correct, should we make some attempt to 
09181         return;       // render the xor outlines off? 
09182     }
09183 
09184     Matrix NewMatrix;
09185     NewMatrix.SetTranslation(PointOnLine - m_StartCoord);
09186     
09187     m_pRange->RenderXOROutlinesOff(NULL, pSpread, &NewMatrix);
09188     EndDrag();
09189     
09190     if (Success)
09191         Success = RecalculateBlend(PointOnLine);
09192 
09193     // bring back the object blobs
09194     // we don't want to see those object blobs whilst dragging
09195     BlobManager* BlobMgr = GetApplication()->GetBlobManager();
09196     if (BlobMgr != NULL)
09197     {
09198             // Decide which blobs we will display
09199             BlobStyle MyBlobs;
09200             
09201             MyBlobs.Object = TRUE;
09202             MyBlobs.Tiny = FALSE;
09203 
09204             BlobMgr->ToolInterest(MyBlobs);
09205     }
09206 
09207     // restore the selection - very important
09208     m_pSelState->Restore();
09209     if (Success)
09210     {   
09211         SelRange* pSel = GetApplication()->FindSelection();
09212         pSel->Update();
09213     }
09214     else
09215         FailAndExecute();
09216     
09217     // Inform all changed nodes that we have finished
09218     ObjChangeFlags cFlags;
09219     ObjChangeParam ObjChange(OBJCHANGE_FINISHED, cFlags, NULL, this);
09220     
09221     /*BOOL ok =*/ UpdateChangedNodes(&ObjChange);
09222     End();
09223     
09224     //TRACEUSER( "Diccon", _T("\nFinished dragging end object\n"));
09225 
09226 }
09227 
09228 
09229 /********************************************************************************************
09230 
09231 >   BOOL OpEditBlendEndObject::RecalculateBlend(DocCoord PointerPos)
09232 
09233     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com> 
09234     Created:    10/9/99
09235     Inputs:     PointerPos - The position to move the end object to
09236     Outputs:    
09237     returns:    TRUE if successful , FALSE otherwise
09238     Purpose:    To recalculate the number of steps in the blend or distance between steps 
09239                 following the change of position of one of the end objects.  Having done this 
09240                 it then creates the actions to perform the change.
09241 
09242 ***********************************************************************************************/
09243 
09244 BOOL OpEditBlendEndObject::RecalculateBlend(DocCoord EndPosition)
09245 {
09246     // check the inputs
09247     if (m_pNodeBlend == NULL) 
09248     {
09249         ERROR3("NodeBlend is not initialised");
09250         return FALSE;
09251     }
09252     // get a pointer to the path
09253     
09254     if (m_pNodeBlendPath == NULL)
09255     {
09256         ERROR3("NodeBlend is not on a path");
09257         return FALSE;
09258     }
09259     // find out how far the point is along the path
09260     double PathLength = m_pNodeBlendPath->GetPathLength();
09261     INT32 DistanceAlongPath = 0;
09262     BOOL Valid = m_pNodeBlendPath->InkPath.GetDistanceToPoint(EndPosition, &DistanceAlongPath);
09263     
09264     // Range::FindFirst() always returns NULL for some reason so I'm forced
09265     // to use FindLast(), given that there is only one this seems ok.
09266     Node* pNode = m_pRange->FindLast();
09267     NodeRenderableInk* pNodeToEdit= NULL;
09268     if (pNode == NULL)
09269     {
09270         ERROR3("Range is empty");
09271         Valid = FALSE;
09272     }
09273     else
09274          pNodeToEdit = (NodeRenderableInk*)pNode;
09275 
09276     if (Valid)
09277     {
09278         // depending on which Edit mode the blend is in try to keep either
09279         // number of steps or distance between steps constant.
09280         double NewPathProportion = DistanceAlongPath / PathLength;
09281         if (m_pNodeBlend->GetEditState() == EDIT_STEPS)
09282         {   
09283             Valid = InsertChangeEndActions(NewPathProportion, EndPosition, pNode);
09284         }
09285         else
09286         {
09287             double StepDistance = m_pNodeBlend->GetDistanceEntered();
09288             Valid = InsertChangeEndActions(NewPathProportion, EndPosition, StepDistance, pNode);
09289         }
09290 
09291     }
09292     
09293 
09294     return Valid;
09295 
09296 }
09297 
09298 
09299 
09300 /********************************************************************************************
09301 
09302 >   BOOL OpEditBlendEndObject::InsertChangeEndActions(double PathProportion, 
09303                                                       DocCoord NewPosition, Node* pNodeToEdit)
09304 
09305     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com> 
09306     Created:    17/9/99
09307     Inputs:     PathProportion - the new proportion along the path to move the end object to
09308                 pNodeToEdit - the blend object that is being edited
09309                 NewPosition - the new position as a coordinate
09310     Outputs:    pNodeToEdit - some members are altered by these actions         
09311     returns:    TRUE if all actions were correctly carried out, else FALSE
09312     Purpose:    To calculate and perform the actions necessary to transform pNode to its new PathProportion
09313                 whilst maintaining a constant number of blend steps. 
09314 
09315    See Also:    Overridden version of this function for use with constant distance between blend steps
09316                 
09317 
09318 ***********************************************************************************************/
09319     
09320 BOOL OpEditBlendEndObject::InsertChangeEndActions(double NewPathProp, DocCoord NewPosition,
09321                                                   Node* pNodeToEdit)
09322 {
09323     // couple of quick checks
09324     if (pNodeToEdit == NULL)
09325     {
09326         ERROR3("Node to edit is NULL");
09327         return FALSE;
09328     }
09329 
09330     if (NewPathProp < 0.0 || NewPathProp > 1.0)
09331     {
09332         ERROR3("Invalid path proportion");
09333         return FALSE;
09334     }
09335 
09336     // find out if this node is the first or last node of the blender on a curve
09337     BOOL NodeIsFirst;
09338     NodeBlender* pNodeBlender = GetBlenderAndPosition(pNodeToEdit, &NodeIsFirst);
09339     BOOL Valid = FALSE;     
09340     if (pNodeBlender == NULL)
09341     {
09342         ERROR3("No NodeBlender");
09343         return FALSE;
09344     }
09345     else
09346     {   
09347         Valid = DoInvalidateNodeRegion((NodeRenderableInk*)m_pNodeBlend,TRUE,FALSE);
09348         if (Valid)  Valid = (InvalidateBoundsAction::Init(this,&UndoActions,m_pNodeBlend,TRUE) != AC_FAIL);
09349 
09350         // change the proportion along the path to the new value
09351         if (Valid)
09352             Valid = InsertChangeProportion(NewPathProp, NodeIsFirst, pNodeBlender);
09353         
09354         double AngleChange = 0.0;
09355         // if we are rotated along the curve then calculate the new angle
09356         if (m_pNodeBlend->IsTangential() && Valid)
09357         {
09358             
09359             NodeBlendPath* pNodeBlendPath = pNodeBlender->GetNodeBlendPath();
09360             if (pNodeBlendPath != NULL)
09361             {
09362                 double NewAngle = 0.0;
09363                 double OldAngle = 0.0;
09364                 double BlendRatio = 0.0;
09365                 ChangeBlenderOpParam ChangeParam;
09366                 if (NodeIsFirst)
09367                 {
09368                     OldAngle = pNodeBlender->GetAngleStart();
09369                     ChangeParam.m_ChangeType = CHANGEBLENDER_ANGLESTART;
09370                     BlendRatio = 0.0;
09371                 }
09372                 else
09373                 {
09374                     OldAngle = pNodeBlender->GetAngleEnd();
09375                     ChangeParam.m_ChangeType = CHANGEBLENDER_ANGLEEND;
09376                     BlendRatio = 1.0;
09377                 }
09378 //              MILLIPOINT PathDistance = (MILLIPOINT)(pNodeBlendPath->GetPathLength());
09379 //              MILLIPOINT PointDistance = (MILLIPOINT)(NewPathProp * PathDistance);
09380                 DocCoord Point;
09381                 Valid = pNodeBlender->GetPointOnNodeBlendPath(BlendRatio, &Point, &NewAngle);
09382                 if (Valid)
09383                 {
09384                     AngleChange = NewAngle - OldAngle;
09385                     TRACEUSER( "Diccon", _T("Moved end: OldAngle: %f, NewAngle %f, Change %f\n"), OldAngle, NewAngle, AngleChange);
09386                     // make it undoable     
09387                     if (!NodeIsFirst)
09388                         ChangeParam.m_NewAngleEnd = NewAngle;
09389                     else
09390                         ChangeParam.m_NewAngleStart = NewAngle;
09391                     ActionCode Ac = ChangeBlenderAction::Init(  this, &UndoActions,
09392                                                                 pNodeBlender,ChangeParam);
09393                     Valid = (Ac !=AC_FAIL);
09394                 }
09395 
09396             }
09397         }
09398         
09399         // transform the edited node to its new location
09400         if (Valid) 
09401             Valid = m_pNodeBlend->TransformNodeToPoint((NodeRenderableInk*)pNodeToEdit,&NewPosition,this,AngleChange);
09402         
09403         // find out if this node is part of another blend, and if so then regenerate it
09404         if (Valid)
09405         {
09406             BOOL OtherEnd;
09407             NodeBlender* pOtherBlender = m_pNodeBlend->NodeIsPartOfBlender( pNodeToEdit, pNodeBlender, &OtherEnd); 
09408             if (pOtherBlender != NULL && pOtherBlender != pNodeBlender)
09409             {
09410                 // if its not on a curve just ask it to reninitialise
09411                 if (!pOtherBlender->IsBlendedOnCurve())
09412                 {
09413                     ChangeBlenderOpParam Param;
09414                     Param.m_ChangeType = CHANGEBLENDER_REGEN;
09415                     Valid = ChangeBlenderAction::Init(this, &UndoActions, pOtherBlender, Param);
09416                 }
09417                 // if it is on a curve work set the new position for the other blender
09418                 else
09419                 {
09420                     ChangeBlenderOpParam ChangeParam;
09421                     if (OtherEnd)
09422                     {
09423                         ChangeParam.m_ChangeType = CHANGEBLENDER_PATHSTART;
09424                         ChangeParam.m_NewPathStart = NewPathProp;
09425                     }
09426                     else
09427                     {
09428                         ChangeParam.m_ChangeType = CHANGEBLENDER_PATHEND;
09429                         ChangeParam.m_NewPathEnd = NewPathProp;
09430                     }
09431                     ActionCode Ac = ChangeBlenderAction::Init(  this, &UndoActions,
09432                                                                 pOtherBlender,ChangeParam);
09433                     Valid = (Ac !=AC_FAIL);
09434 
09435                 }
09436             }
09437         }
09438     }
09439     // regenerate any shadows, bevels or contours
09440     Node* pController = ((Node*)m_pNodeBlend)->FindParent(CC_RUNTIME_CLASS(NodeShadowController));
09441     if (pController != NULL)
09442         pController->RegenerateNode(this);
09443     else 
09444     {
09445         pController = ((Node*)m_pNodeBlend)->FindParent(CC_RUNTIME_CLASS(NodeBevelController));
09446         if (pController != NULL)
09447             pController->RegenerateNode(this);
09448         else
09449         {
09450             pController = ((Node*)m_pNodeBlend)->FindParent(CC_RUNTIME_CLASS(NodeContourController));
09451             if (pController != NULL)
09452                 pController->RegenerateNode(this);
09453         }
09454     }
09455 
09456         
09457     if (Valid)
09458         Valid = DoInvalidateNodeRegion((NodeRenderableInk*)m_pNodeBlend,TRUE,FALSE);
09459     if (Valid)
09460         Valid = (InvalidateBoundsAction::Init(this,&UndoActions,m_pNodeBlend,TRUE) != AC_FAIL);
09461     
09462     return Valid;
09463 }
09464 
09465 
09466 
09467 /********************************************************************************************
09468 
09469 >   BOOL OpEditBlendEndObject::InsertChangeEndActions(double PathProportion, DocCoord NewPosition,
09470                                                        double StepDistance, Node* pNodeToEdit)
09471 
09472     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com> 
09473     Created:    17/9/99
09474     Inputs:     PathProportion - the new proportion along the path to move the end object to
09475                 pNode - the blend object that is being edited
09476                 NewPosition - the new position as a coordinate
09477     Outputs:    pNodeToEdit - some members are altered by these actions         
09478     returns:    TRUE if all actions were correctly carried out, else FALSE
09479     Purpose:    To calculate and perform the actions necessary to transform pNode to its new PathProportion
09480                 whilst maintaining a constant distance between blend steps. This requires calculating a new position for
09481                 the node that is not being edited in order to maintain the correct distance, it may also require
09482                 changing the number of steps in the blend. 
09483 
09484    See Also:    Overridden version of this function for use with constant number of blend steps
09485                 
09486 
09487 ***********************************************************************************************/
09488     
09489 BOOL OpEditBlendEndObject::InsertChangeEndActions(double NewPathProp, DocCoord NewPosition, 
09490                                                   double StepDistance, Node* pNodeToEdit)
09491 {
09492     // couple of quick checks
09493     if (pNodeToEdit == NULL)
09494     {
09495         ERROR3("Node to edit is NULL");
09496         return FALSE;
09497     }
09498     if (NewPathProp < 0.0 || NewPathProp > 1.0)
09499     {
09500         ERROR3("Invalid path proportion");
09501         return FALSE;
09502     }
09503     if (m_pNodeBlend == NULL)
09504     {
09505         ERROR3("m_pNodeBlend is NULL");
09506         return FALSE;
09507     }
09508     // find out if this node is the first or last node of the blend
09509     BOOL NodeIsFirst;
09510     NodeBlender* pNodeBlender = GetBlenderAndPosition(pNodeToEdit, &NodeIsFirst);   
09511     if (pNodeBlender == NULL)
09512     {
09513         ERROR3("No NodeBlender");
09514         return FALSE;
09515     }
09516     // get the variables that we need to pass to CalculateNewNumSteps()
09517     double OtherDistance = 0.0;
09518     double ThisDistance = 0.0;
09519     double StartProp = 0.0;
09520     double EndProp = 1.0;
09521     UINT32 CurrentSteps = m_pNodeBlend->GetNumBlendSteps();
09522     UINT32 NewNumSteps = CurrentSteps;
09523     double BlendDistance = 0.0;
09524     
09525     BOOL Valid = m_pNodeBlend->GetBlendDistance(TRUE, &BlendDistance);
09526     if (Valid)
09527     {
09528         Valid = m_pNodeBlend->GetStartAndEndProportions(&StartProp, &EndProp);
09529         if (Valid)
09530         {
09531             // we must pass the distance of each object from their respective ends,
09532             // i.e. the distance of the end object is the distance from the object to
09533             // the end of the path.
09534             if (NodeIsFirst)
09535             {
09536                 // have we reversed the order? If so then do it as an action
09537                 if (NewPathProp >= EndProp)
09538                 {   
09539                     // Swap the ends
09540                     Valid = InsertChangeProportion(NewPathProp, NodeIsFirst, pNodeBlender);
09541                     // get the new proportions
09542                     if (Valid) Valid = m_pNodeBlend->GetStartAndEndProportions(&StartProp, &EndProp);
09543                     // work out the distances from the closest end to each object
09544                     OtherDistance = BlendDistance * StartProp;
09545                     ThisDistance = BlendDistance * (1-NewPathProp);
09546                     // we have swapped ends
09547                     NodeIsFirst = FALSE;
09548                 }
09549                 else
09550                 {
09551                     OtherDistance = BlendDistance * (1-EndProp);
09552                     ThisDistance = BlendDistance * NewPathProp;
09553                 }
09554             }
09555             else
09556             {
09557                 if (NewPathProp <= StartProp)
09558                 {
09559                     Valid = InsertChangeProportion(NewPathProp, NodeIsFirst, pNodeBlender);
09560                     if (Valid) Valid = m_pNodeBlend->GetStartAndEndProportions(&StartProp, &EndProp);   
09561                     OtherDistance = BlendDistance * (1-EndProp);
09562                     ThisDistance = BlendDistance * NewPathProp;
09563                     NodeIsFirst = TRUE;
09564                 }
09565                 else
09566                 {
09567                     OtherDistance = BlendDistance * StartProp;
09568                     ThisDistance = BlendDistance * (1-NewPathProp);
09569                 }
09570             }
09571             Valid = CalculateNewNumStepsAndPosition(CurrentSteps, BlendDistance, 
09572                                                     StepDistance, &ThisDistance, 
09573                                                     &OtherDistance, &NewNumSteps);
09574 
09575             if (Valid)
09576             {
09577                 double NewOtherProp = 0.0;
09578                 
09579                 // if we are editing the start node make the other distance go from the
09580                 // start of the path
09581                 if (NodeIsFirst)
09582                     OtherDistance = BlendDistance - OtherDistance;
09583                     
09584                 NewOtherProp = OtherDistance / BlendDistance;
09585 
09586                 Valid = DoInvalidateNodeRegion((NodeRenderableInk*)m_pNodeBlend,TRUE,FALSE);
09587                 if (Valid)  Valid = (InvalidateBoundsAction::Init(this,&UndoActions,m_pNodeBlend,TRUE) != AC_FAIL);
09588                 if (Valid)
09589                 {
09590 //                  NodeBlender* pThisBlender = NULL;
09591                     NodeBlender* pOtherBlender = NULL;
09592                     Node* pOtherNode = NULL;
09593                     if (NodeIsFirst)
09594                     {
09595                         pOtherBlender = m_pNodeBlend->FindLastBlender();
09596                         if (pOtherBlender ==  NULL)
09597                         {
09598                             ERROR3("Couldn't find blender");
09599                             return FALSE;
09600                         }
09601                         pOtherNode = pOtherBlender->GetNodeEnd();
09602                     }
09603                     else
09604                     {
09605                         pOtherBlender = m_pNodeBlend->FindFirstBlender();
09606                         if (pOtherBlender ==  NULL)
09607                         {
09608                             ERROR3("Couldn't find blender");
09609                             return FALSE;
09610                         }
09611                         pOtherNode = pOtherBlender->GetNodeStart();
09612                     }
09613 
09614                     if (pOtherNode == NULL)
09615                     {
09616                         ERROR3("Couldn't find other blend node");
09617                         return FALSE;
09618                     }
09619                     // insert a change steps action if we need one
09620                     if (NewNumSteps != CurrentSteps)
09621                     {
09622                         double DistanceEntered = m_pNodeBlend->GetDistanceEntered();
09623                         ChangeBlendStepsAction* pStepAction;
09624                         NodeRenderableInk * pInk = (NodeRenderableInk *)m_pNodeBlend;       
09625                         Valid = ChangeBlendStepsAction::Init(this,&UndoActions,pInk,CurrentSteps,DistanceEntered, &pStepAction) != AC_FAIL;
09626                         m_pNodeBlend->SetNumBlendSteps(NewNumSteps);
09627                     }
09628                     // insert change proportion for the node we edited
09629                     Valid = InsertChangeProportion(NewPathProp, NodeIsFirst, pNodeBlender);
09630                     
09631                     // insert change for the node that was moved in order to retain constant step distance
09632                     if (Valid) Valid = InsertChangeProportion(NewOtherProp, (!NodeIsFirst), pOtherBlender);
09633                     
09634                     // transform the edited node to its new position
09635                     if (Valid) Valid = m_pNodeBlend->TransformNodeToPoint((NodeRenderableInk*)pNodeToEdit,&NewPosition,this,0.0);
09636                     
09637                     // transform the other node to its new position
09638                     if (Valid)
09639                     {   
09640                         DocCoord NewPoint;
09641                         double VoidParam;
09642                         Valid = pOtherBlender->GetPointFromDistance(OtherDistance, &NewPoint, &VoidParam);
09643                         if (Valid) Valid = m_pNodeBlend->TransformNodeToPoint((NodeRenderableInk*)pOtherNode, &NewPoint, this, 0.0);
09644                     
09645                         // find out if we are part of a bevel, shadow or contour, if so then we must regenerate
09646                         Node* pController = m_pNodeBlend->GetParentController();
09647                         if (pController != NULL)
09648                             pController->RegenerateNode(this);
09649 
09650                         if (Valid) Valid = DoInvalidateNodeRegion((NodeRenderableInk*)m_pNodeBlend,TRUE,FALSE);
09651                         if (Valid) Valid = (InvalidateBoundsAction::Init(this,&UndoActions,m_pNodeBlend,TRUE) != AC_FAIL);
09652                     
09653                     }
09654                         
09655                 }
09656             }
09657         }
09658     }
09659 
09660     
09661     return Valid;
09662 
09663 
09664 }
09665 
09666 
09667 /********************************************************************************************
09668 
09669 >   BOOL OpEditBlendEndObject::InsertChangeProportion(double NewProportion, BOOL First,
09670                                                     Node* pNodeToEdit)
09671 
09672     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com> 
09673     Created:    17/9/99
09674     Inputs:     PathProportion - the new proportion along the path to move the end object to
09675                 pNode - the blend object that is being edited
09676                 
09677     Outputs:    pNodeToEdit - some members are altered by these actions         
09678     returns:    TRUE if all actions were correctly carried out, else FALSE
09679     Purpose:    inserts an action setting the proportion along the path variable to its new value 
09680 
09681 ***********************************************************************************************/
09682     
09683 BOOL OpEditBlendEndObject::InsertChangeProportion(double NewProp, BOOL FirstNode, 
09684                                                   NodeBlender* pEditBlender)
09685 {
09686     // insert the change blender action
09687     ChangeBlenderOpParam BlenderParam;
09688     ChangeBlendOpParam   BlendParam;
09689 
09690     BOOL Valid;
09691     // lets see if we need to swap the ends
09692     if (FirstNode)
09693     {
09694         // if the new proportion of path is past the other end then 
09695         // we need to swap ends.
09696         if (NewProp > pEditBlender->GetProportionOfPathDistEnd())
09697         {
09698             BlenderParam.m_ChangeType = CHANGEBLENDER_SWAPENDS;
09699             Valid = ChangeBlenderAction::Init(this, &UndoActions, pEditBlender, BlenderParam);
09700             
09701             if (Valid)
09702             {
09703                 // set the new start proportion to the old end proportion
09704                 BlenderParam.m_ChangeType = CHANGEBLENDER_PATHSTART;
09705                 BlenderParam.m_NewPathStart = pEditBlender->GetProportionOfPathDistEnd();
09706                 BlendParam.NewEndObject  = FIRST;
09707                 Valid = ChangeBlenderAction::Init(this, &UndoActions, pEditBlender, BlenderParam);
09708                 
09709                 if (Valid)
09710                 {
09711                     BlenderParam.m_ChangeType = CHANGEBLENDER_PATHEND;
09712                     BlenderParam.m_NewPathEnd = NewProp;
09713                     BlendParam.NewEndObject  = LAST;
09714                     Valid = ChangeBlenderAction::Init(this, &UndoActions, pEditBlender, BlenderParam);
09715                 }
09716             }           
09717 
09718         }
09719         else
09720         {
09721             // just a regular end edit
09722             BlenderParam.m_ChangeType = CHANGEBLENDER_PATHSTART;
09723             BlenderParam.m_NewPathStart = NewProp;
09724             BlendParam.NewEndObject  = FIRST;
09725             Valid = ChangeBlenderAction::Init(this, &UndoActions, pEditBlender, BlenderParam);
09726         }
09727     }
09728     else 
09729     {
09730         if (NewProp < pEditBlender->GetProportionOfPathDistStart())
09731         {
09732             BlenderParam.m_ChangeType = CHANGEBLENDER_SWAPENDS;
09733             Valid = ChangeBlenderAction::Init(this, &UndoActions, pEditBlender, BlenderParam);  
09734         
09735             if (Valid)
09736             {
09737                 // set the new end proportion to the old start proportion
09738                 BlenderParam.m_ChangeType = CHANGEBLENDER_PATHEND;
09739                 BlenderParam.m_NewPathEnd = pEditBlender->GetProportionOfPathDistStart();
09740                 BlendParam.NewEndObject  = LAST;
09741                 Valid = ChangeBlenderAction::Init(this, &UndoActions, pEditBlender, BlenderParam);
09742                 
09743                 if (Valid)
09744                 {
09745                     BlenderParam.m_ChangeType = CHANGEBLENDER_PATHSTART;
09746                     BlenderParam.m_NewPathStart = NewProp;
09747                     BlendParam.NewEndObject  = FIRST;
09748                     Valid = ChangeBlenderAction::Init(this, &UndoActions, pEditBlender, BlenderParam);
09749                 }
09750             }           
09751 
09752         }
09753         else
09754         {
09755             // just a regular end edit
09756             BlenderParam.m_ChangeType = CHANGEBLENDER_PATHEND;
09757             BlenderParam.m_NewPathEnd = NewProp;
09758             BlendParam.NewEndObject  = LAST;
09759             Valid = ChangeBlenderAction::Init(this, &UndoActions, pEditBlender, BlenderParam);
09760         }
09761     }
09762 
09763     return Valid;
09764 }
09765 
09766 
09767 
09768 
09769 /********************************************************************************************
09770 
09771 >   BOOL OpChangeBlendDistance::CalculateNewNumStepsAndPosition(UINT32 OldNumSteps, 
09772                                                                 double BlendDistance,
09773                                                                 double StepDistance
09774                                                                 double* FixedPosition
09775                                                                 double* MoveablePosition
09776                                                                 UINT32* NewNumSteps)
09777     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com> 
09778     Created:    10/9/99
09779     Inputs:     OldNumSteps - the number of steps in the blend before any changes were applied
09780                 BlendDistance - the length of the NodeBlendPath
09781                 StepDistance  - the distance between steps that we wish to apply.
09782                 FixedDistance - the distance from the end of the path of the object that we would
09783                 like to keep fixed.  Note that it may not always remain fixed.
09784                 MoveableDistance - the distance from the end of the path of the object that we are
09785                 allowing to move.
09786                 NOTE: FixedDistance and MoveableDistance should be measured from the end that they 
09787                 represent.  That is to say if you wish to keep the first blend object fixed then FixedDistance
09788                 should be the distance from the start of the path to the first blend object, MoveableDistance will
09789                 then be the distance FROM THE OTHER END to the end object.
09790 
09791     Outputs:    NewNumSteps - the new number of steps required
09792                 MoveableDistance - the new position of the end that we don't care about
09793                 FixedDistance - in some rare instances we may have adjust the fixed position
09794                 in order to accomodate the desired step distance
09795                 is the last object.         
09796     returns:    TRUE if all went well, FALSE if the step distance gives and invalid number of steps.
09797     Purpose:    Works out the number of steps needed to accomodate the new step distance, as well as 
09798                 working out the new positions of the end objects. 
09799                 This is nearly identical to the function of the same name in OpChangeBlendDistance,
09800                 the difference being that if the operation cannot be performed without moving the 
09801                 fixed node then it will inform the user.
09802                 
09803     See Also:   
09804 ***********************************************************************************************/
09805     
09806 BOOL OpEditBlendEndObject::CalculateNewNumStepsAndPosition(UINT32 OldNumSteps, double BlendDistance,
09807                                                            double StepDistance, double* FixedDistance,
09808                                                            double* MoveableDistance, UINT32* NewNumSteps)
09809 {
09810     // First check parameters
09811     if (OldNumSteps < 0 || StepDistance < 0)
09812     {
09813         ERROR3("Invalid parameter");
09814         return FALSE;
09815     }
09816     if (BlendDistance < (*FixedDistance + *MoveableDistance))
09817     {
09818         ERROR3("Invalid distance parameter");
09819         return FALSE;
09820     }
09821 
09822     //initialise to zero 
09823     *NewNumSteps = 0;
09824 
09825     // get the distance currently occupied by the blend
09826     double DistanceUsed = BlendDistance - (*FixedDistance + *MoveableDistance);
09827 
09828     // try to use all the space 
09829     UINT32 TempNumSteps = (UINT32)(DistanceUsed / StepDistance);
09830 
09831     if (TempNumSteps > 0)
09832     {
09833         *NewNumSteps = TempNumSteps;
09834         double NewDistance = TempNumSteps * StepDistance;
09835         *MoveableDistance = BlendDistance - (*FixedDistance + NewDistance);
09836     }
09837     // thats too long, try moving the other node all the way to the end
09838     else 
09839     {
09840 
09841         TempNumSteps = (UINT32)((BlendDistance - *FixedDistance)/ StepDistance);
09842         if (TempNumSteps > 0)
09843         {
09844             double NewDistance = TempNumSteps * StepDistance;
09845             *NewNumSteps = TempNumSteps;
09846             *MoveableDistance = BlendDistance - (*FixedDistance + NewDistance);
09847         }
09848         else
09849         {
09850             // still didn't work, just quit for now but maybe 
09851             // think about bringing up a dialog asking the user if they 
09852             // wish to go to edit steps mode.
09853             return FALSE;
09854         }
09855     }
09856 
09857     // test to see that what we have is ok.
09858     if (*NewNumSteps > 0)
09859         return TRUE;
09860     else
09861         return FALSE;
09862 }
09863 
09864 
09865 
09866 
09867 
09868 /********************************************************************************************
09869 
09870 >   NodeBlender* OpEditBlendEndObject::GetBlenderAndPosition(Node* pNode, BOOL* pFirst)
09871 
09872     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com> 
09873     Created:    10/9/99
09874     Inputs:     pNode - the blend object that is being edited
09875     Outputs:    pFirst -  TRUE if pNode is the first object of the blender, FALSE if it
09876                 is the last object.         
09877     returns:    pointer to the nodeblender responsible for the blend being edited, or 
09878                 NULL if invalid.
09879     Purpose:    To find the NodeBlender on a curve that uses pNode as either its first or last node, 
09880                 and which position.  
09881                 
09882 
09883 ***********************************************************************************************/
09884     
09885 
09886 
09887 NodeBlender* OpEditBlendEndObject::GetBlenderAndPosition(Node* pEditNode, BOOL* pFirst)
09888 {
09889     if (pEditNode == NULL)
09890     {
09891         ERROR3("Node is NULL");
09892         return NULL;
09893     }
09894 
09895     if (m_pNodeBlend == NULL)
09896     {
09897         ERROR3("m_pNodeBlend is NULL");
09898         return NULL;
09899     }
09900 
09901     NodeBlender* pBlender = m_pNodeBlend->FindFirstBlender();
09902     while (pBlender != NULL)
09903     {
09904         if (pBlender->IsBlendedOnCurve())
09905         {
09906             if (pBlender->GetNodeStart() == pEditNode)
09907             {
09908                 *pFirst = TRUE;
09909                 return pBlender;
09910             }
09911             else if (pBlender->GetNodeEnd() == pEditNode)
09912             {
09913                 *pFirst = FALSE;
09914                 return pBlender;
09915             }
09916             else
09917             {
09918                 // also need to check for compound nodes ....
09919 
09920                 if (pBlender->GetNodeStart ()->IsNodeInSubtree (pEditNode))
09921                 {
09922                     *pFirst = TRUE;
09923                     return pBlender;
09924                 }
09925                 else    // check the end node
09926                 {
09927                     if (pBlender->GetNodeEnd ()->IsNodeInSubtree (pEditNode))
09928                     {
09929                         *pFirst = FALSE;
09930                         return pBlender;
09931                     }
09932                 }
09933             }
09934         }
09935         pBlender = m_pNodeBlend->FindNextBlender(pBlender);
09936     }
09937 
09938     // we didn't find a nodeblender
09939     ERROR3("Couldn't find a nodeblender");
09940     return NULL;
09941 
09942 
09943 }
09944 
09945 
09946 
09947 /********************************************************************************************
09948 
09949 >   BOOL OpEditBlendEndObject::GetClosetPointOnPath(DocCoord PointerPos, DocCoord* ClosestPoint)
09950 
09951     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com> 
09952     Created:    10/9/99
09953     Inputs:     PointerPos - The current position of the mouse in Doc Coords
09954     Outputs:    ClosestPoint - the closest point on the nodeblendpath to pointerpos             
09955     returns:    TRUE if successful , FALSE otherwise
09956     Purpose:    To find the closest point on the nodeblend path to the point clicked or dragged on
09957                 by the user.  The function gets the closest point on all the paths and takes the 
09958                 closest. However this part is only done once as the nodeblendpath is then cached.
09959 
09960 ***********************************************************************************************/
09961 
09962 BOOL OpEditBlendEndObject::GetClosestPointOnPath(DocCoord PointerPos, DocCoord* ClosestPoint)
09963 {
09964     if (m_pNodeBlend == NULL)
09965     {
09966         ERROR3("There is no NodeBlend");
09967         return FALSE;
09968     }
09969     if (m_pNodeBlendPath == NULL)
09970     {
09971         UINT32 NBPCounter = 0;
09972         NodeBlendPath* pNodeBlendPath = m_pNodeBlend->GetNodeBlendPath(NBPCounter);
09973     
09974         if (pNodeBlendPath == NULL)
09975         {
09976             ERROR3("This blend has no nodeblendpath");
09977             return FALSE;
09978         }
09979     
09980         UINT32 ClosestIndex = 0;
09981         double ClosestDistance = 999999999999.;
09982         INT32 ClosestPath=0;
09983         double ClosestMu=0;
09984         while (pNodeBlendPath != NULL)
09985         {
09986             INT32 Index;
09987             double Mu;
09988             double SqrDistance = pNodeBlendPath->InkPath.SqrDistanceToPoint(PointerPos, &Index, &Mu);
09989             if (SqrDistance < ClosestDistance)
09990             {
09991                 ClosestDistance = SqrDistance;
09992                 ClosestPath = NBPCounter;
09993                 ClosestIndex = Index;
09994                 ClosestMu = Mu;
09995             }
09996             pNodeBlendPath = m_pNodeBlend->GetNodeBlendPath(++NBPCounter);
09997         }
09998         pNodeBlendPath = m_pNodeBlend->GetNodeBlendPath(ClosestPath);
09999         DocCoord PointOnLine = pNodeBlendPath->InkPath.ClosestPointTo(ClosestMu, ClosestIndex);
10000         // assign the point
10001         *ClosestPoint = PointOnLine;
10002         // assign the path to the member variable
10003         m_pNodeBlendPath = pNodeBlendPath; 
10004     }
10005     else
10006     {   // if we have already cached the nodeblendpath
10007         INT32 Index=0;
10008         double Mu=0;
10009         /*double SqrDistance = */m_pNodeBlendPath->InkPath.SqrDistanceToPoint(PointerPos, &Index, &Mu);
10010         DocCoord PointOnLine = m_pNodeBlendPath->InkPath.ClosestPointTo(Mu, Index);
10011         *ClosestPoint = PointOnLine;
10012     }
10013 
10014     return TRUE;
10015 
10016 
10017 
10018 }
10019     
10020 
10021 
10022 
10023 /********************************************************************************************
10024 
10025 >   virtual void OpEditBlendEndObject::GetOpName(String_256* OpName) 
10026 
10027     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com> 
10028     Created:    10/9/99
10029     Inputs:     OpName = ptr to str to place op name in
10030     Outputs:    The undo string for the operation
10031     Returns:    
10032     Purpose:    The GetOpName fn is overridden so that we return back a description 
10033                 appropriate to the type of attribute that the operation applies. 
10034     Errors:     -
10035     SeeAlso:    -
10036 
10037 ********************************************************************************************/
10038 
10039 void OpEditBlendEndObject::GetOpName(String_256 * OpName)
10040 {
10041     String_256 test = "test string";
10042 
10043 
10044 }
10045 
10046 
10047 
10048 /********************************************************************************************
10049 
10050 >   void OpEditBlendEndObjects::RenderDragBlobs(DocRect Rect,Spread* pSpread)
10051 
10052     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
10053     Created:    10/9/99
10054     Inputs:     CentrePosition - the centre of the 'X' to be drawn
10055                 pSpread - The spread that the drawing will happen on
10056                 On - are we rendering the blobs on or off?
10057     Purpose:    Calls the XOR outline function and draws an 'X' in the position specified
10058 
10059 ********************************************************************************************/
10060 
10061 void OpEditBlendEndObject::RenderDragBlobs(Spread* pSpread, DocCoord CentrePosition, BOOL On)
10062 {
10063     if (pSpread == NULL)
10064     {
10065         ERROR3("No Spread");
10066         return;
10067     }
10068     if (m_pRange == NULL || m_pTransMatrix == NULL)
10069     {
10070         ERROR3("Null member variables");
10071         return;
10072     }
10073 
10074     Matrix NewMatrix;
10075     NewMatrix.SetTranslation(CentrePosition - m_StartCoord);
10076     
10077     if (On == FALSE)
10078         m_pRange->RenderXOROutlinesOff(NULL, pSpread, m_pTransMatrix);
10079     else
10080     {
10081         m_pRange->RenderXOROutlinesOn(NULL, pSpread, &NewMatrix, NULL);
10082         m_pTransMatrix->SetTranslation(CentrePosition - m_StartCoord);
10083     }
10084     
10085     //we'll render the 'X' directly so we need a render region
10086     
10087     RenderRegion* pRegion = DocView::RenderOnTop(NULL, pSpread, UnclippedEOR);
10088     while ( pRegion != NULL )
10089     {
10090         pRegion->SetLineColour(COLOUR_UNSELECTEDBLOB);
10091         pRegion->SetFillColour(COLOUR_UNSELECTEDBLOB);
10092     
10093                     
10094         // Draw a blob at the centre point
10095         DocRect BlobSize;
10096         BlobManager* pBlobMgr = GetApplication()->GetBlobManager();
10097         if (pBlobMgr != NULL)
10098         {   
10099             pBlobMgr->GetBlobRect(CentrePosition, &BlobSize);
10100     
10101             pRegion->DrawLine(DocCoord(BlobSize.hi.x, BlobSize.hi.y), DocCoord(BlobSize.lo.x, BlobSize.lo.y));
10102             pRegion->DrawLine(DocCoord(BlobSize.lo.x, BlobSize.hi.y), DocCoord(BlobSize.hi.x, BlobSize.lo.y));
10103             pRegion->DrawPixel(DocCoord(BlobSize.hi.x, BlobSize.lo.y));
10104             pRegion->DrawPixel(DocCoord(BlobSize.lo.x, BlobSize.lo.y));
10105         }
10106         pRegion = DocView::GetNextOnTop(NULL);
10107     }
10108             
10109 }
10110 
10111 
10112 /********************************************************************************************
10113 
10114 >   void OpEditBlendEndObjects::RenderSelectedObjectBlobs(Spread* pSpread)
10115 
10116     Author:     Diccon_Yamanaka (Xara Group Ltd) <camelotdev@xara.com>
10117     Created:    28/9/99
10118     Inputs:     pSpread - The spread that the drawing will happen on
10119                 
10120     Purpose:    To render the object blobs of the objects that were in the selection during the
10121                 drag op.  To dig myself out of the hole that I dug for myself in the implementaion
10122                 of the drag op.  Basically if you read the comments in DoDrag() you will be
10123                 aware that we've had to temporarily change the selection for the purposes of this drag.
10124                 However this means that all the objects that should really be selected lose their 
10125                 object blob.  in order to restore them we must go through the stored
10126                 selection by hand and render them individually.  Awful, sorry.
10127     See also:   SelState.h/.cpp DoDrag()
10128 
10129 ********************************************************************************************/
10130 
10131 void OpEditBlendEndObject::RenderSelectedObjectBlobs(Spread* pSpread)
10132 {
10133     // variables we want so long as the selection state member is ok
10134     Node** pSelectionNodes = NULL;
10135     UINT32 NumNodes = 0; 
10136     SelNdRng* pSelectedRanges = NULL;
10137     UINT32 NumRanges = 0;
10138 
10139     // check out the member
10140     if (m_pSelState == NULL)
10141     {
10142         ERROR3("m_pSelState is NULL");
10143         return;
10144     }
10145     else
10146     {
10147         pSelectionNodes = m_pSelState->GetNodeList();
10148         NumNodes = m_pSelState->GetNumNodes();
10149         pSelectedRanges = m_pSelState->GetSelectionList();
10150         NumRanges = m_pSelState->GetNumRanges();
10151     }
10152 
10153     if ((NumNodes == 0 || pSelectionNodes[0] == NULL) && 
10154         (NumRanges == 0 || pSelectedRanges[0].FirstNode == NULL))
10155     {
10156         ERROR3("No selection nodes");
10157         EndDrag();
10158         return;
10159     }
10160 
10161     UINT32 i;
10162     for ( i = 0; i < NumRanges; i++)
10163     {   
10164         NodeRenderable* pNode = (NodeRenderable*)pSelectedRanges[i].FirstNode;
10165         for (UINT32 j = 0; j < pSelectedRanges[i].NumSelected; j++)
10166         {
10167             if (pNode == NULL)
10168             {
10169                 ERROR3("Node is null");
10170                 break;
10171             }
10172 
10173             RenderRegion* pOnTopRegion = DocView::RenderOnTop(NULL, pSpread, ClippedEOR);
10174             while (pOnTopRegion)
10175             {
10176                 pNode->RenderObjectBlobs(pOnTopRegion);
10177 
10178                 // Go find the next region
10179                 pOnTopRegion = DocView::GetNextOnTop(NULL);
10180             }
10181             pNode = (NodeRenderable*)pNode->FindNext(CC_RUNTIME_CLASS(NodeRenderableInk));
10182         }
10183     }
10184 
10185 
10186     for ( i = 0; i < NumNodes; i++)
10187     {   
10188         NodeRenderable* pNode = (NodeRenderable*)pSelectionNodes[i];
10189         RenderRegion* pOnTopRegion = DocView::RenderOnTop(NULL, pSpread, ClippedEOR);
10190         while (pOnTopRegion)
10191         {
10192             pNode->RenderObjectBlobs(pOnTopRegion);
10193 
10194             // Go find the next region
10195             pOnTopRegion = DocView::GetNextOnTop(NULL);
10196         }
10197     }
10198 
10199     
10200 }
10201 
10202 
10203 
10204 //IMPLEMENT_SIMPLE_MODULE( BlendModule, MODULEID_BLEND, BlendTool,
10205 //                          "Blend Tool", "To blend objects", "MarkN" );
10206 

Generated on Sat Nov 10 03:47:33 2007 for Camelot by  doxygen 1.4.4