nodetxts.cpp

Go to the documentation of this file.
00001 // $Id: nodetxts.cpp 1445 2006-07-14 20:15:02Z phil $
00002 /* @@tag:xara-cn@@ DO NOT MODIFY THIS LINE
00003 ================================XARAHEADERSTART===========================
00004  
00005                Xara LX, a vector drawing and manipulation program.
00006                     Copyright (C) 1993-2006 Xara Group Ltd.
00007        Copyright on certain contributions may be held in joint with their
00008               respective authors. See AUTHORS file for details.
00009 
00010 LICENSE TO USE AND MODIFY SOFTWARE
00011 ----------------------------------
00012 
00013 This file is part of Xara LX.
00014 
00015 Xara LX is free software; you can redistribute it and/or modify it
00016 under the terms of the GNU General Public License version 2 as published
00017 by the Free Software Foundation.
00018 
00019 Xara LX and its component source files are distributed in the hope
00020 that it will be useful, but WITHOUT ANY WARRANTY; without even the
00021 implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
00022 See the GNU General Public License for more details.
00023 
00024 You should have received a copy of the GNU General Public License along
00025 with Xara LX (see the file GPL in the root directory of the
00026 distribution); if not, write to the Free Software Foundation, Inc., 51
00027 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
00028 
00029 
00030 ADDITIONAL RIGHTS
00031 -----------------
00032 
00033 Conditional upon your continuing compliance with the GNU General Public
00034 License described above, Xara Group Ltd grants to you certain additional
00035 rights. 
00036 
00037 The additional rights are to use, modify, and distribute the software
00038 together with the wxWidgets library, the wxXtra library, and the "CDraw"
00039 library and any other such library that any version of Xara LX relased
00040 by Xara Group Ltd requires in order to compile and execute, including
00041 the static linking of that library to XaraLX. In the case of the
00042 "CDraw" library, you may satisfy obligation under the GNU General Public
00043 License to provide source code by providing a binary copy of the library
00044 concerned and a copy of the license accompanying it.
00045 
00046 Nothing in this section restricts any of the rights you have under
00047 the GNU General Public License.
00048 
00049 
00050 SCOPE OF LICENSE
00051 ----------------
00052 
00053 This license applies to this program (XaraLX) and its constituent source
00054 files only, and does not necessarily apply to other Xara products which may
00055 in part share the same code base, and are subject to their own licensing
00056 terms.
00057 
00058 This license does not apply to files in the wxXtra directory, which
00059 are built into a separate library, and are subject to the wxWindows
00060 license contained within that directory in the file "WXXTRA-LICENSE".
00061 
00062 This license does not apply to the binary libraries (if any) within
00063 the "libs" directory, which are subject to a separate license contained
00064 within that directory in the file "LIBS-LICENSE".
00065 
00066 
00067 ARRANGEMENTS FOR CONTRIBUTION OF MODIFICATIONS
00068 ----------------------------------------------
00069 
00070 Subject to the terms of the GNU Public License (see above), you are
00071 free to do whatever you like with your modifications. However, you may
00072 (at your option) wish contribute them to Xara's source tree. You can
00073 find details of how to do this at:
00074   http://www.xaraxtreme.org/developers/
00075 
00076 Prior to contributing your modifications, you will need to complete our
00077 contributor agreement. This can be found at:
00078   http://www.xaraxtreme.org/developers/contribute/
00079 
00080 Please note that Xara will not accept modifications which modify any of
00081 the text between the start and end of this header (marked
00082 XARAHEADERSTART and XARAHEADEREND).
00083 
00084 
00085 MARKS
00086 -----
00087 
00088 Xara, Xara LX, Xara X, Xara X/Xtreme, Xara Xtreme, the Xtreme and Xara
00089 designs are registered or unregistered trademarks, design-marks, and/or
00090 service marks of Xara Group Ltd. All rights in these marks are reserved.
00091 
00092 
00093       Xara Group Ltd, Gaddesden Place, Hemel Hempstead, HP2 6EX, UK.
00094                         http://www.xara.com/
00095 
00096 =================================XARAHEADEREND============================
00097  */
00098 
00099 /*
00100 */
00101 
00102 #include "camtypes.h"
00103 #include "nodetxts.h"
00104 
00105 
00106 // Code headers
00107 //#include "app.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00108 //#include "becomea.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00109 #include "blobs.h"                          
00110 #include "cameleps.h"
00111 #include "contmenu.h"
00112 //#include "docview.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00113 //#include "fillattr.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00114 //#include "group.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00115 #include "lineattr.h"
00116 #include "nodetext.h"
00117 #include "nodetxtl.h"
00118 #include "objchge.h"
00119 //#include "opbreak.h"
00120 //#include "ops.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00121 #include "pathproc.h"
00122 //#include "rndrgn.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00123 //#include "trans2d.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00124 //#include "spread.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00125 #include "textops.h"
00126 //#include "tool.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00127 //#include "txtattr.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00128 #include "textfuns.h"
00129 #include "nativeps.h"       // The old style EPS native filter, used in v1.1
00130 #include "ai_epsrr.h"
00131 #include "progress.h"
00132 //#include "camfiltr.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00133 #include "cxftext.h"
00134 //#include "cxfrec.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00135 #include "impstr.h"
00136 #include "unicdman.h"
00137 #include "extender.h"   // for ExtendParams
00138 #include "ngcore.h"     // NameGallery, for stretching functionality
00139 #include "nodepath.h"
00140 
00141 // Resource headers
00142 //#include "mario.h"
00143 //#include "peter.h"
00144 //#include "simon.h"
00145 #include "textinfo.h"
00146 
00147 #ifdef RALPH
00148 #include "ralphcri.h" // For RalphCriticalSection
00149 #endif
00150 
00151 #include "opbevel.h"    // For determining if a gradient fill is used in a TextStory.
00152 
00153 DECLARE_SOURCE("$Revision: 1445 $")             
00154 
00155 CC_IMPLEMENT_DYNAMIC(BaseTextClass, NodeRenderableInk)
00156 CC_IMPLEMENT_MEMDUMP(TextStoryInfo, CC_CLASS_MEMDUMP );
00157 CC_IMPLEMENT_DYNAMIC(TextStory, BaseTextClass)
00158 
00159 // Declare smart memory handling in Debug builds
00160 #define new CAM_DEBUG_NEW
00161 
00162 // statics ...
00163 TextStory* TextStory::pFocusStory = NULL;
00164 
00165 
00167 // BaseTextClass methods
00168 
00169 /********************************************************************************************
00170 >   void BaseTextClass::Init()
00171 
00172     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
00173     Created:    2/2/96
00174     Purpose:    init function called by various constructors
00175 ********************************************************************************************/
00176 
00177 void BaseTextClass::Init()
00178 {
00179     mModifiedByOpFlag           = TRUE;     // ensures node will have metrics recached
00180     mDescendantModifiedByOpFlag = TRUE;     // ensures any children will have metrics cached
00181     mAffectedFlag               = TRUE;     // ensures node will be includes in redraw rect
00182     mDescendantAffectedFlag     = TRUE;     // ensures any children will be included in redraw
00183     mAlreadyWritten             = FALSE;    // ensures the char will be written to v2 files
00184 }
00185 
00186 
00187 /********************************************************************************************
00188 >   BaseTextClass::BaseTextClass()
00189 
00190     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
00191     Created:    21/12/94
00192     Purpose:    Simple BaseTextClass constructor
00193 ********************************************************************************************/
00194 
00195 BaseTextClass::BaseTextClass(): NodeRenderableInk() // Call the base class
00196 {
00197     Init();
00198 }
00199 
00200 
00201 /********************************************************************************************
00202 >   BaseTextClass::~BaseTextClass()
00203 
00204     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
00205     Created:    20/2/95
00206     Purpose:    BaseTextClass destructor
00207 ********************************************************************************************/
00208 
00209 BaseTextClass::~BaseTextClass()
00210 {
00211 }
00212 
00213 
00214 /********************************************************************************************
00215 >   BaseTextClass::BaseTextClass(Node* ContextNode, AttachNodeDirection Direction)
00216 
00217     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
00218     Created:    21/12/94
00219     Inputs:     ContextNode: Pointer to a node which this node is to be attached to.
00220         
00221                 Direction:
00222             
00223                 Specifies the direction in which the node is to be attached to the
00224                 ContextNode. The values this variable can take are as follows:
00225                                 
00226                 PREV      : Attach node as a previous sibling of the context node
00227                 NEXT      : Attach node as a next sibling of the context node
00228                 FIRSTCHILD: Attach node as the first child of the context node
00229                 LASTCHILD : Attach node as a last child of the context node
00230 
00231     Purpose:    The main BaseTextClass constructor
00232 ********************************************************************************************/
00233 
00234 BaseTextClass::BaseTextClass(Node* ContextNode,
00235                      AttachNodeDirection Direction):NodeRenderableInk(ContextNode, Direction)
00236 {
00237     Init();
00238 }
00239 
00240 
00241 /***********************************************************************************************
00242 >   void BaseTextClass::CopyNodeContents(BaseTextClass* NodeCopy)
00243 
00244     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
00245     Created:    3/5/95
00246     Purpose:    This method copies the node's contents to the node pointed to by NodeCopy.
00247     Assumption: Only called when creating new copies and the constructor sets these flags correctly
00248 ***********************************************************************************************/
00249 
00250 void BaseTextClass::CopyNodeContents(BaseTextClass* NodeCopy)
00251 {
00252     // Ask the base class to do its bit
00253     NodeRenderableInk::CopyNodeContents(NodeCopy);
00254 }
00255 
00256 
00257 /***********************************************************************************************
00258 >   void BaseTextClass::PolyCopyNodeContents(NodeRenderable* pNodeCopy)
00259 
00260     Author:     Phil_Martin (Xara Group Ltd) <camelotdev@xara.com>
00261     Created:    18/12/2003
00262     Outputs:    -
00263     Purpose:    Polymorphically copies the contents of this node to another
00264     Errors:     An assertion failure will occur if NodeCopy is NULL
00265     Scope:      protected
00266                                      
00267 ***********************************************************************************************/
00268 
00269 void BaseTextClass::PolyCopyNodeContents(NodeRenderable* pNodeCopy)
00270 {
00271     ENSURE(pNodeCopy, "Trying to copy a node's contents into a NULL node");
00272     ENSURE(pNodeCopy->IsKindOf(CC_RUNTIME_CLASS(BaseTextClass)), "PolyCopyNodeContents given wrong dest node type");
00273 
00274     if (pNodeCopy->IsKindOf(CC_RUNTIME_CLASS(BaseTextClass)))
00275         CopyNodeContents((BaseTextClass*)pNodeCopy);
00276 }
00277 
00278 
00279 
00280 /********************************************************************************************
00281 >   BOOL BaseTextClass::ReCacheNodeAndDescendantsMetrics()
00282 
00283     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
00284     Created:    2/2/96
00285     Returns:    FALSE if fails
00286     Purpose:    Recache metrics in (this node and any text descendants) which have been ModifiedByOp
00287 ********************************************************************************************/
00288 
00289 BOOL BaseTextClass::ReCacheNodeAndDescendantsMetrics(FormatRegion* pFormatRegion)
00290 {
00291        PORTNOTE("text","BaseTextClass::ReCacheNodeAndDescendantsMetrics - do nothing");
00292 #ifndef DISABLE_TEXT_RENDERING
00293     BOOL ok=TRUE;
00294     if (NodeOrDescendantModifiedByOp())
00295     {
00296         pFormatRegion->SaveContext();
00297 
00298         // scan immediate children rendering any text attrs, and recaching metrics on any text node
00299         Node* pNode = FindFirstChild();
00300         while (pNode!=NULL)
00301         {
00302             if (pNode->IsABaseTextClass())
00303                 ok = ok && ((BaseTextClass*)pNode)->ReCacheNodeAndDescendantsMetrics(pFormatRegion);
00304             else if (pNode->IsKindOfTextAttribute())
00305                 pNode->Render(pFormatRegion);
00306             pNode = pNode->FindNext();
00307         }
00308 
00309         // now all child text attrs rendered, recache this node's metrics
00310         if (this->ModifiedByOp())
00311             ok = ok && ReCacheMetrics(pFormatRegion);
00312 
00313         pFormatRegion->RestoreContext();
00314     }
00315 
00316     return ok;
00317 #else
00318     return FALSE;
00319 #endif
00320 }
00321 
00322 
00323 /********************************************************************************************
00324 >   virtual BOOL BaseTextClass::ReCacheMetrics(FormatRegion* pFormatRegion)
00325 
00326     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
00327     Created:    2/2/96
00328     Inputs:     pFormatRegion -
00329     Returns:    FALSE if fails
00330     Purpose:    Recache metrics in text node - does nothing by default
00331 ********************************************************************************************/
00332 
00333 BOOL BaseTextClass::ReCacheMetrics(FormatRegion* pFormatRegion)
00334 {
00335     return TRUE;
00336 }
00337 
00338 
00339 /********************************************************************************************
00340 >   void BaseTextClass::ClearFlags()
00341 
00342     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
00343     Created:    2/2/96
00344     Purpose:    clear all flags in the node
00345 ********************************************************************************************/
00346 
00347 void BaseTextClass::ClearFlags()
00348 {
00349     mModifiedByOpFlag           = FALSE;
00350     mDescendantModifiedByOpFlag = FALSE;
00351     mAffectedFlag               = FALSE;
00352     mDescendantAffectedFlag     = FALSE;
00353     mAlreadyWritten             = FALSE;
00354 }
00355 
00356 
00357 /********************************************************************************************
00358 >   void BaseTextClass::ClearNodeAndDescendantsFlags()
00359 
00360     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
00361     Created:    2/2/96
00362     Purpose:    Clear flags in node and all text descendants
00363     Assumption: if any flag is set the AffectedFlag is set,
00364                 and if AffectedFlag is set, its parents have DescendantAffectedFlag set
00365 ********************************************************************************************/
00366 
00367 void BaseTextClass::ClearNodeAndDescendantsFlags()
00368 {
00369     if (IS_A(this, CaretNode))
00370         SetOpPermission(PERMISSION_UNDEFINED);
00371 
00372     if (NodeOrDescendantAffected())
00373     {
00374         Node* pNode = FindFirstChild();
00375         while (pNode!=NULL)
00376         {
00377             if (pNode->IsABaseTextClass())
00378                 ((BaseTextClass*)pNode)->ClearNodeAndDescendantsFlags();
00379             pNode = pNode->FindNext();
00380         }
00381         ClearFlags();
00382     }
00383 }
00384 
00385 
00386 /********************************************************************************************
00387 >   void BaseTextClass::FlagNodeAndDescendantsModifiedByOpAndParentsHaveDescendantModifiedByOp()
00388 
00389     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
00390     Created:    2/2/96
00391     Purpose:    Flag node and all text descendants that they have been directly modified by an op
00392                 and flag all text parents that they have a text descendant directly modified by an op
00393 
00394 ********************************************************************************************/
00395 
00396 void BaseTextClass::FlagNodeAndDescendantsModifiedByOpAndParentsHaveDescendantModifiedByOp()
00397 {
00398     FlagModifiedByOp();
00399     FlagDescendantsModifiedByOp();
00400     FlagParentsHaveDescendantModifiedByOp();
00401 }
00402 
00403 
00404 /********************************************************************************************
00405 >   void BaseTextClass::FlagParentsHaveDescendantModifiedByOp()
00406 
00407     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
00408     Created:    2/2/96
00409     Purpose:    Flag all text parents that they have a text descendant directly modified by an op
00410 ********************************************************************************************/
00411 
00412 void BaseTextClass::FlagParentsHaveDescendantModifiedByOp()
00413 {
00414     // get parent
00415     Node* pNode = this->FindParent();
00416     if (pNode==NULL)
00417     {
00418 // Can be called when text block is being inserted and does not yet have a parent???
00419 //      ERROR3("BaseTextClass::FlagParentsHaveDescendantModifiedByOp() - node has no parent")
00420         return;
00421     }
00422 
00423     // if parent is a text node, set it's flag and flag it's parents
00424     if (pNode->IsABaseTextClass())
00425     {
00426         // if 'DescendantModifiedByOp' assume all it's parents already have this flag set
00427         BaseTextClass* pBTC = (BaseTextClass*)pNode;
00428         if (pBTC->DescendantModifiedByOp()==FALSE)
00429         {
00430             pBTC->FlagDescendantModifiedByOp();
00431             pBTC->FlagParentsHaveDescendantModifiedByOp();
00432         }
00433     }
00434 }
00435 
00436 
00437 /********************************************************************************************
00438 >   void BaseTextClass::FlagDescendantsModifiedByOp()
00439 
00440     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
00441     Created:    2/2/96
00442     Purpose:    Flag all text descendants as being directly modified by an op
00443 ********************************************************************************************/
00444 
00445 void BaseTextClass::FlagDescendantsModifiedByOp()
00446 {
00447     Node* pNode = FindFirstChild();
00448     while (pNode!=NULL)
00449     {
00450         if (pNode->IsABaseTextClass())
00451         {
00452             BaseTextClass* pBTC = (BaseTextClass*)pNode;
00453             pBTC->FlagModifiedByOp();
00454             pBTC->FlagDescendantsModifiedByOp();
00455         }
00456         pNode = pNode->FindNext();
00457     }
00458 }
00459 
00460 
00461 /********************************************************************************************
00462 >   void BaseTextClass::FlagNodeAndDescendantsAffectedAndParentsHaveDescendantAffected()
00463 
00464     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
00465     Created:    2/2/96
00466     Purpose:    Flag node and all text descendants that they have been 'affected'
00467                 and flag all text parents that they have a text descendant 'affected'
00468 ********************************************************************************************/
00469 
00470 void BaseTextClass::FlagNodeAndDescendantsAffectedAndParentsHaveDescendantAffected()
00471 {
00472     FlagAffected();
00473     FlagDescendantsAffected();
00474     FlagParentsHaveDescendantAffected();
00475 }
00476 
00477 
00478 /********************************************************************************************
00479 >   void BaseTextClass::FlagParentsHaveDescendantAffected()
00480 
00481     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
00482     Created:    2/2/96
00483     Purpose:    Flag all text parents that they have a text descendant 'affected'
00484 ********************************************************************************************/
00485 
00486 void BaseTextClass::FlagParentsHaveDescendantAffected()
00487 {
00488     // get parent
00489     Node* pNode = this->FindParent();
00490     if (pNode==NULL)
00491     {
00492         ERROR3("BaseTextClass::FlagParentsHaveDescendantAffected() - node has no parent");
00493         return;
00494     }
00495 
00496     // if parent is a text node, set it's flag and flag it's parents
00497     if (pNode->IsABaseTextClass())
00498     {
00499         // if 'DescendantAffected' assume all it's parents already have this flag set
00500         BaseTextClass* pBTC = (BaseTextClass*)pNode;
00501         if (pBTC->DescendantAffected()==FALSE)
00502         {
00503             pBTC->FlagDescendantAffected();
00504             pBTC->FlagParentsHaveDescendantAffected();
00505         }
00506     }
00507 }
00508 
00509 
00510 /********************************************************************************************
00511 >   void BaseTextClass::FlagDescendantsAffected()
00512 
00513     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
00514     Created:    2/2/96
00515     Purpose:    Flag all text descendants as being 'affected'
00516 ********************************************************************************************/
00517 
00518 void BaseTextClass::FlagDescendantsAffected()
00519 {
00520     Node* pNode = FindFirstChild();
00521     while (pNode!=NULL)
00522     {
00523         if (pNode->IsABaseTextClass())
00524         {
00525             BaseTextClass* pBTC = (BaseTextClass*)pNode;
00526             pBTC->FlagAffected();
00527             pBTC->FlagDescendantsAffected();
00528         }
00529         pNode = pNode->FindNext();
00530     }
00531 }
00532 
00533 /********************************************************************************************
00534 
00535 >   void BaseTextClass::
00536         FlagPrevTextCharAndDescendantsModifiedByOpAndParentsHaveDescendantModifiedByOp()
00537 
00538 
00539     Author:     Jonathan_Payne (Xara Group Ltd) <camelotdev@xara.com>
00540     Created:    17/10/2000
00541     Purpose:    Flags the previous text node (on the current line only) as modified by op
00542                 (this is useful for kerning where the previous char to the one that is inserted
00543                 needs to be updated so it can find out if it is the left half of a kern pair)
00544 
00545 ********************************************************************************************/
00546 void BaseTextClass::FlagPrevTextCharAndDescendantsModifiedByOpAndParentsHaveDescendantModifiedByOp()
00547 {
00548     if (!this->IsAVisibleTextNode())
00549         return;
00550 
00551     VisibleTextNode* pVTN = static_cast<VisibleTextNode*>(this);
00552     TextChar* pTC = pVTN->FindPrevTextCharInStory();
00553 
00554     BaseTextClass* pBTC = pTC;
00555     if (pBTC)
00556     {
00557         pBTC->FlagModifiedByOp();
00558         pBTC->FlagDescendantsModifiedByOp();
00559         pBTC->FlagParentsHaveDescendantModifiedByOp();
00560     }
00561 }
00562 
00563 /********************************************************************************************
00564 >   void BaseTextClass::UnionNodeAndDescendantsBounds(DocRect* pBounds, BOOL ParentModifiedByOp=FALSE)
00565 
00566     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
00567     Created:    2/2/96
00568     Inputs:     ParentModified - indicates if parent is ModifiedByOp
00569     Output:     pBounds        - updated to include bounds of all affected nodes
00570     Purpose:    Union bounds of the node and any text descendants 'affected' with the given bounds
00571                 Any text nodes which are also 'ModifiedByOp' also have their BlobBounds included
00572                 as they were selected at the start of the op.
00573 
00574     NOTES:      All 'affected' text descendants must have their bounds invalidated
00575                 before the parent can have its bounds invalidated and recached.
00576 
00577                 But (w.r.t. blob bounds) since a selected node can have no selected descendants
00578                 AND BlobBounds are not dependent on descendants, if a node's parent is ModifiedByOp
00579                 this node's blob bounds do not need to be included
00580 
00581                 Should not be including BlobBounds in undo actions as they don't scale
00582                 except if they're text blobs! - what about line & story blobs?
00583 ********************************************************************************************/
00584 
00585 void BaseTextClass::UnionNodeAndDescendantsOldAndNewBounds(DocRect* pBounds, BOOL ParentModifiedByOp)
00586 {
00587     if (NodeOrDescendantAffected())
00588     {
00589         Node* pNode = FindFirstChild();
00590         while (pNode!=NULL)
00591         {
00592             if (pNode->IsABaseTextClass())
00593                 ((BaseTextClass*)pNode)->UnionNodeAndDescendantsOldAndNewBounds(pBounds,ModifiedByOp());
00594             pNode = pNode->FindNext();
00595         }
00596     }
00597 
00598     if (Affected())
00599     {
00600         *pBounds = pBounds->Union(GetBoundingRect());
00601         if (ModifiedByOp() && !ParentModifiedByOp)
00602             *pBounds = pBounds->Union(GetBlobBoundingRect());
00603 
00604         InvalidateBoundingRect();
00605 
00606         *pBounds = pBounds->Union(GetBoundingRect());
00607         if (ModifiedByOp() && !ParentModifiedByOp)
00608             *pBounds = pBounds->Union(GetBlobBoundingRect());
00609     }
00610 }
00611 
00612 
00613 
00614 /********************************************************************************************
00615 >   virtual BOOL BaseTextClass::AllowOp(ObjChangeParam* pParam, BOOL SetOpPermissionState = TRUE,
00616                                                                 BOOL DoPreTriggerEdit = TRUE)
00617 
00618     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>; Karim 19/01/2000
00619     Created:    8/5/95
00620     Inputs:     pParam - pointer to the change parameter object
00621                 SetOpPermissionState - TRUE to set the Nodes permission flags
00622                 DoPreTriggerEdit     = if TRUE then calls NameGallery::PreTriggerEdit.
00623                                        *Must* be TRUE if the calling Op may make any nodes
00624                                        change their bounds, eg move, line width, cut.
00625                                        Use TRUE if unsure.
00626     Returns:    TRUE if the operation can proceede, FALSE to stop it
00627     Purpose:    Generic AllowOp() for all text nodes except TextStories
00628 ********************************************************************************************/
00629 
00630 BOOL BaseTextClass::AllowOp(ObjChangeParam* pParam, BOOL SetOpPermissionState,
00631                                                     BOOL DoPreTriggerEdit)
00632 {
00633     ERROR2IF(pParam==NULL,FALSE,"BaseTextClass::AllowOp() - pParam==NULL");
00634     ERROR2IF(Parent==NULL,FALSE,"BaseTextClass::AllowOp() - Parent==NULL");     // must have at least a TextStory
00635 
00636     // if a parent AllowOp() calls the TextStory AllowOp() this is as far down the tree as we
00637     // need to go to ensure the story is maintained correctly, hence the TextStory AllowOp()
00638     // does not call any child AllowOp()s
00639     // also, for similar reasons, any node in the story need only call it's parents AllowOp()s
00640     // and never its children
00641     // so we should never be called by a parent AllowOp() ...
00642     ERROR3IF(pParam->GetDirection()==OBJCHANGE_CALLEDBYPARENT,"BaseTextClass::AllowOp() - called by parent AllowOp()!");
00643 
00644     // decide if we allow it ...
00645     BOOL allowed=TRUE;
00646     ObjChangeFlags Flags=pParam->GetChangeFlags();
00647     if (pParam->GetDirection()==OBJCHANGE_CALLEDBYCHILD)
00648     {
00649         // clean out the calling-child ptr, so it doesn't get passed around unintentionally.
00650         pParam->SetCallingChild(NULL);
00651 
00652         if (Flags.ReplaceNode || Flags.MultiReplaceNode || Flags.MoveNode)
00653             allowed=FALSE;
00654     }
00655 
00656     // and special case for caret (don't allow it to be deleted or copied)
00657     if (IS_A(this,CaretNode) && pParam->GetDirection()==OBJCHANGE_CALLEDBYOP)
00658         if (Flags.DeleteNode || Flags.CopyNode)
00659             allowed=FALSE;
00660 
00661     // if we didn't allow it set a reason
00662     if (allowed==FALSE)
00663         pParam->SetReasonForDenial(_R(IDE_TEXT_USEDBYSTORY));
00664         
00665 
00666     // if we allowed it, see if our parent allows it
00667     if (allowed)
00668     {
00669         // indicate we're calling from a child AllowOp()
00670         ObjChangeDirection OldDirection=pParam->GetDirection();
00671         pParam->SetCallingChild(this);
00672         pParam->SetDirection(OBJCHANGE_CALLEDBYCHILD);
00673         allowed=Parent->AllowOp(pParam,SetOpPermissionState,DoPreTriggerEdit);
00674         pParam->SetDirection(OldDirection);
00675     }
00676 
00677     // if setting permisions ...
00678     if (SetOpPermissionState)
00679     {
00680         // (assume if parent allowed it, it set permission correctly on itself)
00681         // (and if called by a child, it did all the pre-op processing required)
00682         // (note we should never be called by our parent)
00683         if (allowed)
00684             allowed=PreOpProcessing(pParam);
00685         else
00686             SetOpPermission(PERMISSION_DENIED,TRUE);
00687     }
00688 
00689     // if we're ok so far and were asked to do a PreTriggerEdit, then
00690     // determine whether the Op may change the bounds of some nodes.
00691     // If it may, then call NameGallery::PreTriggerEdit.
00692     if (allowed && DoPreTriggerEdit)
00693     {
00694         // if the Op is non-NULL then query its MayChangeNodeBounds() method.
00695         UndoableOperation* pChangeOp = pParam->GetOpPointer();
00696         if (pChangeOp != NULL && pChangeOp->MayChangeNodeBounds() && NameGallery::Instance())
00697         {
00698             allowed = NameGallery::Instance()->PreTriggerEdit(pChangeOp, pParam, this);
00699         }
00700     }
00701 
00702     // return result (directly, or indirectly via a child AllowOp()) to op
00703     return allowed;
00704 }
00705 
00706 
00707 /********************************************************************************************
00708 >   BOOL BaseTextClass::PreOpProcessing(ObjChangeParam* pParam)
00709 
00710     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
00711     Created:    8/5/95
00712     Inputs:     pParam -
00713     Returns:    FALSE if fails
00714     Purpose:    Perform all preprocessing required before an op is performed on a text node
00715 ********************************************************************************************/
00716 
00717 BOOL BaseTextClass::PreOpProcessing(ObjChangeParam* pParam)
00718 {
00719     // if called by an AllowOp() which itself was NOT called by a child's AllowOp(), do our stuff
00720     // (assume if it WAS called by an AllowOp() which itself was called by a child AllowOp()
00721     // (this routine would have already been called by a child's AllowOp() which itself was
00722     // (called by an AlowOp() called directly by an Op!
00723     if (pParam->GetDirection()!=OBJCHANGE_CALLEDBYCHILD ||
00724         (pParam->GetCallingChild() != NULL &&
00725         pParam->GetCallingChild()->IsNodePath()) )
00726     {
00727         // if the TextStory has already been met by the op,
00728         // just union the affected node's bounds with the current redraw rect
00729         // else (we've been called for the first time in this op for this story) so,
00730         // flag the story has been met, reset the redraw rect, insert a single reformat action for undo
00731         TextStory* pTextStory = FindParentStory();
00732         if (pTextStory->NodeOrDescendantModifiedByOp())
00733             pTextStory->UpdateRedrawRect(GetBoundingRect());
00734         else
00735         {
00736             pTextStory->Validate();
00737             pTextStory->SetRedrawRect(GetBoundingRect());
00738             UndoableOperation* pOp=pParam->GetOpPointer();
00739             if (pOp!=NULL)
00740                 if (PrePostTextAction::Init(pOp,pOp->GetUndoActionList(),pTextStory,TRUE)==AC_FAIL)
00741                     return FALSE;
00742         }
00743         // also need to include blob bounds as obj must be selected to get here!
00744         pTextStory->UpdateRedrawRect(GetBlobBoundingRect());
00745 
00746         // flag the node is directly modified by the op
00747         FlagNodeAndDescendantsModifiedByOpAndParentsHaveDescendantModifiedByOp();
00748 
00749         // flag prev char to allow for kerning
00750         FlagPrevTextCharAndDescendantsModifiedByOpAndParentsHaveDescendantModifiedByOp();
00751     }
00752     return TRUE;
00753 }
00754 
00755 
00756 /********************************************************************************************
00757 >   virtual CCRuntimeClass* BaseTextClass::GetCurrentAttribGroup()
00758 
00759     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
00760     Created:    6/4/95
00761     Returns:    The current attribute group for all text nodes
00762     Purpose:    Every Ink object has an associated current attribute group, If an attribute
00763                 is applied to the object, and it needs to become current then the attribute
00764                 group specified will get updated.
00765 ********************************************************************************************/
00766 
00767 CCRuntimeClass* BaseTextClass::GetCurrentAttribGroup()
00768 {
00769     return (CC_RUNTIME_CLASS(BaseTextClass));
00770 }
00771 
00772 
00773 /********************************************************************************************
00774 >   TextStory* BaseTextClass::FindParentStory()
00775 
00776     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
00777     Created:    15/5/95
00778     Returns:    pointer to the (parent) text story (else NULL if error)
00779     Purpose:    get a pointer to the text story associated with this text node
00780                 NB if 'this' is a text story, return 'this'
00781 ********************************************************************************************/
00782 
00783 TextStory* BaseTextClass::FindParentStory()
00784 {
00785     Node* pNode=this;
00786     while (IS_A(pNode,TextStory)==FALSE)
00787     {
00788         pNode = pNode->FindParent();
00789         if (pNode==NULL || pNode->IsABaseTextClass()==FALSE)
00790             ERROR2(NULL,"BaseTextClass::FindParentStory() - could not find associated TextStory");
00791     }
00792     return (TextStory*)pNode;
00793 }
00794 
00795 
00796 /********************************************************************************************
00797 >   BOOL BaseTextClass::DoHideNode(UndoableOperation* pUndoOp)
00798 
00799     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
00800     Created:    14/6/96
00801     Inputs:     pUndoOp - undoable operation (may be NULL if not undoable)
00802     Returns:    FALSE if fails
00803     Purpose:    As UndoableOperation::DoHideNode()
00804                 BUT can be called non-undoably!!!
00805 ********************************************************************************************/
00806 
00807 BOOL BaseTextClass::DoHideNode(UndoableOperation* pUndoOp)
00808 {
00809     // deselect any node to hide to ensure 'parent of selected' flag correct
00810     if (IsSelected())
00811         SetSelected(FALSE);
00812 
00813     if (pUndoOp!=NULL)
00814         return pUndoOp->DoHideNode(this,FALSE,NULL,TRUE);
00815 
00816     // delete the node
00817     // (no need to update selection as non-undoable code only called for clipboard/import)
00818     // (no need to invalidate bounding box as text has it's own sytem to handle this)
00819     CascadeDelete();
00820     delete this;
00821     return TRUE;
00822 }
00823 
00824 
00825 /********************************************************************************************
00826 >   BOOL BaseTextClass::DoInsertNewNode(UndoableOperation* pUndoOp,
00827                                         Node* pContextNode,
00828                                         AttachNodeDirection Direction)
00829 
00830     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
00831     Created:    14/6/96
00832     Inputs:     pUndoOp             - undoable operation (may be NULL if not undoable)
00833                 pContextNode        - see UndoableOperation::DoInsertNewNode()
00834                 Direction           - see UndoableOperation::DoInsertNewNode()
00835     Returns:    FALSE if fails
00836     Purpose:    As UndoableOperation::DoInsertNewNode()
00837                 BUT can be called non-undoably!!!
00838 ********************************************************************************************/
00839 
00840 BOOL BaseTextClass::DoInsertNewNode(UndoableOperation* pUndoOp,
00841                                     Node* pContextNode,
00842                                     AttachNodeDirection Direction)
00843 {
00844     BOOL ok = TRUE;
00845     if (pUndoOp!=NULL)
00846         ok = pUndoOp->DoInsertNewNode(this, pContextNode, Direction, FALSE,FALSE,FALSE,FALSE);
00847     else
00848     {
00849         // Insert the NewNode into the tree
00850         // (no need to invalidate bounding box as text has it's own sytem to handle this)
00851         // (assume layer is not locked and is visible)
00852         // (do not select the object, normalise it's attrs as this is done later)
00853         AttachNode(pContextNode, Direction);
00854     }
00855 
00856     if (ok) this->FlagNodeAndDescendantsModifiedByOpAndParentsHaveDescendantModifiedByOp();
00857 
00858     // flag prev char to allow for kerning
00859     FlagPrevTextCharAndDescendantsModifiedByOpAndParentsHaveDescendantModifiedByOp();
00860 
00861     return ok;
00862 }
00863 
00864 
00865 /********************************************************************************************
00866 >   BOOL BaseTextClass::DoLocaliseCommonAttributes( UndoableOperation* pUndoOp,
00867                                                     BOOL CheckForDuplicates,
00868                                                     BOOL Global,
00869                                                     AttrTypeSet* pAffectedAttrTypes)
00870 
00871     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
00872     Created:    14/6/96
00873     Inputs:     pUndoOp             - undoable operation (may be NULL if not undoable)
00874                 CheckForDuplicated  - see UndoableOperation::DoLocaliseCommonAttributes()
00875                 Global              - see UndoableOperation::DoLocaliseCommonAttributes()
00876                 pAffectedAttrTypes  - see UndoableOperation::DoLocaliseCommonAttributes()
00877     Returns:    FALSE if fails
00878     Purpose:    As UndoableOperation::DoLocaliseCommonAttributes()
00879                 BUT can be called non-undoably!!!
00880 ********************************************************************************************/
00881 
00882 BOOL BaseTextClass::DoLocaliseCommonAttributes( UndoableOperation* pUndoOp,
00883                                                 BOOL CheckForDuplicates,
00884                                                 BOOL Global,
00885                                                 AttrTypeSet* pAffectedAttrTypes)
00886 {
00887     if (pUndoOp!=NULL)
00888         return pUndoOp->DoLocaliseCommonAttributes(this,CheckForDuplicates,Global,pAffectedAttrTypes);
00889 
00890     return LocaliseCommonAttributes(CheckForDuplicates,Global,pAffectedAttrTypes);
00891 }
00892 
00893 
00894 /********************************************************************************************
00895 >   BOOL BaseTextClass::DoFactorOutCommonChildAttributes(UndoableOperation* pUndoOp,
00896                                                          BOOL Global,
00897                                                          AttrTypeSet* pAffectedAttrTypes)
00898 
00899     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
00900     Created:    14/6/96
00901     Inputs:     pUndoOp             - undoable operation (may be NULL if not undoable)
00902                 Global              - see UndoableOperation::DoFactorOutCommonChildAttributes()
00903                 pAffectedAttrTypes  - see UndoableOperation::DoFactorOutCommonChildAttributes()
00904     Returns:    FALSE if fails
00905     Purpose:    As UndoableOperation::DoFactorOutCommonChildAttributes()
00906                 BUT can be called non-undoably!!!
00907 ********************************************************************************************/
00908 
00909 BOOL BaseTextClass::DoFactorOutCommonChildAttributes(UndoableOperation* pUndoOp,
00910                                                      BOOL Global,
00911                                                      AttrTypeSet* pAffectedAttrTypes)
00912 {
00913     if (pUndoOp!=NULL)
00914         return pUndoOp->DoFactorOutCommonChildAttributes(this,Global,pAffectedAttrTypes);
00915 
00916     return FactorOutCommonChildAttributes(Global,pAffectedAttrTypes);
00917 }
00918 
00919 
00920 /********************************************************************************************
00921 >   void BaseTextClass::GetAttachNodeAndDirectionToAttachFirstChildObject(Node** ppNode,
00922                                                                 AttachNodeDirection* pDir)
00923 
00924     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
00925     Created:    14/6/96
00926     Outputs:    ppNode - context node
00927                 pDir   - attach direction
00928 ********************************************************************************************/
00929 
00930 void BaseTextClass::GetAttachNodeAndDirectionToAttachFirstChildObject(Node** ppNode,
00931                                                             AttachNodeDirection* pDir)
00932 {
00933     *ppNode = this->FindFirstChild(CC_RUNTIME_CLASS(NodeRenderableInk));
00934     *pDir   = PREV;
00935 
00936     // if no object to attach before, try next to last non-object node
00937     if (*ppNode==NULL)
00938     {
00939         *ppNode = this->FindLastChild();
00940         *pDir   = NEXT;
00941     }
00942 
00943     // if no child nodes, set first child of this
00944     if (*ppNode==NULL)
00945     {
00946         *ppNode = this;
00947         *pDir   = FIRSTCHILD;
00948     }
00949 }
00950 
00951 
00952 /********************************************************************************************
00953 >   BOOL BaseTextClass::AddNonLineLevelDescendantAttrsToSet(AttrTypeSet* pAttrTypeSet)
00954 
00955     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
00956     Created:    14/6/96
00957     Inputs:     pAttrTypeSet -
00958     Outputs:    pAttrTypeSet -
00959     Returns:    FALSE if fails
00960 ********************************************************************************************/
00961 
00962 BOOL BaseTextClass::AddNonLineLevelDescendantAttrsToSet(AttrTypeSet* pAttrTypeSet)
00963 {
00964     ERROR2IF(pAttrTypeSet==NULL,FALSE,"BaseTextClass::AddNonLineLevelDescendantAttrsToSet() - pAttrTypeSet==NULL");
00965 
00966     Node* pNode = FindFirstChild();
00967     while (pNode!=NULL)
00968     {
00969         if ( pNode->IsAnAttribute() && !((NodeAttribute*)pNode)->IsALineLevelAttrib() )
00970         {
00971             NodeAttribute* pAttr = (NodeAttribute*)pNode;
00972             if (!pAttrTypeSet->AddToSet(pAttr->GetAttributeType()))
00973                 return FALSE;
00974         }
00975         else if (pNode->IsABaseTextClass())
00976             if (!((BaseTextClass*)pNode)->AddNonLineLevelDescendantAttrsToSet(pAttrTypeSet))
00977                 return FALSE;
00978         pNode = pNode->FindNext();
00979     }
00980     return TRUE;
00981 }
00982 
00983 
00985 // TextStoryInfo methods
00986 
00987 /********************************************************************************************
00988 >   TextStoryInfo::TextStoryInfo()
00989 
00990     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
00991     Created:    4/2/96
00992     Purpose:    default constructor!
00993 ********************************************************************************************/
00994 
00995 TextStoryInfo::TextStoryInfo()
00996 {
00997     pUndoOp              = NULL;
00998     WordWrap             = TRUE;
00999     StoryWidth           = 0;       // assume text at a point
01000     WordWrapping         = FALSE;
01001     pPath                = NULL;
01002     PathLength           = 0;
01003     PathClosed           = FALSE;
01004     UnitDirectionVectorX = 0;
01005     UnitDirectionVectorY = 1;
01006     LeftPathIndent       = 0;
01007     RightPathIndent      = 0;
01008     DescentLine          = 0;
01009     DescentLineValid     = FALSE;
01010 }
01011 
01012 
01014 // TextStory methods
01015 
01016 /********************************************************************************************
01017 >   TextStory::TextStory()
01018 
01019     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
01020     Created:    21/12/94
01021     Purpose:    Simple TextStory constructor, it is required so that SimpleCopy will work.
01022 ********************************************************************************************/
01023 
01024 TextStory::TextStory(): BaseTextClass() // Call the base class
01025 {
01026     Init();
01027 }
01028 
01029 
01030 /********************************************************************************************
01031 >   TextStory::~TextStory()
01032 
01033     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
01034     Created:    20/2/95
01035     Purpose:    The destructor currently just checks to see if the TextStory being deleted
01036                 still has the input focus, and clears it if it has.  One situation where this
01037                 occurs is when deleting a document.  This dosen't clear the selection
01038 ********************************************************************************************/
01039 
01040 TextStory::~TextStory()
01041 {
01042     if (GetFocusStory() == this)
01043         SetFocusStory(NULL);
01044 
01045     if (pImportedStringList != NULL)
01046     {
01047         pImportedStringList->DeleteAll();
01048         delete pImportedStringList;
01049         pImportedStringList = NULL;
01050     }
01051 }
01052 
01053 
01054 /********************************************************************************************
01055 >   TextStory::TextStory(Node* ContextNode, AttachNodeDirection Direction)
01056 
01057     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
01058     Created:    21/12/94
01059     Inputs:     ContextNode: Pointer to a node which this node is to be attached to.
01060         
01061                 Direction:
01062             
01063                 Specifies the direction in which the node is to be attached to the
01064                 ContextNode. The values this variable can take are as follows:
01065                                 
01066                 PREV      : Attach node as a previous sibling of the context node
01067                 NEXT      : Attach node as a next sibling of the context node
01068                 FIRSTCHILD: Attach node as the first child of the context node
01069                 LASTCHILD : Attach node as a last child of the context node
01070 
01071     Purpose:    The main TextStory constructor
01072 ********************************************************************************************/
01073 
01074 TextStory::TextStory(Node* ContextNode,
01075                      AttachNodeDirection Direction):BaseTextClass(ContextNode, Direction)
01076 {
01077     Init();
01078 }
01079 
01080 
01081 /********************************************************************************************
01082 >   void TextStory::Init()
01083 
01084     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
01085     Created:    16/02/95
01086     Outputs:    Set member variables to default values.
01087     Purpose:    Initialies the member variables of the TextStory
01088 ********************************************************************************************/
01089 
01090 void TextStory::Init()
01091 {
01092     StoryMatrix = Matrix();
01093     RedrawRect  = DocRect(0,0,0,0);
01094 
01095     StoryWidth      = 0;
01096     mLeftIndent     = 0;
01097     mRightIndent    = 0;
01098     CachedCaret     = NULL;
01099 
01100     TextOnPathReversed   = FALSE;
01101     TextOnPathTangential = TRUE;
01102     PrintAsShapes        = FALSE;
01103     WordWrapping         = FALSE;
01104     BeingCopied          = FALSE;
01105 
01106     ImportFormatWidth = 0;
01107     ImportBaseShift   = AlignBaseline;
01108 
01109     CharsScale    = 1;
01110     CharsAspect   = 1;
01111     CharsRotation = 0;
01112     CharsShear    = 0;
01113 
01114     pImportedStringList  = NULL;    // Used when importing strings in the v2 file format
01115 
01116     AutoKern = true;
01117 }
01118 
01119 
01120 /********************************************************************************************
01121 >   Node* TextStory::SimpleCopy()
01122 
01123     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
01124     Created:    21/12/94
01125     Returns:    A copy of the node, or NULL if we are out of memory
01126     Purpose:    This method returns a shallow copy of the node with all Node pointers NULL.
01127                 The function is virtual, and must be defined for all derived classes of Node
01128     Errors:     If memory runs out when trying to copy, then ERROR is called with an out of memory
01129                 error and the function returns NULL.
01130 ********************************************************************************************/
01131 
01132 Node* TextStory::SimpleCopy()
01133 {
01134     // Make a new TextStory and then copy things into it
01135     TextStory* NodeCopy = new TextStory();
01136 
01137     ERROR1IF(NodeCopy==NULL, NULL, _R(IDE_NOMORE_MEMORY));
01138 
01139     if (NodeCopy)
01140         CopyNodeContents(NodeCopy);
01141 
01142     return NodeCopy;
01143 }
01144 
01145 
01146 /***********************************************************************************************
01147 >   void TextStory::PolyCopyNodeContents(NodeRenderable* pNodeCopy)
01148 
01149     Author:     Phil_Martin (Xara Group Ltd) <camelotdev@xara.com>
01150     Created:    18/12/2003
01151     Outputs:    -
01152     Purpose:    Polymorphically copies the contents of this node to another
01153     Errors:     An assertion failure will occur if NodeCopy is NULL
01154     Scope:      protected
01155                                      
01156 ***********************************************************************************************/
01157 
01158 void TextStory::PolyCopyNodeContents(NodeRenderable* pNodeCopy)
01159 {
01160     ENSURE(pNodeCopy, "Trying to copy a node's contents into a NULL node");
01161     ENSURE(IS_A(pNodeCopy, TextStory), "PolyCopyNodeContents given wrong dest node type");
01162 
01163     if (IS_A(pNodeCopy, TextStory))
01164         CopyNodeContents((TextStory*)pNodeCopy);
01165 }
01166 
01167 
01168 
01169 /***********************************************************************************************
01170 >   void TextStory::CopyNodeContents(TextStory* NodeCopy)
01171 
01172     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
01173     Created:    28/4/93
01174     Outputs:    A copy of this node
01175     Purpose:    This method copies the node's contents to the node pointed to by NodeCopy.
01176     Errors:     An assertion failure will occur if NodeCopy is NULL
01177 ***********************************************************************************************/
01178 
01179 void TextStory::CopyNodeContents(TextStory* NodeCopy)
01180 {
01181     // Ask the base class to do its bit
01182     BaseTextClass::CopyNodeContents(NodeCopy);
01183 
01184     // Copy the rest of the data (which should not be that set by the default constructor?)
01185     NodeCopy->StoryMatrix           = StoryMatrix;
01186 
01187     NodeCopy->StoryWidth            = StoryWidth;
01188 
01189     NodeCopy->TextOnPathReversed    = TextOnPathReversed;
01190     NodeCopy->TextOnPathTangential  = TextOnPathTangential;
01191     NodeCopy->PrintAsShapes         = PrintAsShapes;
01192     NodeCopy->WordWrapping          = WordWrapping;
01193 
01194     NodeCopy->ImportFormatWidth     = ImportFormatWidth;
01195     NodeCopy->ImportBaseShift       = ImportBaseShift;
01196 
01197     NodeCopy->CharsScale            = CharsScale;
01198     NodeCopy->CharsAspect           = CharsAspect;
01199     NodeCopy->CharsRotation         = CharsRotation;
01200     NodeCopy->CharsShear            = CharsShear;
01201     
01202     NodeCopy->mLeftIndent           = mLeftIndent;
01203     NodeCopy->mRightIndent          = mRightIndent;
01204 
01205     NodeCopy->AutoKern              = AutoKern;
01206 }
01207 
01208 
01209 /********************************************************************************************
01210 >   String TextStory::Describe(BOOL Plural, BOOL Verbose = TRUE)        
01211 
01212     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
01213     Created:    21/12/94
01214     Inputs:     Plural: Singular or plural description
01215     Returns:    A string describing the node
01216     Purpose:    Gives a description of the TextStory node for the status line etc
01217 ********************************************************************************************/
01218 
01219 String TextStory::Describe(BOOL Plural, BOOL Verbose)       
01220 {
01221     if (Plural)
01222         return(String(_R(IDS_DESCRIBE_TEXTSTORYP)));
01223     else
01224         return(String(_R(IDS_DESCRIBE_TEXTSTORYS)));
01225 }
01226 
01227 
01228 /********************************************************************************************
01229 >   UINT32 TextStory::GetNodeSize() const
01230 
01231     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
01232     Created:    21/12/94
01233     Returns:    The size of the node in bytes
01234     Purpose:    For finding the size of the node
01235 ********************************************************************************************/
01236 
01237 UINT32 TextStory::GetNodeSize() const
01238 {
01239     return (sizeof(TextStory));
01240 }
01241 
01242 
01243 /********************************************************************************************
01244 >   static TextStory* TextStory::CreateTextObject(DocCoord Anchor)
01245 
01246     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
01247     Created:    25/03/95
01248     Inputs:     Anchor - The position the text story should be created at.
01249     Returns:    A pointer to the root of the text object subtree if successful, else NULL
01250     Purpose:    Creates a Text object.
01251     Errors:     An ERROR will be set if we run out of memory
01252 ********************************************************************************************/
01253 
01254 TextStory* TextStory::CreateTextObject(DocCoord Anchor)
01255 {
01256     Matrix StoryMat(Anchor.x, Anchor.y);
01257     return CreateTextObject(StoryMat);
01258 }
01259 
01260 
01261 /********************************************************************************************
01262 >   static TextStory* TextStory::CreateTextObject(Matrix TheMatrix)
01263 
01264     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
01265     Created:    25/03/95
01266     Inputs:     TheMatrix - the matrix to initialise the story with (includes inital position)
01267     Returns:    A pointer to the root of the text object subtree if successful, else NULL
01268     Purpose:    Creates a Text object
01269     Errors:     An ERROR will be set if we run out of memory
01270 ********************************************************************************************/
01271 
01272 TextStory* TextStory::CreateTextObject(Matrix TheMatrix)
01273 {
01274     // Pointers to objects we will create to construct a TextStory
01275     TextStory* pTextStory = NULL;
01276     TextLine* pTextLine = NULL;
01277 //  CaretNode* pCaret = NULL;
01278 //  EOLNode* pEOLN = NULL;
01279     BOOL ok = TRUE;
01280 
01281     // Try to create the root TextStory node
01282     pTextStory = new TextStory();
01283     ok = (pTextStory != NULL);
01284 
01285     // Set the matrix so that the origin is translated to the start point
01286     if (ok)
01287         pTextStory->SetStoryMatrix(TheMatrix);
01288 
01289     // Create a line for the story
01290     if (ok)
01291     {
01292         pTextLine = new TextLine(pTextStory, LASTCHILD);
01293         ok = (pTextLine != NULL);
01294     }
01295 
01296     // Add an EOLNode to the line
01297     if (ok)
01298         ok = (new EOLNode(pTextLine, FIRSTCHILD) != NULL);
01299 
01300     // Create the TextStory's Caret node
01301     if (ok)
01302         ok = (new CaretNode(pTextLine, FIRSTCHILD) != NULL);
01303 
01304     // Clean up if failure
01305     if (!ok && (pTextStory != NULL))
01306     {
01307         pTextStory->CascadeDelete();
01308         delete pTextStory;
01309         pTextStory = NULL;
01310     }
01311 
01312     return pTextStory;
01313 }
01314 
01315 
01316 /********************************************************************************************
01317 >   void TextStory::GetDebugDetails(StringBase* Str)
01318 
01319     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
01320     Created:    1/2/95
01321     Outputs:    Str: String giving debug info about the node
01322     Purpose:    For obtaining debug information about the Node.
01323                 This fn can be deleted before we ship
01324 ********************************************************************************************/
01325 
01326 void TextStory::GetDebugDetails(StringBase* Str)
01327 {
01328 #if DEBUG_TREE
01329 
01330     BaseTextClass::GetDebugDetails(Str);
01331 
01332     String_256          TempStr;
01333     String_256          TempStr2;
01334     TCHAR               floatStr[20];
01335         
01336     (*Str) += TEXT( "\r\nText story Data Dump\r\n" );
01337 
01338     fixed16 abcd[4];
01339     INT32   ef[2];
01340     StoryMatrix.GetComponents(abcd, ef);
01341 
01342     TempStr._MakeMsg( TEXT("\r\nMatrix\r\n"));  
01343     (*Str) += TempStr;
01344     camSnprintf( floatStr, 20, _T("%f,%f"), abcd[0].MakeDouble(), abcd[1].MakeDouble() );
01345     TempStr._MakeMsg( TEXT("a, b :\t#1%s\r\n"), floatStr);
01346     (*Str) += TempStr;
01347     camSnprintf( floatStr, 20, _T("%f,%f"), abcd[2].MakeDouble(), abcd[3].MakeDouble() );
01348     TempStr._MakeMsg( TEXT("c, d :\t#1%s\r\n"), floatStr);
01349     (*Str) += TempStr;
01350     TempStr._MakeMsg( TEXT("e, f :\t#1%ld,\t#2%ld\r\n"), ef[0], ef[1]);
01351     (*Str) += TempStr;
01352 
01353     TempStr._MakeMsg( TEXT("\r\nIndents")); 
01354     (*Str) += TempStr;
01355     TempStr._MakeMsg( TEXT("\tLeft\t#1%ld\r\n"), GetLeftIndent());
01356     (*Str) += TempStr;
01357     TempStr._MakeMsg( TEXT("\tRight\t#1%ld\r\n"), GetRightIndent());
01358     (*Str) += TempStr;
01359     TempStr._MakeMsg( TEXT("\tStory Width\t#1%ld\r\n"), GetStoryWidth());
01360     (*Str) += TempStr;
01361 
01362     TempStr._MakeMsg( TEXT("\r\nFlags"));   
01363     (*Str) += TempStr;
01364     if (IsTextOnPathReversed())
01365         TempStr._MakeMsg( TEXT("\tText on path is reversed\r\n"));  
01366     else
01367         TempStr._MakeMsg( TEXT("\tText on path is NOT reversed\r\n"));  
01368     (*Str) += TempStr;
01369     if (IsTextOnPathTangential())
01370         TempStr._MakeMsg( TEXT("\tText on path is tangential\r\n"));    
01371     else
01372         TempStr._MakeMsg( TEXT("\tText on path is NOT tangential\r\n"));    
01373     (*Str) += TempStr;
01374     if (IsPrintingAsShapes())
01375         TempStr._MakeMsg( TEXT("\tText is printing as shapes\r\n"));    
01376     else
01377         TempStr._MakeMsg( TEXT("\tText is NOT printing as shapes\r\n"));    
01378     (*Str) += TempStr;
01379     if (IsWordWrapping())
01380         TempStr._MakeMsg( TEXT("\tText is word wrapping\r\n"));
01381     else
01382         TempStr._MakeMsg( TEXT("\tText is NOT word wrapping\r\n"));
01383     (*Str) += TempStr;
01384     if (IsAutoKerning())
01385         TempStr._MakeMsg( TEXT("\tText is auto kerned\r\n"));
01386     else
01387         TempStr._MakeMsg( TEXT("\tText is NOT auto kerned\r\n"));
01388     (*Str) += TempStr;
01389 
01390     TempStr._MakeMsg( TEXT("\tCharsScale = #1%ld\r\n"), CharsScale.GetRawLong() );
01391     (*Str) += TempStr;
01392     TempStr._MakeMsg( TEXT("\tCharsAspect = #1%ld\r\n"), CharsAspect.GetRawLong() );
01393     (*Str) += TempStr;
01394     TempStr._MakeMsg( TEXT("\tCharsRotation = #1%ld\r\n"), CharsRotation.GetRawLong() );
01395     (*Str) += TempStr;
01396     TempStr._MakeMsg( TEXT("\tCharsShear = #1%ld\r\n"), CharsShear.GetRawLong() );
01397     (*Str) += TempStr;
01398     
01399 
01400 #endif
01401 }
01402 
01403 
01404 /********************************************************************************************
01405 >   CaretNode* TextStory::GetCaret()
01406 
01407     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
01408     Created:    10/2/95
01409     Returns:    A pointer to the TextStory's caret node, or NULL
01410     Purpose:    Returns a pointer to the caret node of this TextStory, NULL will be returned
01411                 if there is no Caret.
01412 ********************************************************************************************/
01413 
01414 CaretNode* TextStory::GetCaret()
01415 {
01416     // if there's a caret pointer in the cache then use it
01417     if (CachedCaret)
01418         return CachedCaret;
01419 
01420     // otherwise find the thing inside the story
01421     Node* pNode=FindFirstDepthFirst();
01422     while (pNode != NULL)
01423     {
01424         if (IS_A(pNode,CaretNode))
01425             break;
01426         pNode=pNode->FindNextDepthFirst(this);
01427     }
01428     
01429     // if we can't find the thing its trouble!
01430     ERROR2IF(pNode==NULL,NULL,"TextStory::GetCaret() - could not find caret!");
01431 
01432     CachedCaret = (CaretNode*)pNode;    
01433     return CachedCaret;
01434 }
01435 
01436 
01437 
01438 /********************************************************************************************
01439 >   virtual BOOL TextStory::AllowOp(ObjChangeParam* pParam, BOOL SetOpPermissionState = TRUE,
01440                                                             BOOL DoPreTriggerEdit = TRUE)
01441 
01442     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>; Karim 19/01/2000
01443     Created:    6/5/95
01444     Inputs:     pParam - pointer to the change parameter object
01445                 SetOpPermissionState - TRUE to set the Nodes permission flags
01446                 DoPreTriggerEdit     - if TRUE then calls NameGallery::PreTriggerEdit.
01447                                        *Must* be TRUE if the calling Op may make any nodes
01448                                        change their bounds, eg move, line width, cut.
01449                                        Use TRUE if unsure.
01450     Returns:    TRUE if the operation can proceede, FALSE to stop it
01451     Purpose:    Allows the TextStory to abort the following operations under the following
01452                 circumstances
01453 ********************************************************************************************/
01454 BOOL TextStory::AllowOp(ObjChangeParam* pParam, BOOL SetOpPermissionState,
01455                                                 BOOL DoPreTriggerEdit)
01456 {
01457     ERROR2IF(pParam==NULL,FALSE,"TextStory::AllowOp() - pParam==NULL");
01458 
01459     // decide if we allow it ...
01460 
01461     // Karim 14/11/2000
01462     //  Added the !=OBJCHANGE_CALLEDBYCHILD clause.
01463     //  TextStories don't usually pass AllowOp down to their children.
01464     //  This is an exception - see TextStory::AllowOp_AccountForCompound for more info.
01465     BOOL allowed=TRUE;
01466     ObjChangeFlags Flags=pParam->GetChangeFlags();
01467     if (pParam->GetDirection() != OBJCHANGE_CALLEDBYCHILD)
01468         AllowOp_AccountForCompound(pParam, SetOpPermissionState, DoPreTriggerEdit);
01469 
01470     else
01471     {
01472         // we disallow changes to our text chars, but any child NodePath is fair game.
01473         if (pParam->GetCallingChild() == NULL || !pParam->GetCallingChild()->IsNodePath())
01474         {
01475             if (Flags.ReplaceNode || Flags.MultiReplaceNode)
01476             {
01477                 pParam->SetReasonForDenial(_R(IDE_TEXT_USEDBYSTORY));
01478                 allowed=FALSE;
01479             }
01480         }
01481     }
01482 
01483     // if we allow it, (and our parents weren't calling us) see if our parents do ...
01484     if (allowed && pParam->GetDirection()!=OBJCHANGE_CALLEDBYPARENT && Parent!=NULL)
01485     {
01486         // pass a temporary copy of the ObjChangeParam to our parent indicating call direction
01487         // and if all our children are deleted we know we don't delete ourselves so clear delete flag
01488         ObjChangeFlags NewFlags=pParam->GetChangeFlags();
01489         if (pParam->GetDirection()==OBJCHANGE_CALLEDBYCHILD)
01490             NewFlags.DeleteNode=FALSE;
01491         ObjChangeParam ObjParam(pParam->GetChangeType(), NewFlags, pParam->GetChangeObj(),
01492                                 pParam->GetOpPointer(),  OBJCHANGE_CALLEDBYCHILD);
01493         ObjParam.SetCallingChild(this);
01494         allowed=Parent->AllowOp(&ObjParam,SetOpPermissionState,DoPreTriggerEdit);
01495     }
01496 
01497     // if setting permisions ...
01498     if (SetOpPermissionState)
01499     {
01500         if (allowed)
01501         {
01502             if (Parent!=NULL)
01503                 Parent->SetOpPermission(PERMISSION_ALLOWED);
01504 
01505             // if post process required,
01506             // ensure our OnChildChange is called for post processing (by setting permission on ourself),
01507             // and do all pre-op processing we need to do for text
01508             if (pParam->GetDirection()!=OBJCHANGE_CALLEDBYPARENT || Flags.Attribute || Flags.TransformNode)
01509             {
01510                 SetOpPermission(PERMISSION_ALLOWED);
01511                 allowed=PreOpProcessing(pParam);
01512             }
01513         }
01514         else
01515             SetOpPermission(PERMISSION_DENIED,TRUE);
01516     }
01517 
01518     // if we're ok so far and were asked to do a PreTriggerEdit, then
01519     // determine whether the Op may change the bounds of some nodes.
01520     // If it may, then call NameGallery::PreTriggerEdit.
01521     if (allowed && DoPreTriggerEdit)
01522     {
01523         // if the Op is non-NULL then query its MayChangeNodeBounds() method.
01524         UndoableOperation* pChangeOp = pParam->GetOpPointer();
01525         if (pChangeOp != NULL && pChangeOp->MayChangeNodeBounds() && NameGallery::Instance())
01526         {
01527             allowed = NameGallery::Instance()->PreTriggerEdit(pChangeOp, pParam, this);
01528         }
01529     }
01530 
01531     // Ilan 8/5/00
01532     // Inform geom linked attrs of the change.
01533     if(allowed)
01534     {
01535         UndoableOperation* pChangeOp = pParam->GetOpPointer();
01536         BOOL InformGeomLinkedAttrs = SetOpPermissionState && pChangeOp && pChangeOp->MayChangeNodeBounds();
01537         if(InformGeomLinkedAttrs)
01538         {
01539             NodeAttribute* pNA = FindFirstGeometryLinkedAttr();
01540             while(pNA)
01541             {
01542                 pNA->LinkedNodeGeometryHasChanged(pChangeOp);
01543                 pNA = pNA->FindNextGeometryLinkedAttr();
01544             }
01545         }
01546     }
01547 
01548     // return result (directly, or indirectly via a child AllowOp()) to op
01549     return allowed;
01550 }
01551 
01552 
01553 
01554 /********************************************************************************************
01555 
01556 >   virtual BOOL TextStory::AllowOp_AccountForCompound( ObjChangeParam* pParam,
01557                                                         BOOL SetOpPermissionState,
01558                                                         BOOL DoPreTriggerEdit   )
01559     Author:     Karim_MacDonald (Xara Group Ltd) <camelotdev@xara.com>
01560     Created:    14/11/2000
01561 
01562     Purpose:    Req'd so that the curve bit of text-on-a-curve gets AllowOp messages, thus
01563                 allowing feathered curve bits to update correctly.
01564 
01565                 This is a very much cut-down version of Node::AllowOp_AccountForCompound,
01566                 tailored to the way TextStories behave - DON'T COPY IT!
01567 
01568     See also:   Node::AllowOp_AccountForCompound().
01569 
01570 ********************************************************************************************/
01571 BOOL TextStory::AllowOp_AccountForCompound(ObjChangeParam* pParam, BOOL SetOpPermissionState,
01572                                                               BOOL DoPreTriggerEdit)
01573 {
01574     // we only do the biz if an attr changes, or if we're transformin' or regeneratin'.
01575     ObjChangeFlags Flags = pParam->GetChangeFlags();
01576     if (Flags.Attribute || Flags.TransformNode || Flags.RegenerateNode)
01577     {
01578         ObjChangeDirection OldDirection = pParam->GetDirection();
01579         pParam->SetDirection(OBJCHANGE_CALLEDBYPARENT);
01580 
01581         for (Node*  pNode =  FindFirstChild();
01582                     pNode != NULL;
01583                     pNode =  pNode->FindNext())
01584         {
01585             if (pNode->IsNodePath())
01586                 pNode->AllowOp(pParam, SetOpPermissionState, DoPreTriggerEdit);
01587         }
01588 
01589         pParam->SetDirection(OldDirection);
01590     }
01591 
01592     return TRUE;
01593 }
01594 
01595 
01596 
01597 /********************************************************************************************
01598 >   virtual ChangeCode TextStory::OnChildChange(ObjChangeParam* pParam)
01599 
01600     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
01601     Created:    14/03/95
01602     Inputs:     pParam  =   pointer to a object change parameter class
01603     Returns:    CC_OK       if we have successfully processed the change.
01604                 CC_FAIL     if we cannot handle this particular change and must prevent the
01605                             child from continuing
01606     Purpose:    This function is called at the end of any operation which has affected a TextStory
01607                 in order to (undoably) reformat the story and redraw affected bits
01608 ********************************************************************************************/
01609 ChangeCode TextStory::OnChildChange(ObjChangeParam* pParam)
01610 {
01611     ERROR2IF(pParam==NULL,CC_FAIL,"TextStory::OnChildChange() - pParam==NULL");
01612 
01613     // ignore all but OBJCHANGE_FINISHED codes
01614     if (pParam->GetChangeType()!=OBJCHANGE_FINISHED)
01615         return CC_OK;
01616 
01617     // The AllowOp system will have called the CaretNode and it will have set
01618     // PERMISSION_DENIED on itself. We must ensure that that is cleared at the
01619     // end of the operation.
01620     if (IS_A(this, CaretNode))
01621         SetOpPermission(PERMISSION_UNDEFINED);
01622 
01623     // if we have an op pointer format and redraw the story undoably else just do it
01624     // (ie consecutive attribute applies only do the first one undoably)
01625     BOOL                ok = TRUE;
01626     UndoableOperation* pOp=pParam->GetOpPointer();
01627     if (pOp!=NULL)
01628     {
01629         if (ok)
01630             ok = PrePostTextAction::DoFormatStory(pOp,this);
01631         if (ok)
01632         {
01633             DocRect temprect = RedrawRect;
01634             // Don't call ReleaseCached here without very good reason
01635             // Some Ops which call this function have carefully controlled
01636             // their cacheing so that unwanted re-renders are avoided.
01637             // Calling ReleaseCached here blows all that control away
01638             if (!pParam->GetRetainCachedData())
01639             {
01640                 ReleaseCached(TRUE, FALSE);
01641                 BOOL bFoundEffects = FALSE;
01642                 DocRect effectrect = GetEffectStackBounds(&bFoundEffects);
01643                 if (bFoundEffects) temprect = temprect.Union(effectrect);
01644             }
01645 
01646             ok = pOp->DoInvalidateRegion(FindParentSpread(), temprect);
01647         }
01648     }
01649     else
01650     {
01651         if (ok)
01652             ok = FormatAndChildren(pOp,TRUE);
01653 
01654         BaseDocument* pOwnerDoc = FindOwnerDoc();
01655         ERROR2IF(pOwnerDoc==NULL,CC_FAIL,"TextStory::OnChildChange() - pDoc==NULL");
01656         if (ok && IS_A(pOwnerDoc, Document))
01657         {
01658             DocRect temprect = RedrawRect;
01659             if (!pParam->GetRetainCachedData())
01660             {
01661                 ReleaseCached(TRUE, FALSE);
01662                 BOOL bFoundEffects = FALSE;
01663                 DocRect effectrect = GetEffectStackBounds(&bFoundEffects);
01664                 if (bFoundEffects) temprect = temprect.Union(effectrect);
01665             }
01666             ((Document*)pOwnerDoc)->ForceRedraw(FindParentSpread(), temprect, TRUE, this, FALSE);
01667         }
01668     }
01669 
01670     if (ok)
01671         return CC_OK;
01672     else
01673         return CC_FAIL;
01674 }
01675 
01676 
01677 /********************************************************************************************
01678 >   virtual BOOL TextStory::CanBecomeA(BecomeA* pBecomeA)
01679 
01680     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
01681     Created:    10/02/94
01682     Inputs:     InkClass: The class of object to turn into
01683                 pNumObjects = ptr to place number of objects of type pClass that will be created (Note: can be NULL).
01684                               *pNumObects in undefined on entry
01685     Returns:    TRUE if the node, or any of its children can transmogrify themselves to become
01686                 an InkClass object
01687     Purpose:    This function is used by the convert to shapes operation. It determines if
01688                 the node or any of its children can convert themselves into an InkClass object.
01689 
01690                 The number you put into pNumObjects (if it's not NULL) should exactly equal the total number
01691                 of pClass objects you create.  It should NOT contain any additional objects you may produce
01692                 such as group objects for containing the pClass object, or attributes.
01693 
01694                 Also, the entry value of *pNumObjects cannot be assumed to be 0.
01695 ********************************************************************************************/
01696 
01697 BOOL TextStory::CanBecomeA(BecomeA* pBecomeA)
01698 {
01699     // A TextStory can become a NodePath
01700     if (pBecomeA->BAPath())
01701     {
01702         if (pBecomeA->IsCounting())
01703         {
01704             // Sum the number of paths our immediate children will create
01705             Node* pNode = FindFirstDepthFirst();
01706             while (pNode!=NULL && pNode!=this)
01707             {
01708                 // Call child to update BecomeA count
01709                 pNode->CanBecomeA(pBecomeA);
01710 
01711                 // Text chars don't have their own CanBecomeA, so count them here.
01712                 if (IS_A(pNode, TextChar) && !((TextChar*)pNode)->IsAVisibleSpace())
01713                     pBecomeA->AddCount(1);
01714 
01715                 pNode = pNode->FindNextDepthFirst(this);
01716             }
01717         }
01718 
01719         return TRUE;
01720     }
01721 
01722     return FALSE;
01723 }
01724 
01725 
01726 /********************************************************************************************
01727 >   virtual BOOL TextStory::DoBecomeA(BecomeA* pBecomeA)
01728 
01729     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
01730     Created:    30/3/95
01731     Inputs:     pBecomeA - ptr to class containing info needed to become a new type of node.
01732     Returns:    FALSE if fails
01733     Purpose:    Transforms the object into another type of object.
01734 ********************************************************************************************/
01735 
01736 BOOL TextStory::DoBecomeA(BecomeA* pBecomeA)
01737 {
01738 
01739 // BODGE TEXT - pass back should delete nodes if it fails - though MarkN claimed not when I wrote this!
01740 
01741     ERROR2IF(pBecomeA==NULL,FALSE,"TextStory::DoBecomeA() - pBecomeA==NULL");
01742 
01743     // here to overcome scope problem
01744     Node* pNode=NULL;
01745 
01746     // create a format region to keep an attribute stack
01747     FormatRegion FRegion;
01748     FormatRegion* pFormatRegion=&FRegion;
01749     if (pFormatRegion->Init(this)==FALSE)
01750         return FALSE;
01751     pFormatRegion->SaveContext();
01752 
01753     // if not passing back, create a NodeGroup to encompass the story
01754     // BODGE - should use ALLOC_WITH_FAIL
01755     NodeGroup* pStoryNodeGroup=NULL;
01756     if (pBecomeA->GetReason()!=BECOMEA_PASSBACK)
01757     {
01758         pStoryNodeGroup=new NodeGroup;
01759         if (pStoryNodeGroup==NULL)
01760             goto Fail;
01761 
01762         // render story level attrs and copy non-text attrs
01763         pNode=FindFirstChild();
01764         while (pNode)
01765         {
01766             if (pNode->IsAnAttribute())
01767             {
01768                 pNode->Render(pFormatRegion);   // render attributes
01769                 if (!pNode->IsKindOfTextAttribute())
01770                     if (pNode->CopyNode(pStoryNodeGroup, LASTCHILD)==FALSE)
01771                         goto Fail;
01772             }
01773             pNode=pNode->FindNext();
01774         }
01775     }
01776 
01777     // convert all the story's childs either attaching to the StoryNodeGroup or PassingBack
01778     pNode=FindFirstChild();
01779     while (pNode)
01780     {
01781         // convert all TextLines to groups (inc attrs and chars)
01782         if (IS_A(pNode,TextLine))
01783         {
01784             TextLine* pTextLine=(TextLine*)pNode;
01785             NodeGroup* pLineNodeGroup=NULL;
01786             if (pTextLine->CreateNodeGroup(&pLineNodeGroup,pFormatRegion,pBecomeA)==FALSE)
01787                 goto Fail;
01788             if (pBecomeA->GetReason()!=BECOMEA_PASSBACK)
01789                 pLineNodeGroup->AttachNode(pStoryNodeGroup,LASTCHILD);
01790         }
01791         // copy any NodePath with it's children attrs
01792         if (pNode->IsNodePath())
01793         {
01794             NodePath* pNodePath=(NodePath*)pNode;
01795             NodePath* pNodePathCopy=(NodePath*)pNodePath->SimpleCopy();
01796             if (pNodePathCopy==NULL)
01797                 goto Fail;
01798             if (pNodePath->CopyChildrenTo(pNodePathCopy)==FALSE)
01799                 goto Fail;
01800             if (pBecomeA->GetReason()!=BECOMEA_PASSBACK)
01801             {
01802                 pNodePathCopy->AttachNode(pStoryNodeGroup,LASTCHILD);
01803                 pBecomeA->PassBack(pNodePathCopy, pNodePath);
01804             }
01805             else
01806                 if (pBecomeA->PassBack(pNodePathCopy,pNodePath)==FALSE)
01807                     goto Fail;
01808         }
01809         pNode=pNode->FindNext();
01810     }
01811 
01812     // Must do this here or else the current attributes in the region
01813     // will have been deleted
01814     pFormatRegion->RestoreContext();
01815 
01816     // now either insert the replace the story or pass it back
01817     switch (pBecomeA->GetReason())
01818     {
01819         case BECOMEA_REPLACE:
01820         {
01821             UndoableOperation* pUndoOp = pBecomeA->GetUndoOp();
01822 
01823             // Deselect the node
01824             SetSelected(TRUE);      // ensures all children are deselected
01825             SetSelected(FALSE);
01826 
01827             // are we undoable or not ...
01828             if (pUndoOp!=NULL)
01829             {
01830                 // hide the text story
01831                 NodeHidden* pNodeHidden;
01832                 if (pUndoOp->DoHideNode(this,FALSE,&pNodeHidden)==FALSE)
01833                     goto FailNoRestore;
01834                 // Insert the NodeGroup next to the old hidden TextStory
01835                 if (!pUndoOp->DoInsertNewNode(pStoryNodeGroup,pNodeHidden,NEXT,FALSE,FALSE,FALSE,FALSE))
01836                     goto FailNoRestore;
01837 
01838             }
01839             else
01840             {
01841                 // attach the NodeGroup next to the text story, then delete 'this' TextStory - eek!
01842                 pStoryNodeGroup->AttachNode(this,NEXT);
01843                 CascadeDelete();
01844                 delete this;
01845             }
01846 
01847             // Select the group, but only if the caller wants us too (moulds don't, for example)
01848             if (pBecomeA->Reselect() && pUndoOp!=NULL)
01849                 if (!pUndoOp->DoSelectNode(pStoryNodeGroup,FALSE))
01850                     return FALSE;
01851 
01852             break;
01853         }
01854 
01855         case BECOMEA_PASSBACK:
01856             // all pass back done by children!
01857             break;
01858 
01859         default:
01860             ERROR2_PF(FALSE,("Unknown BecomeA reason %d",pBecomeA->GetReason()));
01861             break;
01862     }
01863 
01864     return TRUE;
01865 
01866 
01867 Fail:
01868     pFormatRegion->RestoreContext();
01869 
01870 FailNoRestore:
01871     if (pStoryNodeGroup)
01872     {
01873         pStoryNodeGroup->CascadeDelete();
01874         delete pStoryNodeGroup;
01875     }
01876 
01877     return TRUE;
01878 }
01879 
01880 
01881 /********************************************************************************************
01882 >   static TextStory* TextStory::GetFocusStory()
01883 
01884     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
01885     Created:    16/02/95
01886     Returns:    Pointer to the TextStory with the active caret (can be NULL).
01887     Purpose:    This function should be used to get a pointer to the TextStory with the input
01888                 focus.  There can be only one such story with an active caret at any time,
01889                 although there does not have to aways be one (in this case NULL is returned)
01890 ********************************************************************************************/
01891 
01892 TextStory* TextStory::GetFocusStory()
01893 {
01894     return pFocusStory;
01895 }
01896 
01897 
01898 /********************************************************************************************
01899 >   static void TextStory::SetFocusStory(TextStory* pNewStory)
01900 
01901     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
01902     Created:    16/2/95
01903     Inputs:     pNewStory - pointer to a TextStory to giv the input focus to
01904                             NULL to give no TextStory the input focus
01905     Returns:    FALSE if an error occured
01906     Purpose:    This function should be used to set the pointer with the input focus.
01907                 Setting the pointer to NULL clears the into focus.
01908     Errors:     Attempts to set the intput focus to a non TextStory object.   (ERROR3)  
01909 ********************************************************************************************/
01910 
01911 void TextStory::SetFocusStory(TextStory* pNewStory)
01912 {
01913     if ((pNewStory != NULL) && (!IS_A(pNewStory,TextStory)) )
01914     {
01915         ERROR3("Attempted to set FocusTextStory to a non-TextStory object");
01916         pNewStory = NULL;
01917     }
01918 
01919     pFocusStory = pNewStory;
01920 }
01921 
01922 
01923 /********************************************************************************************
01924 >   NodePath TextStory::GetTextPath()
01925 
01926     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
01927     Created:    10/02/94
01928     Returns:    A pointer to the path or NULL if none
01929     Purpose:    This function should be used to get a pointer to the path the text is on.
01930                 NULL is returned if there is no text path.
01931 ********************************************************************************************/
01932 NodePath* TextStory::GetTextPath() const
01933 {
01934     return (NodePath*)FindFirstChild(CC_RUNTIME_CLASS(NodePath));
01935 }
01936 
01937 
01938 /********************************************************************************************
01939 >   TextLine* TextStory::FindFirstLine()
01940 
01941     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
01942     Created:    27/03/95
01943     Returns:    A pointer to the TextStory's first line
01944     Purpose:    This function should be used to obtain a pointer to the TextStory's
01945                 first TextLine node.
01946 ********************************************************************************************/
01947 TextLine* TextStory::FindFirstLine() const
01948 {
01949     return (TextLine*)FindFirstChild(CC_RUNTIME_CLASS(TextLine));
01950 }
01951 
01952 
01953 /********************************************************************************************
01954 >   TextLine* TextStory::FindLastLine()
01955 
01956     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
01957     Created:    24/7/96
01958     Returns:    ptr to last TextLine in story
01959 ********************************************************************************************/
01960 TextLine* TextStory::FindLastLine() const
01961 {
01962     return (TextLine*)FindLastChild(CC_RUNTIME_CLASS(TextLine));
01963 }
01964 
01965 
01966 /********************************************************************************************
01967 >    void TextStory::RenderObjectBlobs(RenderRegion* pRenderRegion)
01968 
01969     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
01970     Created:    26/4/95
01971     Inputs:     pRenderRegion -
01972     Purpose:    Render the text story's object blobs
01973 ********************************************************************************************/
01974 
01975 void TextStory::RenderObjectBlobs(RenderRegion* pRenderRegion)
01976 {
01977 #if !defined(EXCLUDE_FROM_RALPH)
01978     if (pRenderRegion != NULL)
01979     {
01980         pRenderRegion->SetLineColour(COLOUR_NONE);
01981         pRenderRegion->SetFillColour(COLOUR_UNSELECTEDBLOB);
01982 
01983         pRenderRegion->DrawBlob(GetBlobPosAndSize(), BT_UNSELECTED);
01984     }
01985     else
01986         ERROR3("TextStory::RenderTinyBlobs() - pRenderRegion==NULL");
01987 #endif
01988 }
01989 
01990 
01991 /********************************************************************************************
01992 >    void TextStory::RenderTinyBlobs(RenderRegion* pRenderRegion)
01993 
01994     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
01995     Created:    26/4/95
01996     Inputs:     pRenderRegion -
01997     Purpose:    Render the text story's tiny blobs
01998 ********************************************************************************************/
01999 
02000 void TextStory::RenderTinyBlobs(RenderRegion* pRenderRegion)
02001 {
02002 #if !defined(EXCLUDE_FROM_RALPH)
02003     if (pRenderRegion != NULL)
02004     {
02005         pRenderRegion->SetLineColour(COLOUR_NONE);
02006         pRenderRegion->SetFillColour(COLOUR_UNSELECTEDBLOB);
02007         pRenderRegion->DrawBlob(GetBlobPosAndSize(), BT_UNSELECTED);
02008     }
02009     else
02010         ERROR3("TextStory::RenderTinyBlobs() - pRenderRegion==NULL");
02011 #endif
02012 }
02013 
02014 
02015 /********************************************************************************************
02016 >   DocRect TextStory::GetBlobBoundingRect();
02017 
02018     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
02019     Created:    26/4/95
02020     Returns:    The rectangle of the document covered by the blobs of this text story
02021 ********************************************************************************************/
02022 
02023 DocRect TextStory::GetBlobBoundingRect()
02024 {
02025     // get the bounds of the  test story's blob
02026     INT32     BlobSize=0;
02027     DocCoord BlobPos=GetBlobPosAndSize(&BlobSize);
02028     DocRect  BlobBounds(BlobPos,BlobPos);
02029     
02030     BlobBounds.Inflate(BlobSize/2);
02031 
02032     IncludeChildrensBoundingRects(&BlobBounds);
02033     return BlobBounds;
02034 }
02035 
02036 
02037 /********************************************************************************************
02038 >   DocCoord TextStory::GetBlobPosAndSize(INT32* pSize=NULL)
02039 
02040     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
02041     Created:    26/4/95
02042     Outputs:    pSize - size of a blob if required
02043     Returns:    coord to position blob
02044 ********************************************************************************************/
02045 
02046 DocCoord TextStory::GetBlobPosAndSize(INT32* pSize)
02047 {
02048 #if !defined(EXCLUDE_FROM_RALPH)
02049     // Get the bounds of the text story
02050     DocRect StoryBounds=GetBoundingRect();
02051 
02052     // get the size of a blob
02053     INT32 BlobSize=0;
02054     BlobManager* pBlobMgr=GetApplication()->GetBlobManager();
02055     if (pBlobMgr)
02056         BlobSize=pBlobMgr->GetBlobSize();
02057     else
02058         ERROR3("TextStory::GetObjectBlobPos() - GetBlobManager() returned NULL");
02059 
02060     if (pSize) *pSize=BlobSize;
02061     return DocCoord( StoryBounds.lo.x-BlobSize/2, StoryBounds.hi.y+BlobSize/2 );
02062 #else
02063     return DocCoord(0,0);
02064 #endif
02065 }       
02066 
02067 
02068 /********************************************************************************************
02069 >   virtual BOOL TextStory::HidingNode()
02070 
02071     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
02072     Created:    20/2/95
02073     Outputs:    Can update pFocusStory if it points to this TextStory
02074     Returns:    The base class result
02075     Purpose:    Called when this TextStory is hidden - Clears the input focus from this
02076                 TextStory if it currently has it, then calls the parent class.
02077 ********************************************************************************************/
02078 
02079 BOOL TextStory::HidingNode()
02080 {
02081     // Clear the focus
02082     if (GetFocusStory() == this)
02083     {
02084         SetFocusStory(NULL);
02085     }
02086 
02087     // Call the parent for it's processing
02088     return BaseTextClass::HidingNode();
02089 }
02090 
02091 
02092 /********************************************************************************************
02093 >   BOOL TextStory::MoveCaretToCharacter(VisibleTextNode* pChar, AttachNodeDirection Dir)
02094 
02095     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
02096     Created:    22/3/95
02097     Inputs:     pChar - points to the character to position the caret by    
02098                 Dir - direction relative to character to position
02099     Returns:    TRUE if the caret was successfuly moved
02100                 FALSE if an error occured
02101     Purpose:    This routine moves the caret to the left or right of a specified char
02102 ********************************************************************************************/
02103 
02104 BOOL TextStory::MoveCaretToCharacter(VisibleTextNode* pChar, AttachNodeDirection Dir)
02105 {
02106     ERROR2IF(Dir!=PREV && Dir!=NEXT,FALSE,"Dir must be PREV or NEXT");
02107     ERROR2IF(pChar==NULL,FALSE,"Attempted to move the caret to NULL");
02108     CaretNode* pCaret=GetCaret();
02109     ERROR2IF(pCaret==NULL,FALSE,"TextStory::MoveCaretToCharacter() - TextStory has no caret");
02110 
02111     // ensure not inserted after EOL
02112     AttachNodeDirection AttachDir = Dir;
02113     if (pChar->IsAnEOLNode() && AttachDir==NEXT)
02114         AttachDir = PREV;
02115 
02116     pCaret->MoveNode(pChar, AttachDir);
02117     pCaret->HasMoved();
02118     return AttachCaretAttributes();
02119 }
02120 
02121 
02122 /********************************************************************************************
02123 >   VisibleTextNode* TextStory::GetPrevWordChar(VisibleTextNode* pStartChar)
02124 
02125     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
02126     Created:    8/7/96
02127     Inputs:     pStartChar - char to move from (usually the caret)
02128     Returns:    ptr to first char in previous word, or char after EOL or EOL
02129 ********************************************************************************************/
02130 
02131 VisibleTextNode* TextStory::GetPrevWordChar(VisibleTextNode* pStartChar)
02132 {
02133     ERROR2IF(pStartChar==NULL,NULL,"TextStory::GetPrevWordChar() - pStartChar==NULL");
02134 
02135     // skips chars until non-space found, then return ptr to last non-space before space
02136     // also return ptr to char immediately after EOL and EOL
02137     BOOL NonSpaceFound = FALSE;
02138     VisibleTextNode* pVTN = pStartChar;
02139     while (1)
02140     {
02141         VisibleTextNode* pPrevVTN = pVTN->FindPrevVTNInStory();
02142 
02143         // if at start of story, return ptr to first VTN
02144         if (pPrevVTN==NULL)
02145             return pVTN;
02146 
02147         // if EOL found, if we've moved, return ptr to char after EOL, else return EOL
02148         if (pPrevVTN->IsAnEOLNode())
02149         {
02150             if (pVTN!=pStartChar)
02151                 return pVTN;
02152             else
02153                 return pPrevVTN;
02154         }
02155 
02156         // if prev char not a space, flag this,
02157         // else prev char a space and if non-space found, return ptr to this char
02158         if (!pPrevVTN->IsAVisibleSpace())
02159             NonSpaceFound = TRUE;
02160         else if (NonSpaceFound)
02161             return pVTN;
02162 
02163         pVTN = pPrevVTN;
02164     }
02165 }
02166 
02167 
02168 /********************************************************************************************
02169 >   VisibleTextNode* TextStory::GetNextWordChar(VisibleTextNode* pStartChar)
02170 
02171     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
02172     Created:    8/7/96
02173     Inputs:     pStartChar - char to move from (usually the caret)
02174     Returns:    ptr to first char in next word, or EOL or char after EOL
02175 ********************************************************************************************/
02176 
02177 VisibleTextNode* TextStory::GetNextWordChar(VisibleTextNode* pStartChar)
02178 {
02179     ERROR2IF(pStartChar==NULL,NULL,"TextStory::GetNextWordChar() - pStartChar==NULL");
02180     VisibleTextNode* pVTN = pStartChar;
02181 
02182     // skip the caret so we always start on a char
02183     if (pStartChar->IsACaret())
02184     {
02185         pVTN = pVTN->FindNextVTNInStory();
02186         ERROR2IF(pVTN==NULL,NULL,"TextStory::GetNextWordChar() - no VTN after caret");
02187     }
02188 
02189     // skips chars until space found, then return pointer to first non-space
02190     // also return ptr to EOL and char immediately after EOL
02191     BOOL SpaceFound = FALSE;
02192     while (1)
02193     {
02194         VisibleTextNode* pNextVTN = pVTN->FindNextVTNInStory();
02195 
02196         // if this is an EOL return pointer to next char if not at end of story
02197         if (pVTN->IsAnEOLNode())
02198         {
02199             if (pNextVTN!=NULL)
02200                 return pNextVTN;
02201             else
02202                 return pVTN;
02203         }
02204 
02205         // if next char is an EOL return pointer to it
02206         ERROR2IF(pNextVTN==NULL,NULL,"TextStory::GetNextWordChar() - story deos not end in EOL");
02207         if (pNextVTN->IsAnEOLNode())
02208             return pNextVTN;
02209 
02210         // if char a space, flag it,
02211         // else non-space and if space found, return ptr to non-space
02212         if (pVTN->IsAVisibleSpace())
02213             SpaceFound = TRUE;
02214         else if (SpaceFound)
02215             return pVTN;
02216 
02217         pVTN = pNextVTN;
02218     }
02219 }
02220 
02221 
02222 /********************************************************************************************
02223 >   BOOL TextStory::MoveCaretLeftAChar()
02224 
02225     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
02226     Created:    7/8/96
02227     Returns:    FALSE if fails
02228     Purpose:    This routine moves the caret to the left by one position
02229 ********************************************************************************************/
02230 
02231 BOOL TextStory::MoveCaretLeftAChar()
02232 {
02233     CaretNode* pCaret = GetCaret();
02234     ERROR2IF(pCaret==NULL,FALSE,"TextStory::MoveCaretLeftAChar() - Story has no charet");
02235 
02236     VisibleTextNode* pNewCaretPos = pCaret->FindPrevVTNInStory();
02237     if (pNewCaretPos==NULL)
02238         return TRUE;    // do nothing if at start of story
02239 
02240     return MoveCaretToCharacter(pNewCaretPos, PREV);
02241 }
02242 
02243 
02244 /********************************************************************************************
02245 >   BOOL TextStory::MoveCaretRightAChar()
02246 
02247     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
02248     Created:    7/8/96
02249     Returns:    FALSE if fails
02250     Purpose:    This routine moves the caret to the right by one position
02251 ********************************************************************************************/
02252 
02253 BOOL TextStory::MoveCaretRightAChar()
02254 {
02255     CaretNode* pCaret = GetCaret();
02256     ERROR2IF(pCaret==NULL,FALSE,"TextStory::MoveCaretRightAChar() - Story has no caret");
02257 
02258     VisibleTextNode* pNewCaretPos = pCaret->FindNextVTNInStory();
02259     ERROR2IF(pNewCaretPos==NULL,FALSE,"TextStory::MoveCaretRightAChar() - no VTN after caret in story!");
02260     pNewCaretPos = pNewCaretPos->FindNextVTNInStory();
02261     if (pNewCaretPos==NULL)
02262         return TRUE;    // if at end of story, do nothing
02263         
02264     return MoveCaretToCharacter(pNewCaretPos, PREV);
02265 }
02266 
02267 
02268 /********************************************************************************************
02269 >   BOOL TextStory::MoveCaretLeftAWord()
02270 
02271     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
02272     Created:    29/8/95
02273     Returns:    TRUE/FALSE for success/failure
02274     Purpose:    This routine moves the caret to the left by a word.
02275                 If the caret is in a word it is moved to the start of that word, else
02276                 the caret is positioned at the start of the previous word
02277 ********************************************************************************************/
02278 
02279 BOOL TextStory::MoveCaretLeftAWord()
02280 {
02281     CaretNode* pCaret = GetCaret();     // Obtain the caret
02282     ERROR2IF(pCaret == NULL, FALSE, "Story had no caret");
02283 
02284     // Get the new caret position
02285     VisibleTextNode* pStartOfPrev = GetPrevWordChar(pCaret);
02286     ERROR2IF(pStartOfPrev == NULL, FALSE, "Didn't find prev word start");
02287 
02288     // Move the caret
02289     if (pStartOfPrev != pCaret)
02290         return MoveCaretToCharacter(pStartOfPrev, PREV);
02291     else
02292         return TRUE;
02293 }
02294 
02295 
02296 /********************************************************************************************
02297 >   BOOL TextStory::MoveCaretRightAWord()
02298 
02299     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
02300     Created:    29/9/95
02301     Returns:    TRUE if the caret was moved a word to the right.
02302                 FALSE if the caret is already positioned at the end of the TextLine
02303     Purpose:    This routine moves the caret to the right by a word.  It caret is positioned
02304                 to the left of the first character encountered after a space.
02305 ********************************************************************************************/
02306 
02307 BOOL TextStory::MoveCaretRightAWord()
02308 {
02309     CaretNode* pCaret = GetCaret();     // Obtain the caret
02310     ERROR2IF(pCaret == NULL, FALSE, "Story had no caret");
02311 
02312     // Get the new caret position
02313     VisibleTextNode* pStartOfNext = GetNextWordChar(pCaret);
02314     ERROR2IF(pStartOfNext == NULL, FALSE, "Didn't find next word start");
02315 
02316     // Move the caret
02317     if (pStartOfNext != pCaret)
02318         return MoveCaretToCharacter(pStartOfNext, PREV);
02319     else
02320         return TRUE;
02321 }
02322 
02323 
02324 /********************************************************************************************
02325 >   BOOL TextStory::MoveCaretToStartOfLine()
02326 
02327     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
02328     Created:    29/8/95
02329     Returns:    TRUE for success, FALSE if there was an error
02330     Purpose:    This routine moves the caret to the start of the TextLine that it is on
02331 ********************************************************************************************/
02332 
02333 BOOL TextStory::MoveCaretToStartOfLine()
02334 {
02335     CaretNode* pCaret = GetCaret();     // Obtain the caret
02336     ERROR2IF(pCaret == NULL, FALSE, "Story had no caret");
02337 
02338     // Get its parent (which should be a TextLine)
02339     TextLine* pParent = (TextLine*)pCaret->FindParent();
02340     ERROR2IF(!IS_A(pParent,TextLine), FALSE, "Parent of Caret was not a TextLine!");
02341 
02342     // Get the first child of the TextLine
02343     VisibleTextNode* pFirstChar = pParent->FindFirstVTN();
02344 
02345     // Only move the caret if we need to
02346     if (pFirstChar != pCaret)
02347         return MoveCaretToCharacter(pFirstChar, PREV);
02348     else
02349         return TRUE;
02350 }
02351 
02352 
02353 /********************************************************************************************
02354 >   BOOL TextStory::MoveCaretToEndOfLine()
02355 
02356     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
02357     Created:    9/7/96
02358     Returns:    FALSE if fails
02359     Purpose:    Move caret to end of TextLine which it is on
02360 ********************************************************************************************/
02361 
02362 BOOL TextStory::MoveCaretToEndOfLine()
02363 {
02364     CaretNode* pCaret = GetCaret();
02365     ERROR2IF(pCaret==NULL,FALSE,"TextStory::MoveCaretToEndOfLine() - story has no caret");
02366     TextLine* pCaretLine = (TextLine*)pCaret->FindParent();
02367     ERROR2IF(pCaretLine==NULL || !IS_A(pCaretLine,TextLine), FALSE, "Parent of Caret was not a TextLine!");
02368 
02369     VisibleTextNode* pLastChar = pCaretLine->FindLastVTN();
02370     if (pLastChar->IsAnEOLNode())
02371     {
02372         pLastChar = pLastChar->FindPrevVTNInLine();
02373         ERROR2IF(pLastChar==NULL,FALSE,"TextStory::MoveCaretToEndOfLine() - should be at least 1 other VTN on line - the caret!");
02374     }
02375 
02376     if (pLastChar!=pCaret)
02377         return MoveCaretToCharacter(pLastChar, NEXT);
02378 
02379     return TRUE;
02380 }                                       
02381 
02382 
02383 /********************************************************************************************
02384 >   BOOL TextStory::AttachCaretAttributes()
02385 
02386     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
02387     Created:    15/03/95
02388     Returns:    TRUE if the attributes were successfully applied to the caret
02389                 FALSE if an error occured
02390     Purpose:    This function should be called after the caret has been moved to its new
02391                 location.  It deletes all its previous children, and copies the children of
02392                 the character to the left of it.  If there isn't anything to the left then
02393                 the attributes to the right are used; if there isn't anything to the right
02394                 then no attributes are attached so the defaults are used.
02395                 This function broacasts an attribute changed message
02396     Errors:     Returns TRUE if it worked, FALSE if the copy failed
02397 ********************************************************************************************/
02398 
02399 BOOL TextStory::AttachCaretAttributes()
02400 {
02401     CaretNode* pCaret = GetCaret();
02402     BOOL Success = TRUE;
02403     BOOL TellPeople = FALSE;
02404     ERROR2IF(pCaret==NULL,FALSE,"Text story didn't have caret");
02405 
02406     // Identify where were are going to get the new attributes from
02407     VisibleTextNode* pAttributeSource = pCaret->FindPrevVTNInLine();
02408     if (pAttributeSource == NULL)
02409         pAttributeSource = pCaret->FindNextVTNInLine();
02410 
02411     // See if the attributes are the same as the current ones
02412 /*  if (pAttributeSource != NULL)
02413     {
02414         Node* pCaretChild = pCaret->FindFirstChild();
02415         Node* pSourceChild = pAttributeSource->FindFirstChild();
02416 
02417         if ( ( (pCaretChild == NULL) && (pSourceChild != NULL) ) ||
02418              ( (pCaretChild != NULL) && (pSourceChild == NULL) ) )
02419         {
02420             TellPeople = TRUE;
02421         }
02422 
02423         if ( (pCaretChild != NULL) && (pSourceChild != NULL) )
02424         {
02425             while ((pCaretChild != NULL) && (pSourceChild != NULL))
02426             {
02427                 if ( (pCaretChild->IsAnAttribute()) && (pSourceChild->IsAnAttribute()) )
02428                 {
02429                     if (! ( *((NodeAttribute*)pCaretChild) == *((NodeAttribute*)pSourceChild) ))
02430                         TellPeople = TRUE;
02431                 }
02432                 else
02433                     TellPeople = TRUE;
02434                 pCaretChild = pCaretChild->FindNext();
02435                 pSourceChild = pSourceChild->FindNext();
02436             }
02437         }
02438     }
02439     else */
02440         TellPeople = TRUE;
02441 
02442     if (pAttributeSource != NULL)
02443     {
02444         // Delete the children of the caret
02445         pCaret->DeleteChildren(pCaret->FindFirstChild());
02446 
02447         // Attempt to copy the attributes of the previous character across
02448         Node* pToCopy = pAttributeSource->FindFirstChild(CC_RUNTIME_CLASS(NodeAttribute));
02449         while (Success && (pToCopy != NULL))
02450         {
02451             Success = pToCopy->CopyNode(pCaret, LASTCHILD);
02452             pToCopy = pToCopy->FindNext(CC_RUNTIME_CLASS(NodeAttribute));
02453         }
02454     }
02455 
02456     // Update other people
02457     if (TellPeople)
02458         GetApplication()->FindSelection()->Update();
02459 
02460     return Success;
02461 }
02462 
02463 
02464 /********************************************************************************************
02465 >   virtual void TextStory::Transform(TransformBase& transform)
02466 
02467     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
02468     Created:    9/3/95
02469     Inputs:     transform - some form of transform
02470     Purpose:    transform a text story and any children capable of being transformed
02471 ********************************************************************************************/
02472 
02473 void TextStory::Transform(TransformBase& transform)
02474 {
02475     BOOL ok=TRUE;
02476     if (IS_A(&transform, Trans2DMatrix))
02477     {
02478         // apply transform to story matrix
02479         StoryMatrix *= ((Trans2DMatrix&)transform).GetMatrix();
02480 
02481         // decompose new story matrix
02482         FIXED16  Scale=1;
02483         FIXED16  Aspect=1;
02484         ANGLE    Rotation=0;
02485         ANGLE    Shear=0;
02486         DocCoord Translation(0,0);
02487         ok=StoryMatrix.Decompose(&Scale,&Aspect,&Rotation,&Shear,&Translation);
02488 //TRACEUSER( "Ed", _T("\nNew        Story Matrix - Scale=%8.5f, Aspect=%8.5f, Rotation=%9.4f, Shear=%8.5f, Translation=%d,%d\n"),Scale.MakeDouble(),Aspect.MakeDouble(),Rotation.MakeDouble()*180/PI,Shear.MakeDouble()*180/PI,Translation.x,Translation.y);
02489         if (Scale<0)
02490         {
02491             Scale=-Scale;
02492             Shear=-Shear;
02493         }
02494 
02495         // if the scale or aspect in the matrix deviate from unit by more than a specified amount normalise them
02496         const FIXED16 NormaliseLimit=0.001;
02497         FIXED16 AbsScaleError  = Scale-1;
02498         FIXED16 AbsAspectError = Aspect-1;
02499         AbsScaleError  =  AbsScaleError<0 ? -AbsScaleError  : AbsScaleError;
02500         AbsAspectError = AbsAspectError<0 ? -AbsAspectError : AbsAspectError;
02501         if (ok && (AbsScaleError>NormaliseLimit || AbsAspectError>NormaliseLimit) )
02502         {
02503             // determine if we can handle aspect ratio transforms in the attributes
02504             BOOL TransformAspect=(GetTextPath()==NULL);
02505             if (TransformAspect==FALSE)
02506                 Aspect=1;   
02507 
02508             // any change in width applied to attributes must also be applied to the story width
02509             FIXED16 AbsAspect = Aspect<0 ? -Aspect : Aspect;
02510             if (ok)
02511                 SetStoryWidth( XLONG(GetStoryWidth()) * Scale * AbsAspect );
02512             if (ok)
02513                 SetLeftIndent( XLONG(GetLeftIndent()) * Scale * AbsAspect );
02514             if (ok)
02515                 SetRightIndent( XLONG(GetRightIndent()) * Scale * AbsAspect );
02516 
02517             // create a set containing attributes which will be transformed
02518             AttrTypeSet AffectedAttrs;
02519             if (ok) ok=AffectedAttrs.AddToSet(CC_RUNTIME_CLASS(AttrTxtFontSize));
02520             if (ok) ok=AffectedAttrs.AddToSet(CC_RUNTIME_CLASS(AttrTxtLineSpace));
02521             if (ok) ok=AffectedAttrs.AddToSet(CC_RUNTIME_CLASS(AttrTxtBaseLine));
02522             if (ok) ok=AffectedAttrs.AddToSet(CC_RUNTIME_CLASS(AttrTxtLeftMargin));
02523             if (ok) ok=AffectedAttrs.AddToSet(CC_RUNTIME_CLASS(AttrTxtRightMargin));
02524             if (ok) ok=AffectedAttrs.AddToSet(CC_RUNTIME_CLASS(AttrTxtFirstIndent));
02525             if (ok) ok=AffectedAttrs.AddToSet(CC_RUNTIME_CLASS(AttrTxtRuler));
02526             if (ok && TransformAspect) ok=AffectedAttrs.AddToSet(CC_RUNTIME_CLASS(AttrTxtAspectRatio));
02527 
02528             // We add all attributes, including the defaults to the TextStory
02529             if (ok) ok=MakeAttributeComplete(NULL, TRUE, &AffectedAttrs, TRUE);
02530             // Now localise the stories attributes, globally, checking for duplicates
02531             if (ok) ok=LocaliseCommonAttributes(TRUE,TRUE,&AffectedAttrs);
02532 
02533             // Before we transform the TextStory we must add all non-common defaults to the
02534             // children of the TextLines.
02535             if (ok)
02536             {
02537                 // Scan all lines, localising and factoring out, to add defaults
02538                 TextLine* pLine = this->FindFirstLine();
02539                 while (pLine!=NULL)
02540                 {
02541                     ok = pLine->LocaliseCommonAttributes(TRUE,FALSE,&AffectedAttrs);
02542                     if (ok) ok = pLine->FactorOutCommonChildAttributes(FALSE,&AffectedAttrs);
02543                     pLine = pLine->FindNextLine();
02544                 }
02545             }
02546             // At this stage there should be no invalidly placed defaults in the tree
02547                 
02548             // apply scale/aspect to attribtes
02549             if (ok)
02550             {
02551                 // for the entire TextStory sub-tree, transform and of the text attributes
02552                 Node* pNode=FindFirstDepthFirst();
02553                 while (pNode!=NULL)
02554                 {
02555                     if (pNode->IsAnAttribute() && pNode->IsKindOfTextAttribute())
02556                         ((AttrTxtBase*)pNode)->BaseLineRelativeTransform(Scale,Aspect);
02557                     pNode=pNode->FindNextDepthFirst(this);
02558                 }
02559 
02560                 // normalise the attributes which may have been affected
02561                 if (ok) ok=FactorOutCommonChildAttributes(FALSE,&AffectedAttrs);
02562 
02563                 // even if factor out failed, remove effect of attribute scale/(aspect) from story matrix
02564                 Matrix AttrChanges, AttrSpace, StoryChanges;
02565                 AttrChanges.Compose(Scale,Aspect);                  // effect of scale/aspect on attrs in attr space
02566                 AttrSpace.Compose(1,1,Rotation,Shear,Translation);  // story to attr space transfom
02567                 StoryChanges=AttrSpace.Inverse();                   // transform into attr space
02568                 StoryChanges*=AttrChanges.Inverse();                // remove effect of attr scale/aspect
02569                 StoryChanges*=AttrSpace;                            // transform back into doc space
02570                 StoryMatrix*=StoryChanges;                          // effect of scale/aspect on attrs in story space
02571             }
02572 
02573             if (ok)
02574             {
02575                 // Finally factor out the attributes globally
02576                 ok = FactorOutCommonChildAttributes(TRUE,&AffectedAttrs);
02577             }
02578 
02579             AffectedAttrs.DeleteAll();
02580         }
02581 
02582         if (!ok)
02583         {
02584             InformError();
02585             return;
02586         }
02587     }
02588     else
02589         ERROR3("TextStory::Transform() - can't handle non-Trans2DMatrix transforms!");
02590 
02591 #if 0
02592 {
02593     FIXED16  Scale=1;
02594     FIXED16  Aspect=1;
02595     ANGLE    Rotation=0;
02596     ANGLE    Shear=0;
02597     DocCoord Translation(0,0);
02598     StoryMatrix.Decompose(&Scale,&Aspect,&Rotation,&Shear,&Translation);
02599     TRACEUSER( "Ed", _T("Normalised Story Matrix - Scale=%8.5f, Aspect=%8.5f, Rotation=%9.4f, Shear=%8.5f, Translation=%d,%d\n"),Scale.MakeDouble(),Aspect.MakeDouble(),Rotation.MakeDouble()*180/PI,Shear.MakeDouble()*180/PI,Translation.x,Translation.y);
02600 }
02601 #endif
02602 
02603     InvalidateBoundingRect();
02604     TransformChildren(transform);
02605 
02606     if (transform.bSolidDrag)
02607         FormatAndChildren();
02608 }
02609 
02610 
02611 /********************************************************************************************
02612 >   VisibleTextNode* TextStory::GetSelectionEnd(BOOL* pDirection = NULL)
02613 
02614     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
02615     Created:    20/03/95
02616     Inputs:     pDirection - Pointer to a BOOL that is filled in with the direction of the selection
02617     Outputs:    FALSE if the the selection end if to the left of the Anchor.
02618                 TRUE if the the selection end if to the right of the Anchor.
02619     Returns:    Pointer to the last selected node in the text selection (at the other end from
02620                 the caret)
02621     Purpose:    This function should be called to find what character nodes are selected.
02622                 The return value is a pointer to the last selected node, at the other end of
02623                 the selected characters from the anchor (which is the caret).
02624                 NULL will be returned if there is no selection.
02625 ********************************************************************************************/
02626 
02627 VisibleTextNode* TextStory::GetSelectionEnd(BOOL* pDirection)
02628 {
02629     CaretNode* pCaret = GetCaret();
02630     ERROR2IF(pCaret==NULL, NULL, "Story has no caret");
02631     VisibleTextNode* pNextChar = NULL;
02632 
02633     // Get the direction of the selection
02634     BOOL Forward = TRUE;
02635     pNextChar = pCaret->FindPrevVTNInStory();
02636     if ((pNextChar != NULL) && pNextChar->IsSelected())
02637         Forward = FALSE;
02638     if (pDirection != NULL)
02639         *pDirection = Forward;
02640 
02641     // Loop to the end of the selection
02642     VisibleTextNode* pResult = NULL;
02643     if (Forward)
02644         pNextChar = pCaret->FindNextVTNInStory();
02645     else
02646         pNextChar = pCaret->FindPrevVTNInStory();
02647     while ((pNextChar != NULL) && pNextChar->IsSelected())
02648     {
02649         pResult = pNextChar;
02650         if (Forward)
02651             pNextChar = pNextChar->FindNextVTNInStory();
02652         else
02653             pNextChar = pNextChar->FindPrevVTNInStory();
02654     }
02655 
02656     if (pResult == pCaret)
02657         pResult = NULL;
02658 
02659     return pResult;
02660 }
02661 
02662 
02663 /********************************************************************************************
02664 >   virtual BOOL TextStory::FormatAndChildren(UndoableOperation* pUndoOp=NULL,
02665                                               BOOL UseNodeFlags=FALSE, BOOL WordWrap=TRUE)
02666 
02667     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
02668     Created:    17/03/95
02669     Inputs:     pUndoOp      - pointer to undoable op (NULL if not undoable)
02670                 UseNodeFlags - TRUE  => use each node's ModifiedByOpflags to determine what has changed
02671                                FALSE => assume the whole story has changed
02672                 WordWrap     - indicates if wordwrapping should be undertaken
02673                                (FALSE for undo/redo as history will restore previous wrap state
02674                                or stories on clipboard, where they should be unwrapped
02675                                or when non-undoably affecting the tree in the doc)
02676     Returns:    FALSE if fails
02677     Purpose:    Reformat a text story
02678 ********************************************************************************************/
02679 
02680 BOOL TextStory::FormatAndChildren(UndoableOperation* pUndoOp, BOOL UseNodeFlags, BOOL WordWrap)
02681 {
02682 #ifndef DISABLE_TEXT_RENDERING
02683     // if whole story is affected, just flag all children as 'ModifiedByOp'
02684     if (UseNodeFlags==FALSE)
02685         FlagNodeAndDescendantsModifiedByOpAndParentsHaveDescendantModifiedByOp();
02686 
02687     // if DOing the format (as opposed to undo/redo)
02688     // ensure all lines in paragraphs have same line level attrs as first line
02689     // flagging any changed lines as modified, before metrics are recached
02690     if (WordWrap)
02691     {
02692         Validate(FALSE);
02693         TextLine* pLine = this->FindFirstLine();
02694         while (pLine!=NULL)
02695         {
02696             if (pLine->FindEOLNode()==NULL)
02697                 if (!pLine->EnsureNextLineOfParagraphHasSameLineLevelAttrs(pUndoOp))
02698                     return FALSE;
02699             pLine = pLine->FindNextLine();
02700         }
02701     }
02702 
02703     // recache metrics in any nodes in story 'ModifiedByOp'
02704     // Karim 02/03/2000 - braces added because this FormatRegion needs to go out of scope
02705     // *before* people start localising attributes in one of the fn's called later on.
02706     {
02707         FormatRegion FormattingRegion;
02708         if (FormattingRegion.Init(this)==FALSE)
02709             return FALSE;
02710         ReCacheNodeAndDescendantsMetrics(&FormattingRegion);
02711     }
02712 
02713     // if text on path, get untransfomed (reversed) copy and associated information
02714     TextStoryInfo StoryInfo;
02715     StoryInfo.pUndoOp  = pUndoOp;
02716     StoryInfo.WordWrap = WordWrap;
02717     if (GetTextPath()!=NULL)
02718     {
02719         if (CreateUntransformedPath(&StoryInfo)==FALSE)
02720             return FALSE;
02721     }
02722 
02723     // determine the story's width, and if it is word wrapping or not
02724     if (StoryInfo.pPath==NULL)
02725         StoryInfo.StoryWidth = GetStoryWidth();     // 0 if text at point
02726     else
02727     {
02728         StoryInfo.StoryWidth = StoryInfo.PathLength;
02729         if (IsWordWrapping())
02730             // we might just as well store the logical width in the story as well
02731             // (for the benefit of displaying it on the ruler)
02732             StoryWidth = StoryInfo.PathLength - StoryInfo.LeftPathIndent - StoryInfo.RightPathIndent;
02733     }
02734     StoryInfo.WordWrapping = IsWordWrapping();
02735 
02736     // format each line in story
02737     TextLine* pLine = this->FindFirstLine();
02738     while (pLine!=NULL)
02739     {
02740         if (pLine->Format(&StoryInfo)==FALSE)
02741             return FALSE;
02742         pLine = pLine->FindNextLine();
02743     }
02744 
02745     // delete the untransformed copy of the path if there was one
02746     if (StoryInfo.pPath!=NULL)
02747         delete StoryInfo.pPath;
02748 
02749     // ensure the caret is in the correct place
02750     CaretNode* pCaret=GetCaret();
02751     ERROR2IF(pCaret==NULL,FALSE,"TextStory::Format() - pCaret==NULL");
02752     if (pCaret->HasMoved()==FALSE)
02753         return FALSE;
02754 
02755     // update the redraw rect to encompass all the affected nodes
02756     UnionNodeAndDescendantsOldAndNewBounds(&RedrawRect);
02757 
02758     // clear all the flags in the story
02759     ClearNodeAndDescendantsFlags();
02760 
02761     Validate();
02762 #endif
02763     return TRUE;
02764 }
02765 
02766 
02767 /*******************************************************************************************
02768     void TextStory::PreExportRender(RenderRegion* pRegion)
02769 
02770     Author:     Chris_Snook (Xara Group Ltd) <camelotdev@xara.com>
02771     Created:    2/2/95
02772     Inputs:     pRegion - points to the export render region
02773     Purpose:    This function is called when the render function passes through this node
02774                 It outputs the  Text Object start and end tokens
02775 ********************************************************************************************/
02776 
02777 void TextStory::PreExportRender(RenderRegion* pRegion)
02778 {
02779 #if EXPORT_TEXT
02780     BOOL exportingAsShapes = FALSE;
02781 
02782     // Determine whether or not to export AI text as shapes (i.e. if there are any 
02783     //  gradient fills in the text.
02784     if (pRegion->IsKindOf(CC_RUNTIME_CLASS(AIEPSRenderRegion)))
02785     {
02786         exportingAsShapes = IsGradientFilled ();
02787     }
02788 
02789     // if there are gradient fills, then don't export text info, just set the RenderRegion up
02790     //  so that it's ready for converting text to shapes.
02791     if (exportingAsShapes)
02792     {
02793         ((AIEPSRenderRegion*) pRegion)->SetTextAsShapes (exportingAsShapes);
02794     }
02795     // if there are no gradient fills, or we're not doing AI export, then carry on as normal.
02796     else
02797     {
02798         if (pRegion->IsKindOf(CC_RUNTIME_CLASS(EPSRenderRegion)))
02799         {
02800             // Output "start text object" token
02801             EPSExportDC *pDC = (EPSExportDC *) CCDC::ConvertFromNativeDC(pRegion->GetRenderDC());
02802             EPSRenderRegion* pEPSRegion = (EPSRenderRegion*)pRegion;
02803 
02804             NodePath* pPath = GetTextPath();
02805 
02806             // Text Story type
02807             if (pPath)
02808                 pDC->OutputValue((INT32)2);
02809             else
02810                 pDC->OutputValue((INT32)0);
02811 
02812             // text object token
02813             pDC->OutputToken(_T("To"));
02814             pDC->OutputNewLine();
02815 
02816             // output the story matrix. We need to make this relative to the spread the
02817             // story is on.
02818             Matrix TempMatrix;
02819             TempMatrix = StoryMatrix;
02820 
02821             // if we're exporting to illustrator, make the text position relative to
02822             // the page.
02823             if (pRegion->IsKindOf(CC_RUNTIME_CLASS(AIEPSRenderRegion)))
02824             {
02825                 DocCoord pos;
02826                 TempMatrix.GetTranslation(pos);
02827 
02828                 Spread* pCurSpread = FindParentSpread();
02829                 if (pCurSpread)
02830                 {
02831                     DocCoord result;
02832                     pCurSpread->SpreadCoordToPagesCoord(&result, pos);
02833                     TempMatrix.SetTranslation(result);
02834                 }
02835             }
02836 
02837             pDC->OutputMatrix(&TempMatrix);
02838             // null start point
02839             pDC->OutputValue((INT32)0);
02840             // start path
02841             pDC->OutputToken(_T("Tp"));
02842             pDC->OutputNewLine();
02843 
02844             // export the path
02845             if (pPath)
02846             {
02847                 // if it's an AI EPS region, we need to save the text attributes after the TP token, or 
02848                 //  Photoshop screws up.
02849                 if (pRegion->IsKindOf (CC_RUNTIME_CLASS (AIEPSRenderRegion)) == FALSE)
02850                 {
02851                     // Need to call this for any text attributes that may have
02852                     // changed
02853                     pEPSRegion->GetValidTextAttributes();
02854                 }
02855 
02856                 // Save the attribute context
02857                 pRegion->SaveContext();
02858 
02859                 // Save the attributes associated with this path.
02860                 Node* pNode = pPath->FindPrevious();
02861                 while (pNode)
02862                 {
02863                     if (pNode->IsAnAttribute())
02864                     {
02865                         ((NodeAttribute*)pNode)->Render(pEPSRegion);
02866                     }
02867                     pNode=pNode->FindPrevious();
02868                 }
02869 
02870                 pNode = pPath->FindFirstChild();
02871                 while (pNode)
02872                 {
02873                     if (pNode->IsAnAttribute())
02874                     {
02875                         ((NodeAttribute*)pNode)->Render(pEPSRegion);
02876                     }
02877                     pNode=pNode->FindNext();
02878                 }
02879 
02880         /*  
02881                 CMapPtrToPtr* pAttribMap = new CMapPtrToPtr(30);
02882                 if (pAttribMap == NULL)
02883                     return;
02884 
02885                 // Now find any attributes that are applied to this node.
02886                 BOOL FoundAttrs = pPath->FindAppliedAttributes(pAttribMap);
02887 
02888                 if (FoundAttrs)
02889                 {
02890                     // iterating all (key, value) pairs
02891                     for (POSITION Pos = pAttribMap->GetStartPosition(); Pos != NULL;)
02892                     {
02893                         void *pType,*pVal;
02894                         pAttribMap->GetNextAssoc(Pos,pType,pVal);
02895 
02896                         NodeAttribute* pAttr = (NodeAttribute*)pVal;
02897                         pAttr->Render(pEPSRegion);
02898                     }
02899                 }
02900         */
02901                 if (IsTextOnPathReversed())
02902                     (pPath->InkPath).Reverse();
02903 
02904                 pEPSRegion->ExportPath(&(pPath->InkPath),FALSE);
02905 
02906                 if (IsTextOnPathReversed())
02907                     (pPath->InkPath).Reverse();
02908                 
02909                 // restore the old context.
02910                 pRegion->RestoreContext();
02911 
02912             }
02913 
02914             // end path
02915             pDC->OutputToken(_T("TP"));
02916             pDC->OutputNewLine();
02917 
02918             if (pRegion->IsKindOf(CC_RUNTIME_CLASS(AIEPSRenderRegion)))
02919             {
02920                 // Need to call this for any text attributes that may have
02921                 // changed.
02922                 //
02923                 // (ChrisG - 8/11/00). Removed, as this should be done by the
02924                 //  individual characters themselves, and can screw up the export
02925                 //  if it isn't, (AI doesn't like lots of font and render changes
02926                 //  without actually drawing any text).
02927         //          pEPSRegion->GetValidTextAttributes();
02928 
02929                 // (ChrisG - 8/11/00) Added support for automatic kerning.
02930                 if (AutoKern)
02931                     pDC->OutputValue ((INT32)1);
02932                 else
02933                     pDC->OutputValue ((INT32)0);
02934 
02935                 pDC->OutputToken (_T("TA"));                   // automatic kerning TA
02936                 pDC->OutputNewLine();
02937                 pDC->OutputToken(_T("0 0 0 TC"));              // Character spacing TC
02938                 pDC->OutputNewLine();
02939                 pDC->OutputToken(_T("100 100 100 TW"));        // Word spacing TW
02940                 pDC->OutputNewLine();
02941                 pDC->OutputToken(_T("0 0 0 Ti"));              // Line indentation Ti
02942                 pDC->OutputNewLine();
02943 
02944                 // (ChrisG - 3/11/00)
02945                 // Export the overflow text if the text is on a path
02946                 if (pPath)
02947                     ((AIEPSRenderRegion *) pRegion)->OverflowTextStart ();
02948             }   
02949         }
02950 
02951         if (pRegion->IsKindOf(CC_RUNTIME_CLASS(NativeRenderRegion)))
02952         {
02953             // Output "extras" token for text
02954             EPSExportDC *pDC = (EPSExportDC *) pRegion->GetRenderDC();
02955 //          EPSRenderRegion* pEPSRegion = (EPSRenderRegion*)pRegion;
02956 
02957             INT32 WordWrapping = (IsWordWrapping() ? 1 : 0);
02958             pDC->OutputValue(WordWrapping);
02959             pDC->OutputValue(GetStoryWidth());
02960             double CScale = CharsScale.MakeDouble();
02961             pDC->OutputReal(CScale);
02962             double CAspect = CharsAspect.MakeDouble();
02963             pDC->OutputReal(CAspect);
02964             double CRotation = CharsRotation.MakeDouble();
02965             pDC->OutputReal(CRotation);
02966             double CShear = CharsShear.MakeDouble();
02967             pDC->OutputReal(CShear);
02968             INT32 AsShapes = ((PrintAsShapes) ? (1) : (0));
02969             pDC->OutputValue(AsShapes);
02970 
02971             // Output version zero and token
02972             pDC->OutputValue((INT32)0);
02973             pDC->OutputToken(_T("ctex"));
02974             pDC->OutputNewLine();
02975 
02976             // Version one stuff
02977         /*      pDC->OutputValue(GetLeftMargin());
02978             pDC->OutputValue(GetRightMargin());
02979 
02980             // Output version and token
02981             pDC->OutputValue((INT32)1);
02982             pDC->OutputToken(_T("ctex"));
02983             pDC->OutputNewLine();*/
02984         }
02985     }
02986 #endif
02987 }
02988 
02989 
02990 /*******************************************************************************************
02991 >   BOOL TextStory::ExportRender(RenderRegion* pRegion)
02992 
02993     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
02994     Created:    14/4/95
02995     Inputs:     pRegion - points to the export render region
02996     Returns:    TRUE if rendered OK (FALSE=>use normal rendering)
02997     Purpose:    This function is called when the render function passes through this node
02998                 It outputs the  Text Object start and end tokens
02999 ********************************************************************************************/
03000 
03001 BOOL TextStory::ExportRender(RenderRegion* pRegion)
03002 {
03003 #if EXPORT_TEXT
03004     // (ChrisG 8/11/00) - Reset the 'exporting text as shapes' flag, so that it's valid for 
03005     //  the next text story - also don't export the 'end of text object' data if we aren't 
03006     //  exporting a text object.
03007     BOOL endTextObject = TRUE;
03008 
03009     if (pRegion->IsKindOf(CC_RUNTIME_CLASS(AIEPSRenderRegion)))
03010     {
03011         if (((AIEPSRenderRegion*) pRegion)->GetTextAsShapes () == TRUE)
03012         {
03013             endTextObject = FALSE;
03014         }
03015         ((AIEPSRenderRegion*) pRegion)->SetTextAsShapes (FALSE);
03016     }
03017     
03018     // don't export the 'end text object' if we shouldn't
03019     if (pRegion->IsKindOf(CC_RUNTIME_CLASS(EPSRenderRegion)) && (endTextObject))
03020     {
03021         // Output "End text object" token
03022         EPSExportDC *pDC = (EPSExportDC *) pRegion->GetRenderDC();
03023         pDC->OutputToken(_T("TO"));
03024         pDC->OutputNewLine();
03025 
03026         // output token if text is wrapped
03027         if (pRegion->IsKindOf(CC_RUNTIME_CLASS(NativeRenderRegion)))
03028         {
03029             if (WillStoryWrapOnPath())
03030             {
03031                 pDC->OutputValue((INT32)EOTAG_TEXTWRAPPED);
03032                 pDC->OutputToken(_T("cso"));
03033                 pDC->OutputNewLine();
03034                 pDC->OutputToken(_T("ceo"));
03035                 pDC->OutputNewLine();
03036             }
03037         }
03038         return TRUE;
03039     }
03040 #endif
03041     return FALSE;   
03042 }
03043 
03044 
03045 /********************************************************************************************
03046 
03047   > BOOL TextStory::EnsureStoryHasCaret()
03048 
03049     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
03050     Created:    28/8/96
03051     Returns:    FALSE if fails
03052     Purpose:    Makes sure the story has a caret node
03053 
03054 ********************************************************************************************/
03055 
03056 BOOL TextStory::EnsureStoryHasCaret()
03057 {
03058     TextLine* pFirstLine = FindFirstLine();
03059     TextLine* pLine = pFirstLine;
03060 
03061     if (pFirstLine != NULL)
03062     {
03063         // Scan all the lines looking for a caret node
03064         while (pLine != NULL)
03065         {
03066             // Does the line have a caret?
03067             if (pLine->FindCaret() != NULL)
03068                 return TRUE;
03069 
03070             // Get next line
03071             pLine = pLine->FindNextLine();
03072         }
03073 
03074         // No caret in the text story, so lets stick one in the first line
03075         // just before the first visible text node
03076         VisibleTextNode* pVTN = pFirstLine->FindFirstVTN();
03077         if (pVTN != NULL)
03078         {
03079             CaretNode* pCaret = new CaretNode(pVTN,PREV);
03080             if (pCaret != NULL)
03081                 return TRUE;
03082         }
03083     }
03084 
03085     return FALSE;
03086 }
03087 
03088 
03089 
03090 /********************************************************************************************
03091  >  BOOL TextStory::PostImport()
03092 
03093     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
03094     Created:    05/04/95
03095     Returns:    FALSE if fails
03096     Purpose:    This functions is called following the complete import of a document.  It
03097                 allows nodes to complete their post import initialisation once they are in
03098                 complete tree.
03099                 The TextStory and its children are formatted following loading.
03100 ********************************************************************************************/
03101 
03102 BOOL TextStory::PostImport()
03103 {
03104 #ifdef RALPH
03105     // We don't want to be rendering at the same time as doing this, because we get
03106     // blobby text problems - fun, but bad !
03107     RalphCriticalSection RalphCS;
03108 #endif
03109 
03110     // Expand any strings that might have been imported from the v2 file format
03111     if (!ExpandImportedStrings())
03112         return FALSE;
03113 
03114     // Make sure it has a caret
03115     if (!EnsureStoryHasCaret())
03116         return FALSE;
03117 
03118     // if not newly inserted, ignore!
03119     if (!ModifiedByOp())
03120         return TRUE;
03121 
03122     // format to a width if one is specified
03123     if (GetImportFormatWidth()!=0)
03124     {
03125         SetWordWrapping(TRUE);
03126         SetStoryWidth(GetImportFormatWidth());
03127         ImportFormatWidth=0;                // prevent it happening on subsequent imports (as all stories are called!)
03128     }
03129 
03130     // account for any corel baseline shift style
03131     BOOL ok = TRUE;
03132     if (ok && ImportBaseShift!=AlignBaseline)
03133     {
03134         ok=CorelStyleBaselineShift(ImportBaseShift);
03135         ImportBaseShift=AlignBaseline;      // prevent it happening on subsequent imports (as all stories are called!)
03136     }
03137 
03138     // format and redraw
03139     if (ok) ok=FormatAndChildren();
03140     if (ok)
03141     {
03142         BaseDocument* pDoc = FindOwnerDoc();
03143         if (pDoc!=NULL && IS_A(pDoc, Document))
03144         {
03145             DocRect temprect = RedrawRect;
03146             ReleaseCached(TRUE, FALSE);
03147             BOOL bFoundEffects = FALSE;
03148             DocRect effectrect = GetEffectStackBounds(&bFoundEffects);
03149             if (bFoundEffects) temprect = temprect.Union(effectrect);
03150             ((Document*)pDoc)->ForceRedraw(FindParentSpread(), RedrawRect, TRUE, this);
03151         }
03152     }
03153 
03154     return ok;
03155 }
03156 
03157 
03158 
03159 /********************************************************************************************
03160 >   BOOL TextStory::PostDuplicate(UndoableOperation* pOp)
03161 
03162     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
03163     Created:    7/10/96
03164     Inputs:     pOp - pointer to operation
03165     Outputs:    -
03166     Returns:    TRUE/FALSE for success/failure
03167     Purpose:    This function is called following the (simple)copy of a text story.
03168                 The TextStory and its children are re-formatted.
03169 ********************************************************************************************/
03170 BOOL TextStory::PostDuplicate(UndoableOperation* pOp)
03171 {
03172     ERROR2IF(pOp==NULL, FALSE, "Operation pointer was NULL");
03173     
03174     return PrePostTextAction::DoFormatStory(pOp, this);
03175 }
03176 
03177 
03178 
03179 /********************************************************************************************
03180 >   BOOL TextStory::CorelStyleBaselineShift(BaseShiftEnum BaseShift);
03181 
03182     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
03183     Created:    5/7/95
03184     Inputs:     BaseShift -
03185     Returns:    FALSE if fails
03186     Purpose:    
03187 ********************************************************************************************/
03188 
03189 BOOL TextStory::CorelStyleBaselineShift(BaseShiftEnum BaseShift)
03190 {
03191     TextLine* pTextLine = this->FindFirstLine();
03192     ERROR2IF(pTextLine==NULL,FALSE,"TextStory::CorelStyleBaselineShift() - couldn't find text line in story");
03193     MILLIPOINT BaselineShift=0;
03194     MILLIPOINT LineAscent=(pTextLine->GetLineAscent()*4)/5;
03195     switch (BaseShift)
03196     {
03197         case AlignAscenders:  BaselineShift=-LineAscent; break;
03198         case AlignDescenders: BaselineShift=-pTextLine->GetLineDescent(); break;
03199         case AlignCentres:    BaselineShift=(-LineAscent-pTextLine->GetLineDescent())/2; break;
03200         default: break;
03201     }
03202     TxtBaseLineAttribute BaselineAttr(BaselineShift);
03203     BOOL ok = ((AttributeValue*)&BaselineAttr)->MakeNode(this,FIRSTCHILD) != NULL;
03204     
03205     return ok;
03206 }
03207 
03208 
03209 /********************************************************************************************
03210 >   BOOL TextStory::ImportFix_MoveScaleToAttrs();
03211 
03212     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
03213     Created:    4/6/95
03214     Returns:    FALSE if fails
03215     Purpose:    Move the scale from the story's matrix down to its attributes
03216                 (ie fix up docs which were created with partly developed by code!)
03217 ********************************************************************************************/
03218 
03219 BOOL TextStory::ImportFix_MoveScaleToAttrs()
03220 {
03221     BOOL ok=TRUE;
03222     // remove scale from story matrix
03223     // Localise
03224     // for attrs on story/line/chars if txtattr and fontsize/linespace/baseline, scale
03225     // FactorOut
03226     return ok;
03227 }
03228 
03229 
03230 /*******************************************************************************************
03231 >   BOOL TextStory::CreateUntransformedPath(TextStoryInfo* pStoryInfo)
03232 
03233     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
03234     Created:    17/4/95
03235     Outputs:    pStoryInfo - info required about the path
03236     Returns:    FALSE if fails
03237     Purpose:    Create a copy of the story's path removing the Story matrix transform
03238                 Also, reverse path if text is reversed on the path
03239     Note:       This should really be broken up into member functions of Path
03240 ********************************************************************************************/
03241 
03242 BOOL TextStory::CreateUntransformedPath(TextStoryInfo* pStoryInfo)
03243 {
03244     ERROR2IF(pStoryInfo==NULL,FALSE,"TextStory::CreateUntransformedPath() - pStoryInfo==NULL");
03245 
03246     // get pointers to info in text story's NodePath
03247     NodePath* pNodePath = GetTextPath();
03248     ERROR2IF(pNodePath==NULL,FALSE,"TextStory::CreateUntransformedPath() - the TextStory has no path!!");
03249     INT32      NumCoords       = pNodePath->InkPath.GetNumCoords();
03250     DocCoord* pNodePathCoords = pNodePath->InkPath.GetCoordArray();
03251     PathVerb* pNodePathVerbs  = pNodePath->InkPath.GetVerbArray();
03252     BOOL ok = (NumCoords>=2 && pNodePathCoords!=NULL && pNodePathVerbs!=NULL);
03253     ERROR2IF(!ok,FALSE,"TextStory::CreateUntransformedPath() - problem with NodePath in story");
03254 
03255     // create a copy of the transformed path
03256     Path* pPath = new Path;
03257     ERROR2IF(pPath==NULL,FALSE,"TextStory::CreateUntransformedPath() - failed to crete path");
03258     if (ok) ok=pPath->Initialise(NumCoords, 12);
03259     if (ok) ok=pPath->CopyPathDataFrom(pNodePathCoords, pNodePathVerbs, NumCoords, TRUE);
03260     DocCoord* pPathCoords = NULL;
03261     PathVerb* pPathVerbs  = NULL;
03262     if (ok)
03263     {
03264         pPathCoords = pPath->GetCoordArray();
03265         pPathVerbs  = pPath->GetVerbArray();
03266         ok = (pPathCoords!=NULL && pPathVerbs!=NULL);
03267         if (!ok) ERROR2RAW("TextStory::CreateUntransformedPath() - Problem copying path");
03268     }
03269 
03270     // if text reversed on path, reverse path
03271     if (ok && IsTextOnPathReversed())
03272         pPath->Reverse();
03273 
03274     // remove the story transform from the copy of the path
03275     if (ok)
03276     {
03277         Matrix matrix = GetStoryMatrix();
03278         matrix = matrix.Inverse();
03279         matrix.transform((Coord*)pPathCoords, NumCoords);
03280     }
03281 
03282     // find the untransformed path length
03283     if (ok)
03284     {
03285         double fPathLength = 0;
03286         ProcessLength PathLengthProcess(64);
03287         ok = PathLengthProcess.PathLength(pPath, &fPathLength);
03288         pStoryInfo->PathLength = (MILLIPOINT)(fPathLength+0.5);
03289     }
03290 
03291     // determine if the path is closed
03292     if (ok)
03293         pStoryInfo->PathClosed = ( (pPathVerbs[NumCoords-1] & PT_CLOSEFIGURE) != 0);
03294 
03295     // determine direction of perpendicular to first point (downwards)
03296     if (ok)
03297     {
03298         // get the x,y offsets ratios for the line position at a perpendicular to the first point on the path ...
03299         double dx = pPathCoords[1].x-pPathCoords[0].x;
03300         double dy = pPathCoords[1].y-pPathCoords[0].y;
03301         double LineLength = sqrt(dx*dx+dy*dy);
03302         pStoryInfo->UnitDirectionVectorX = -dy/LineLength;
03303         pStoryInfo->UnitDirectionVectorY =  dx/LineLength;
03304     }
03305 
03306     pStoryInfo->LeftPathIndent  = GetLeftIndent();
03307     pStoryInfo->RightPathIndent = GetRightIndent();
03308     
03309     // if not ok and we created a path, delete it
03310     if (!ok && pPath!=NULL)
03311     {
03312         delete pPath;
03313         pPath=NULL;
03314     }
03315     
03316     // set output
03317     pStoryInfo->pPath = pPath;
03318 
03319     return ok;
03320 }
03321 
03322 
03323 /*******************************************************************************************
03324 >   void TextStory::MatrixFitToPath()
03325 
03326     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
03327     Created:    17/4/95
03328     Purpose:    modify TextStory matrix appropriately when fitting text to a path
03329 ********************************************************************************************/
03330 
03331 void TextStory::MatrixFitToPath()
03332 {
03333     DocCoord Translation(0,0);
03334     BOOL ok=GetpStoryMatrix()->Decompose(&CharsScale,&CharsAspect,&CharsRotation,&CharsShear,&Translation);
03335     ERROR2IF(!ok,void(0),"TextStory::MatrixFitToPath() - Matrix::Decompose() failed");
03336 
03337     // if text rotated to 2nd/3rd quadrant, reverse text on curve
03338     if (CharsRotation<(ANGLE)(-PI/2) || CharsRotation>(ANGLE)(PI/2))
03339     {
03340         ReverseTextOnPath();
03341         CharsRotation = (CharsRotation>0) ? CharsRotation-PI : CharsRotation+PI;
03342     }
03343 
03344     StoryMatrix=Matrix(Translation.x,Translation.y);        // keep a record of translation
03345 }
03346 
03347 
03348 /*******************************************************************************************
03349 >   void TextStory::MatrixRemoveFromPath()
03350 
03351     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
03352     Created:    17/4/95
03353     Purpose:    modify TextStory matrix appropriately when removing text from a path
03354 ********************************************************************************************/
03355 
03356 void TextStory::MatrixRemoveFromPath()
03357 {
03358     if (IsTextOnPathReversed())
03359     {
03360         CharsRotation = (CharsRotation>0) ? CharsRotation-PI : CharsRotation+PI;
03361         ReverseTextOnPath();
03362     }
03363     Matrix CharsMatrix;
03364     CharsMatrix.Compose(CharsScale,CharsAspect,CharsRotation,CharsShear);
03365     CharsMatrix*=StoryMatrix;
03366     SetStoryMatrix(CharsMatrix);
03367 }
03368 
03369 
03370 /********************************************************************************************
03371 >   VisibleTextNode* TextStory::FindFirstVTN()
03372 
03373     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
03374     Created:    27/04/95
03375     Returns:    A pointer to the first VisibleTextNode in the story.  NULL if error
03376 ********************************************************************************************/
03377 VisibleTextNode* TextStory::FindFirstVTN() const
03378 {
03379     TextLine* pFirstLine = this->FindFirstLine();
03380     ERROR2IF(pFirstLine==NULL,NULL,"TextStory::FindFirstVisibleTextNode() - No TextLine in the TextStory");
03381     return pFirstLine->FindFirstVTN();
03382 }
03383 
03384 
03385 /********************************************************************************************
03386 >   VisibleTextNode* TextStory::FindLastVTN()
03387 
03388     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
03389     Created:    8/7/96
03390     Returns:    A pointer to the last VisibleTextNode in the story.  NULL if error
03391 ********************************************************************************************/
03392 VisibleTextNode* TextStory::FindLastVTN() const
03393 {
03394     TextLine* pLastLine = this->FindLastLine();
03395     ERROR2IF(pLastLine==NULL,NULL,"TextStory::FindLastVTN() - story has no lines!");
03396     return pLastLine->FindLastVTN();
03397 }
03398 
03399 
03400 /********************************************************************************************
03401 >   virtual BOOL TextStory::OnNodePopUp(Spread* pSpread, DocCoord PointerPos, ContextMenu* pMenu)
03402 
03403     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
03404     Created:    27/04/95
03405     Inputs:     pSpread     The spread in which things are happening
03406                 PointerPos  The Location of the mouse pointer at the time of the click
03407                 pMenu       The menu to which items should be added
03408     Returns:    BOOL - TRUE if the node claims the click as its own and FALSE if it is
03409                 not interested in the click
03410     Purpose:    Allows the TextStory to respond to pop up menu clicks on itself.
03411 ********************************************************************************************/
03412 
03413 BOOL TextStory::OnNodePopUp(Spread* pSpread, DocCoord PointerPos, ContextMenu* pMenu)
03414 {
03415 #if !defined(EXCLUDE_FROM_RALPH)
03416     BOOL ok = TRUE;
03417     ok = ok && pMenu->BuildCommand(TOOL_OPTOKEN_TEXT, TRUE);
03418 //  ok = ok && pMenu->BuildCommand(OPTOKEN_APPLYLEFTJUSTIFY);
03419 //  ok = ok && pMenu->BuildCommand(OPTOKEN_APPLYCENTREJUSTIFY);
03420 //  ok = ok && pMenu->BuildCommand(OPTOKEN_APPLYRIGHTJUSTIFY);
03421 //  ok = ok && pMenu->BuildCommand(OPTOKEN_APPLYFULLJUSTIFY, TRUE);
03422 //  WEBSTER-ranbirr-13/11/96
03423 
03424     ok = ok && pMenu->BuildCommand(OPTOKEN_REVERSESTORYPATH);
03425 #ifndef WEBSTER
03426     ok = ok && pMenu->BuildCommand(OPTOKEN_TOGGLEPRINTASSHAPES, TRUE);
03427 #endif //webster
03428     return ok;
03429 #else
03430     return FALSE;
03431 #endif
03432 }
03433 
03434 
03435 /********************************************************************************************
03436 >   BOOL TextStory::DeleteSelectedText(UndoableOperation* pUndoOp)
03437 
03438     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
03439     Created:    20/2/96
03440     Inputs:     pUndoOp -
03441     Returns:    FALSE if fails
03442     Purpose:    Delete all selected text objects in story, deleteing whole lines where possible
03443 ********************************************************************************************/
03444 
03445 BOOL TextStory::DeleteSelectedText(UndoableOperation* pUndoOp)
03446 {
03447 #if !defined(EXCLUDE_FROM_RALPH)
03448     ERROR2IF(pUndoOp==NULL,FALSE,"TextStory::DeleteSelectedText() - pUndoOp==NULL");
03449     BOOL    ok = DeleteSelectedTextLines(pUndoOp);
03450     if (ok) ok = DeleteSelectedTextCharacters(pUndoOp);
03451     return ok;
03452 #else
03453     return FALSE;
03454 #endif
03455 }
03456 
03457 
03458 /********************************************************************************************
03459 >   BOOL TextStory::DeleteSelectedTextLines(UndoableOperation* pUndoOp)
03460 
03461     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
03462     Created:    9/7/96
03463     Inputs:     pUndoOp -
03464     Returns:    FALSE if fails
03465     Purpose:    Called by DeleteSelectedText to delete all selected TextLines
03466                 (including TextLines will all children selected)
03467 ********************************************************************************************/
03468 
03469 BOOL TextStory::DeleteSelectedTextLines(UndoableOperation* pUndoOp)
03470 {
03471 #if !defined(EXCLUDE_FROM_RALPH)
03472     ERROR2IF(pUndoOp==NULL,FALSE,"TextStory::DeleteSelectedTextLines() - pUndoOp==NULL");
03473 
03474     // Delete all selected lines except the last one
03475     BOOL AttrsLocalisedOnStory = FALSE;
03476     TextLine* pLine = this->FindFirstLine();
03477     ERROR2IF(pLine==NULL,FALSE,"TextStory::DeleteSelectedTextLines() - story has no lines");
03478     while (pLine!=NULL)
03479     {
03480         TextLine* pNextLine = pLine->FindNextLine();
03481         if (pLine->WholeLineSelected())
03482         {
03483             // If caret on line to be deleted, move it to next line
03484             CaretNode* pCaret = pLine->FindCaret();
03485             if (pCaret!=NULL)
03486             {
03487                 BaseTextClass*      pAttachNode = NULL;
03488                 AttachNodeDirection AttachDir   = PREV;
03489                 TextLine* pPrevLine = pLine->FindPrevLine();
03490                 if (pNextLine!=NULL)
03491                 {
03492                     pAttachNode = pNextLine->FindFirstVTN();
03493                     ERROR2IF(pAttachNode==NULL,FALSE,"TextStory::DeleteSelectedTextLines() - text line has no VTN");
03494                     AttachDir = PREV;
03495                 }
03496                 else if (pPrevLine!=NULL)
03497                 {
03498                     pAttachNode = pPrevLine->FindLastVTN();
03499                     ERROR2IF(pAttachNode==NULL,FALSE,"TextStory::DeleteSelectedTextLines() - text line has no VTN");
03500                     if (IS_A(pAttachNode,EOLNode))
03501                         AttachDir = PREV;
03502                     else
03503                         AttachDir = NEXT;
03504                 }
03505                 else // deleting only line in story, so create a new one
03506                 {
03507                     pAttachNode = TextLine::CreateEmptyTextLine();
03508                     if (pAttachNode==NULL)
03509                         return FALSE;
03510                     if (!pUndoOp->DoInsertNewNode(pAttachNode,pLine,NEXT, FALSE,FALSE,FALSE,FALSE))
03511                         return FALSE;
03512                     AttachDir = FIRSTCHILD;
03513                 }
03514 
03515                 if (!PositionCaretAction::DoStoreCaretPosition(pUndoOp,this))
03516                     return FALSE;
03517                 pCaret->MakeAttributeComplete();
03518                 pCaret->MoveNode(pAttachNode,AttachDir);    // caret should not be selected
03519                 pCaret->NormaliseAttributes();
03520             }
03521 
03522             // if not already localised, loaclise attrs on story, then hide line
03523             if (!AttrsLocalisedOnStory)
03524                 if (!pUndoOp->DoLocaliseCommonAttributes(this,FALSE,TRUE))
03525                     return FALSE;
03526             AttrsLocalisedOnStory = TRUE;
03527             if (!pLine->DoHideNode(pUndoOp))
03528                 return FALSE;
03529         }
03530         pLine = pNextLine;
03531     }
03532 
03533     // if attrs were loaclised on story, factor out
03534     if (AttrsLocalisedOnStory)
03535         if (!pUndoOp->DoFactorOutCommonChildAttributes(this, TRUE))
03536             return FALSE;
03537 #endif
03538     return TRUE;
03539 }
03540 
03541 
03542 /********************************************************************************************
03543 >   BOOL TextStory::DeleteSelectedTextCharacters(UndoableOperation* pUndoOp)
03544 
03545     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
03546     Created:    20/2/96
03547     Inputs:     pUndoOp -
03548     Returns:    FALSE if fails
03549     Purpose:    delete all (remaining) selected VTN in story,
03550                 joining lines if EOL deleted and story not word wrapping
03551 ********************************************************************************************/
03552 
03553 BOOL TextStory::DeleteSelectedTextCharacters(UndoableOperation* pUndoOp)
03554 {
03555 #if !defined(EXCLUDE_FROM_RALPH)
03556     ERROR2IF(pUndoOp==NULL,FALSE,"TextStory::DeleteSelectedTextCharacters() - pUndoOp==NULL");
03557 
03558     // get a set of non line level attrs on this story, which are to be localised to char level
03559     AttrTypeSet NonLineLevelAttrs;
03560     BOOL ok = this->AddNonLineLevelDescendantAttrsToSet(&NonLineLevelAttrs);
03561 
03562     // delete all the selected chars (except caret) in the story, joining line if EOL deleted
03563     TextLine* pLine = this->FindFirstLine();
03564     ERROR2IF(pLine==NULL,FALSE,"TextStory::DeleteSelectedTextCharacters() - story has no lines");
03565     while (ok && pLine!=NULL)
03566     {
03567         BOOL AttrsLocalisedOnLine = FALSE;
03568         VisibleTextNode* pVTN = pLine->FindFirstVTN();
03569         ERROR2IF(pVTN==NULL,FALSE,"TextStory::DeleteSelectedTextCharacters() - line has no VTN");
03570         while (ok && pVTN!=NULL)
03571         {
03572             // ensure last EOL in story not selected (so not deleted)
03573             if (pVTN->IsAnEOLNode() && pVTN->IsSelected() && pLine->FindNextLine()==NULL)
03574             {
03575                 ERROR3("TextStory::DeleteSelectedTextCharacters() - last EOL of the story selected");
03576                 pVTN->DeSelect(FALSE);
03577             }
03578 
03579             // if char not to be deleted, skip to next char, else ...
03580             if (!pVTN->IsSelected() || pVTN->IsACaret())
03581                 pVTN = pVTN->FindNextVTNInLine();
03582             else
03583             {
03584                 // if attrs not already loaclised on this line, do so
03585                 if (!AttrsLocalisedOnLine)
03586                     ok = pUndoOp->DoLocaliseCommonAttributes(pLine,FALSE,TRUE,&NonLineLevelAttrs);
03587                 AttrsLocalisedOnLine = TRUE;
03588 
03589                 // if deleting EOL move all chars from next line to end of this and hide next line
03590                 if (pVTN->IsAnEOLNode())
03591                 {
03592                     TextLine* pNextLine = pLine->FindNextLine();
03593                     ERROR2IF(pNextLine==NULL,FALSE,"TextStory::DeleteSelectedTextCharacters() - trying to delete last EOL in story!");
03594                     VisibleTextNode* pFirstVTN = pNextLine->FindFirstVTN();
03595                     VisibleTextNode* pLastVTN  = pNextLine->FindLastVTN();
03596                     ERROR2IF(pFirstVTN==NULL || pLastVTN==NULL,FALSE,"TextStory::DeleteSelectedTextCharacters() - no VTN on next line");
03597 
03598                     if (ok) ok = pUndoOp->DoLocaliseCommonAttributes(pNextLine,FALSE,TRUE,&NonLineLevelAttrs);
03599                     if (ok) ok = pFirstVTN->DoMoveNodes(pUndoOp,pLastVTN,pLine,LASTCHILD);
03600                     if (ok) ok = pNextLine->DoHideNode(pUndoOp);
03601                 }
03602 
03603                 // hide VTN, getting pointer to next
03604                 VisibleTextNode* pNextVTN = pVTN->FindNextVTNInLine();
03605                 if (ok) ok = pVTN->DoHideNode(pUndoOp);
03606                 pVTN = pNextVTN;
03607             }
03608         }
03609         // if ok and attrs were localised on the line, factor out
03610         if (ok && AttrsLocalisedOnLine)
03611             ok = pUndoOp->DoFactorOutCommonChildAttributes(pLine,TRUE,&NonLineLevelAttrs);
03612 
03613         pLine = pLine->FindNextLine();
03614     }
03615 
03616     return ok;
03617 #else
03618     return FALSE;
03619 #endif
03620 }
03621 
03622 
03623 /********************************************************************************************
03624 >   INT32 TextStory::BaseComplexCopy(CopyStage Stage, Range& RangeToCopy, Node** pOutput)
03625 
03626     Author:     Mike_Kenny (Xara Group Ltd) <camelotdev@xara.com>
03627     Created:    3/5/95
03628     Inputs:     Stage       - either COPYOBJECT
03629                                      COPYFINISHED
03630                 RangeToCopy - Describes the range which is currently being copied.
03631                 pOutput     - Depends on the Stage parameter
03632                               if Stage=
03633                               COPYOBJECT
03634                                 Then the node pointer pOutput points at NULL. It should be
03635                                 set on exit to point at the copied object or tree of objects
03636                               COPYFINISHED
03637                                 Then the node pointer pOutput points at the resulting copy
03638                                 from COPYOBJECT as it has been inserted into the destination
03639                                 tree.
03640     Returns:    -1  = The routine failed to make a copy.
03641                  0  = No copy has been made.
03642                 +1  = pOutput points to the copy.
03643     Purpose:
03644 ********************************************************************************************/
03645 
03646 INT32 TextStory::BaseComplexCopy(CopyStage Stage, Range& RangeToCopy, Node** pOutput)
03647 {
03648     ERROR2IF(pOutput==NULL,FALSE,"TextStory::RootComplexCopy() called with NULL output pointer");
03649 
03650     switch (Stage)
03651     {
03652         case COPYOBJECT:
03653             if (!BeingCopied)
03654             {
03655                 BeingCopied = TRUE;
03656                 if (Copy(RangeToCopy, pOutput))
03657                     return 1;
03658                 else
03659                     return -1;
03660             }
03661             return 0;
03662             break;
03663 
03664         case COPYFINISHED:
03665             if (BeingCopied)
03666             {
03667                 BeingCopied = FALSE;
03668                 Node* pCopy = (*pOutput);
03669                 if (pCopy && (IS_A(pCopy,TextStory)))
03670                     ((TextStory*)pCopy)->FormatAndChildren(NULL,FALSE,FALSE);
03671             }
03672             return 0;
03673             break;
03674 
03675         default:
03676             return -1;
03677             break;
03678     }
03679 }
03680 
03681 
03682 /********************************************************************************************
03683 >   BOOL TextStory::Copy(Range& RangeToCopy, Node** pOutput)
03684 
03685     Author:     Mike_Kenny (Xara Group Ltd) <camelotdev@xara.com>
03686     Created:    3/5/95
03687     Inputs:     RangeToCopy - Describes the range which is currently being copied.
03688                 pOutput     - a pointer to a node. Set this pointer to point at the tree copy
03689     Returns:    FALSE if fails
03690     Purpose:    Make a copy of the text story and all children which lie inside RangeToCopy
03691 ********************************************************************************************/
03692 
03693 BOOL TextStory::Copy(Range& RangeToCopy, Node** pOutput)
03694 {
03695     ERROR2IF(pOutput==NULL,FALSE,"TextStory::Copy() called with NULL output pointer");
03696 
03697     /* Notes: ok, at this stage, each object in the copy range has been called with
03698        MakeAttributesComplete() apart from those that state they are complex
03699        copy nodes. Here I expect simply to make attributes complete on the
03700        text story and assume the rest of the subtree is correct. Until I changed this
03701        OpCopy used to MakeAttributesComplete on the whole selected range. This was a
03702        problem, as we are actually copying more than the selected range. We are in
03703        effect grouping the selected range under a parent. On pasting, the paste code
03704        only (sensibly) normalises on all first level siblings and hence misses our
03705        'grouped' children, the result being all selected characters having thousands of
03706        atts applied to them which cannot be factored out.
03707     */
03708 
03709     // copy the story
03710     TextStory* pStoryCopy = (TextStory*)SimpleCopy();
03711     BOOL ok = (pStoryCopy!=NULL);
03712 
03713     // Make the attrs complete so we can copy the story correctly
03714     if (ok) ok = MakeAttributeComplete();
03715     if (ok)
03716     {
03717         // Mark all nodes in this current range                     // why is this not part of SetStoryMarkers?!
03718         Node* pNode = RangeToCopy.FindFirst(TRUE);
03719         while (pNode)
03720         {
03721             pNode->SetMarker(TRUE);
03722             pNode=RangeToCopy.FindNext(pNode,TRUE);
03723         }
03724 
03725         // specifically set the flags to copy the correct nodes
03726         SetStoryMarkers(RangeToCopy);
03727         ok = CopyChildrenTo(pStoryCopy, ccMARKED);
03728 
03729         // Now that EOLNodes take attributes we have all sorts of bother with the last EOL.
03730         // The user can't put attributes onto it.  We must copy
03731         // the attributes applied to the ATC prior to the last EOL onto the EOL.
03732         if (ok)
03733         {
03734             VisibleTextNode* pLastNode = pStoryCopy->FindLastVTN();
03735             AbstractTextChar* pPrevLast = NULL;
03736             if (pLastNode!=NULL)
03737                 pPrevLast = (AbstractTextChar*)pLastNode->FindPrevious(CC_RUNTIME_CLASS(AbstractTextChar));
03738             if (pLastNode!=NULL && pPrevLast!=NULL)
03739             {
03740                 // Clear out any attributes off the last EOLNode
03741                 Node* pLastChild = pLastNode->FindFirstChild();
03742                 if (pLastChild!=NULL)
03743                     pLastNode->DeleteChildren(pLastChild);
03744             
03745                 // Copy the correct ones onto it.
03746                 if (pPrevLast->FindFirstChild() != NULL)
03747                     ok = pPrevLast->CopyChildrenTo(pLastNode);
03748             }
03749         }
03750 
03751         // Mark all nodes in this current range                     // is this not made redundant by ClearMarks()?!
03752         pNode = RangeToCopy.FindFirst(TRUE);
03753         while (pNode)
03754         {
03755             pNode->SetMarker(FALSE);
03756             pNode=RangeToCopy.FindNext(pNode,TRUE);
03757         }
03758 
03759         ClearMarks();               // clear flags on source story
03760         pStoryCopy->ClearMarks();   // clear flags on destination story
03761         ResetEOLs(FALSE);           // make EOLs physical again (ie not virtual!)   // why does this take a param?!
03762         NormaliseAttributes();      // on source story                              // why not dest?
03763     }
03764 
03765     // unwrap the story on the clipboard
03766     if (ok) ok = pStoryCopy->UnWrapStory(NULL);
03767 
03768     // insert a caret into the copied story
03769     CaretNode*       pCaret      = NULL;
03770     VisibleTextNode* pAttachNode = NULL;
03771     if (ok) pAttachNode = pStoryCopy->FindFirstVTN();
03772     ok = (pAttachNode!=NULL);
03773     if (ok) pCaret = new CaretNode;
03774     ok = (pCaret!=NULL);    // should an error be set if caret not created?
03775     if (ok) pCaret->AttachNode(pAttachNode, PREV);
03776 
03777     // if we failed but a story copy was created, delete it
03778     if (!ok && pStoryCopy!=NULL)
03779     {
03780         pStoryCopy->CascadeDelete();
03781         delete pStoryCopy;
03782         pStoryCopy = NULL;
03783     }
03784 
03785     *pOutput = pStoryCopy;
03786     return ok;
03787 }
03788 
03789 
03790 /********************************************************************************************
03791 >   BOOL TextStory::UnWrapStory(UndoableOperation* pUndoOp)
03792 
03793     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
03794     Created:    10/3/96
03795     Inputs:     pUndoOp - undoable operation (NULL if not undo-able)
03796     Purpose:    Join consecutive soft lines into single hard lines
03797                 and append a final (virtual) EOL to the story if none
03798 ********************************************************************************************/
03799 
03800 BOOL TextStory::UnWrapStory(UndoableOperation* pUndoOp)
03801 {
03802     TextLine* pThisLine = FindFirstLine();
03803     ERROR2IF(pThisLine==NULL,FALSE,"TextStory::UnWrapStory() - story has no text lines!");
03804     while (pThisLine!=NULL)
03805     {
03806         TextLine* pNextLine = pThisLine->FindNextLine();
03807         if (pThisLine->FindEOLNode()==NULL)
03808         {
03809             if (pNextLine!=NULL)
03810             {
03811                 // wrap next line back to end of this
03812                 VisibleTextNode* pLastVTN = pNextLine->FindLastVTN();
03813                 ERROR2IF(pLastVTN==NULL,FALSE,"TextStory::UnWrapStory() - no VTN on soft line");
03814                 pLastVTN->WrapFromStartOfLineBack(pUndoOp);
03815             }
03816             else
03817             {
03818                 // add a virtual EOL to keep Mike's stuff from falling over
03819                 EOLNode* pEOL = new EOLNode;
03820                 if (pEOL==NULL)
03821                     return FALSE;
03822                 pEOL->SetVirtual(TRUE);
03823                 pEOL->AttachNode(pThisLine,LASTCHILD);
03824             }
03825         }
03826         else
03827             pThisLine = pNextLine;  // move onto start of next paragraph
03828     }
03829 
03830     // ensure op flags not set in story on clipboard
03831     ClearNodeAndDescendantsFlags();
03832 
03833     Validate();
03834 
03835     return TRUE;
03836 }
03837 
03838 
03839 /********************************************************************************************
03840 >   void TextStory::SetStoryMarkers(Range& RangeToCopy)
03841 
03842     Author:     Mike_Kenny (Xara Group Ltd) <camelotdev@xara.com>
03843     Created:    3/5/95
03844     Inputs:     RangeToCopy - Describes the range which is currently being copied.
03845     Purpose:    Scan through the text story setting the necessary copyme bits to make
03846                 sure the text story is copied correctly. The range is used to determin
03847                 what items are actually being copied. If characters are being copied,
03848                 we need to copy a line and text story as well.
03849 ********************************************************************************************/
03850 
03851 void TextStory::SetStoryMarkers(Range& RangeToCopy)
03852 {
03853     // find the first object in the story
03854     Node* pNode = FindFirstChild();
03855     while (pNode)
03856     {
03857         if (pNode->IsAnAttribute())
03858         {
03859             pNode->MarkNodeAndChildren();
03860         }
03861         else
03862         {       
03863             // ok check if its a line and if so do all the character level stuff
03864             if (IS_A(pNode,TextLine))
03865             {
03866                 // find the first child of this particular line
03867                 Node* cNode = NULL;
03868                 Node* qNode = pNode->FindFirstChild();
03869                 while (qNode)
03870                 {
03871                     // Mark in range characters, kern codes and other abstract characters
03872                     // Only copy the char if it is in the search range.
03873                     if (qNode->IsMarked())
03874                     {
03875                         qNode->MarkNodeAndChildren();
03876                         // Save a copy of this chap for later
03877                         cNode=qNode;
03878                     }
03879                     qNode=qNode->FindNext();
03880                 }
03881 
03882                 // we've found an item inrange in this line.....
03883                 if (cNode)
03884                 {
03885                     // Make sure we mark the line too
03886                     qNode = cNode->FindParent();
03887                     qNode->SetMarker(TRUE);
03888 
03889                     // take all the line level attributes with us too
03890                     qNode = qNode->FindFirstChild();
03891                     while (qNode)
03892                     {
03893                         if (qNode->IsAnAttribute())
03894                             qNode->MarkNodeAndChildren();
03895                         qNode=qNode->FindNext();
03896                     }
03897 
03898                     // Now we need to make sure we take the end of line node with us.
03899                     qNode=cNode->FindParent();
03900                     qNode=qNode->FindFirstChild(CC_RUNTIME_CLASS(EOLNode));
03901 
03902                     ERROR3IF((qNode) && (!IS_A(qNode,EOLNode)),"Where has the lines EOLnode gone? - (TextStory::SetStoryMarkers())");
03903 
03904                     // We assume the last node is an EOLnode.
03905                     if (qNode && IS_A(qNode,EOLNode))
03906                     {
03907                         if (!qNode->IsMarked())
03908                         {
03909                             // It wasn't already marked ie it can't have been 'inrange'
03910                             qNode->MarkNodeAndChildren();
03911                             // We need to mark the eolnode as transient
03912                             // ie so we know the difference between unselected
03913                             // and selected nodes.
03914                             ((EOLNode*)qNode)->SetVirtual(TRUE);
03915                         }
03916                     }
03917                 }
03918             }
03919             else
03920             {
03921                 // otherwise its not a line and its not an attribute so is it in the range?
03922                 // if its in range, take it and its children with us
03923                 if (pNode->IsMarked())
03924                     pNode->MarkNodeAndChildren();
03925             }
03926         }
03927     
03928         // Scan for the next object
03929         pNode=pNode->FindNext();
03930     }
03931 
03932     /* I suppose we need to take the caret too,
03933        erh no we need to wait and insert a caret later, it could be in a different line
03934        to the selection if select inside has occured! and hence wont be copied properly
03935 
03936        CaretNode* pCaret = GetCaret();
03937        if (pCaret)
03938            pCaret->MarkNodeAndChildren(); */
03939 
03940 }
03941 
03942 
03943 /********************************************************************************************
03944 >   void TextStory::ResetEOLs(BOOL Status)
03945 
03946     Author:     Mike_Kenny (Xara Group Ltd) <camelotdev@xara.com>
03947     Created:    10/5/95
03948     Inputs:     Status - the status to set for each EOL node.
03949     Purpose:    Scan through all end of line nodes in this text story and set the status
03950                 of their virtual flag
03951 ********************************************************************************************/
03952 
03953 void TextStory::ResetEOLs(BOOL Status)
03954 {
03955     // find the first line in this story
03956     TextLine* pLine = this->FindFirstLine();
03957     while (pLine)
03958     {
03959         // for each line, set its EOL nodes status
03960         EOLNode* pEOL = pLine->FindEOLNode();
03961         if (pEOL!=NULL)
03962             pEOL->SetVirtual(Status);
03963         
03964         pLine=pLine->FindNextLine();
03965     }
03966 }
03967 
03968 
03969 /********************************************************************************************
03970 >   INT32 TextStory::BaseComplexHide(UndoableOperation *pOp)
03971 
03972     Author:     Mike_Kenny (Xara Group Ltd) <camelotdev@xara.com>
03973     Created:    3/5/95
03974     Inputs:     pOp - a pointer to an undoable operation
03975     Returns:    -1  = The routine failed to hide this node.
03976                  0  = Ignored, this object does not support complex hide operations
03977                 +1  = The node and possibly various others have been hidden correctly.
03978     Purpose: This function handles the text story being hidden during operations such as
03979              OpDelete / OpCut / etc. The function is called indirectly via it child nodes.
03980              It should never be called directly. Its purpose is to cope with various parts
03981              of the text story being hidden. It is not possible to DoHideNodes() on a
03982              selection of the text stories children as this may leave the story in an invalid
03983              state. Hence all children ask their parent text story to sort out the problem.
03984              The function is usually called from a derived version of Node::ComplexHide()
03985 ********************************************************************************************/
03986 
03987 INT32 TextStory::BaseComplexHide(UndoableOperation *pOp)
03988 {
03989     ERROR2IF(pOp==NULL, -1, "Operation pointer is NULL in TextStory::BaseComplexHide()");
03990 
03991     if (!DeleteSelectedText(pOp))
03992         return -1;
03993 
03994 #if !defined(EXCLUDE_FROM_RALPH)
03995     // try to regain the caret on completion
03996     TextInfoBarOp::RegainCaretOnOpEnd();
03997 #endif
03998 
03999     return 1;
04000 }
04001 
04002 
04003 /********************************************************************************************
04004 >   static TextStory* TextStory::CreateFromChars(DocCoord Pos, char* pChars, WCHAR* pWChars, Document* pCreateDoc,
04005                                                  LOGFONT* pLogFont=NULL, BOOL ControlCodes=FALSE
04006                                                  DocColour* pColour=NULL)
04007 
04008     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
04009     Created:    25/5/95
04010     Inputs:     Pos          - coord in doc at which to position TextStory
04011                 pChars       - pointer to array of chars  ('\0' terminated)
04012                 pWChars      - pointer to array of WCHARs ('\0' terminated)
04013                 pCreateDoc   - pointer to document story is to be created in
04014                 pLogFont     - optional pointer to a LogFont from which to derive attributes
04015                 ControlCodes - TRUE if control codes should also be processed
04016                 pColour      - text colour
04017     Returns:    Pointer to TextStory, or NULL if fails
04018     Purpose:    Create a TextStory from an array of chars OR WCHARs
04019                 and optionally account for control codes
04020                 and a LogFont (ie size/aspect/bold/italic/typeface/rotation)
04021 
04022     Notes:      Calls ContinueSlowJob periodically to allow progress displays to be updated
04023 
04024 ********************************************************************************************/
04025 
04026 TextStory* TextStory::CreateFromChars(DocCoord Pos, char* pChars, WCHAR* pWChars, Document* pCreateDoc,
04027                                       LOGFONT* pLogFont, BOOL ControlCodes, DocColour* pColour)
04028 {
04029     ERROR2IF(pChars==NULL && pWChars==NULL || pChars!=NULL && pWChars!=NULL, NULL,
04030              "TextStory::CreateFromCharArray() - must specify one and only one char array");
04031     ERROR2IF(pCreateDoc==NULL, NULL, "No creation document");
04032 
04033     // create an empty text story, getting pointers to TextLine and Caret
04034     TextStory* pTextStory=TextStory::CreateTextObject(Pos);
04035     TextLine*  pTextLine =NULL;
04036     CaretNode* pCaretNode=NULL;
04037     BOOL ok=(pTextStory!=NULL);
04038     if (ok)
04039     {
04040         pTextLine = pTextStory->FindFirstLine();
04041         if (pTextLine!=NULL)
04042             pCaretNode=pTextLine->FindCaret();
04043         if (pCaretNode==NULL)
04044         {
04045             ok=FALSE;
04046             ERROR2RAW("TextStory::CreateFromChars() - failed to find TextLine or Caret");
04047         }
04048     }
04049 
04050     // Put the current attributes onto the story
04051     if (ok)
04052         ok = pCreateDoc->GetAttributeMgr().ApplyCurrentAttribsToNode(pTextStory);
04053 
04054     // Before forcing on attributes we need to delete the current instances first
04055     if (ok)
04056         ok = DeleteChildAttribute(pTextStory, CC_RUNTIME_CLASS(AttrFillGeometry));
04057 //  if (ok)
04058 //      ok = DeleteChildAttribute(pTextStory, CC_RUNTIME_CLASS(AttrStrokeTransp));
04059     if (ok)
04060         ok = DeleteChildAttribute(pTextStory, CC_RUNTIME_CLASS(AttrLineWidth));
04061     if (ok)
04062         ok = DeleteChildAttribute(pTextStory, CC_RUNTIME_CLASS(AttrStrokeColour));
04063     
04064     // add black fill colour, no/transparent line colour, 0.25pt line width attributes
04065     if (ok)
04066     {
04067         FlatFillAttribute     FlatFillAttr(pColour!=NULL ? *pColour : AttributeManager::DefaultBlack);
04068         StrokeTranspAttribute StrokeTranspAttr(255);
04069         LineWidthAttribute    LineWidthAttr(250);
04070         if (ok) ok = ((AttributeValue*)    &FlatFillAttr)->MakeNode(pTextLine,PREV) != NULL;
04071 //      if (ok) ok = ((AttributeValue*)&StrokeTranspAttr)->MakeNode(pTextLine,PREV) != NULL;
04072         if (ok) ok = ((AttributeValue*)   &LineWidthAttr)->MakeNode(pTextLine,PREV) != NULL;
04073         // BODGE TEXT - this is the only way this attribute will work!!!!
04074         AttributeValue* pAttr=NULL;
04075         DocColour trans(COLOUR_TRANS);
04076         if (ok) ok = NULL!=(pAttr=new StrokeColourAttribute(trans));
04077         if (ok) ok = NULL!=pAttr->MakeNode(pTextLine,PREV);
04078         if (pAttr!=NULL) delete pAttr;
04079     }
04080 
04081     // if we have a LogFont, set story rotation, add typeface, size, aspect, bold, italic attributes
04082     if (ok && pLogFont!=NULL)
04083     {
04084         // Delete the current instances of the attribute first
04085         if (ok)
04086             ok = DeleteChildAttribute(pTextStory, CC_RUNTIME_CLASS(AttrTxtFontTypeface));
04087         if (ok)
04088             ok = DeleteChildAttribute(pTextStory, CC_RUNTIME_CLASS(AttrTxtFontSize));
04089         if (ok)
04090             ok = DeleteChildAttribute(pTextStory, CC_RUNTIME_CLASS(AttrTxtAspectRatio));
04091         if (ok)
04092             ok = DeleteChildAttribute(pTextStory, CC_RUNTIME_CLASS(AttrTxtBold));
04093         if (ok)
04094             ok = DeleteChildAttribute(pTextStory, CC_RUNTIME_CLASS(AttrTxtItalic));
04095 
04096         if (ok)
04097         {
04098             FontInfo info;
04099             ok=TextManager::GetInfoFromLogFont(&info,pLogFont);
04100             if (ok)
04101             {
04102                 Matrix StoryMatrix(info.Rotation);
04103                 StoryMatrix.translate(Pos.x,Pos.y);
04104                 pTextStory->SetStoryMatrix(StoryMatrix);
04105 
04106                 TxtFontTypefaceAttribute FontAttr(info.Handle);
04107                 TxtFontSizeAttribute     SizeAttr(info.Size);
04108                 TxtAspectRatioAttribute  AspectAttr(info.Aspect);
04109                 TxtBoldAttribute         BoldAttr(info.Bold);
04110                 TxtItalicAttribute       ItalicAttr(info.Italic);
04111                 if (ok) ok = ((AttributeValue*)  &FontAttr)->MakeNode(pTextLine,PREV) != NULL;
04112                 if (ok) ok = ((AttributeValue*)  &SizeAttr)->MakeNode(pTextLine,PREV) != NULL;
04113                 if (ok) ok = ((AttributeValue*)&AspectAttr)->MakeNode(pTextLine,PREV) != NULL;
04114                 if (ok) ok = ((AttributeValue*)  &BoldAttr)->MakeNode(pTextLine,PREV) != NULL;
04115                 if (ok) ok = ((AttributeValue*)&ItalicAttr)->MakeNode(pTextLine,PREV) != NULL;
04116             }
04117         }
04118     }
04119 
04120     // insert each char before the caret
04121     INT32  index=0;
04122     WCHAR LastChar=0;
04123     while (ok)
04124     {
04125         // get a char from the appropriate array, increase index, exitting if none left
04126         WCHAR ch = 0;
04127         if (pChars==NULL)
04128             ch = pWChars[index++] ;
04129         else
04130         {
04131             // Read the next character from the array
04132             if (UnicodeManager::IsDBCSOS() && UnicodeManager::IsDBCSLeadByte(pChars[index]))
04133             {
04134                 ch = UnicodeManager::ComposeMultiBytes(pChars[index], pChars[index+1]);
04135                 index += 2;
04136             }
04137             else
04138             {
04139                 ch = (unsigned char)(pChars[index]);
04140                 index += 1;
04141             }
04142 
04143             // Convert into Unicode if it's not a control char
04144             if ((pChars != NULL) && (ch>=32 && ch!=127))
04145                 ch = UnicodeManager::MultiByteToUnicode(ch);
04146         }
04147 
04148         if (ch=='\0')
04149             break;
04150 
04151         // if not a control code, just insert the char to the left of the caret
04152         // else, if handling control codes, do what is required
04153         if (ch>=32 && ch!=127)
04154         {
04155             TextChar* pTextChar = new TextChar(pCaretNode, PREV, ch);
04156             ok=(pTextChar!=NULL);
04157         }
04158         else if (ControlCodes)
04159         {
04160             switch (ch)
04161             {
04162                 case '\n':
04163                 {
04164                     // Give the progress system a chance to update its displays
04165                     ContinueSlowJob();
04166 
04167                     // if part of '\r\n' pair ignore it as already insereted new line,
04168                     // else continue into '\r' case and insert a new line
04169                     if (LastChar=='\r')
04170                         break;
04171                 }
04172                 case '\r':  // insert a new line
04173                 {
04174                     pTextLine = TextLine::CreateEmptyTextLine(pTextLine, NEXT);
04175                     ok=(pTextLine!=NULL);
04176                     if (ok) pCaretNode->MoveNode(pTextLine,FIRSTCHILD);
04177                     break;
04178                 }
04179                 case '\t':  // insert a horizontal tab
04180                 {
04181                     HorizontalTab* pTab = new HorizontalTab(pCaretNode, PREV);
04182                     ok = (pTab != NULL);
04183                     break;
04184                 }
04185                 default:
04186                     TRACE( _T("Control Code %d, not yet supported\n"),ch);
04187             }
04188         }
04189         LastChar=ch;
04190     }
04191 
04192     if (ok)
04193     {
04194         EOLNode* pLastEOL=(EOLNode*)(pCaretNode->FindNext());
04195         ok=(pLastEOL!=NULL);
04196         if (ok)
04197             pLastEOL->SetVirtual(TRUE);
04198         else
04199             ERROR2RAW("TextStory::CreateFromChars() - couldn't EOL after caret!");
04200     }
04201 
04202     // if we failed to finish the job, tidy up
04203     if (!ok)
04204     {
04205         pTextStory->CascadeDelete();
04206         delete pTextStory;
04207         pTextStory=NULL;
04208     }
04209 
04210     return pTextStory;
04211 }
04212 
04213 
04214 /********************************************************************************************
04215 >   BOOL TextStory::WillStoryWrapOnPath()
04216 
04217     Author:     Mike_Kenny (Xara Group Ltd) <camelotdev@xara.com>
04218     Created:    11/6/95
04219     Inputs:     -
04220     Returns:    TRUE if the text story will wrap on this path
04221                 FALSE if not
04222     Purpose:    Calculates whether the formatter will wrap any line of text around the path
04223                 It calculates this by checking the position of the last character (or VTN in
04224                 each line). If this wraps it is assumed the whole line will wrap.
04225                 Note text is assumed to only ever wrap on a path which is closed.
04226 ********************************************************************************************/
04227 
04228 BOOL TextStory::WillStoryWrapOnPath()
04229 {
04230 // BODGE TEXT - horrible copy of code elsewhere
04231 
04232     // first find the text path.
04233     NodePath* pNodePath = GetTextPath();
04234     if (pNodePath==NULL)
04235         return FALSE;
04236     Path* pPath = &(pNodePath->InkPath);
04237 
04238     // if the path is open, text never wraps.
04239     INT32 NumPathCoords=pPath->GetNumCoords();
04240     if (NumPathCoords<2)
04241         return FALSE;
04242 
04243     BOOL PathClosed=FALSE;
04244     PathVerb* pPathVerbs=pPath->GetVerbArray();
04245     ERROR2IF(pPathVerbs==NULL,FALSE,"TextStory::IsStoryWrappedOnpath() - pPathVerbs==NULL");
04246     if (pPathVerbs[NumPathCoords-1] & PT_CLOSEFIGURE)
04247         PathClosed=TRUE;
04248 
04249     if (!PathClosed)
04250         return FALSE;
04251     
04252     // find the untransformed path length
04253     MILLIPOINT PathLength = 0;
04254     double fPathLength=0;
04255     ProcessLength PathLengthProcess(64);
04256     if (PathLengthProcess.PathLength(pPath,&fPathLength))
04257         PathLength=(MILLIPOINT)fPathLength;
04258 
04259     FIXED16 scale  = GetCharsScale();
04260     FIXED16 xscale = (scale<0 ? -scale : scale) * GetCharsAspect();
04261 
04262     BOOL Wrapped = FALSE;
04263     TextLine* pLine = FindFirstLine();
04264     while (pLine && !Wrapped)
04265     {
04266         Wrapped = pLine->WillLineWrapOnPath(xscale,PathLength);
04267         pLine = pLine->FindNextLine();
04268     }
04269 
04270     return Wrapped;
04271 }
04272 
04273 
04274 /********************************************************************************************
04275 >   static DocCoord TextStory::GetPathBlobPos(MILLIPOINT IndentLength, NodePath* pPath)
04276 
04277     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
04278     Created:    16/12/95
04279     Inputs:     IndentLength - the indent length
04280                 pPath - pointer to the path
04281     Returns:    The position of a point along the path
04282     Purpose:    Gets the location of the of a point a certian distance along the path.
04283                 This function is static so others, eg the drag op, can call it and it's
04284                 scary math
04285 ********************************************************************************************/
04286 
04287 DocCoord TextStory::GetPathBlobPos(MILLIPOINT IndentLength, NodePath* pPath)
04288 {
04289     DocCoord Result(0,0);
04290     pPath->InkPath.GetPointAtDistance(IndentLength, &Result);
04291     return Result;
04292 }
04293 
04294 
04295 /********************************************************************************************
04296 >   BOOL TextStory::GetCharacterAtPoint(BOOL InBounds, DocCoord Point, VisibleTextNode** pHitChar)
04297 
04298     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
04299     Created:    22/1/96
04300     Inputs:     InBounds - if TRUE then Point must be within the bounds of the story to find
04301                             a character
04302                 Point - the location in question
04303     Outputs:    pHitChar - the character the point is near, NULL if there isn't a character
04304                 ToLeft - true if the point is to the left of the character, FALSE if to the right
04305     Returns:    TRUE for success, FALSE if an error occured
04306     Purpose:    Given a location works out the nearest character to that location.
04307 ********************************************************************************************/
04308 
04309 BOOL TextStory::GetCharacterAtPoint(BOOL InBounds, DocCoord Point, VisibleTextNode** pHitChar, BOOL* ToLeft)
04310 {
04311     ERROR3IF(GetTextPath()!=NULL, "GetCharacterAtPoint : Results are unpredictable for stories on a path");
04312     
04313     // Get inital pointers, checking them
04314     ERROR2IF(pHitChar==NULL || ToLeft==NULL, FALSE, "NULL output param");
04315     CaretNode* pCaret = GetCaret();
04316     ERROR2IF(pCaret == NULL, FALSE, "Story has no caret");
04317     TextLine* pFirstLine = FindFirstLine();
04318     TextLine* pLastLine = FindLastLine();
04319     ERROR2IF((pFirstLine==NULL || pLastLine==NULL), FALSE, "Story has no text lines");
04320 
04321     // Set outputs to defaults
04322     *pHitChar = NULL;
04323     *ToLeft = FALSE;
04324 
04325     // Untransform the point via the story matrix
04326     DocCoord UTPoint = Point;
04327     Matrix StoryMat = GetStoryMatrix();
04328     StoryMat=StoryMat.Inverse();
04329     StoryMat.transform(&UTPoint);
04330 
04331     // Check to see wether the point is within the untransformed bounds of the story
04332     if (InBounds)
04333     {
04334         DocRect UTStoryBounds = GetUTStoryBounds();
04335         if (!UTStoryBounds.ContainsCoord(UTPoint))
04336         {
04337             *pHitChar = NULL;
04338             *ToLeft = FALSE;
04339             return TRUE;
04340         }
04341     }
04342 
04343     // See if the point is above the first line
04344     INT32 HighestPointFound = INT32_MIN;
04345     INT32 LowestPointFound = INT32_MAX;
04346     TextLine* HighestLine = NULL;
04347     TextLine* LowestLine = NULL;
04348     
04349     // Scan through the lines to see if one contains the point
04350     INT32 PrevHigh = pFirstLine->GetPosInStory() + pFirstLine->GetLineAscent();
04351     INT32 PrevLow = pFirstLine->GetPosInStory() + pFirstLine->GetLineDescent();
04352     TextLine* pLine = pFirstLine;
04353     while (pLine != NULL)
04354     {
04355         INT32 HighestPoint = pLine->GetPosInStory() + pLine->GetLineAscent();
04356         INT32 LowestPoint = pLine->GetPosInStory() + pLine->GetLineDescent();
04357 
04358         // Update the highest and lowest known points in the story
04359         if (HighestPointFound < HighestPoint)
04360         {
04361             HighestPointFound = HighestPoint;
04362             HighestLine = pLine;
04363         }
04364         if (LowestPointFound > LowestPoint)
04365         {
04366             LowestPointFound = LowestPoint;
04367             LowestLine = pLine;
04368         }
04369 
04370         // Get the bounds of the current and previous lines
04371         INT32 Highest = max( PrevHigh, max( PrevLow, max( HighestPoint, LowestPoint ) ) );
04372         INT32 Lowest  = min( PrevHigh, min( PrevLow, min( HighestPoint, LowestPoint ) ) );
04373 
04374         if ((UTPoint.y <= Highest) && (UTPoint.y >= Lowest))
04375             break;
04376 
04377         PrevHigh = HighestPoint;
04378         PrevLow = LowestPoint;
04379         pLine = pLine->FindNextLine();
04380     }
04381 
04382     // If pLine is NULL then the point is above/below the story
04383     if (pLine == NULL)
04384     {
04385         if (UTPoint.y >= HighestPointFound)
04386             pLine = HighestLine;
04387         if (UTPoint.y <= LowestPointFound)
04388             pLine = LowestLine;
04389     }
04390     ERROR2IF(pLine == NULL, FALSE, "Didn't find a line");
04391 
04392     // Find a character on the line
04393     BOOL ToLeftPosChar = TRUE;
04394     VisibleTextNode* pPointerChar = pLine->FindCharAtDistAlongLine(UTPoint.x, &ToLeftPosChar);
04395     if (pPointerChar->IsAnEOLNode())
04396         ToLeftPosChar = FALSE;
04397     if (pPointerChar->IsACaret())
04398     {
04399         if (pPointerChar->FindNextVTNInLine()!=NULL)
04400             pPointerChar = pPointerChar->FindNextVTNInLine();
04401         else
04402             pPointerChar = pPointerChar->FindPrevVTNInLine();
04403     }
04404 
04405    *pHitChar = pPointerChar;
04406    *ToLeft = ToLeftPosChar;
04407 
04408    return TRUE;
04409 }
04410 
04411 
04412 /********************************************************************************************
04413 >   DocCoord TextStory::GetLeftIndentPos()
04414 
04415     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
04416     Created:    14/3/96
04417     Returns:    The position of this stories path left indent
04418     Purpose:    Gets the position in the document of the left indent of this story.
04419 ********************************************************************************************/
04420 DocCoord TextStory::GetLeftIndentPos() const
04421 {
04422     ERROR3IF(GetTextPath() == NULL, "Story not on a path, LeftIndent unused");
04423 
04424     if (GetTextPath() != NULL)
04425     {
04426         if (IsTextOnPathReversed())
04427         {
04428             INT32 TotalPathLength = (INT32)GetTextPath()->InkPath.GetPathLength();
04429             return GetPathBlobPos(TotalPathLength-GetLeftIndent(), GetTextPath());
04430         }
04431         else
04432             return GetPathBlobPos(GetLeftIndent(), GetTextPath());
04433     }
04434 
04435 
04436     else
04437         return DocCoord(0,0);
04438 }
04439 
04440 
04441 
04442 /********************************************************************************************
04443 >   DocCoord TextStory::GetRightIndentPos()
04444 
04445     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
04446     Created:    16/8/96
04447     Returns:    The position of this stories right indent
04448     Purpose:    Gets the position in the document of the right indent of this story.
04449                 Remember that the right inrent is relative to the RHS of the path
04450 ********************************************************************************************/
04451 DocCoord TextStory::GetRightIndentPos() const
04452 {
04453     ERROR3IF(GetTextPath() == NULL, "Story not on a path, RightIndent unused");
04454 
04455     if (GetTextPath() != NULL)
04456     {
04457         if (IsTextOnPathReversed())
04458         {
04459             return GetPathBlobPos(GetRightIndent(), GetTextPath());
04460         }
04461         else
04462         {
04463             INT32 TotalPathLength = (INT32)GetTextPath()->InkPath.GetPathLength();
04464             return GetPathBlobPos(TotalPathLength-GetRightIndent(), GetTextPath());
04465         }
04466     }
04467     else
04468         return DocCoord(0,0);
04469 }
04470 
04471 
04472 
04473 /********************************************************************************************
04474 
04475 >   virtual BOOL TextStory::WritePreChildrenWeb(BaseCamelotFilter *pFilter)
04476     virtual BOOL TextStory::WritePreChildrenNative(BaseCamelotFilter *pFilter)
04477 
04478     Author:     Andy_Hayward (Xara Group Ltd) <camelotdev@xara.com>
04479     Created:    10/07/96
04480     Inputs:     pFilter = ptr to the filter to write out to
04481     Returns:    TRUE if successful, FALSE otherwise
04482     Purpose:    Writes out a records associated with this node
04483 
04484     We currently call a helper class to do the work for us. Its been written this way
04485     to (a) keep all the text exporting code in one place (cxftext.h, cxftext.cpp),
04486     (b) to stop this file and its header from becoming bloated (reducing dependencies
04487     and build times) and (c) because I feel its neater (ie I want to).          Ach
04488 
04489 ********************************************************************************************/
04490 
04491 BOOL TextStory::WritePreChildrenWeb(BaseCamelotFilter *pFilter)
04492 {
04493 #ifdef DO_EXPORT
04494     return CXaraFileTxtStory::WritePreChildrenWeb(pFilter, this);
04495 #else
04496     return FALSE;
04497 #endif
04498 }
04499 
04500 BOOL TextStory::WritePreChildrenNative(BaseCamelotFilter *pFilter)
04501 {
04502 #ifdef DO_EXPORT
04503     return CXaraFileTxtStory::WritePreChildrenNative(pFilter, this);
04504 #else
04505     return FALSE;
04506 #endif
04507 }
04508 
04509 BOOL TextStory::CanWriteChildrenWeb(BaseCamelotFilter* pFilter)
04510 {
04511 #ifdef DO_EXPORT
04512     return CXaraFileTxtStory::CanWriteChildrenWeb(pFilter, this);
04513 #else
04514     return FALSE;
04515 #endif
04516 }
04517 
04518 BOOL TextStory::CanWriteChildrenNative(BaseCamelotFilter* pFilter)
04519 {
04520 #ifdef DO_EXPORT
04521     return CXaraFileTxtStory::CanWriteChildrenNative(pFilter, this);
04522 #else
04523     return FALSE;
04524 #endif
04525 }
04526 
04527 /********************************************************************************************
04528 
04529 >   BOOL TextStory::WriteBeginChildRecordsWeb(BaseCamelotFilter* pFilter);
04530     BOOL TextStory::WriteBeginChildRecordsNative(BaseCamelotFilter* pFilter);
04531     BOOL TextStory::WriteEndChildRecordsWeb(BaseCamelotFilter* pFilter);
04532     BOOL TextStory::WriteEndChildRecordsNative(BaseCamelotFilter* pFilter);
04533 
04534     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
04535     Created:    21/08/96
04536     Inputs:     pFilter = ptr to the filter to write out to
04537     Returns:    TRUE if successful, FALSE otherwise
04538     Purpose:    Writes out child records associated with the text story
04539 
04540     It uses the CXaraFileTxtStory class, because this is where all the rest of the code for
04541     text story output is.
04542 
04543 ********************************************************************************************/
04544 
04545 BOOL TextStory::WriteBeginChildRecordsWeb(BaseCamelotFilter* pFilter)
04546 {
04547 #ifdef DO_EXPORT
04548     return CXaraFileTxtStory::WriteBeginChildRecordsWeb(pFilter, this);
04549 #else
04550     return FALSE;
04551 #endif
04552 }
04553 
04554 BOOL TextStory::WriteBeginChildRecordsNative(BaseCamelotFilter* pFilter)
04555 {
04556 #ifdef DO_EXPORT
04557     return CXaraFileTxtStory::WriteBeginChildRecordsNative(pFilter, this);
04558 #else
04559     return FALSE;
04560 #endif
04561 }
04562 
04563 BOOL TextStory::WriteEndChildRecordsWeb(BaseCamelotFilter* pFilter)
04564 {
04565 #ifdef DO_EXPORT
04566     return CXaraFileTxtStory::WriteEndChildRecordsWeb(pFilter, this);
04567 #else
04568     return FALSE;
04569 #endif
04570 }
04571 
04572 BOOL TextStory::WriteEndChildRecordsNative(BaseCamelotFilter* pFilter)
04573 {
04574 #ifdef DO_EXPORT
04575     return CXaraFileTxtStory::WriteEndChildRecordsNative(pFilter, this);
04576 #else
04577     return FALSE;
04578 #endif
04579 }
04580 /********************************************************************************************
04581 
04582 >   void TextStory::AddImportedString(ImportedString* pImportedString)
04583 
04584     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
04585     Created:    14/8/96
04586     Inputs:     pImportedString = ptr to the imported string object
04587     Outputs:    -
04588     Returns:    -
04589     Purpose:    Adds the object to the list of imported strings
04590 
04591 ********************************************************************************************/
04592 
04593 void TextStory::AddImportedString(ImportedString* pImportedString)
04594 {
04595     if (pImportedStringList == NULL)
04596         pImportedStringList = new ImportedStringList;
04597 
04598     if (pImportedStringList != NULL)
04599         pImportedStringList->AddTail(pImportedString);
04600 }
04601 
04602 
04603 /********************************************************************************************
04604 
04605 >   BOOL TextStory::ExpandImportedStrings()
04606 
04607     Author:     Mark_Neves (Xara Group Ltd) <camelotdev@xara.com>
04608     Created:    14/8/96
04609     Inputs:     pImportedString = ptr to the imported string object
04610     Outputs:    -
04611     Returns:    -
04612     Purpose:    Expands any strings that were imported into a series of text characters.
04613 
04614                 When text strings are imported from the v2 file format, the first char in the
04615                 string gets inserted into the tree, which marks the position of the begining of the
04616                 string.
04617 
04618                 The whole string is then saved (in an ImportedString object), along with the
04619                 inserted text char node, until post-import.
04620 
04621                 Post-import then inserts text chars for the rest of the string next to the first
04622                 text char of the string, copying child attrs from the first char to each of the string's
04623                 remaining chars, to ensure that they look correct.
04624 
04625                 Why do this on post import?  Why not do it when you get a string record from the file?
04626 
04627                 Well, at the point that the string record is read, we don't know what child attrs will
04628                 be applied to it, because, if there are any child attrs, they will appear later in the file.
04629 
04630                 There are a number of ways to do it, but they all involve doing something to the string
04631                 *after* you know that the child attrs have been read in.
04632 
04633                 Post-import was considered a good place because the necessary processing can be done
04634                 before the story is formatted, it doesn't assume anything about the order of records
04635                 in the file format, and it was a Tuesday.
04636 
04637 
04638 ********************************************************************************************/
04639 
04640 BOOL TextStory::ExpandImportedStrings()
04641 {
04642     // If the list is empty, get out of here, now!
04643     if (pImportedStringList == NULL)
04644         return TRUE;
04645 
04646     BOOL ok = TRUE;
04647 
04648     // Expand each string in the list
04649     ImportedString* pImportedString = pImportedStringList->GetHead();
04650     while (ok && pImportedString != NULL)
04651     {
04652         // get the char node, and a copy of the original record
04653         TextChar* pChar = pImportedString->GetFirstChar();
04654         CXaraFileRecord* pRecord = pImportedString->GetRecord();
04655 
04656         if (pChar != NULL && pRecord != NULL)
04657         {
04658             WCHAR wChar;
04659 
04660             // The first char is read and discarded because pChar contains the char.
04661             ok = pRecord->ReadWCHAR(&wChar);
04662 
04663             // Find out the size of the record (and hence the num chars in the string)
04664             // Num chars = Size/2 (Unicode - double byte chars)
04665             UINT32 Size = pRecord->GetSize();
04666 
04667             // We only iterate until Size <=2, because we have read and discarded the first
04668             // char in the string.
04669             for (;ok && Size > 2;Size-=2)
04670             {
04671                 // Read the next char code
04672                 ok = pRecord->ReadWCHAR(&wChar);
04673 
04674                 TextChar* pNewChar = NULL;
04675 
04676                 if (ok) pNewChar = new TextChar();              // Create a new TextChar
04677                 if (ok) ok = (pNewChar != NULL);                //
04678                 if (ok) pNewChar->SetUnicodeValue(wChar);       // Set the char's code
04679                 if (ok) pNewChar->AttachNode(pChar,NEXT);       // Attach it next to the last inserted char
04680                 if (ok) ok = pChar->CopyChildAttrs(pNewChar);   // Make sure it looks the same as the last char
04681                 if (ok) pChar = pNewChar;                       // pNewChar now becomes the last char inserted
04682             }
04683         }
04684 
04685         // Get the next imported string
04686         pImportedString = pImportedStringList->GetNext(pImportedString);
04687     }
04688 
04689     // After expanding the strings, throw them away, and throw the list away too.
04690     pImportedStringList->DeleteAll();
04691     delete pImportedStringList;
04692     pImportedStringList = NULL;
04693 
04694     return ok;
04695 }
04696 
04697 
04698 
04699 /********************************************************************************************
04700 >   void TextStory::Validate(BOOL EnsureOpFlagsReset=TRUE)
04701 
04702   Author:   Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
04703   Created:  16/6/96
04704   Inputs:   EnsureOpFlagsReset -
04705   Purpose:  Perform various integrity checks on a text story in debug builds
04706 ********************************************************************************************/
04707 
04708 #ifdef _DEBUG
04709 #define VALIDATE
04710 #define TRACEIF(a,b) { if (a) { TRACE b; } }
04711 #endif
04712 
04713 void TextStory::Validate(BOOL EnsureOpFlagsReset)
04714 {
04715 PORTNOTE("text","Removed 'Ed' validate from TextStory::Validate")
04716 #ifndef EXCLUDE_FROM_XARALX
04717 #ifdef VALIDATE
04718 if (IsUserName("Ed"))
04719 {
04720     // choose parent to check from
04721     Node* pNode = this;
04722     if (pNode==NULL)
04723     {
04724         ERROR3("TestStory::Validate() - could not find desired parent, using top parent");
04725         pNode = this;
04726     }
04727 
04728     static INT32 count=0;
04729     if ((count++ % 12)==0)
04730         CheckSubtree(pNode);
04731 
04732     ERROR3IF(CharsScale !=FIXED16(1), "TextStory::Validate() - CharsScale!=1");
04733     ERROR3IF(CharsAspect!=FIXED16(1), "TextStory::Validate() - CharsAspect!=1");
04734     ERROR3IF(CharsRotation       !=0, "TextStory::Validate() - CharsRoation!=0 - not yet supported");
04735     ERROR3IF(ImportFormatWidth   !=0, "TextStory::Validate() - ImportFormatWidth!=0");
04736     ERROR3IF(!TextOnPathTangential,   "TextStory::Validate() - TextOnPathTangential!=0 - not yet supported");
04737     ERROR3IF(ImportBaseShift!=AlignBaseline,"TextStory::Validate() - ImportBaseShift!=AlignBaseline");
04738     VisibleTextNode* pLastVTNInStory = this->FindLastVTN();
04739     ERROR3IF(pLastVTNInStory==NULL,"TextStory::Validate() - story has no VTN!");
04740     ERROR3IF(!pLastVTNInStory->IsAnEOLNode(),"TextStory::Validate() - last VTN in story is not an EOLNode");
04741 
04742     FIXED16  Scale    = 1;
04743     FIXED16  Aspect   = 1;
04744     ANGLE    Rotation = 0;
04745     ANGLE    Shear    = 0;
04746     DocCoord Translation(0,0);
04747     if (StoryMatrix.Decompose(&Scale,&Aspect,&Rotation,&Shear,&Translation))
04748     {
04749         const FIXED16 NormaliseLimit = 0.001;
04750         if (Scale<0)
04751         {
04752             Scale = -Scale;
04753             Shear = -Shear;
04754         }
04755         FIXED16 AbsScaleError  = Scale-1;
04756         FIXED16 AbsAspectError = Aspect-1;
04757         AbsScaleError  =  AbsScaleError<0 ? -AbsScaleError  : AbsScaleError;
04758         AbsAspectError = AbsAspectError<0 ? -AbsAspectError : AbsAspectError;
04759         if (AbsScaleError>NormaliseLimit || AbsAspectError>NormaliseLimit)
04760             ERROR3("TextStory::Validate() - StroyMatrix scale or aspect not normalised!");
04761     }
04762     else
04763         ERROR3("TextStory::Validate() - failed to decompose story matrix");
04764 
04765     BOOL LineFound  = FALSE;
04766     BOOL PathFound  = FALSE;
04767     BOOL CaretFound = FALSE;
04768     Node* pNodeInStory = this->FindFirstChild();
04769     while (pNodeInStory!=NULL)
04770     {
04771         if (EnsureOpFlagsReset && pNodeInStory->IsABaseTextClass())
04772         {
04773             BaseTextClass* pBTC = (BaseTextClass*)pNodeInStory;
04774             ERROR3IF(pBTC->Affected(),              "TextStory::Validate() - AffectedFlag Set!");
04775             ERROR3IF(pBTC->DescendantAffected(),    "TextStory::Validate() - DescendantAffectedFlag Set!");
04776             ERROR3IF(pBTC->ModifiedByOp(),          "TextStory::Validate() - ModifiedByOpFlag Set!");
04777             ERROR3IF(pBTC->DescendantModifiedByOp(),"TextStory::Validate() - DescendantModifiedByOpFlag Set!");
04778         }
04779         if (pNodeInStory->IsAnAttribute())
04780         {
04781             ERROR3IF(LineFound,"TextStory::Validate() - Attribute found after first line in story");
04782             ERROR3IF(PathFound,"TextStory::Validate() - Attribute found after path in story");
04783             NodeAttribute* pAttr = (NodeAttribute*)pNodeInStory;
04784             if (pAttr->IsALineLevelAttrib() && !TextLine::IsAttrTypeLineLevel(pAttr->GetAttributeType()))
04785                 ERROR3("TextStory::Validate() - TextLine::IsAttrTypeLineLevel() out of date?");
04786         }
04787         else if (IS_A(pNodeInStory,NodePath))
04788         {
04789             ERROR3IF(LineFound,"TextStory::Validate() - Path is not first object in story");
04790             CheckLeaf(pNodeInStory);
04791             PathFound = TRUE;
04792         }
04793         else if (IS_A(pNodeInStory,TextLine))
04794         {
04795             BOOL VTNFound   = FALSE;
04796             BOOL EOLFound   = FALSE;
04797             Node* pNodeInLine = pNodeInStory->FindFirstChild();
04798             while (pNodeInLine!=NULL)
04799             {
04800                 if (EnsureOpFlagsReset && pNodeInStory->IsABaseTextClass())
04801                 {
04802                     BaseTextClass* pBTC = (BaseTextClass*)pNodeInStory;
04803                     ERROR3IF(pBTC->Affected(),              "TextStory::Validate() - AffectedFlag Set!");
04804                     ERROR3IF(pBTC->DescendantAffected(),    "TextStory::Validate() - DescendantAffectedFlag Set!");
04805                     ERROR3IF(pBTC->ModifiedByOp(),          "TextStory::Validate() - ModifiedByOpFlag Set!");
04806                     ERROR3IF(pBTC->DescendantModifiedByOp(),"TextStory::Validate() - DescendantModifiedByOpFlag Set!");
04807                 }
04808                 if (pNodeInLine->IsAnAttribute())
04809                 {
04810                     ERROR3IF(VTNFound,"TextStory::Validate() - Attribute found after first VTN in line");
04811                     NodeAttribute* pAttr = (NodeAttribute*)pNodeInLine;
04812                     if (pAttr->IsALineLevelAttrib() && !TextLine::IsAttrTypeLineLevel(pAttr->GetAttributeType()))
04813                         ERROR3("TextStory::Validate() - TextLine::IsAttrTypeLineLevel() out of date?");
04814                 }
04815                 else if (IS_A(pNodeInLine,CaretNode))
04816                 {
04817                     ERROR3IF(CaretFound,"TextStory::Validate() - story has more than 1 caret");
04818                     CheckLeaf(pNodeInLine);
04819                     VTNFound   = TRUE;
04820                     CaretFound = TRUE;
04821                 }
04822                 else if (IS_A(pNodeInLine,EOLNode))
04823                 {
04824                     ERROR3IF(EOLFound,"TextStory::Validate() - line has more than 1 EOL");
04825                     CheckLeaf(pNodeInLine);
04826                     VTNFound = TRUE;
04827                     EOLFound = TRUE;
04828                 }
04829                 else if (pNodeInLine->IsAVisibleTextNode())
04830                 {
04831                     ERROR3IF(EOLFound,"TextStory::Validate() - VTN found after EOL on line");
04832                     CheckLeaf(pNodeInLine);
04833                     VTNFound = TRUE;
04834                 }
04835                 else if (!pNodeInLine->IsNodeHidden())
04836                     ERROR3("TextStory::Validate() - unknown node in line (ie not a NodeAttribute, VisibleTextNode or NodeHidden)");
04837                 pNodeInLine = pNodeInLine->FindNext();
04838             }
04839             LineFound = TRUE;
04840         }
04841         else if (!pNodeInStory->IsNodeHidden())
04842             ERROR3("TextStory::Validate() - unknown node in story (ie not a NodeAttribute, NodePath, TextLine or NodeHidden)");
04843         pNodeInStory = pNodeInStory->FindNext();
04844     }
04845     ERROR3IF(!LineFound, "TextStory::Validate() - story has no lines!");
04846 //  ERROR3IF(!CaretFound,"TextStory::Validate() - story has no caret!");
04847     TRACEIF(!CaretFound,("TextStory::Validate() - story has no caret!\n"));
04848 }
04849 #endif
04850 #endif
04851 }
04852 
04853 
04854 /********************************************************************************************
04855 >   void TextStory::CheckLeaf(Node* pLeafNode)
04856 
04857     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
04858     Created:    17/6/96
04859     Inputs:     pLeafNode -
04860     Purpose:    Ensure no Linelevel attrs below line level
04861 ********************************************************************************************/
04862     
04863 void TextStory::CheckLeaf(Node* pLeafNode)
04864 {
04865 #ifdef VALIDATE
04866     if (pLeafNode==NULL)
04867     {
04868         ERROR3("TextStory::CheckLeaf() - pNode==NULL");
04869         return;
04870     }
04871 
04872     Node* pNodeOnLeaf = pLeafNode->FindFirstChild();
04873     while (pNodeOnLeaf!=NULL)
04874     {
04875         if (pNodeOnLeaf->IsAnAttribute())
04876         {
04877             if ( ((NodeAttribute*)pNodeOnLeaf)->IsALineLevelAttrib() )
04878             {
04879                 if (IS_A(pLeafNode,CaretNode))
04880                     TRACE( _T("TextStory::CheckLeaf() - Line level attr applied to caret!\n"));
04881                 else
04882                     ERROR3("TextStory::CheckLeaf() - Line level attr applied below line level");
04883             }
04884         }
04885         pNodeOnLeaf = pNodeOnLeaf->FindNext();
04886     }
04887 #endif
04888 }
04889 
04890 
04891 /********************************************************************************************
04892 >   void TextStory::CheckSubtree(Node*        pNode,
04893                                  AttrTypeSet* pParentAttrSet      = NULL,
04894                                  BOOL*        pDescendantSelected = NULL)
04895 
04896     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
04897     Created:    17/6/96
04898     Inputs:     pNode               -
04899                 pParentAttrSet      -
04900     Outputs:    pDescendantSelected -
04901     Purpose:    Validate a subtree, checking for:
04902                 - duplication of attribute on node or parent (except caret)
04903                 - nodes with child objects must be flagged as compound
04904                 - attributes   are not flagged as selected or parent of selected
04905                 - hidden nodes are not flagged as selected,   parent of selected or compound
04906                 - node is not both flagged as selected and parent of selected
04907                 - node with    selected descendant is     flagged as parent of selected
04908                 - node with no selected descendant is not flagged as parent of selected
04909                 - no sibling attributes
04910 ********************************************************************************************/
04911 
04912 #include "nodedoc.h"
04913 
04914 void TextStory::CheckSubtree(Node* pNode, AttrTypeSet* pParentAttrSet, BOOL* pDescendantSelected)
04915 {
04916 #ifdef VALIDATE
04917     if (pNode==NULL)
04918     {
04919         ERROR3("CheckSubtree() - pNode==NULL");
04920         return;
04921     }
04922 
04923 PORTNOTE("text","Removed AfxCheckMemory")
04924 #ifndef EXCLUDE_FROM_XARALX
04925     ERROR3IF(!AfxCheckMemory(),"CheckSubtree() - memory corrupt!");
04926 #endif
04927 
04928     // get ptr to doc (if not an isolated subtree) as children are default attrs
04929     NodeDocument* pDoc = NULL;
04930     if (IS_A(pNode,NodeDocument))
04931         pDoc = (NodeDocument*)pNode;
04932     else
04933         pDoc = (NodeDocument*)(pNode->FindParent(CC_RUNTIME_CLASS(NodeDocument)));
04934 
04935     // flag attr set not yet coied - only do so if we have to as it is SLOOOW
04936     AttrTypeSet* pAttrSet = NULL;
04937 
04938     BOOL ChildSelected    = FALSE;
04939     BOOL ChildParentOfSel = FALSE;
04940     BOOL ChildObjectFound = FALSE;
04941     Node* pChildNode = pNode->FindFirstChild();
04942     while (pChildNode)
04943     {
04944         TRACEIF( pChildNode->IsSelected() && pChildNode->IsParentOfSelected(), 
04945             ( _T("CheckSubtree() - node (%s) both selected and parent of selected\n"), GetNodeInfo(pChildNode) ) );
04946         ChildSelected    |= pChildNode->IsSelected();
04947         ChildParentOfSel |= pChildNode->IsParentOfSelected();
04948         if (pChildNode->IsAnAttribute())
04949         {
04950             // if not already done, if no Attr set passed in, create one else copy the given one
04951             if (pAttrSet==NULL)
04952             {
04953                 if (pParentAttrSet==NULL)
04954                     pAttrSet = new AttrTypeSet;
04955                 else
04956                     pAttrSet = pParentAttrSet->CopySet();
04957             }
04958 
04959             TRACEIF( ChildObjectFound,      ( _T("CheckSubtree() - attribute (%s) found after child object\n"), GetNodeInfo(pChildNode) ) );
04960             NodeAttribute* pAttr = (NodeAttribute*)pChildNode;
04961             TRACEIF(pAttr->IsSelected(),    ( _T("CheckSubtree() - attribute (%s) selected\n"), GetNodeInfo(pAttr) ) );
04962             TRACEIF(pAttr->IsParentOfSelected(),( _T("CheckSubtree() - attribute (%s) parent of selected\n"), GetNodeInfo(pAttr) ) );
04963             if (!IS_A(pNode,CaretNode))
04964             {
04965                 // if attrbute already in set it is a duplicate of one on a parent (exc defaults)
04966                 CCRuntimeClass* pAttrType = pAttr->GetAttributeType();
04967                 TRACEIF( pAttrSet->InSet(pAttrType), ( _T("CheckSubtree() - attribute (%s) duplicate of one on self (%s) or parent\n"),
04968                     GetNodeInfo(pAttr),GetNodeInfo(pNode)));
04969 
04970                 // if not an isolated subtree and not default attr, check for duplicated default attrs
04971                 if (pDoc!=NULL && pNode!=pDoc)
04972                 {
04973                     NodeAttribute* pDefaultAttr = (NodeAttribute*)(pDoc->FindFirstChild(pAttrType));
04974                     TRACEIF( pDefaultAttr==NULL, ( _T("CheckSubtree() - attribute (%s) found for which there is no default!\n"),
04975                         GetNodeInfo(pAttr)));
04976                     TRACEIF( (*pDefaultAttr)==(*pAttr), ( _T("CheckSubtree() - duplicate of default attr (%s) found!\n"),
04977                         GetNodeInfo(pAttr)));
04978                 }
04979 
04980                 // if not default doc attrs, add to attr set
04981                 if (pNode!=pDoc)
04982                     if (pAttrSet->AddToSet(pAttrType)==FALSE)
04983                         ERROR3("CheckSubtree() - AttrTypeSet::AddToSet() failed");
04984             }
04985         }
04986         else if (pChildNode->IsNodeHidden())
04987         {
04988             NodeHidden* pNodeHidden = (NodeHidden*)pChildNode;
04989             TRACEIF(pNodeHidden->IsSelected()        ,( _T("CheckSubtree() - hidden node (%s) selected\n"),
04990                 GetNodeInfo(pChildNode)));
04991             TRACEIF(pNodeHidden->IsParentOfSelected(),( _T("CheckSubtree() - hidden node (%s) parent of selected\n"),
04992                 GetNodeInfo(pChildNode)));
04993             TRACEIF(pNodeHidden->IsCompound(),        ( _T("CheckSubtree() - hidden node (%s) is compound\n"), 
04994                 GetNodeInfo(pChildNode)));
04995         }
04996         else
04997         {
04998             Node* pNode = pChildNode;
04999             BOOL DescendantSelected = FALSE;
05000             // if not already done, if no Attr set passed in, create one else copy the given one
05001             if (pAttrSet==NULL)
05002             {
05003                 if (pParentAttrSet==NULL)
05004                     pAttrSet = new AttrTypeSet;
05005                 else
05006                     pAttrSet = pParentAttrSet->CopySet();
05007             }
05008             CheckSubtree(pNode, pAttrSet, &DescendantSelected);
05009             TRACEIF( DescendantSelected && !pChildNode->IsParentOfSelected(),
05010                 ( _T("CheckSubtree() - descendant selected but node (%s) not parent of selected\n"), GetNodeInfo(pNode)));
05011 //          TRACEIF(!DescendantSelected &&       pNode->IsParentOfSelected(),("CheckSubtree() - node (%s) parent of selected but no descendant selected\n",GetNodeInfo(pNode)));
05012             if (!DescendantSelected && pNode->IsParentOfSelected())
05013                 TRACE( _T("CheckSubtree() - node (%s) parent of selected but no descendant selected\n"),GetNodeInfo(pNode));
05014             ChildObjectFound = TRUE;
05015         }
05016         pChildNode = pChildNode->FindNext();
05017     }
05018     TRACEIF( ChildObjectFound && !pNode->IsCompound(), ( _T("CheckSubtree() - child object found on non-compound node (%s)\n"),
05019         GetNodeInfo(pNode)));
05020 
05021     if (pAttrSet!=NULL)
05022     {
05023         pAttrSet->DeleteAll();
05024         delete pAttrSet;
05025     }
05026 
05027     if (pDescendantSelected!=NULL)
05028         *pDescendantSelected = ChildSelected || ChildParentOfSel;
05029 #endif
05030 }
05031 
05032 
05033 /********************************************************************************************
05034 >   TCHAR* TextStory::GetNodeInfo(Node* pNode)
05035 
05036     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
05037     Created:    11/7/96
05038     Inputs:     pNode -
05039     Purpose:    return string with CC_RUNTIME_CLASS name of node and address
05040 ********************************************************************************************/
05041 
05042 TCHAR* TextStory::GetNodeInfo(Node* pNode)
05043 {
05044     static TCHAR pDesc[256];
05045     if (pNode!=NULL)
05046         camSnprintf( pDesc, 256, _T("%s @ 0x%p\0"), pNode->GetRuntimeClass()->GetClassName(), pNode );
05047     else
05048         camSnprintf( pDesc, 256, _T("\0") );
05049     return pDesc;
05050 }
05051 
05052 
05053 
05054 /********************************************************************************************
05055 >   void TextStory::DeleteChildAttribute(NodeRenderableInk* pParent, CCRuntimeClass* pReqdAttrib)
05056 
05057     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
05058     Created:    12/8/96
05059     Inputs:     pParent - Parent node of the attribute
05060                 pReqdAttrib - Attribute type to delete
05061     Outputs:    -
05062     Returns:    TRUE/FALSE for success/failure
05063     Purpose:    Finds and deletes a child attribute of the given type
05064     SeeAlso:    TextStory::CreateFromChars.
05065 ********************************************************************************************/
05066 BOOL TextStory::DeleteChildAttribute(NodeRenderableInk* pParent, CCRuntimeClass* pReqdAttrib)
05067 {
05068     ERROR2IF(pParent==NULL || pReqdAttrib==NULL, FALSE, "NULL parameter");
05069 
05070     NodeAttribute* pAppliedAttr = pParent->GetChildAttrOfType(pReqdAttrib);
05071 
05072     ERROR3IF(pAppliedAttr==NULL, "Attribute not found");
05073         
05074     if (pAppliedAttr != NULL)
05075     {
05076         pAppliedAttr->CascadeDelete();
05077         delete pAppliedAttr;
05078     }
05079 
05080     return TRUE;
05081 }
05082 
05083 
05084 
05085 /********************************************************************************************
05086 >   BOOL TextStory::OKToExport()
05087 
05088     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
05089     Created:    30/8/96
05090     Inputs:     -
05091     Outputs:    -
05092     Returns:    FALSE if this story should not be exported (ie it's duff).  TRUE otherwise
05093     Purpose:    Determines wether or not this text story should be exported.  If there are no
05094                 characters in it then we don't want to save it as it will cause bother when
05095                 loaded back.
05096 ********************************************************************************************/
05097 BOOL TextStory::OKToExport()
05098 {
05099     // See if we have any visible characters inside us.
05100     VisibleTextNode* pVTN = FindFirstVTN();
05101     BOOL FoundCharacter = FALSE;
05102     while (pVTN!=NULL && !FoundCharacter)
05103     {
05104         if (pVTN->IsATextChar())
05105             FoundCharacter = !pVTN->IsAVisibleSpace();
05106         pVTN = pVTN->FindNextVTNInStory();
05107     }
05108 
05109     if (FoundCharacter)
05110         return TRUE;
05111 
05112     // If we get here then we are an empty story.  Is it safe to not export us?
05113     // Scan up the tree.  If we reach a layer without passing though compounds other than groups it's fine.
05114     Node* pParent = FindParent();
05115     while (pParent!=NULL && !pParent->IsLayer())
05116     {
05117         if (pParent->IsCompound() && !IS_A(pParent, NodeGroup))
05118             return TRUE;    // Must export the story as the compound may rely on its presence.
05119 
05120         pParent = pParent->FindParent();
05121     }
05122 
05123     // If we get here it's safe to not export this story.
05124     return FALSE;
05125 }
05126 
05127 
05128 
05129 /********************************************************************************************
05130 >   DocRect TextStory::GetUTStoryBounds()
05131 
05132     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
05133     Created:    5/9/96
05134     Inputs:     -
05135     Outputs:    -
05136     Returns:    The untransformed bounds of the story
05137     Purpose:    The bounding box of the story in it's untransformed space.
05138 ********************************************************************************************/
05139 DocRect TextStory::GetUTStoryBounds()
05140 {
05141     // Get pointers to first and last lines in story
05142     TextLine* pFirstLine = FindFirstLine();
05143     TextLine* pLastLine = FindLastLine();
05144     ERROR3IF((pFirstLine==NULL || pLastLine==NULL), "Story has no text lines");
05145 
05146     if (pFirstLine!=NULL && pLastLine!=NULL)
05147     {
05148         // Get the width of the widest line in the story
05149         MILLIPOINT WidestLineWidth = 0;
05150         TextLine* pLine = pFirstLine;
05151         while (pLine != NULL)
05152         {
05153             VisibleTextNode* pLastVTN = pLine->FindLastVTN();
05154 
05155             if (pLastVTN!=NULL)
05156             {
05157                 const MILLIPOINT Dist = pLastVTN->CalcCharDistAlongLine(TRUE);
05158 
05159                 if (Dist > WidestLineWidth)
05160                     WidestLineWidth = Dist;
05161             }
05162 
05163             pLine = (TextLine*)pLine->FindNext(CC_RUNTIME_CLASS(TextLine));
05164         }
05165 
05166         INT32 LowY = pLastLine->GetPosInStory() + pLastLine->GetLineDescent();
05167         INT32 HighY = -pFirstLine->GetLineAscent();
05168 
05169         return DocRect( 0,  // Err, this only works for left justified stories!
05170                         (LowY < HighY) ? LowY : HighY,
05171                         WidestLineWidth,
05172                         (LowY < HighY) ? HighY : LowY);
05173     }
05174     else
05175         return DocRect(0,0,0,0);
05176 }
05177 
05178 
05179 
05180 /********************************************************************************************
05181 
05182 >   DocRect TextStory::ValidateExtend(const ExtendParams& ExtParams)
05183 
05184     Author:     Karim_MacDonald (Xara Group Ltd) <camelotdev@xara.com>
05185     Created:    01/12/1999
05186     Inputs:     ExtParams       parameters describing the extension.
05187     Returns:    TRUE if this text story can be validly extended,
05188                 FALSE otherwise.
05189     Purpose:    Tests to see whether this text-story's extend-centre is positioned
05190                 so as to make an extend operation irreversible.
05191     See also:   Extender class.
05192 
05193 ********************************************************************************************/
05194 DocRect TextStory::ValidateExtend(const ExtendParams& ExtParams)
05195 {
05196     // if we lie on a curve, we must test that curve and our attributes,
05197     // otherwise we must test ourself, then pass the test on to our children.
05198     DocRect drMinExtend(INT32_MAX, INT32_MAX, INT32_MAX, INT32_MAX);
05199     NodePath* pnPath = GetTextPath();
05200     if (pnPath == NULL)
05201     {
05202         DocCoord doccArray[1] = { FindExtendCentre() };
05203         drMinExtend = Extender::ValidateControlPoints(1, doccArray, ExtParams);
05204 
05205         // if we didn't invalidate the extension, we must call the base class
05206         // implementation, which will validate our children.
05207         if (drMinExtend.lo.x == INT32_MAX &&
05208             drMinExtend.lo.y == INT32_MAX &&
05209             drMinExtend.hi.x == INT32_MAX &&
05210             drMinExtend.hi.y == INT32_MAX)
05211             drMinExtend = Node::ValidateExtend(ExtParams);
05212     }
05213     else
05214     {
05215         drMinExtend = pnPath->ValidateExtend(ExtParams);
05216 
05217         DocRect drThisMinExtend;
05218         for (   Node* pChildNode = FindFirstChild();
05219                 pChildNode != NULL;
05220                 pChildNode = pChildNode->FindNext() )
05221         {
05222             if (!pChildNode->IsAnAttribute())
05223                 continue;
05224 
05225             drThisMinExtend = pChildNode->ValidateExtend(ExtParams);
05226             if (drMinExtend.lo.x > drThisMinExtend.lo.x) drMinExtend.lo.x = drThisMinExtend.lo.x;
05227             if (drMinExtend.lo.y > drThisMinExtend.lo.y) drMinExtend.lo.y = drThisMinExtend.lo.y;
05228             if (drMinExtend.hi.x > drThisMinExtend.hi.x) drMinExtend.hi.x = drThisMinExtend.hi.x;
05229             if (drMinExtend.hi.y > drThisMinExtend.hi.y) drMinExtend.hi.y = drThisMinExtend.hi.y;
05230         }
05231     }
05232 
05233     return drMinExtend;
05234 }
05235 
05236 
05237 
05238 /********************************************************************************************
05239 
05240 >   void TextStory::Extend(const ExtendParams& ExtParams)
05241 
05242     Author:     Karim_MacDonald (Xara Group Ltd) <camelotdev@xara.com>
05243     Created:    01/12/1999
05244     Inputs:     ExtParams       parameters describing the extension.
05245     Purpose:    Perform an Extend operation on this text-story.
05246     See also:   Extender class.
05247 
05248 ********************************************************************************************/
05249 void TextStory::Extend(const ExtendParams& ExtParams)
05250 {
05251     // if we lie on a curve, we must extend that curve, together with our attributes,
05252     // otherwise we must extend ourself, then extend our children.
05253     NodePath* pnPath = GetTextPath();
05254     if (pnPath == NULL)
05255     {
05256         // do the extension operations on ourself.
05257         // text stories never stretch, they just translate.
05258         TransformTranslateNoStretchObject(ExtParams);
05259         TransformTranslateObject(ExtParams);
05260 
05261         // do the base-class implementation to extend our children.
05262         Node::Extend(ExtParams);
05263     }
05264     else
05265     {
05266         // extend our path.
05267         pnPath->Extend(ExtParams);
05268 
05269         // extend our attributes.
05270         for (   Node* pChildNode = FindFirstChild();
05271                 pChildNode != NULL;
05272                 pChildNode = pChildNode->FindNext() )
05273         {
05274             if (pChildNode->IsAnAttribute())
05275                 pChildNode->Extend(ExtParams);
05276         }
05277     }
05278 }
05279 
05280 
05281 
05282 /********************************************************************************************
05283 
05284 >   DocCoord TextStory::FindExtendCentre()
05285 
05286     Author:     Karim_MacDonald (Xara Group Ltd) <camelotdev@xara.com>
05287     Created:    01/12/1999
05288     Returns:    The centre-point which the extender routines use to determine whether
05289                 an extension is valid, and in which detection it should occur.
05290     Purpose:    Find the extend-centre reference point.
05291     See also:   Extender class.
05292 
05293 ********************************************************************************************/
05294 DocCoord TextStory::FindExtendCentre()
05295 {
05296     DocRect rectNode = GetBoundingRect();
05297     DocCoord doccCentre = rectNode.Centre();
05298     switch ( FindJustification() )
05299     {
05300     case JCENTRE:
05301     case JFULL:
05302         // do nothing - the centre of the rectangle is required.
05303         break;
05304 
05305     case JRIGHT:
05306         doccCentre.x = rectNode.hi.x;
05307         break;
05308 
05309     case JLEFT:
05310     default:
05311         doccCentre.x = rectNode.lo.x;
05312         break;
05313     }
05314 
05315     return doccCentre;
05316 }
05317 
05318 
05319 
05320 /********************************************************************************************
05321 
05322 >   Justification TextStory::FindJustification()
05323 
05324     Author:     Karim_MacDonald (Xara Group Ltd) <camelotdev@xara.com>
05325     Created:    01/12/1999
05326     Returns:    The Justification of this text-story.
05327     Purpose:    Used in conjunction with Extend() and its sister functions to determine which
05328                 way a text-story should extend. If you use this function for another purpose,
05329                 read it and make sure it does what you think it should do first.
05330     Notes:      Be aware that this function uses IsKindOf() to
05331                 test this node's children within the tree.
05332     See also:   Extender class.
05333 
05334 ********************************************************************************************/
05335 Justification TextStory::FindJustification()
05336 {
05337     // first, search for a text-justify attribute underneath this TextStory in the tree.
05338     Node* pNode = FindFirstDepthFirst();
05339     while (pNode != NULL && !pNode->IsKindOf(CC_RUNTIME_CLASS(AttrTxtJustification)))
05340         pNode = pNode->FindNextDepthFirst(this);
05341 
05342     // if we didn't find one, then find the attribute currently applied to pTextStory.
05343     BOOL bFoundAttr = FALSE;
05344     AttrTxtJustification* pAttrJustify = NULL;
05345     if (pNode != NULL)
05346     {
05347         pAttrJustify = (AttrTxtJustification*)pNode;
05348         bFoundAttr = TRUE;
05349     }
05350     else
05351         bFoundAttr = FindAppliedAttribute(CC_RUNTIME_CLASS(AttrTxtJustification), (NodeAttribute**)&pAttrJustify);
05352 
05353     // if we retrieved an attribute, get its value.
05354     // if we have no luck, we'll just use JLEFT as the default.
05355     Justification justify = JLEFT;
05356     if (bFoundAttr)
05357     {
05358         TxtJustificationAttribute* textJustAttr = (TxtJustificationAttribute*)pAttrJustify->GetAttributeValue();
05359         if (textJustAttr != NULL)
05360             justify = textJustAttr->justification;
05361     }
05362 
05363     return justify;
05364 }
05365 
05366 String_256 TextStory::GetStoryAsString()
05367 {
05368     String_256 Content = "";
05369     String_256 AddTemplate = "x";
05370     Node * pTextLine = FindFirstChild(CC_RUNTIME_CLASS(TextLine));
05371 
05372     INT32 StringMaxSize = 255;
05373 
05374     while (pTextLine && StringMaxSize > 0)
05375     {
05376         Node * pChar = pTextLine->FindFirstChild(CC_RUNTIME_CLASS(TextChar));
05377 
05378         while (pChar && StringMaxSize > 0)
05379         {
05380             *AddTemplate = (TCHAR) (((TextChar *)pChar)->GetUnicodeValue());
05381             Content += AddTemplate;
05382 
05383             StringMaxSize--;
05384 
05385             pChar = pChar->FindNext(CC_RUNTIME_CLASS(TextChar));
05386         }
05387 
05388         pTextLine = pTextLine->FindNext(CC_RUNTIME_CLASS(TextLine));
05389 
05390         if (pTextLine)
05391         {
05392             Content += _T("\n");
05393             StringMaxSize -=2;
05394         }
05395 
05396     }
05397 
05398     return Content;
05399 }
05400 
05401 
05402 
05403 /********************************************************************************************
05404 
05405 >   BOOL TextStory::IsGradientFilled()
05406 
05407     Author:     Chris_Gallimore (Xara Group Ltd) <camelotdev@xara.com>
05408     Created:    9/11/00
05409     Returns:    Whether the text story is gradient filled (true), or not (false)
05410     Purpose:    Looks through the list of nodes under the text story node to see if there 
05411                 are any gradient fills on this object.
05412     .
05413     See also:   TextStory::PreExportRender
05414 
05415 ********************************************************************************************/
05416 BOOL TextStory::IsGradientFilled ()
05417 {
05418     ListItem    *pItem          = NULL;
05419     List        GradientList;
05420     BOOL        filled          = FALSE;
05421 
05422     // Now get the list of nodes.
05423     BevelTools::GetAllNodesUnderNode ( this, &GradientList,
05424                                        CC_RUNTIME_CLASS ( AttrFillGeometry ) );
05425 
05426     // (ChrisG 8/11/00) - Cycle through the gradient list finding if there are any
05427     //  gradient fills.
05428     pItem = GradientList.GetHead ();
05429     while ( (pItem != NULL) && (filled == FALSE) )
05430     {
05431         NodeListItem        *pNodeItem  = ( NodeListItem* ) pItem;
05432         AttrFillGeometry    *pFill      = ( AttrFillGeometry* ) pNodeItem->pNode;
05433 
05434         // if it is a graduated colour fill, then it is gradient filled
05435         if ( pFill->IsAColourFill () &&         // Is it a colour fill?
05436              pFill->IsAGradFill ())             // Is it a graduated fill?
05437         {
05438             filled = TRUE;
05439         }
05440 
05441         // Get the next list item.
05442         pItem = GradientList.GetNext ( pItem );
05443     }
05444 
05445     GradientList.DeleteAll();
05446 
05447     return filled;
05448 }

Generated on Sat Nov 10 03:46:11 2007 for Camelot by  doxygen 1.4.4