// $Id: group.cpp 1688 2006-08-10 12:05:20Z gerry $ /* @@tag:xara-cn@@ DO NOT MODIFY THIS LINE ================================XARAHEADERSTART=========================== Xara LX, a vector drawing and manipulation program. Copyright (C) 1993-2006 Xara Group Ltd. Copyright on certain contributions may be held in joint with their respective authors. See AUTHORS file for details. LICENSE TO USE AND MODIFY SOFTWARE ---------------------------------- This file is part of Xara LX. Xara LX is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. Xara LX and its component source files are distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Xara LX (see the file GPL in the root directory of the distribution); if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ADDITIONAL RIGHTS ----------------- Conditional upon your continuing compliance with the GNU General Public License described above, Xara Group Ltd grants to you certain additional rights. The additional rights are to use, modify, and distribute the software together with the wxWidgets library, the wxXtra library, and the "CDraw" library and any other such library that any version of Xara LX relased by Xara Group Ltd requires in order to compile and execute, including the static linking of that library to XaraLX. In the case of the "CDraw" library, you may satisfy obligation under the GNU General Public License to provide source code by providing a binary copy of the library concerned and a copy of the license accompanying it. Nothing in this section restricts any of the rights you have under the GNU General Public License. SCOPE OF LICENSE ---------------- This license applies to this program (XaraLX) and its constituent source files only, and does not necessarily apply to other Xara products which may in part share the same code base, and are subject to their own licensing terms. This license does not apply to files in the wxXtra directory, which are built into a separate library, and are subject to the wxWindows license contained within that directory in the file "WXXTRA-LICENSE". This license does not apply to the binary libraries (if any) within the "libs" directory, which are subject to a separate license contained within that directory in the file "LIBS-LICENSE". ARRANGEMENTS FOR CONTRIBUTION OF MODIFICATIONS ---------------------------------------------- Subject to the terms of the GNU Public License (see above), you are free to do whatever you like with your modifications. However, you may (at your option) wish contribute them to Xara's source tree. You can find details of how to do this at: http://www.xaraxtreme.org/developers/ Prior to contributing your modifications, you will need to complete our contributor agreement. This can be found at: http://www.xaraxtreme.org/developers/contribute/ Please note that Xara will not accept modifications which modify any of the text between the start and end of this header (marked XARAHEADERSTART and XARAHEADEREND). MARKS ----- Xara, Xara LX, Xara X, Xara X/Xtreme, Xara Xtreme, the Xtreme and Xara designs are registered or unregistered trademarks, design-marks, and/or service marks of Xara Group Ltd. All rights in these marks are reserved. Xara Group Ltd, Gaddesden Place, Hemel Hempstead, HP2 6EX, UK. http://www.xara.com/ =================================XARAHEADEREND============================ */ /* //*/ #include "camtypes.h" #include "saveeps.h" #include "ccdc.h" //#include "docview.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "osrndrgn.h" //#include "simon.h" //#include "group.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "mario.h" //#include "opscale.h" //#include "becomea.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "objchge.h" #include "blobs.h" //#include "app.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "cxfrec.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "cxfrech.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "cxftags.h" //#include "camfiltr.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "rechgrp.h" #include "cmxrendr.h" //#include "cmxexdc.h" #include "extender.h" #include "nodeblnd.h" //#include "document.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "nodebldr.h" //#include "capturemanager.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "phil.h" // for _R(IDB_CACHE_MARK) #include "lineattr.h" //#include "spread.h" - in camtypes.h [AUTOMATICALLY REMOVED] //#include "dibutil.h" - in camtypes.h [AUTOMATICALLY REMOVED] #include "nodeliveeffect.h" #include "scanrr.h" #include "pmaskrgn.h" #include "blndhelp.h" //#include "mrhbits.h" #include "transop.h" #include "opscale.h" CC_IMPLEMENT_DYNAMIC(NodeGroup, NodeCompound) CC_IMPLEMENT_DYNAMIC(NodeListItemWithDocPtr, NodeListItem) CC_IMPLEMENT_DYNAMIC(GroupRecordHandler, CamelotRecordHandler) // Give this file in memory dumps // Declare smart memory handling in Debug builds #define new CAM_DEBUG_NEW /********************************************************************************************* > NodeGroup::NodeGroup() Author: Simon_Maneggio (Xara Group Ltd) Created: 13/5/93 Inputs: - Outputs: Returns: - Purpose: This constructor creates a NodeGroup linked to no other, with all status flags false and an uninitialised bounding rectangle. Errors: **********************************************************************************************/ NodeGroup::NodeGroup(): NodeCompound() { blenderNode = NULL; } /*********************************************************************************************** > void NodeGroup::NodeGroup ( Node* ContextNode, AttachNodeDirection Direction, BOOL Locked = FALSE, BOOL Mangled = FALSE, BOOL Marked = FALSE, BOOL Selected = FALSE, ) Author: Simon_Maneggio (Xara Group Ltd) Created: 19/10/93 Inputs: ContextNode: Pointer to a node which this node is to be attached to. Direction: Specifies the direction in which this node is to be attached to the ContextNode. The values this variable can take are as follows: PREV : Attach node as a previous sibling of the context node NEXT : Attach node as a next sibling of the context node FIRSTCHILD: Attach node as the first child of the context node LASTCHILD : Attach node as a last child of the context node The remaining inputs specify the status of the node: Locked: Is node locked ? Mangled: Is node mangled ? Marked: Is node marked ? Selected: Is node selected ? Outputs: - Returns: - Purpose: This method initialises the node and links it to ContextNode in the direction specified by Direction. All necessary tree links are updated. Errors: An assertion error will occur if ContextNode is NULL ***********************************************************************************************/ NodeGroup::NodeGroup(Node* ContextNode, AttachNodeDirection Direction, BOOL Locked, BOOL Mangled, BOOL Marked, BOOL Selected ):NodeCompound(ContextNode, Direction, Locked, Mangled, Marked, Selected) { blenderNode = NULL; } void NodeGroup::PreExportRender(RenderRegion* pRegion) { #ifdef DO_EXPORT if (pRegion->IsKindOf(CC_RUNTIME_CLASS(EPSRenderRegion))) { // Output "start group" token EPSExportDC *pDC = (EPSExportDC *) pRegion->GetRenderDC(); pDC->OutputToken(_T("u")); pDC->OutputNewLine(); } PORTNOTE("cmx", "Removed use of CMXRenderRegion") #ifndef EXCLUDE_FROM_XARALX else if(pRegion->IsKindOf(CC_RUNTIME_CLASS(CMXRenderRegion))) { // mark start of a group... CMXExportDC *pDC = (CMXExportDC *) pRegion->GetRenderDC(); DocRect BBox = GetBoundingRect(); pDC->StartGroup(&BBox); } #endif #endif } BOOL NodeGroup::ExportRender(RenderRegion* pRegion) { #ifdef DO_EXPORT if (pRegion->IsKindOf(CC_RUNTIME_CLASS(EPSRenderRegion))) { // Output "end group" token EPSExportDC *pDC = (EPSExportDC *) pRegion->GetRenderDC(); pDC->OutputToken(_T("U")); pDC->OutputNewLine(); // Tell caller we rendered ourselves ok return TRUE; } PORTNOTE("cmx", "Removed use of CMXRenderRegion") #ifndef EXCLUDE_FROM_XARALX else if(pRegion->IsKindOf(CC_RUNTIME_CLASS(CMXRenderRegion))) { // mark start of a group... CMXExportDC *pDC = (CMXExportDC *) pRegion->GetRenderDC(); pDC->EndGroup(); return TRUE; } #endif #endif // Render thid node in the normal way return FALSE; } /********************************************************************************************* > virtual BOOL NodeGroup::IsCompound(void) const Author: Simon_Maneggio (Xara Group Ltd) Created: 19/4/93 Inputs: - Outputs: - Returns: TRUE if the node is a compound object, will return TRUE Purpose: For determining quickly if the node is a compound object Errors: **********************************************************************************************/ // Phil, 22/10/2004. Implemented in Group.h where it will be automatically inlined //BOOL NodeGroup::IsCompound() const //{ // return TRUE; // cos it is //} /******************************************************************************************** > void NodeGroup::RenderAfterSubtree(RenderRegion* pRender) Author: Phil_Martin (Xara Group Ltd) Created: 17/06/2004 Inputs: pRender - The region to render into Purpose: Capture the group as a cached bitmap ********************************************************************************************/ void NodeGroup::RenderAfterSubtree(RenderRegion* pRegion) { // Deal with group transparency capture // Call Helper function to run all my cacheing functionality for me... // If we have Effect attrs applied then the user wants "tight group transparency" if (HasEffectAttrs()) CaptureTight(pRegion); else CaptureCached(pRegion); } /******************************************************************************************** > SubtreeRenderState NodeGroup::RenderSubtree(RenderRegion* pRender, Node** ppNextNode, BOOL bClip) Author: Phil_Martin (Xara Group Ltd) Created: 29/06/2004 Inputs: pRender - The region render into (or use as context for decisions about subtree) ppNextNode - Address of node pointer for next node to render or run to after this bClip - flag indicating whether to clip or not Purpose: Do clever stuff on the way into a subtree, possibly modifying rendering behaviour. ********************************************************************************************/ SubtreeRenderState NodeGroup::RenderSubtree(RenderRegion* pRender, Node** ppNextNode, BOOL bClip) { if (pRender == NULL) // If no render region supplied, assume we need to be rendered return SUBTREE_ROOTANDCHILDREN; // Go find out about my bounding rectangle DocRect BoundingRect = GetBoundingRect(); DocRect ClipRect = pRender->GetClipRect(); if (bClip && !ClipRect.IsIntersectedWith(BoundingRect)) // If not within the clipping rect then return SUBTREE_NORENDER; // Don't render us or our children // Ask Helper function to set up cacheing for me... // If we have Effect attrs applied then the user wants "tight group transparency" if (HasEffectAttrs()) { if (RenderTight(pRender)) return SUBTREE_NORENDER; } else { if (RenderCached(pRender)) // If we can find a cached bitmap for this node and render it { //TRACEUSER("Phil", _T("Skipping cached group %x %s\n"), this, GetRuntimeClass()->m_lpszClassName); return SUBTREE_NORENDER; // Then tell the renderer to move on without doing any more... } //TRACEUSER("Phil", _T("Re-rendering cached group %x %s\n"), this, GetRuntimeClass()->m_lpszClassName); } return SUBTREE_ROOTANDCHILDREN; // Else we must render ourselves and our children } /******************************************************************************************** > virtual BOOL NodeGroup::RenderTight(RenderRegion* pRender) Author: Phil_Martin (Xara Group Ltd) Created: 11/01/2005 Returns: TRUE if the node managed to render a cached version of itself then pNextNode contains the pointer to the node to continue rendering from FALSE if the node did not render a cached version of itself and thus normal rendering is required Purpose: Protected Helper function Allow this node to render itself using cached data if it has it and for that cached data to represent an arbitrary section of the document (usually a subtree but can be more) For this implementation we assume that our cached info will represent just this subtree. Also start to capture the rendered info into a cached bitmap if possible SeeAlso: NodeRenderableBounded::CaptureCached ********************************************************************************************/ BOOL NodeGroup::RenderTight(RenderRegion* pRender) { // If the caller wants his bitmap to respond to Effect Attributes // Then we have to behave quite differently than a normal cache/capture sequence // We must grab the bitmap regardless of the cache-enabling flag // We must grab at a controlled resolution // We must grab the whole thing // We must grab at 32BPP RGBT // // If the rendering quality is outline - no point showing cached bitmaps... if (pRender->RRQuality.GetFillQuality() <= Quality::Solid) return FALSE; // If the rendering system is doing black and white only then we can't sensibly // produce a monochrome version of the cached bitmap... if (pRender->IsVeryMono() || pRender->IsHitDetect()) return FALSE; // Tight groups need to be rendered in the complex bitmap stages of printing if (pRender->IsKindOf(CC_RUNTIME_CLASS(ScanningRenderRegion))) { ScanningRenderRegion* pScanner = (ScanningRenderRegion*)pRender; pScanner->ComplexShapeFoundWrapper(); return TRUE; // Return TRUE so that rendering will NOT continue into the group normally } CBitmapCache* pBitmapCache = Camelot.GetBitmapCache(); if (pBitmapCache==NULL) return FALSE; // If this node is definitely not cached, skip the cache rendering stuff... DocRect cliprect = pRender->GetClipRect(); DocRect bounds = GetBoundingRect(); // Assure new bounds are recalced if they were invalid BOOL bRendered = FALSE; CBitmapCacheKey inky(this, GetTightGroupPixelWidth(pRender)); // No need to check BackmostChanged node here because we never capture // our background in a 24BPP bitmap we always capture at 32BPP using cfLOCKEDTRANSPARENT // Look for a cached bitmap at the appropriate pixel size... CCachedBitmap cbmp; BOOL bFound = pBitmapCache->Lookup(inky, cbmp); if (bFound) { // If cached bitmap covers the current clipped object rect, we can use it... DocRect clippedboundrect = cliprect.Intersection(bounds); if (cbmp.GetCachedRect().ContainsRect(clippedboundrect)) { //TRACEUSER("Phil", _T("Rendering cached bitmap for %x %s\n"), this, GetRuntimeClass()->GetClassName() ); bRendered = pRender->RenderBits(cbmp.pbmpInfo, cbmp.pbmpBits, &cbmp.coord0, 3, TRUE, this); } } if (bRendered) return bRendered; // If we couldn't find or render a cached bitmap then try to cache a new one // double ScaledPixelWidth = pRender->GetScaledPixelWidth(); // Work out how much of the object we propose to capture DocRect viewrect = cliprect; DocRect CaptureRect = bounds; if (!pRender->IsPrinting()) { View* pView = pRender->GetRenderView(); Spread* pSpread = pRender->GetRenderSpread(); if (pView && pSpread) { viewrect = pView->GetDocViewRect(pSpread); pSpread->DocCoordToSpreadCoord(&viewrect); if (!viewrect.IsValid() || !viewrect.ContainsRect(cliprect)) viewrect = cliprect; } } CaptureRect = CaptureRect.Intersection(viewrect); // Proposed capture area is obj x viewport // If our capture rect is invalid (no intersection) then don't do any rendering if (!CaptureRect.IsValid()) return(TRUE); if (!pRender->IsPrinting()) { // If we're nearly going to capture the whole object // (Compare area of bounding rect with area of view rect so that bitmap sizes don't get out of control) XLONG areaBounds = (XLONG)bounds.Width()*(XLONG)bounds.Height(); XLONG area2View = (XLONG)2*(XLONG)viewrect.Width()*(XLONG)viewrect.Height(); if (areaBounds < area2View) { // Then grab the whole thing // (Bounding Rect is small enough for us to capture the whole object ignoring clipping rect) CaptureRect = bounds; } } // Inflate by one pixel all round to allow for anti-aliasing effects at the edges CaptureRect.Inflate((INT32)GetTightGroupPixelWidth(pRender)); BOOL bFullCoverage = CaptureRect.ContainsRect(bounds); // Finally start the Capture @ 32BPP CaptureFlags caFlags = CaptureFlags(cfLOCKEDTRANSPARENT | cfUNCLIP | (bFullCoverage ? cfFULLCOVERAGE : cfNONE)); //TRACEUSER("Phil", _T("Starting TightGroup Capture %x, %f\n"), this, GetTightGroupPixelsPerInch(pRender)); pRender->StartCapture(this, CaptureRect, CAPTUREINFO(ctNESTABLE, caFlags), TRUE, FALSE, GetTightGroupPixelsPerInch(pRender)); return bRendered; } /******************************************************************************************** > virtual BOOL NodeGroup::CaptureTight(RenderRegion* pRender) Author: Phil_Martin (Xara Group Ltd) Created: 11/01/2005 Returns: TRUE if captured data was cached Purpose: Protected Helper function Use the CaptureManager to capture the results of rendering, cache them and associate them with this Ink node SeeAlso: NodeRenderableBounded::RenderCached ********************************************************************************************/ BOOL NodeGroup::CaptureTight(RenderRegion* pRender) { // If the caller wants his bitmap to respond to Effect Attributes // Then we have to behave quite differently than a normal cache/capture sequence // We must grab the bitmap regardless of the cache-enabling flag // We must grab at a controlled resolution // We must grab the whole thing // We must grab at 32BPP RGBT Capture* pCapture = pRender->GetTopCapture(); if (pCapture==NULL) // If nothing was captured return FALSE; // Then do nothing CBitmapCache* pBitmapCache = Camelot.GetBitmapCache(); if (pBitmapCache==NULL) { return FALSE; } // ERROR3IF(IsDragged() || m_bCachedBitmapShared, "CaptureTight asked to capture when its dragged"); // ERROR3IF(IsDragged(), "CaptureTight asked to capture when its dragged"); // Only stop the capture if we started it // (means we only capture whole subtrees at the mo.) BOOL bCached = FALSE; if (pCapture->GetOwner()==this) { // End this capture: // Blit capture to screen // Retain bitmap because we will release it ourselves onyl if we fail to cache it LPBITMAPINFO lpInfo = NULL; LPBYTE lpBits = NULL; DocRect CaptureRect = GetBoundingRect(); // Set maximum size we allow BOOL bFullCoverage = pCapture->HasFullCoverage(); pRender->StopCapture(this, FALSE, FALSE, &lpInfo, &lpBits, &CaptureRect); // If the capture gave us back a bitmap, try to cache it if (lpInfo && lpBits && CaptureRect.IsValid()) { //TRACEUSER("Phil", _T("Ending TightGroup Capture SUCCEEDED %x, %f\n"), this, GetTightGroupPixelWidth(pRender)); // Cache the ORIGINAL bitmap as Option 0 // See also, SetOriginalBitmap double PixelWidth = GetTightGroupPixelWidth(pRender); CBitmapCacheKey inky(this, PixelWidth, 0); CCachedBitmap cbmp; cbmp.pbmpBits = lpBits; cbmp.pbmpInfo = lpInfo; cbmp.SetCachedRect(CaptureRect); cbmp.nPriority = CACHEPRIORITY_TEMPBITMAP_HIGH; cbmp.bFullCoverage = bFullCoverage; if ( cbmp.IsValid() && !pRender->IsKindOf(CC_RUNTIME_CLASS(PrintingMaskedRenderRegion)) ) { pBitmapCache->StoreBitmap(inky, cbmp); MayBeCached = TRUE; bCached = TRUE; } // Render the bitmap taking effect attrs into account... if (lpInfo && lpBits) pRender->RenderBits(lpInfo, lpBits, CaptureRect, TRUE, this); } // If we failed to cache the captured bitmap then release it if (lpInfo!=NULL && lpBits!=NULL && !bCached) { //TRACEUSER("Phil", _T("Ending TightGroup Capture FAILED %x, %f\n"), this, GetTightGroupPixelWidth(pRender)); FreeDIB(lpInfo, lpBits, NULL, FALSE); } } return bCached; } /******************************************************************************************** > virtual double NodeGroup::GetTightGroupPixelsPerInch(RenderRegion* pRender) Author: Phil_Martin (Xara Group Ltd) Created: 20/01/2005 Inputs: - Returns: pixel width for capturing Purpose: Get width of pixels for use in capturing this group as a tight group bitmap ********************************************************************************************/ double NodeGroup::GetTightGroupPixelsPerInch(RenderRegion* pRender) const { double dPPI = 0.0; if (NodeBitmapEffect::GroupPixelsPerInch==0) { // 0 means automatic // Automatic means get resolution in context // Context means either respond to a fixed resolution above us in the tree // or get resolution from current view double dViewPPI = 96.0; double dViewScaledPPI = 96.0; if (pRender==NULL) { View* pView = View::GetCurrent(); if (pView) dViewPPI = 72000.0 / pView->GetPixelWidth().MakeDouble(); dViewScaledPPI = 72000.0 / pView->GetScaledPixelWidth().MakeDouble(); } else { dViewPPI = pRender->GetPixelsPerInch(); dViewScaledPPI = 72000.0 / pRender->GetScaledPixelWidth(); } Node* pParent = this->FindParent(); while (pParent && !pParent->IsLayer()) { if (pParent->IsBitmapEffect()) { dPPI = ((NodeBitmapEffect*)pParent)->GetPixelsPerInchValue(); if (dPPI!=0) break; // If we found an effect at all then ensure we use default effect PPI dPPI = dViewPPI; } pParent = pParent->FindParent(); } // If we found no effects above us then try to operate at zoomed view res // for best results on screen. if (dPPI==0) { // See also OpEffectLock::DoEffectNodeOp // We should capture at at least 200dpi for printing // We should capture at a size that's at least as good as the current zoomed pixel size // (We rely on the capture being clipped to keep bitmap sizes under control at high zoom factors) // (We should reduce the resolution if the bitmap will be huge...) dPPI = dViewScaledPPI; } } // Try to limit bitmap size if dpi is getting large inside a wrapped // RenderRegion. // Without this restriction, tight groups inside shadows become a problem // because this function does not pick up the reduced resolution of the // offscreen render region used by the shadow to create its silhouette. if (pRender && pRender->IsWrappedRender() && dPPI>480) { dPPI = 480; } if (dPPI==0) dPPI = NodeBitmapEffect::GroupPixelsPerInch; if (dPPI==0) { // Shouldn't ever reach this clause but just in case... ERROR3("GetPixelsPerInch can't get sensible PPI so defaulting to 96"); dPPI = 96.0; } TRACEUSER("Phil", _T("GTGPPI %f\n"), dPPI); return dPPI; } /******************************************************************************************** > double NodeGroup::GetTightGroupPixelWidth(RenderRegion* pRender) const Author: Phil_Martin (Xara Group Ltd) Created: 20/01/2005 Inputs: - Returns: pixel width for capturing Purpose: Get width of pixels for use in capturing this group as a tight group bitmap ********************************************************************************************/ /*double NodeGroup::GetTightGroupPixelWidth(RenderRegion* pRender) const { return 72000.0/GetTightGroupPixelsPerInch(pRender); }*/ /*double NodeGroup::GetTightGroupPixelWidth(RenderRegion* pRender) const { // We should capture at at least 200dpi for printing // We should capture at a size that's at least as good as the current zoomed pixel size // (We rely on the capture being clipped to keep bitmap sizes under control at high zoom factors) // (We should reduce the resolution if the bitmap will be huge...) double pixwidth = 72000.0/200.0; double scaledwidth = 72000.0/200.0; View* pView = View::GetCurrent(); if (pView!=NULL) scaledwidth = pView->GetScaledPixelWidth().MakeDouble(); // If our proposed pixels are bigger than screen pixels we'll use screen pixel width instead if (pixwidth > scaledwidth) pixwidth = scaledwidth; return pixwidth; }*/ /******************************************************************************************** > void NodeGroup::IsValidEffectAttr(CCRuntimeClass* pAttrClass) const Author: Phil_Martin (Xara Group Ltd) Created: 18/01/2005 Inputs: Attribute class Returns: TRUE if the attr class is a valid effect attr Purpose: Determine whether this type of attribute can be an effect attribute On this node at this time. ********************************************************************************************/ /*BOOL NodeGroup::IsValidEffectAttr(CCRuntimeClass* pAttrClass) const { return (AttrFillGeometry::s_bGroupTransparency && (pAttr->IsATranspFill() || pAttr->IsKindOf(CC_RUNTIME_CLASS(AttrTranspChange)) || pAttr->IsKindOf(CC_RUNTIME_CLASS(AttrStrokeTranspChange) ) ); }*/ /******************************************************************************************** > void NodeGroup::IsValidEffectAttr(NodeAttribute* pAttr) const Author: Phil_Martin (Xara Group Ltd) Created: 18/01/2005 Inputs: Attribute Returns: TRUE if the attr is a valid effect attr Purpose: Determine whether this attribute instance can be an effect attribute On this node at this time. ********************************************************************************************/ BOOL NodeGroup::IsValidEffectAttr(NodeAttribute* pAttr) const { // return (AttrFillGeometry::s_bGroupTransparency && // If pref allows group transparency // (pAttr->IsATranspFill() || // And the attribute is a transparency type // pAttr->IsKindOf(CC_RUNTIME_CLASS(AttrTranspChange)) || // Return TRUE to apply the attr as an effect // pAttr->IsKindOf(CC_RUNTIME_CLASS(AttrStrokeTranspChange)) // ) // ); CCRuntimeClass* pAttrType = pAttr->GetAttributeType(); return (AttrFillGeometry::s_bGroupTransparency && // If pref allows group transparency (pAttr->IsATranspFill() || pAttrType == CC_RUNTIME_CLASS(AttrTranspFillGeometry) || //->IsKindOf(CC_RUNTIME_CLASS(AttrTranspChange)) || pAttrType == CC_RUNTIME_CLASS(AttrStrokeTransp) || //pAttr->IsKindOf(CC_RUNTIME_CLASS(AttrStrokeTranspChange)) || pAttrType == CC_RUNTIME_CLASS(AttrTranspFillMapping) ) ); } /*********************************************************************************************** > virtual void NodeGroup::Render(RenderRegion* pRRegion) Author: Phil_Martin (Xara Group Ltd) Created: 19/01/2005 Inputs: Pointer to a render region Purpose: Will render the group iff we have it cached - for hit detection reasons ***********************************************************************************************/ // NOTE: No need for a render function because this effect does not add any pixel data // to those rendered by its children - just let the children render themselves into // the hit-detection bitmap /* void NodeGroup::Render(RenderRegion* pRender) { // Tight groups need to be rendered in the complex bitmap stages of printing if (HasEffectAttrs() && pRender->IsKindOf(CC_RUNTIME_CLASS(ScanningRenderRegion))) { ScanningRenderRegion* pScanner = (ScanningRenderRegion*)pRender; pScanner->ComplexShapeFoundWrapper(); } }*/ /*void NodeGroup::Render(RenderRegion* pRender) { CBitmapCache* pBitmapCache = Camelot.GetBitmapCache(); if (pBitmapCache!=NULL && HasEffectAttrs() && pRender->IsHitDetect()) { CBitmapCacheKey inky(this, GetTightGroupPixelWidth(pRender)); CCachedBitmap cbmp; BOOL bFound = pBitmapCache->Lookup(inky, cbmp); if (bFound) pRender->RenderBits(cbmp.pbmpInfo, cbmp.pbmpBits, &cbmp.coord0, 3, TRUE, this); } }*/ /******************************************************************************************** > void NodeGroup::RenderObjectBlobs(RenderRegion* pRender) Author: Rik_Heywood (Xara Group Ltd) Created: 23/6/94 Inputs: pRender - The region to draw the blobs in Purpose: Renders the object blobs ********************************************************************************************/ void NodeGroup::RenderObjectBlobs(RenderRegion* pRegion) { NodeCompound::RenderObjectBlobs(pRegion); #if !defined(EXCLUDE_FROM_RALPH) // Find out about the groups bounding rect DocRect BoundingRect = GetBoundingRect(); // Find out where to draw the blobs DocCoord Low = BoundingRect.LowCorner(); DocCoord High = BoundingRect.HighCorner(); // Set the colours of the blobs pRegion->SetFillColour(COLOUR_UNSELECTEDBLOB); pRegion->SetLineColour(COLOUR_NONE); // Draw all the blobs pRegion->DrawBlob(Low, BT_UNSELECTED); pRegion->DrawBlob(High, BT_UNSELECTED); pRegion->DrawBlob(DocCoord(Low.x, High.y), BT_UNSELECTED); pRegion->DrawBlob(DocCoord(High.x, Low.y), BT_UNSELECTED); #endif } /******************************************************************************************** > void NodeGroup::RenderTinyBlobs(RenderRegion* pRender) Author: Rik_Heywood (Xara Group Ltd) Created: 23/6/94 Inputs: pRender - The region to draw the blobs in Purpose: Renders the tiny blobs for a group (A Single blob on the topmost child object in the group) ********************************************************************************************/ void NodeGroup::RenderTinyBlobs(RenderRegion* pRegion) { #if !defined(EXCLUDE_FROM_RALPH) if (NodeRenderableBounded::bShowCacheBlobs) { // To help see what the cacheing system is doing // If this node has a cached bitmap associated with it // Render a special blob to show that... CBitmapCache* pBitmapCache = Camelot.GetBitmapCache(); if (pBitmapCache && MayBeCached && !HasEffectAttrs()) { CBitmapCacheKey inky(this, pRegion->GetScaledPixelWidth()); //GetScaledPixelWidthDouble? CCachedBitmap cbmp; BOOL bFound = pBitmapCache->Lookup(inky, cbmp); if (bFound) { // Find out about the groups bounding rect DocRect BoundingRect = GetBoundingRect(); // Draw a blob if (cbmp.IsTransparent()) pRegion->DrawBitmapBlob(DocCoord(BoundingRect.hi.x - pRegion->GetScaledPixelWidth(), BoundingRect.lo.y - pRegion->GetScaledPixelWidth()), _R(IDB_CACHE_MARK_32)); else pRegion->DrawBitmapBlob(DocCoord(BoundingRect.hi.x - pRegion->GetScaledPixelWidth(), BoundingRect.lo.y - pRegion->GetScaledPixelWidth()), _R(IDB_CACHE_MARK_24)); } } } // get the topmost object in this group and tell it to render its tiny blobs. Node* pNode = FindLastChild(); while (pNode != NULL && !pNode->IsAnObject()) pNode = pNode->FindPrevious(); // empty groups are not allowed! if (pNode == NULL) { ERROR3("NodeGroup::RenderTinyBlobs; This group is empty! Shouldn't be!"); return; } else { ((NodeRenderableInk*)pNode)->RenderTinyBlobs(pRegion); } #endif } /******************************************************************************************** > void NodeGroup::Transform( TransformBase& Trans ) Author: Rik_Heywood (Xara Group Ltd) Created: 23/6/94 Inputs: Trans - A transformation object Purpose: Transforms the group object and all its children. ********************************************************************************************/ void NodeGroup::Transform( TransformBase& Trans ) { View* pView = View::GetCurrent(); if (GroupCanTransformCached(Trans) && pView) // if (GroupCanTransformCached() && pView) { // Note! View::GetScaledPixelWidth returns an unrounded fractional value where // RenderRegion::GetScaledPixelWidth return a rounded integer!!! // // Note! It would be better not to call HasEffectAttrs() here because it can be slow in large groups if (HasEffectAttrs()) TransformTight(Trans, GetTightGroupPixelsPerInch()); else TransformCached(Trans, (MILLIPOINT)(pView->GetScaledPixelWidth().MakeDouble()+0.5)); } else { // Our cached data can't be transformed so we must remove it because // it will now be out of date // We must do it directly because ReleaseCached does conditional // stuff based on IsDragged //TRACEUSER("Phil", _T("NodeGroup::Transform releasing cache\n")); CBitmapCache* pBitmapCache = Camelot.GetBitmapCache(); if (pBitmapCache!=NULL) { CBitmapCacheKey inky(this, 42); pBitmapCache->RemoveAllOwnedBitmaps(inky); MayBeCached = FALSE; Trans.bHaveTransformedCached = FALSE; Trans.bTransformYourChildren = TRUE; } } // Optimisation: // If we have transformed our cached data // And we're dragging // And we were holding cached data (sanity check) // (And /assuming/ that the cached data fully represents the child objects!!!) // // Then we can avoid transforming our children to save time // // This is safe because we know that the dragged objects will not remain permanently in the // document so it doesn't matter if they're not up to date... // BOOL bTransCache = Trans.bHaveTransformedCached; if (Trans.bTransformYourChildren) { // Transform all the children NodeRenderableInk::TransformChildren(Trans); } else { // We can get away with just transforming our effect attrs TransformEffectAttrs(Trans); Trans.bHaveTransformedChildren = FALSE; } Trans.bHaveTransformedCached = bTransCache; // If we have successfully transformed the cached data for this // contour then we can simply transform the bounds to suit... // (Some derived classes might not be able to compute bounds // if their children have not been transformed.) // (Bounds have to be correct for RenderCached to allow the cached // bitmap to be used during dragging.) // // We can only do this for translations at the moment // if (Trans.bHaveTransformedCached && Trans.IsTranslation()) { Trans.Transform(&BoundingRectangle.lo, 1); Trans.Transform(&BoundingRectangle.hi, 1); /* The following code doesn't work because it keeps on transforming the results of previous transformed bounding rects, iteratively getting bigger and bigger. It needs to be able to work on the original bounding rect all the time DocCoord temp[4]; temp[0] = BoundingRectangle.lo; temp[1] = BoundingRectangle.hi; temp[2] = DocCoord(BoundingRectangle.lox, BoundingRectangle.hiy); temp[3] = DocCoord(BoundingRectangle.hix, BoundingRectangle.loy); Trans.Transform(temp, 4); BoundingRectangle.lo = temp[0]; BoundingRectangle.hi = temp[0]; BoundingRectangle.IncludePoint(temp[1]); BoundingRectangle.IncludePoint(temp[2]); BoundingRectangle.IncludePoint(temp[3]); */ } else { // The groups bounding rect is no longer valid // ERROR3IF(!IS_A(this, NodeGroup) && !Trans.bHaveTransformedChildren, "Compound children not transformed but compound bounding rect depends on them\n"); InvalidateBoundingRect(); } // -------------------------------------------------------------------- // CGS: We need to check for a blend on a path here .... if (IS_A (this, NodeBlend)) { NodeBlend* ptrBlend = (NodeBlend*) this; NodeBlender* ptrBlender = (NodeBlender*) ptrBlend->FindFirstBlender (); if (ptrBlender) { ptrBlender->UpdateBlendStartAngles ((Trans2DMatrix&) Trans); BOOL done = FALSE; while (!done) { ptrBlender = ptrBlend->FindNextBlender (ptrBlender); if (!ptrBlender) { done = TRUE; } else { ptrBlender->UpdateBlendStartAngles ((Trans2DMatrix&) Trans); } } } } } /******************************************************************************************** > virtual void NodeGroup::TransformTight(TransformBase& Trans, double dTestPixelWidth) Author: Phil_Martin (Xara Group Ltd) Created: 22/10/2004 Inputs: Trans - Transformation dPixelWidth - PixelWidth to be tested specifically to control Trans.bHaveTransformedCached Returns: - Purpose: Transform all the cached bitmaps associated with this node SeeAlso: NodeRenderableInk::RenderCached, CaptureCached ********************************************************************************************/ void NodeGroup::TransformTight(TransformBase& Trans, double dTestPixelWidth) { CBitmapCache* pBitmapCache = Camelot.GetBitmapCache(); CBitmapCacheKey inky(this, 42); CCacheKeyMap::iterator pos = pBitmapCache->GetStartPosition(); CCachedBitmap abitmap = pBitmapCache->FindNextOwnedBitmap(pos, inky); MayBeCached = abitmap.IsValid(); // Update MayBeCached here because we can BOOL bTransformedTested = FALSE; while (abitmap.IsValid()) { abitmap.Transform(Trans); pBitmapCache->StoreBitmap(inky, abitmap); if (inky.GetPixelWidth() == dTestPixelWidth && abitmap.IsTransparent()) bTransformedTested = TRUE; abitmap = pBitmapCache->FindNextOwnedBitmap(pos, inky); } // We can only continue to transform cached things if all our bitmaps are transparent (32BPP) // And if we actually had some cached data to transform if (!bTransformedTested) { Trans.bHaveTransformedCached = FALSE; Trans.bTransformYourChildren = TRUE; } } /******************************************************************************************** > virtual DocRect NodeGroup::GetEorDragBoundingRect() Author: David_McClarnon (Xara Group Ltd) Created: 20/1/2000 Inputs: None Returns: The eor drag bounding rect for all of my children Purpose: Gets the eor drag bounding rect for all my children SeeAlso: - ********************************************************************************************/ DocRect NodeGroup::GetEorDragBoundingRect() { Node * pChild = FindFirstChild(); DocRect dr(0,0,0,0); while (pChild) { if (pChild->IsBounded()) { dr = dr.Union(((NodeRenderableBounded *)pChild)->GetEorDragBoundingRect()); } pChild = pChild->FindNext(); } return dr; } /******************************************************************************************** > DocRect NodeGroup::GetBoundingRect(BOOL DontUseAttrs=FALSE, BOOL HitTest=FALSE) Author: Rik_Heywood (Xara Group Ltd) Created: 23/6/94 Inputs: DontUseAttrs - TRUE if the attrs associated with the node should be ignored. defaults to FALSE HitTest - TRUE if being called during HitTest Returns: The Groups bounding rect Purpose: If the groups bounding rect is known then it is returned. If not, it is re-calculated. ********************************************************************************************/ DocRect NodeGroup::GetBoundingRect(BOOL DontUseAttrs, BOOL HitTest) { if (!IsBoundingRectValid || DontUseAttrs) { // Optimisation: // If we're holding cached data that is a full representation of our children // And we're being dragged // And the bounding box should include attrs (the cached data includes attrs) // // Then we can avoid calling our children recursively to get their bounding // rectangles because the info is all contained in our cached data's bounding // rectangle. // if (MayBeCached && IsDragged() && !DontUseAttrs) { CBitmapCache* pBitmapCache = Camelot.GetBitmapCache(); CCachedBitmap cbmp; BOOL bFound = FALSE; if (HasEffectAttrs()) { CBitmapCacheKey inky(this, GetTightGroupPixelWidth()); bFound = pBitmapCache->Lookup(inky, cbmp); bFound = bFound && cbmp.bFullCoverage; } else { View* pCurrentView = View::GetCurrent(); if (pCurrentView) { // Note! View::GetScaledPixelWidth returns an unrounded fractional value where // RenderRegion::GetScaledPixelWidth return a rounded integer!!! CBitmapCacheKey inky(this, (MILLIPOINT)(pCurrentView->GetScaledPixelWidth().MakeDouble()+0.5)); bFound = pBitmapCache->Lookup(inky, cbmp); bFound = bFound && cbmp.bFullCoverage; } } if (bFound) { BoundingRectangle = cbmp.GetCachedRect(); IsBoundingRectValid = TRUE; return BoundingRectangle; } } // Find the groups first child DocRect NewRect(0,0,0,0); Node* pNode = FindFirstChild(); while (pNode!=NULL) { // if this node is bounded, union its bounds with the others if (pNode->IsBounded()) NewRect = NewRect.Union(((NodeRenderableBounded*)pNode)->GetBoundingRect(DontUseAttrs)); // Get the next node in the list pNode = pNode->FindNext(); } if (DontUseAttrs) { // just return this rectangle as it is not the nodes true bounding rect return NewRect; } // copy the new docrect into the node bounding rect BoundingRectangle = NewRect; // Mark the bounding rect as valid IsBoundingRectValid = TRUE; } // Return the resulting bounding rect return BoundingRectangle; } /******************************************************************************************** > DocRect NodeGroup::GetBlobBoundingRect() Author: Ed_Cornes (Xara Group Ltd) (re-written) Created: 29/6/95 Returns: DocRect - The bounding rect of the group and its blobs Purpose: Finds the bounding rect of the group along with its blobs. Its main use is to determine which areas of the document to invalidate ********************************************************************************************/ DocRect NodeGroup::GetBlobBoundingRect() { DocRect NewRect = DocRect(0,0,0,0); #if !defined(EXCLUDE_FROM_RALPH) // Find the Blob manager BlobManager* pBlobMgr = GetApplication()->GetBlobManager(); if (pBlobMgr!= NULL) { BlobStyle VisibleBlobs = pBlobMgr->GetCurrentInterest(TRUE); if (VisibleBlobs.Object) { DocRect BoundingRect = GetBoundingRect(); // Object blobs are visible, so include blob in each // corner of the group bounds DocRect BlobSize; // Find out where the blobs will be drawn DocCoord Low = BoundingRect.LowCorner(); DocCoord High = BoundingRect.HighCorner(); // Include the object blob in each corner pBlobMgr->GetBlobRect(Low, &BlobSize); NewRect = NewRect.Union(BlobSize); pBlobMgr->GetBlobRect(High, &BlobSize); NewRect = NewRect.Union(BlobSize); pBlobMgr->GetBlobRect(DocCoord(Low.x, High.y), &BlobSize); NewRect = NewRect.Union(BlobSize); pBlobMgr->GetBlobRect(DocCoord(High.x, Low.y), &BlobSize); NewRect = NewRect.Union(BlobSize); } } Node* pNode=FindFirstChild(); while (pNode!=NULL) { if (pNode->IsBounded()) NewRect = NewRect.Union(((NodeRenderableBounded*)pNode)->GetBlobBoundingRect()); pNode = pNode->FindNext(); } IncludeChildrensBoundingRects(&NewRect); #endif return NewRect; } /******************************************************************************************** > virtual String NodeGroup::Describe(BOOL Plural, BOOL Verbose = TRUE) Author: Simon_Maneggio (Xara Group Ltd) Created: 18/6/93 Inputs: Plural: Flag indicating if the string description should be plural or singular. Outputs: - Retuns: Description of the group node Purpose: To return a description of the Group object in either the singular or the plural. This method is called by the DescribeRange method. The description will always begin with a lower case letter. Errors: (Need to do this) SeeAlso: - ********************************************************************************************/ String NodeGroup::Describe(BOOL Plural, BOOL Verbose) { if (Plural) return(String(_R(IDS_GROUP_DESCRP))); else return(String(_R(IDS_GROUP_DESCRS))); }; /*********************************************************************************************** > Node* NodeGroup::SimpleCopy() Author: Simon_Maneggio (Xara Group Ltd) Created: 28/4/93 Inputs: - Outputs: Returns: A copy of the node, or NULL if memory has run out Purpose: This method returns a shallow copy of the node with all Node pointers NULL. The function is virtual, and must be defined for all derived classes. Errors: If memory runs out when trying to copy, then ERROR is called with an out of memory error and the function returns NULL. **********************************************************************************************/ Node* NodeGroup::SimpleCopy() { NodeGroup* NodeCopy; NodeCopy = new NodeGroup(); ERRORIF(NodeCopy == NULL, _R(IDE_NOMORE_MEMORY), NULL); CopyNodeContents(NodeCopy); return (NodeCopy); } /*********************************************************************************************** > void NodeGroup::CopyNodeContents(NodeGroup* pCopyOfNode) Author: Phil_Martin (Xara Group Ltd) Created: 22/10/2004 Inputs: pCopyOfNode - The node to copy data to Outputs: - Returns: - Purpose: Copies the data from this node to pCopyOfNode by first calling the base class to get it to copy its stuff, and then copying its own stuff Scope: protected SeeAlso: NodeGroup::CopyNodeContents ***********************************************************************************************/ void NodeGroup::CopyNodeContents(NodeGroup* pCopyOfNode) { NodeRenderableInk::CopyNodeContents(pCopyOfNode); pCopyOfNode->m_bBlendStartNode = m_bBlendStartNode; // NodeController? pCopyOfNode->m_bBlendEndNode = m_bBlendEndNode; // NodeController? pCopyOfNode->m_pBlendCreatedByNode = m_pBlendCreatedByNode; // NodeController? CopyCached(pCopyOfNode); // Copy cached bitmaps } /*********************************************************************************************** > void NodeGroup::PolyCopyNodeContents(NodeRenderable* pNodeCopy) Author: Phil_Martin (Xara Group Ltd) Created: 22/10/2004 Outputs: - Purpose: Polymorphically copies the contents of this node to another Errors: An assertion failure will occur if NodeCopy is NULL Scope: protected ***********************************************************************************************/ void NodeGroup::PolyCopyNodeContents(NodeRenderable* pNodeCopy) { ENSURE(pNodeCopy, "Trying to copy a node's contents into a NULL node"); ENSURE(IS_A(pNodeCopy, NodeGroup), "PolyCopyNodeContents given wrong dest node type"); if (IS_A(pNodeCopy, NodeGroup)) CopyNodeContents((NodeGroup*)pNodeCopy); } /******************************************************************************************** > virtual BOOL NodeGroup::CloneNode(Node** ppNodeCopy, BOOL bLightweight) Author: Phil_Martin (Xara Group Ltd) Created: 23/07/2005 Inputs: bLightweight - Only copy what you really need, Make references to original large data items instead of copying them if possible Outputs: NodeCopy: A deep copy of the node if Successful Returns: - Purpose: This method outputs a 'deep' copy of the node. It is the same as CopyNode except that the copy is not linked into the tree. Override if your class can interpret the lightweight flag Errors: - SeeAlso: Node::CopyNode ********************************************************************************************/ /*BOOL NodeGroup::CloneNode(Node** ppNodeCopy, BOOL bLightweight) { // Don't let derived classes be copied as NodeGroups // (There must be a better way to do this!) if (!IS_A(this, NodeGroup)) return NodeCompound::CloneNode(ppNodeCopy, bLightweight); // TODO: Do clever stuff with bLightWeight!!!! NodeGroup* pNewGroup = new NodeGroup(); ERRORIF(pNewGroup == NULL, _R(IDE_NOMORE_MEMORY), FALSE); NodeRenderableInk::CopyNodeContents(pNewGroup); // CopyCached(pNewGroup, bLightweight); // Copy cached bitmaps CopyCached(pNewGroup); // Copy cached bitmaps // Possible optimisation: // If a lightweight copy is requested and GroupCanTransformCached==TRUE // and we find we have a cached bitmap that will be used during dragging // then we could possibly avoid copying the children! // if (!CopyChildren(Child, pNewGroup, ccALL)) // Out of memory { DeleteChildren(pNewGroup->FindFirstChild()); delete pNewGroup; return FALSE; } *ppNodeCopy = pNewGroup; return TRUE; } */ /******************************************************************************************** > virtual UINT32 NodeGroup::GetNodeSize() const Author: Simon_Maneggio (Xara Group Ltd) Created: 6/10/93 Inputs: - Outputs: - Returns: The size of the node in bytes Purpose: For finding the size of the node SeeAlso: Node::GetSubtreeSize ********************************************************************************************/ UINT32 NodeGroup::GetNodeSize() const { return (sizeof(NodeGroup)); } /******************************************************************************************** > BOOL NodeGroup::OnClick( DocCoord PointerPos, ClickType Click, ClickModifiers ClickMods, Spread* pSpread ) Author: Simon_Maneggio (Xara Group Ltd) Created: 27/09/94 Inputs: PointerPos - The Location of the mouse pointer at the time of the click Click - The type of click received (single, double, drag etc) ClickMods - The modifiers to the click (eg shift, control etc ) Returns: TRUE if the node claims the click as its own and FALSE if it is not interested in the click Purpose: Determines if the user has started a drag on one of the groups blobs. If they have then it starts the groups resize operation ********************************************************************************************/ BOOL NodeGroup::OnClick( DocCoord PointerPos, ClickType Click, ClickModifiers ClickMods, Spread* pSpread ) { #if !defined(EXCLUDE_FROM_RALPH) // Find a view and if we failed to find a view, then just don't claim the click DocView *pDocView = DocView::GetSelected(); if (pDocView==NULL) return FALSE; // we only handle the click if we can confirm that object blobs are being displayed. BlobManager* pBlobMgr = GetApplication()->GetBlobManager(); if (pBlobMgr == NULL) return FALSE; if (!pBlobMgr->GetCurrentInterest().Object) return FALSE; // Find the DocRect of blob size around the point that was clicked on DocRect MouseBlob; OSRenderRegion::GetBlobRect(pDocView->GetViewScale(), PointerPos, BT_SELECTEDLARGEST, &MouseBlob); INT32 StartBlob = 0; // If the user has clicked on a blob we will need to set the centre of rotation to the // oposite corner DocCoord OpCorner; // Has the user clicked on one of the group's blobs ? if (MouseBlob.ContainsCoord(BoundingRectangle.lo)) // Bottom left { StartBlob = 6; OpCorner = BoundingRectangle.hi; } else if (MouseBlob.ContainsCoord(BoundingRectangle.hi)) // Top right { StartBlob = 3; OpCorner = BoundingRectangle.lo; } else if (MouseBlob.ContainsCoord(DocCoord(BoundingRectangle.lo.x,BoundingRectangle.hi.y))) // Top left { StartBlob = 1; OpCorner = DocCoord(BoundingRectangle.hi.x,BoundingRectangle.lo.y); } else if (MouseBlob.ContainsCoord(DocCoord(BoundingRectangle.hi.x,BoundingRectangle.lo.y))) // Bottom right { StartBlob = 8; OpCorner = DocCoord(BoundingRectangle.lo.x,BoundingRectangle.hi.y); } if (StartBlob != 0) { // User has clicked on a blob if (Click==CLICKTYPE_DRAG) { // Try to create the operation to scale the group OpScaleTrans* pOp = new OpScaleTrans(); if (pOp == NULL) { InformError(); return TRUE; // Claim the click though } // Create a range containing only this object // RangeControl RngCtl = { TRUE, FALSE, FALSE }; static TransformData TransData; // Setup the transform info TransData.CentreOfTrans = OpCorner; TransData.StartBlob = StartBlob; TransData.LockAspect = FALSE; TransData.LeaveCopy = FALSE; TransData.ScaleLines = TRUE; TransData.TransFills = TRUE; TransData.pRange = 0; TransformBoundingData BoundingData; BoundingData.x = BoundingRectangle.lo.x; BoundingData.y = BoundingRectangle.lo.y; BoundingData.Width = BoundingRectangle.Width(); BoundingData.Height = BoundingRectangle.Height(); BoundingData.XScale = (FIXED16) 1; BoundingData.YScale = (FIXED16) 1; BoundingData.Rotation = (ANGLE) 0; BoundingData.Shear = (ANGLE) 0; // Start the drag pOp->DragStarted(&TransData, NULL, // Don't report to any tool &BoundingData, // Must give bounds data or call fails PointerPos, pSpread, ClickMods, DocCoord(0,0), this ); } // we have used that click up return TRUE; } #endif // we did not use the click, so let someone else try return FALSE; } /******************************************************************************************** > virtual ChangeCode NodeGroup::OnChildChange(ObjChangeParam* pParam) Author: Mark_Neves (Xara Group Ltd) Created: 07/02/95 Inputs: pParam = pointer to a object change parameter class Returns: CC_OK if we have successfully processed the change. CC_FAIL if we cannot handle this particular change and must prevent the child from continuing Purpose: This function should be overridden in derived object classes. Composite objects can use this function to respond to one of their children undergoing a change. They should return CC_FAIL whenever they are unable to cope with the change. Groups will hide themselves if the op has left it in a state where it has no bounds, i.e. all it's node renderable bounded children are gone. SeeAlso: WarnParentOfChange(),AllowOp(); ********************************************************************************************/ ChangeCode NodeGroup::OnChildChange(ObjChangeParam* pParam) { ERROR2IF(pParam == NULL,CC_FAIL,"pParam == NULL"); ObjChangeType cType = pParam->GetChangeType(); if (cType == OBJCHANGE_FINISHED) { InvalidateBoundingRect(); // The groups DocRect Rect = GetBoundingRect(); if (Rect.IsEmpty()) { UndoableOperation* pOp = pParam->GetOpPointer(); if (pOp != NULL) { Node* pParent = FindParent(); if (!pOp->DoHideNode(this,TRUE)) { return CC_FAIL; } // This bit is a bit scary, but should work ! // After a deletion we factor out all attributes on the parent of this group if (pParent && pParent->IsCompound()) { if (!pOp->DoFactorOutCommonChildAttributes(((NodeRenderableInk*)pParent),TRUE)) { return CC_FAIL; } } } } } return NodeCompound::OnChildChange(pParam); } //--------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------- /******************************************************************************************** > virtual BOOL NodeGroup::WritePreChildrenWeb(BaseCamelotFilter* pFilter) Author: Mark_Neves (Xara Group Ltd) Created: 17/6/96 Inputs: pFilter = ptr to the filter Returns: TRUE if record is written, FALSE if not Purpose: Writes the group record to the filter SeeAlso: - ********************************************************************************************/ BOOL NodeGroup::WritePreChildrenWeb(BaseCamelotFilter* pFilter) { #ifdef DO_EXPORT ERROR2IF(pFilter == NULL,FALSE,"NULL filter param"); CXaraFileRecord Rec(TAG_GROUP,0); BOOL ok = pFilter->Write(&Rec); return ok; #else return FALSE; #endif } //-------------------------------------------------------------- // See NodeGroup::WritePreChildrenWeb(BaseCamelotFilter* pFilter) // BOOL NodeGroup::WritePreChildrenNative(BaseCamelotFilter* pFilter) { #ifdef DO_EXPORT return WritePreChildrenWeb(pFilter); #else return FALSE; #endif } /******************************************************************************************** > virtual BOOL NodeGroup::WriteBoundsRecord(BaseCamelotFilter* pFilter) Author: Gerry_Iles (Xara Group Ltd) Created: 25/10/05 Inputs: pFilter = ptr to filter to write to Returns: TRUE if ok, FALSE otherwise Purpose: Write out a record containing the bounds of this object ********************************************************************************************/ BOOL NodeGroup::WriteBoundsRecord(BaseCamelotFilter* pFilter) { BOOL ok = TRUE; // Write out the record DocRect Rect = GetBoundingRect(); if (HasEffectAttrs()) { CamelotFileRecord Rec(pFilter, TAG_COMPOUNDRENDER, TAG_COMPOUNDRENDER_SIZE); ok = Rec.Init(); if (ok) ok = Rec.WriteUINT32(0); // Reserved if (ok) ok = Rec.WriteCoord(Rect.lo); if (ok) ok = Rec.WriteCoord(Rect.hi); if (ok) ok = pFilter->Write(&Rec); } else { CamelotFileRecord Rec(pFilter, TAG_OBJECTBOUNDS, TAG_OBJECTBOUNDS_SIZE); ok = Rec.Init(); if (ok) ok = Rec.WriteCoord(Rect.lo); if (ok) ok = Rec.WriteCoord(Rect.hi); if (ok) ok = pFilter->Write(&Rec); } return(ok); } /******************************************************************************************** > virtual BOOL NodeGroup::WillWriteBounds(BaseCamelotFilter* pFilter) Author: Gerry_Iles (Xara Group Ltd) Created: 25/10/05 Inputs: pFilter = ptr to filter to write to Returns: TRUE if this node will write out a bounds record Purpose: Determines if the down/up pair need to be written ********************************************************************************************/ BOOL NodeGroup::WillWriteBounds(BaseCamelotFilter* pFilter) { return(HasEffectAttrs() || pFilter->GetBoundsWriteLevel() >= BWL_COMPOUND); } /******************************************************************************************** > virtual BOOL NodeGroup::WriteBeginChildRecordsNative(BaseCamelotFilter* pFilter) Author: Phil_Martin (Xara Group Ltd) Created: 30/09/2005 Inputs: pFilter = ptr to filter to write to Returns: TRUE if ok, FALSE otherwise Purpose: Begin to write out your child records, in the native format The base class will write out a TAG_DOWN record, but only if it has a child ptr SeeAlso: WritePostChildrenWeb(), WritePreChildrenWeb() ********************************************************************************************/ /* BOOL NodeGroup::WriteBeginChildRecordsNative(BaseCamelotFilter* pFilter) { BOOL ok = TRUE; Node* pChild = FindFirstChild(); if (pChild != NULL) { // The group is considered to have children ONLY if at least one child is not a hidden node. if (!pChild->IsNodeHidden() || (pChild->FindNextNonHidden() != NULL)) { ok = pFilter->WriteZeroSizedRecord(TAG_DOWN); if (ok) ok = WriteBoundsRecord(pFilter); } } return ok; } */ /******************************************************************************************** > virtual BOOL NodeGroup::WriteBeginChildRecordsWeb(BaseCamelotFilter* pFilter) Author: Phil_Martin (Xara Group Ltd) Created: 30/09/2005 Inputs: pFilter = ptr to filter to write to Returns: TRUE if ok, FALSE otherwise Purpose: Begin to write out you child records, in the native format The base class will write out a TAG_DOWN record, but only if it has a child ptr SeeAlso: WritePostChildrenWeb(), WritePreChildrenWeb() ********************************************************************************************/ /* BOOL NodeGroup::WriteBeginChildRecordsWeb(BaseCamelotFilter* pFilter) { return WriteBeginChildRecordsNative(pFilter); } */ /******************************************************************************************** > virtual BOOL NodeGroup::AreYouSafeToRender() Author: Mark_Neves (Xara Group Ltd) Created: 18/9/96 Inputs: - Returns: TRUE if safe to render, FALSE otherwise Purpose: Group nodes are always safe to render, but derived group nodes (such as blends and text, etc) are not safe until their post import functions have been called SeeAlso: - ********************************************************************************************/ BOOL NodeGroup::AreYouSafeToRender() { if (IS_A(this,NodeGroup)) return TRUE; else return NodeRenderableInk::AreYouSafeToRender(); } /******************************************************************************************** > DocRect NodeGroup::ValidateExtend(const ExtendParams& ExtParams) Author: Karim_MacDonald (Xara Group Ltd) Created: 5/25/00 Inputs: ExtParams parameters describing the extension. Outputs: Returns: TRUE if this group can be validly extended, FALSE otherwise. Purpose: Tests to see whether this group's extend-centre is positioned so as to make an extend operation irreversible. Note that we do not pass the ValidateExtend() call on to our kids, as our extend behaviour means that our children don't need extending or validating. Errors: See also: ********************************************************************************************/ //DocRect NodeGroup::ValidateExtend(const ExtendParams& ExtParams) //{ // DocCoord doccArray[1] = { FindExtendCentre() }; // DocRect drMinExtend = Extender::ValidateControlPoints(1, doccArray, ExtParams); // // return drMinExtend; //} /******************************************************************************************** > void NodeGroup::Extend(const ExtendParams& ExtParams) Author: Karim_MacDonald (Xara Group Ltd) Created: 5/25/00 Inputs: ExtParams parameters describing the extension. Outputs: this NodeGroup will be extended in accordance with ExtParams. Returns: Purpose: Perform an Extend operation on this group. Behaviour is as follows: * the group extends separately along each axis. * if the group is asked to stretch, it translates itself so that its centre is where it would be if it had stretched. * if the group is asked to extend, it is translated as a whole, as described by ExtParams. Note that we do not pass the Extend() call on to our children, as our extend behaviours result in our children being transformed appropriately anyway. Errors: See also: class Extender ********************************************************************************************/ //void NodeGroup::Extend(const ExtendParams& ExtParams) //{ // // do the extension operations on ourself. // TransformTranslateNoStretchObject(ExtParams); // TransformTranslateObject(ExtParams); //} ///////////////////////////////////////////////////////////////////////////////////////////////// // Implementation of NodeListItem ///////////////////////////////////////////////////////////////////////////////////////////////// /******************************************************************************************** > NodeListItemWithDocPtr::NodeListItemWithDocPtr() Author: Jim_Lynn (Xara Group Ltd) Created: 22/7/94 Inputs: - Outputs: - Returns: - Purpose: default constructor. Initialises pNode = NULL Errors: - SeeAlso: - ********************************************************************************************/ NodeListItemWithDocPtr::NodeListItemWithDocPtr() { pNode = NULL; } /******************************************************************************************** > NodeListItemWithDocPtr::NodeListItemWithDocPtr(Node* WhichNode) Author: Jim_Lynn (Xara Group Ltd) Created: 22/7/94 Inputs: - Outputs: - Returns: - Purpose: Constructor for NodeListItem which sets the pNode variable to WhichNode Errors: - SeeAlso: - ********************************************************************************************/ NodeListItemWithDocPtr::NodeListItemWithDocPtr(Node* WhichNode) { pNode = WhichNode; } //--------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------- /******************************************************************************************** > virtual UINT32* GroupRecordHandler::GetTagList() Author: Mark_Neves (Xara Group Ltd) Created: 17/6/96 Inputs: - Returns: Ptr to a list of tag values, terminated by CXFRH_TAG_LIST_END Purpose: Provides the record handler system with a list of records handled by this handler SeeAlso: - ********************************************************************************************/ UINT32* GroupRecordHandler::GetTagList() { static UINT32 TagList[] = {TAG_GROUP, TAG_COMPOUNDRENDER, TAG_OBJECTBOUNDS, CXFRH_TAG_LIST_END}; return (UINT32*)&TagList; } /******************************************************************************************** > virtual BOOL GroupRecordHandler::HandleRecord(CXaraFileRecord* pCXaraFileRecord) Author: Mark_Neves (Xara Group Ltd) Created: 17/6/96 Inputs: pCXaraFileRecord = ptr to record to handle Returns: TRUE if handled successfuly FALSE otherwise Purpose: Handles the given record. SeeAlso: - ********************************************************************************************/ BOOL GroupRecordHandler::HandleRecord(CXaraFileRecord* pCXaraFileRecord) { ERROR2IF(pCXaraFileRecord == NULL,FALSE,"pCXaraFileRecord is NULL"); BOOL ok = TRUE; switch (pCXaraFileRecord->GetTag()) { case TAG_GROUP: ok = HandleGroupRecord(pCXaraFileRecord); break; case TAG_COMPOUNDRENDER: case TAG_OBJECTBOUNDS: // Do nothing // We ignore TAG_COMPOUNDRENDER records because they are really just a hint to // external XAR stream handlers ok = TRUE; break; default: ok = FALSE; ERROR3_PF(("I don't handle records with the tag (%d)\n",pCXaraFileRecord->GetTag())); break; } return ok; } /******************************************************************************************** BOOL GroupRecordHandler::HandleGroupRecord(CXaraFileRecord* pCXaraFileRecord) Author: Mark_Neves (Xara Group Ltd) Created: 17/6/96 Inputs: pCXaraFileRecord = ptr to record to handle Returns: TRUE if handled successfuly FALSE otherwise Purpose: Handles the given record. The record has to be a Line colour record SeeAlso: - ********************************************************************************************/ BOOL GroupRecordHandler::HandleGroupRecord(CXaraFileRecord* pCXaraFileRecord) { ERROR2IF(pCXaraFileRecord == NULL,FALSE,"pCXaraFileRecord is NULL"); ERROR2IF(pCXaraFileRecord->GetTag() != TAG_GROUP,FALSE,"I don't handle this tag type"); NodeGroup* pGroup = new NodeGroup; BOOL ok = FALSE; if (pGroup != NULL) ok = InsertNode(pGroup); return ok; } /******************************************************************************************** > virtual void GroupRecordHandler::GetRecordDescriptionText(CXaraFileRecord* pRecord,StringBase* pStr) Author: Phil_Martin (Xara Group Ltd) Created: 30/09/2005 Inputs: pRecord = ptr to a record pStr = ptr to string to update Returns: - Purpose: Produce a textual description of the specified record Errors: - SeeAlso: - ********************************************************************************************/ #ifdef XAR_TREE_DIALOG void GroupRecordHandler::GetRecordDescriptionText(CXaraFileRecord* pRecord,StringBase* pStr) { if (pStr == NULL || pRecord == NULL) return; TCHAR s[256]; // Call base class first CamelotRecordHandler::GetRecordDescriptionText(pRecord,pStr); switch (pRecord->GetTag()) { case TAG_GROUP: break; case TAG_COMPOUNDRENDER: { INT32 reserved = 0; pRecord->ReadINT32(&reserved); camSprintf(s,_T("Reserved\t\t= %d\r\n"), reserved); (*pStr) += s; } nobreak; // fall through case TAG_OBJECTBOUNDS: DocRect rbounds; pRecord->ReadCoord(&rbounds.lo); pRecord->ReadCoord(&rbounds.hi); camSprintf(s, _T("Bounds\t\t= %d, %d, %d, %d\r\n"), rbounds.lo.x, rbounds.lo.y, rbounds.hi.x, rbounds.hi.y); (*pStr) += s; break; } } #endif