// $Id: rsmooth.cpp 1492 2006-07-20 19:19:48Z alex $ /* @@tag:xara-cn@@ DO NOT MODIFY THIS LINE ================================XARAHEADERSTART=========================== Xara LX, a vector drawing and manipulation program. Copyright (C) 1993-2006 Xara Group Ltd. Copyright on certain contributions may be held in joint with their respective authors. See AUTHORS file for details. LICENSE TO USE AND MODIFY SOFTWARE ---------------------------------- This file is part of Xara LX. Xara LX is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. Xara LX and its component source files are distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Xara LX (see the file GPL in the root directory of the distribution); if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ADDITIONAL RIGHTS ----------------- Conditional upon your continuing compliance with the GNU General Public License described above, Xara Group Ltd grants to you certain additional rights. The additional rights are to use, modify, and distribute the software together with the wxWidgets library, the wxXtra library, and the "CDraw" library and any other such library that any version of Xara LX relased by Xara Group Ltd requires in order to compile and execute, including the static linking of that library to XaraLX. In the case of the "CDraw" library, you may satisfy obligation under the GNU General Public License to provide source code by providing a binary copy of the library concerned and a copy of the license accompanying it. Nothing in this section restricts any of the rights you have under the GNU General Public License. SCOPE OF LICENSE ---------------- This license applies to this program (XaraLX) and its constituent source files only, and does not necessarily apply to other Xara products which may in part share the same code base, and are subject to their own licensing terms. This license does not apply to files in the wxXtra directory, which are built into a separate library, and are subject to the wxWindows license contained within that directory in the file "WXXTRA-LICENSE". This license does not apply to the binary libraries (if any) within the "libs" directory, which are subject to a separate license contained within that directory in the file "LIBS-LICENSE". ARRANGEMENTS FOR CONTRIBUTION OF MODIFICATIONS ---------------------------------------------- Subject to the terms of the GNU Public License (see above), you are free to do whatever you like with your modifications. However, you may (at your option) wish contribute them to Xara's source tree. You can find details of how to do this at: http://www.xaraxtreme.org/developers/ Prior to contributing your modifications, you will need to complete our contributor agreement. This can be found at: http://www.xaraxtreme.org/developers/contribute/ Please note that Xara will not accept modifications which modify any of the text between the start and end of this header (marked XARAHEADERSTART and XARAHEADEREND). MARKS ----- Xara, Xara LX, Xara X, Xara X/Xtreme, Xara Xtreme, the Xtreme and Xara designs are registered or unregistered trademarks, design-marks, and/or service marks of Xara Group Ltd. All rights in these marks are reserved. Xara Group Ltd, Gaddesden Place, Hemel Hempstead, HP2 6EX, UK. http://www.xara.com/ =================================XARAHEADEREND============================ */ // Implementation file containing routines to control the interactive smoothing // of selected path regions /* */ #include "camtypes.h" //#include "paths.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "rsmooth.h" #include "nodepath.h" //#include "docview.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include //#include "mike.h" //#include "undoop.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "document.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "objchge.h" #include "nodeblnd.h" #include "nodebldr.h" #include "ophist.h" CC_IMPLEMENT_DYNCREATE( OpRetroSmooth, SelOperation ) DECLARE_SOURCE("$Revision: 1492 $"); // Declare smart memory handling in Debug builds #define new CAM_DEBUG_NEW /******************************************************************************************** > RetroSmooth::RetroSmooth() Author: Mike_Kenny (Xara Group Ltd) Created: 11/11/94 Purpose: RetroSmooth constructor Sets default values for internal variables used by the RetroSmooth class. SeeAlso: - ********************************************************************************************/ RetroSmooth::RetroSmooth() { pRetroNode = NULL; pRetroSpread = NULL; RetroSmoothAcc = SMOOTH_MIN; RetroSmoothing = FALSE; RetroPathCRC = 0; RetroSelCRC = 0; RetroSmoothPercent = 0; m_bRender = TRUE; } /******************************************************************************************** > BOOL RetroSmooth::Initialise() Author: Mike_Kenny (Xara Group Ltd) Created: 11/11/94 Inputs: - Returns: TRUE - then the retro smooth initialisation has succedded FALSE - then no memory to perform the retro smooth. The caller should abort the operation. Purpose: Initialise various internal lists of data used by the retrosmooth class. ********************************************************************************************/ BOOL RetroSmooth::Initialise() { BOOL ok = RetroPath.Initialise(12,12); if (ok) ok = RetroEorPath.Initialise(12,12); if (ok) ok = RetroEorRegion.Initialise(12,12); return ok; } /******************************************************************************************** > void RetroSmooth::Changing(NodePath* pNodePath, Spread* pSpread, double smooth) Author: Mike_Kenny (Xara Group Ltd) Created: 11/11/94 Inputs: pNodePath = pointer to a node path to smooth pSpread = pointer to the nodes spread smooth = smoothness level to use Returns: Purpose: This function is called when a change has occured to the retrosmooth input control device, and it wishes to inform the retrosmoother. The function takes a node and spread and renders a new smoothed eor'd version of the path to the display device. No changes occur to the NodePath provided. All changes occur internally to this smooth class. If this is the first of a sequence of calls to the changing function, a cached version of the path is created, along with information about the selected regions within the path. On subsequent calls, the cached version of the path will be used to create a smooth display path. ********************************************************************************************/ void RetroSmooth::Changing(NodePath* pNodePath, Spread* pSpread, double smooth) { /* // Find the docview that we are looking at DocView* pDocView = DocView::GetCurrent(); // Refit the path - First work out the current error factor double ScaleFactor = (pDocView->GetViewScale()).MakeDouble(); double ErrorLevel = (64 + (160*smooth)) / ScaleFactor; RetroSmoothAcc = ErrorLevel * ErrorLevel; */ // calculate a useful smooth accuracy double newacc = SMOOTH_MIN + (SMOOTH_MAX - SMOOTH_MIN)*pow((smooth/100), 3.0); if (newacc == RetroSmoothAcc) return; // keep track of some variables for later RetroSmoothAcc = newacc; RetroSmoothPercent = smooth; pRetroSpread = pSpread; // if this is the first call to us then do the setup if (!RetroSmoothing) if (!RetroSmooth::Starting(pNodePath)) return; // finally we are in a position to start retro activity. RetroSmoothing = TRUE; RetroSmooth::Update(); } /******************************************************************************************** > void RetroSmooth::Finished() Author: Mike_Kenny (Xara Group Ltd) Created: 11/11/94 Inputs: - Returns: - Purpose: This function should be called to terminate the retro smooth action. The data cached during the calls to RetroSmooth::Changing() will now be used to actually affect the path held in the document. ********************************************************************************************/ void RetroSmooth::Finished() { // if no retro path, just do nothing if (!pRetroNode || !RetroSmoothing) return; // tidy up after retro smoothing RetroEorRegion.Empty(); RecordPathRegions(&RetroEorPath); RetroPathCRC = RetroEorPath.CalcCRC(); RetroSelCRC = RetroEorPath.CalcSelectedCRC(); RetroSmoothing = FALSE; // finally create an op to really smooth the tree object // path and execute it. OpRetroSmooth* pOpRetroSmooth = new OpRetroSmooth; if (pOpRetroSmooth != NULL) pOpRetroSmooth->DoRetroSmooth(pRetroNode, &RetroEorPath, RetroSmoothAcc); } /******************************************************************************************** > void RetroSmooth::Invalidate() Author: Mike_Kenny (Xara Group Ltd) Created: 22/11/94 Inputs: - Returns: - Purpose: Invalidates all cached information about the currently active retro smoothing path. Cached paths and region buffers are compacted. ********************************************************************************************/ void RetroSmooth::Invalidate() { pRetroNode = NULL; pRetroSpread = NULL; RetroSmoothAcc = SMOOTH_MIN; RetroSmoothing = FALSE; RetroPathCRC = 0; RetroSelCRC = 0; RetroPath.ClearPath(TRUE); RetroEorPath.ClearPath(TRUE); RetroEorRegion.Empty(); } /******************************************************************************************** > double RetroSmooth::ReturnCachedAccuracy(Path* pPath) Author: Mike_Kenny (Xara Group Ltd) Created: 23/11/94 Inputs: pPath = pointer to a path Returns: a percentage value for the current smoothness of this curve Purpose: This function can be used to determin the smoothness of any curve. It will simply check for a match between the path given and an internal cached representation. If a match is found the current smoothness is returned, ie the last value passed to the RetroSmooth::Changing() function. If no match is found a value of 0 is returned, meaning the curve has not been smoothed. ********************************************************************************************/ double RetroSmooth::ReturnCachedAccuracy(Path* pPath) { // calculate the cyclic redundancy for this path. INT32 new_crc = pPath->CalcCRC(); INT32 sel_crc = pPath->CalcSelectedCRC(); if ((RetroPathCRC == new_crc) && (RetroSelCRC == sel_crc)) return RetroSmoothPercent; return 0; } /******************************************************************************************** BOOL RetroSmooth::Starting(NodePath* pDocNodePath) Author: Mike_Kenny (Xara Group Ltd) Created: 11/11/94 Inputs: pDocNodePath = pointer to a node path to smooth Returns: Purpose: Set up the internal data structures for this new NodePath. This allows us to continue resmoothing the path given new smooth values, until the outside world calls RetroSmooth::Finished. We cache the path and its selected regions to help us with some hairy redraw problems. SeeAlso: RetroSmooth::Update() ********************************************************************************************/ BOOL RetroSmooth::Starting(NodePath* pDocNodePath) { if (pRetroSpread == NULL) return FALSE; // calculate the cyclic redundancy for this path. INT32 new_crc = pDocNodePath->InkPath.CalcCRC(); INT32 sel_crc = pDocNodePath->InkPath.CalcSelectedCRC(); if (!new_crc) return FALSE; if ((RetroPathCRC == new_crc) && (RetroSelCRC == sel_crc)) { pRetroNode = pDocNodePath; TranslateRetroData(&(pDocNodePath->InkPath)); return TRUE; } // if our data doesn't match the path given then we are now // in the situation where we need to reto smooth a new path // if there are no selected points in the path then we have // nothing to retro smooth if (!sel_crc) return FALSE; return NewPathToSmooth(pDocNodePath); } /******************************************************************************************** void RetroSmooth::TranslateRetroData(Path* pPath) Author: Mike_Kenny (Xara Group Ltd) Created: 22/11/94 Inputs: pPath = a path to translate the cached retro path to Returns: - Purpose: Translate the retro path data to the origin of pPath ********************************************************************************************/ void RetroSmooth::TranslateRetroData(Path* pPath) { ENSURE(pPath !=NULL, "NULL path passed to RetroSmooth::TranslateRetroData()"); if (pPath == NULL) return; if (pPath->GetNumCoords() <= 0) return; DocCoord* Coords = pPath->GetCoordArray(); DocCoord* RCoords = RetroPath.GetCoordArray(); INT32 transx = Coords[0].x - RCoords[0].x; INT32 transy = Coords[0].y - RCoords[0].y; RetroPath.Translate(transx,transy); RetroEorPath.Translate(transx,transy); } /******************************************************************************************** void RetroSmooth::Update() Author: Mike_Kenny (Xara Group Ltd) Created: 11/11/94 Inputs: - Returns: - Purpose: We have been given a new smooth value. We need to resmooth our internal data and render an eor version to the display device. What we try to do (to avoid hideous eoring problems) is this (1) Create a temporary path and copy our cached path data to it (2) Create a temp region list to save smoothed region start end indexes (3) Go through a loop smoothing each region in the temp eor path whilst recording the new smoothed region indexes as we go. (4) Check this new data against our previous cached eorpath and region list. (5) If all match then do nothing as the smoothed path has not changed since the last smooth even though we know the smooth value has. else render the cached eor path to the output device, removing the item copy the temporary eor path and region list to the cache eor path and region list render the cached eor path to the output device (6) exit ********************************************************************************************/ void RetroSmooth::Update() { INT32 numcoords = RetroPath.GetNumCoords(); // create a temp path to smooth Path RenderPath; if (!RenderPath.Initialise(numcoords, 12)) return; RenderPath.CloneFrom(RetroPath); DynArray TempRegions; if (!TempRegions.Initialise(RetroEorRegion.NumItems(),12)) return; INT32 Region = 0; INT32 index = 0; INT32 start,end; // now scan the path for regions of interest while ((indexend); INT32 sel=0; if (nstartend) sel |= 2; if (sel==0) sel=4; if (nstart != nend) { if (RetroSmoothAcc != SMOOTH_MIN) { BOOL ok = RenderPath.SmoothSection(nstart,®ion.last,RetroSmoothAcc,sel); if (!ok) return; } if (!TempRegions.AddItem(region)) return; Region++; } numcoords = RenderPath.GetNumCoords(); index = region.last; if (!RenderPath.FindNextEndPoint(&index)) index=numcoords; } // now we've smoothed the path, we need to check to see if its worth // updating the version on screen. INT32 new_crc = RenderPath.CalcCRC(); if (new_crc != RetroPathCRC) { // get rid of the old eor path and put the new path back on again. if (RenderRegions()) { RetroEorPath.CloneFrom(RenderPath); RetroEorRegion.CloneFrom(&TempRegions); RenderRegions(); RetroPathCRC = new_crc; } } } /******************************************************************************************** statci BOOL RetroSmooth::FinishedNoUndo(NodePath* pNodePath, double SmoothValue) Author: Mike_Kenny (Xara Group Ltd) Created: 11/11/94 Inputs: pNodePath = pointer to a node path to use, the should generally be the same nodepath that was previously passed in to Changing() Returns: TRUE if successful, FALSE otherwise Purpose: Finishes off the smoothing operation by replacing the path in pNodePath with the Retropath that we have generated. This differs from the regular Finished function because it does not alter the tree and it does not launch an operation, therefore it is not undoable. See Also: If you want to know why it had to be non-undoable look at the implementation of OpDrawBrush, where it is used in this way. ********************************************************************************************/ BOOL RetroSmooth::FinishedNoUndo(NodePath* pNodePath) { ERROR2IF(pNodePath == NULL, FALSE, "pNodePath is NULL in RetroSmooth::RetroSmoothNoUndo"); BOOL ok = TRUE; #ifdef _DEBUG ok = RetroEorPath.CheckPathValid(); #endif if (ok) { pNodePath->InkPath.ClearPath(FALSE); pNodePath->InkPath.CopyPathDataFrom(&RetroEorPath); } RetroEorPath.ClearPath(FALSE); return ok; } /******************************************************************************************** void RetroSmooth::SetRenderFlag(BOOL Value) Author: Diccon_Yamanaka (Xara Group Ltd) Created: 12/4/2000 Inputs: Value - the value to set the flag Returns: Purpose: This sets the flag that says whether or not to bother doing EOR rendering. Notes: You may wish to set the flag to false if you are using the retrosmoother separately from the Bezier tool. It will mean that you can call one-off smoothing procedures without having to put up with all those blobs being rendered. ********************************************************************************************/ void RetroSmooth::SetRenderFlag(BOOL Value) { m_bRender = Value; } /******************************************************************************************** BOOL RetroSmooth::NewPathToSmooth(NodePath* pNodePath) Author: Mike_Kenny (Xara Group Ltd) Created: 11/11/94 Inputs: pNodePath = pointer to a node path to use. Returns: Purpose: Given a new document node path object, we need to initialise various buffers to hold temporary data. Notes: We keep a copy of the path data to refer back to each time a retro smooth changing message comes through. This allows us to go back to the original shape of the curve and smooth from that each time the user starts dragging the slider around, on the same path. We also keep a transient copy of the path that we are actually smoothing so that we can draw and undraw the sections which are changing. ********************************************************************************************/ BOOL RetroSmooth::NewPathToSmooth(NodePath* pNodePath) { pRetroNode = pNodePath; // clear the path elements but dont bother moving memory RetroPath.ClearPath(FALSE); RetroEorPath.ClearPath(FALSE); // take a local copy of some useful variables Path* pPath = &(pNodePath->InkPath); INT32 numcoords = pPath->GetNumCoords(); if (RetroPath.MakeSpaceInPath(numcoords)) { if (RetroEorPath.MakeSpaceInPath(numcoords)) { RetroPath.CopyPathDataFrom(pPath); RetroEorPath.CopyPathDataFrom(pPath); } else { RetroPath.Compact(); return FALSE; } } else return FALSE; // clear any regions RetroEorRegion.Empty(); // try to record the path regions if (!RecordPathRegions(pPath)) { RetroPath.ClearPath(TRUE); RetroEorPath.ClearPath(TRUE); return FALSE; } return TRUE; } /******************************************************************************************** BOOL RetroSmooth::RenderRegions() Author: Mike_Kenny (Xara Group Ltd) Created: 11/11/94 Inputs: - Returns: TRUE - then regions have been eor rendered FALSE - then no regions have been rendered Purpose: Scan through the cached region list, rendering the path and blobs in each region. Extra Note: Diccon 12/4/2000 I want to use the RetroSmoother as a one-off smoothing procedure without the EOR Rendering, therefore I have added an extra flag which returns TRUE from this function without actually rendering anything. ********************************************************************************************/ BOOL RetroSmooth::RenderRegions() { // if our flag is set then just return if (!m_bRender) return TRUE; // check we've actually got a region to render INT32 numregions = RetroEorRegion.NumItems(); if (!numregions) return TRUE; INT32 regionsize = FindMasterRegion(); if (!regionsize) return FALSE; // create a render path to copy sections into Path RenderPath; if (!RenderPath.Initialise(regionsize,12)) return FALSE; // Go into a RenderOnTop loop RenderRegion* pRegion = DocView::RenderOnTop(NULL, pRetroSpread, ClippedEOR); while (pRegion!=NULL) { // render each region by copying the section into our temp path // and rendering each of these. INT32 reg = numregions; while (reg>0) { reg--; INT32 start = RetroEorRegion[reg].first; if (!RetroEorRegion[reg].Finclusive) RetroEorPath.FindNextEndPoint(&start); INT32 end = RetroEorRegion[reg].last; if (!RetroEorRegion[reg].Linclusive) RetroEorPath.FindPrev(&end); INT32 length = end - start + 1; if (length>0) { // make a path to render if (RetroEorPath.MakePathFromSection(start, length, &RenderPath)) { RenderPath.RenderPathBlobs(pRegion); // set a colour during our render // pRegion->SetLineColour(COLOUR_XOREDIT); // pRegion->SetLineWidth(0); // pRegion->DrawPath(&RenderPath); RenderPath.ClearPath(FALSE); } } } pRegion = DocView::GetNextOnTop(NULL); } return TRUE; } /******************************************************************************************** INT32 RetroSmooth::FindMasterRegion() Author: Mike_Kenny (Xara Group Ltd) Created: 11/11/94 Inputs: Returns: The largest region Purpose: Scans the region list and finds the region with the greatest number of coordinates in it, returning that number ********************************************************************************************/ INT32 RetroSmooth::FindMasterRegion() { INT32 numcoords = RetroEorRegion.NumItems(); INT32 largest=0; for (INT32 i=0; ilargest) largest = length; } return largest; } /******************************************************************************************** BOOL RetroSmooth::MatchRegions(NodePath* pNodePath) Author: Mike_Kenny (Xara Group Ltd) Created: 11/11/94 Inputs: pNodePath - a nodepath object pointer Returns: TRUE - if the selected regions within pNodePath match those of our cached path. FALSE - if no match is found. Purpose: Scan through the path data in pNodePath and match selected regions with our cached version. If the regions match, then we may be able to use our previously cached none smooth path as the source for a new retro active smooth, rather than pNodePath. ********************************************************************************************/ BOOL RetroSmooth::MatchRegions(NodePath* pNodePath) { // ok, scan through all sel regions in this cached // object and match against those in the searchpath // if they all match, then return true. return TRUE; } /******************************************************************************************** > BOOL RetroSmooth::RecordPathRegions(Path* pPath) Author: Mike_Kenny (Xara Group Ltd) Created: 11/11/94 Inputs: pPath = Returns: Purpose: Record the selected regions of the path on the original regions list or the smoothed regions ********************************************************************************************/ BOOL RetroSmooth::RecordPathRegions(Path* pPath) { INT32 index = 0; INT32 ncoords = pPath->GetNumCoords(); // The reasons are deeply rooted why I'm about to use a doccoord class // to hold two longs which represent the start and end of a region. // Its because I cannot use 'templates' at the moment to define an // associative array, so the dynamic array class I'm about to used is // typed to hold doccoords instead of generic 'types'. INT32 start; INT32 end; while ( (indexFindSelectionLimits(index,&start,&end) ) { Set PtSet; PtSet.first = start; PtSet.last = end; pPath->ExpandRange(&PtSet.first,&PtSet.last,1); PtSet.Finclusive = !(PtSet.firstend); if (!RetroEorRegion.AddItem(PtSet)) { index = -1; break; } index=PtSet.last; if (!pPath->FindNextEndPoint(&index)) index=ncoords; } // make sure we've tidied up incase of failure if (index==-1) { RetroEorRegion.Empty(); return FALSE; } return TRUE; } /******************************************************************************************** void RetroSmooth::AlterRegion(INT32 index, INT32 newstart, INT32 newend) Author: Mike_Kenny (Xara Group Ltd) Created: 16/11/94 Purpose: Alters a specific regions start,end indexes. ********************************************************************************************/ void RetroSmooth::AlterRegion(INT32 index, INT32 newstart, INT32 newend) { Set set; set.first = newstart; set.last = newend; set.Finclusive = TRUE; set.Linclusive = TRUE; RetroEorRegion[index] = set; } /******************************************************************************************** > OpRetroSmooth::OpRetroSmooth() Author: Mike_Kenny (Xara Group Ltd) Created: 18/11/94 Purpose: OpRetroSmooth() constructor SeeAlso: - ********************************************************************************************/ OpRetroSmooth::OpRetroSmooth() { // Dummy constructor } /******************************************************************************************** > BOOL OpRetroSmooth::Init() Author: Mike_Kenny (Xara Group Ltd) Created: 26/7/94 Inputs: - Outputs: - Returns: TRUE if the operation could be successfully initialised FALSE if no more memory could be allocated Purpose: OpRetroSmooth initialiser method Errors: ERROR will be called if there was insufficient memory to allocate the operation. SeeAlso: - ********************************************************************************************/ BOOL OpRetroSmooth::Init() { return (RegisterOpDescriptor(0, // tool ID _R(IDS_RETROSMOOTHOP), // string resource ID CC_RUNTIME_CLASS(OpRetroSmooth), // runtime class for Op OPTOKEN_RETROSMOOTH, // Ptr to token string OpRetroSmooth::GetState, // GetState function 0, // help ID _R(IDBBL_RETROSMOOTHOP), // bubble help ID 0 // resource ID =0 )); } /******************************************************************************************** > OpState OpRetroSmooth::GetState(String_256*, OpDescriptor*) Author: Mike_Kenny (Xara Group Ltd) Created: 20/10/94 Inputs: - Outputs: - Returns: The state of the OpRetroSmooth Purpose: For finding the OpRetroSmooth state. Errors: - SeeAlso: - ********************************************************************************************/ OpState OpRetroSmooth::GetState(String_256* UIDescription, OpDescriptor*) { OpState OpSt; // Always enabled at the moment. return OpSt; } /******************************************************************************************** > void OpRetroSmooth::GetOpName(String_256* OpName) Author: Mike_Kenny (Xara Group Ltd) Created: 20/10/94 Inputs: - Outputs: The undo string for the operation Returns: Purpose: The GetOpName fn is overridden so that we return back a description appropriate to the type of attribute that the operation applies. ********************************************************************************************/ void OpRetroSmooth::GetOpName(String_256* OpName) { *OpName = String_256(_R(IDS_UNDO_RETROSMOOTHOP)); } /******************************************************************************************** > void OpRetroSmooth::DoRetroSmooth(NodePath* pThisNode, double smoothacc) Author: Mike_Kenny (Xara Group Ltd) Created: 29/9/94 Inputs: pThisNode = pointer to a path node in the tree to smooth smoothacc = accuracy value to smooth to Outputs: - Returns: - Purpose: This operation will smooth a node path given an accuracy. It will create a new node in the tree after the node specified and hide node passed if all is succesfull. ********************************************************************************************/ void OpRetroSmooth::DoRetroSmooth(NodePath* pThisNode, Path* pPath, double smoothacc) { BOOL ok; // remember the selection before the operation if (!DoStartSelOp(FALSE,FALSE)) { FailAndExecute(); End(); return; } // also check for blends needing to be re-initialised NodeBlender * pBlender = NULL; NodeBlend * pBlend = NULL; NodeBlend* pOrigBlend = NULL; pBlender = (NodeBlender *)pThisNode->FindNext(CC_RUNTIME_CLASS(NodeBlender)); if (!pBlender) pBlender = (NodeBlender *)pThisNode->FindPrevious(CC_RUNTIME_CLASS(NodeBlender)); if (pBlender) { pBlend = (NodeBlend *)pBlender->FindParent(); //pBlend->forOpRetroSmooth = FALSE; pOrigBlend = pBlend; } // Invalidate the region CALL_WITH_FAIL( DoInvalidateNodeRegion(pThisNode, TRUE, FALSE), this, ok); if (!ok) { FailAndExecute(); End(); return; } ObjChangeFlags cFlags; cFlags.ReplaceNode = TRUE; ObjChangeParam ObjChange(OBJCHANGE_STARTING,cFlags,pThisNode,this); if (pThisNode->AllowOp(&ObjChange)) { // create the new node path CALL_WITH_FAIL( DoMakeNodeFromPath(pThisNode, pPath, NEXT, TRUE), this, ok); if (!ok) { FailAndExecute(); End(); return; } // see if were part of a blend .... pBlender = (NodeBlender *)pThisNode->FindNext(CC_RUNTIME_CLASS(NodeBlender)); if (pBlender) { pBlend = (NodeBlend *)pBlender->FindParent(); if (InitBlendAction::InitOnBlender(this, GetUndoActionList(), pBlender, TRUE, TRUE) != AC_OK) { ERROR2RAW("Couldn't Initialise blend action"); } // re-insert this action at the head of the list pBlender->Reinit((NodeRenderableInk*) pThisNode->FindNext (), pBlender->GetNodeEnd(), FALSE); // Invalidate the region CALL_WITH_FAIL( DoInvalidateNodeRegion(pBlender, TRUE, FALSE), this, ok); if (!ok) { FailAndExecute(); End(); return; } } else { pBlender = (NodeBlender *)pThisNode->FindPrevious(CC_RUNTIME_CLASS(NodeBlender)); if (pBlender) { pBlend = (NodeBlend *)pBlender->FindParent(); if (InitBlendAction::InitOnBlender(this, GetUndoActionList(), pBlender, TRUE, TRUE) != AC_OK) { ERROR2RAW("Couldn't Initialise blend action"); } // re-insert this action at the head of the list pBlender->Reinit(pBlender->GetNodeStart(), (NodeRenderableInk*) pThisNode->FindNext (), FALSE); // Invalidate the region CALL_WITH_FAIL( DoInvalidateNodeRegion(pBlender, TRUE, FALSE), this, ok); if (!ok) { FailAndExecute(); End(); return; } } } if (pBlender) { NodeBlend* ptrBlend = (NodeBlend*) pBlender->FindParent (); ERROR3IF (!IS_A (ptrBlend, NodeBlend), "NodeBlend is not a NodeBlend!"); BOOL done = FALSE; NodeBlender* ptrNode = ptrBlend->FindFirstBlender (); while (!done) { if (ptrNode != pBlender) { if (ptrNode->GetNodeStart () == pThisNode) { if (InitBlendAction::InitOnBlender(this, GetUndoActionList(), ptrNode, TRUE, TRUE) != AC_OK) { ERROR2RAW("Couldn't Initialise blend action"); } ptrNode->Reinit((NodeRenderableInk*) pThisNode->FindNext (), NULL, FALSE); } if (ptrNode->GetNodeEnd () == pThisNode) { if (InitBlendAction::InitOnBlender(this, GetUndoActionList(), ptrNode, TRUE, TRUE) != AC_OK) { ERROR2RAW("Couldn't Initialise blend action"); } ptrNode->Reinit(NULL, (NodeRenderableInk*) pThisNode->FindNext (), FALSE); } // Invalidate the region CALL_WITH_FAIL( DoInvalidateNodeRegion(ptrNode, TRUE, FALSE), this, ok); if (!ok) { FailAndExecute(); End(); return; } } ptrNode = ptrBlend->FindNextBlender (ptrNode); if (!ptrNode) { done = TRUE; } } } ObjChange.Define(OBJCHANGE_FINISHED,cFlags,pThisNode,this); if (!UpdateChangedNodes(&ObjChange)) { FailAndExecute(); End(); return; } // Now we've formed a smoothed path, let's hide the original CALL_WITH_FAIL( DoHideNode(pThisNode,TRUE), this, ok); if (!ok) { FailAndExecute(); End(); return; } } //pOrigBlend->forOpRetroSmooth = FALSE; // DoSmoothNodePath(pThisNode,smoothacc),this,ok); End(); } /******************************************************************************************** > DynArray::DynArray() Author: Mike_Kenny (Xara Group Ltd) Created: 16/11/94 Purpose: DynArray constructor ********************************************************************************************/ DynArray::DynArray() { UsedSlots = 0; UnusedSlots = 0; SlotInitSize = 0; SlotAllocSize = 0; BlockHandle = BAD_MHANDLE; } /******************************************************************************************** > DynArray::~DynArray() Author: Mike_Kenny (Xara Group Ltd) Created: 16/11/94 Purpose: Destructor - releases the memory that the template class has been using to store items in. ********************************************************************************************/ DynArray::~DynArray() { if (BlockHandle != BAD_MHANDLE) ReleaseBlock(BlockHandle); } /******************************************************************************************** > BOOL DynArray::Initialise(INT32 inititems, INT32 newitems) Author: Mike_Kenny (Xara Group Ltd) Created: 16/11/94 Inputs: inititems = The number of items of the list can hold initially newitems = The number of items to allocate for each time we run out of space Returns: TRUE if we got all the memory we needed, FALSE if not Purpose: Allocates memory for a list to hold a particular type of object Errors: Can fail if it runs out of memory. The function will return FALSE ********************************************************************************************/ BOOL DynArray::Initialise(INT32 inititems, INT32 newitems) { ERROR3IF((newitems & 3),"DynArray::Initialise called with none quad byte 'new' multiple"); if (inititems<12) inititems=12; if (newitems<12) newitems=12; if (BlockHandle == BAD_MHANDLE) BlockHandle = ClaimBlock(sizeof(ITEM_TYPE)*inititems); else { // some ones initialising an already existant buffer // so lets just resize it to fit. INT32 SizeDiff = (UsedSlots+UnusedSlots) - inititems; BOOL ok; if (SizeDiff>0) ok = IncreaseBlock(BlockHandle,sizeof(ITEM_TYPE)*SizeDiff); else { SizeDiff = -SizeDiff; ok = DecreaseBlock(BlockHandle,sizeof(ITEM_TYPE)*SizeDiff); } if (!ok) return FALSE; } if (BlockHandle==BAD_MHANDLE) return FALSE; UsedSlots = 0; UnusedSlots = inititems; SlotInitSize = inititems; SlotAllocSize = newitems; return TRUE; } /******************************************************************************************** > TYPE_ITEM* DynArray::GetFirstItem() Author: Mike_Kenny (Xara Group Ltd) Created: 16/11/94 Inputs: - Returns: a pointer to the start of this dynamic array. Purpose: Get a pointer to the start of this array. The ptr can then be used in conjunction with the [] operator to access the stored values with array operations. ********************************************************************************************/ ITEM_TYPE* DynArray::GetFirstItem() { return ((ITEM_TYPE*)DescribeHandle(BlockHandle)); } /******************************************************************************************** > BOOL DynArray::MakeSpaceInList(const INT32 NumSlots) Author: Mike_Kenny (Xara Group Ltd) Created: 16/11/94 Inputs: NumSlots = the number of slots to ensure free Returns: TRUE if NumSlots are now freely available FALSE if unable to ensure NumSlots of free space. Purpose: Ensure that there are NumSlots free within this dynamic array. ********************************************************************************************/ BOOL DynArray::MakeSpaceInList(const INT32 NumSlots) { if (UnusedSlots BOOL DynArray::AddItem(ITEM_TYPE& item) Author: Mike_Kenny (Xara Group Ltd) Created: 16/11/94 Inputs: a reference to an item to store in the array Returns: TRUE if the item was successfully stored FALSE if the item could not be stored due to lack of memory Purpose: Store the array item by increasing the size of the array by one item and placing the item at this position. ********************************************************************************************/ BOOL DynArray::AddItem(ITEM_TYPE& item) { if (!MakeSpaceInList(1)) return FALSE; ITEM_TYPE* List = (ITEM_TYPE*) DescribeHandle(BlockHandle); List[UsedSlots] = item; UsedSlots++; UnusedSlots--; return TRUE; } /******************************************************************************************** > void DynArray::Empty(BOOL compress = TRUE) Author: Mike_Kenny (Xara Group Ltd) Created: 16/11/94 Inputs: - Returns: - Purpose: Remove all stored items from this array. All array accesses via the [] operator will now be faulted. Use AddItem() to begin reconstructing values in the array. ********************************************************************************************/ void DynArray::Empty(BOOL compress) { if (UsedSlots>0) { // reset the path definitions UnusedSlots+=UsedSlots; UsedSlots=0; // make sure we compress memory if necessary if (compress) Compact(); // ignore any error coming from here } } /******************************************************************************************** > void DynArray:::DeleteItems(INT32 firstitem, INT32 numitems) Author: Mike_Kenny (Xara Group Ltd) Created: 16/11/94 Inputs: first item = an index of the first item to start the deletion at (0..n-1) num items = the number of items to delete (0<=n<=NumItems) Returns: - Purpose: Delete a set of items from this dynamic array. Example: 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 L = [ 9,8,7,6,5,4,3,2,1,0 ] DeleteItems(3, 4) L = [ 9,8,7,2,1,0 ] ********************************************************************************************/ void DynArray::DeleteItems(INT32 firstitem, INT32 numitems) { ENSURE(firstitem=0, "Specified position is off begining of array in DynArray::DeleteItems"); if (numitems<=0) return; ITEM_TYPE* List = (ITEM_TYPE*) DescribeHandle(BlockHandle); INT32 Moving = UsedSlots-(firstitem+numitems); if (Moving>0) memmove((void*)(&List[firstitem]), (void*)(&List[firstitem+numitems]), Moving*sizeof(ITEM_TYPE)); UsedSlots -= numitems; UnusedSlots += numitems; Compact(); } /******************************************************************************************** > void DynArray::Compact() Author: Mike_Kenny (Xara Group Ltd) Created: 16/11/94 Inputs: - Returns: - Purpose: Checks for excessive amounts of unused space left at the end of a dynamic array, (possibly left from a Empty(FALSE) call) and removes it. The quantity of space removed is always an exact multiple of newitems (See Initialise()) ********************************************************************************************/ void DynArray::Compact() { INT32 unused = UnusedSlots / SlotAllocSize; unused-=1; if (unused>0) { if (DecreaseBlock(BlockHandle, sizeof(ITEM_TYPE)*(unused*SlotAllocSize))) UnusedSlots-=unused*SlotAllocSize; } } /******************************************************************************************** > ITEM_TYPE& DynArray::operator[](const INT32 i) Author: Mike_Kenny (Xara Group Ltd) Created: 16/11/94 Inputs: i = the index of an item in this dynamic array Returns: A reference to the indexed item in this dynamic array Purpose: Overload the array operator to allow array access functionality for this dynamic set of items. This functions provides both item reteival and item assignment for example MyArray[i] = item, item = MyArray[i] Errors: Access or assignment will be faulted if the index i is outside the range of items stored in the array. ********************************************************************************************/ ITEM_TYPE& DynArray::operator[](const INT32 i) { ENSURE(i>=0 && i BOOL DynArray::CloneFrom(ITEM_TYPE* SourceArray) Author: Mike_Kenny (Xara Group Ltd) Created: 16/11/94 Inputs: SourceArray = pointer to a source dynamic array Returns TRUE if successful FALSE if unable in increase the size of this dynamic array to cope with all data in the supplied array. Purpose: Replace the data within this dynamic array with that of the other array supplied ********************************************************************************************/ BOOL DynArray::CloneFrom(DynArray* SourceArray) { // Make space to copy all the data from the other path into this one INT32 SlotsToCopy = SourceArray->UsedSlots; INT32 SlotsToAdd = SlotsToCopy-UsedSlots; if (SlotsToAdd>0) { if (!MakeSpaceInList(SlotsToAdd)) return FALSE; } // Get the arrays ITEM_TYPE* pSource = (ITEM_TYPE*) DescribeHandle(SourceArray->BlockHandle); ITEM_TYPE* pDestin = (ITEM_TYPE*) DescribeHandle(BlockHandle); memmove((void*)(pDestin), (void*)(pSource), SlotsToCopy*sizeof(ITEM_TYPE)); // Now update the array variables UsedSlots += SlotsToAdd; UnusedSlots -= SlotsToAdd; return TRUE; }