textops.cpp

Go to the documentation of this file.
00001 // $Id: textops.cpp 1446 2006-07-14 21:53:52Z 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 // Implementation of the text tool
00099 
00100 // This file contains the implementation of the following classes 
00101 
00102 // class OpTextUndoable:    All Undoable text operations are derived from this base class. It
00103 //                          currently provides a set of useful DO functions for text ops.
00104 // class OpCreateTextObject:This is the op which gets executed to create a text object
00105 // class OpTextFormat:      This op groups together those text operations which will cause
00106 //                          the TextStory to be formatted.
00107 // class OpFitTextToCurve:  Self explanatory
00108 
00109 // class OpTextCaret:       Derived from Operation. This Op groups together all caret operations.
00110 
00111 
00112 /*
00113 */
00114 
00115 #include "camtypes.h"
00116 #include "textops.h"    
00117 
00118 #include "dlgmgr.h"
00119 // Resource files
00120 //#include "markg.h"    
00121 
00122 // Code headers
00123 //#include "app.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00124 //#include "attrmgr.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00125 #include "blobs.h"
00126 #include "clipint.h"
00127 //#include "document.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00128 //#include "docview.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00129 #include "invalid.h"
00130 #include "layer.h"
00131 #include "nodetext.h"
00132 #include "nodetxtl.h"
00133 #include "nodetxts.h"
00134 #include "objchge.h"
00135 //#include "ops.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00136 //#include "paths.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00137 #include "progress.h"
00138 #include "textacts.h"
00139 #include "textfuns.h"
00140 #include "textinfo.h"
00141 #include "texttool.h"
00142 //#include "trans2d.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00143 //#include "peter.h"
00144 //#include "simon.h"
00145 //#include "spread.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00146 #include "ndtxtpth.h"
00147 #include "cutop.h"
00148 //#include "will.h"
00149 #include "fontman.h"
00150 //#include "barsdlgs.h"         // for the fit txt to curve bar control
00151 #include "bubbleid.h"
00152 //#include "nodecomp.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00153 #include "slicehelper.h"
00154 //#include "becomea.h" - in camtypes.h [AUTOMATICALLY REMOVED]
00155 #include "opliveeffects.h"
00156 #include "ophist.h"
00157 #include "nodedoc.h"
00158 #include "nodepath.h"
00159 
00160 extern void Beep();
00161 
00162 DECLARE_SOURCE( "$Revision: 1446 $" );
00163 
00164 
00165 // CC_IMPLEMENTS for all classes
00166 #if !defined(EXCLUDE_FROM_RALPH)
00167 // Base class
00168 CC_IMPLEMENT_DYNCREATE(OpTextUndoable, SelOperation)
00169 
00170 // Undoable ops
00171 CC_IMPLEMENT_DYNCREATE(OpDeleteTextStory, SelOperation)
00172 CC_IMPLEMENT_DYNCREATE(OpCreateTextObject, OpTextUndoable)
00173 CC_IMPLEMENT_DYNCREATE(OpTextFormat, OpTextUndoable)
00174 CC_IMPLEMENT_DYNCREATE(OpFitTextToCurve, OpTextUndoable)
00175 CC_IMPLEMENT_DYNCREATE(OpTextKern, OpTextUndoable)
00176 CC_IMPLEMENT_DYNCREATE(OpTextAutoKern, OpTextUndoable)
00177 CC_IMPLEMENT_DYNCREATE(OpReverseStoryPath, OpTextUndoable)
00178 
00179 // Class city for dragging indent blobs on stories
00180 CC_IMPLEMENT_DYNAMIC(OpDragStoryIndent, OpTextUndoable)
00181 CC_IMPLEMENT_DYNAMIC(OpDragStoryPathIndent, OpDragStoryIndent)
00182 CC_IMPLEMENT_DYNAMIC(OpDragStoryNonPathIndent, OpDragStoryIndent)
00183 CC_IMPLEMENT_DYNCREATE(OpDragStoryPathLeftIndent, OpDragStoryPathIndent)
00184 CC_IMPLEMENT_DYNCREATE(OpDragStoryPathRightIndent, OpDragStoryPathIndent)
00185 CC_IMPLEMENT_DYNCREATE(OpDragStoryNonPathLeftIndent, OpDragStoryNonPathIndent)
00186 CC_IMPLEMENT_DYNCREATE(OpDragStoryNonPathRightIndent, OpDragStoryNonPathIndent)
00187 
00188 CC_IMPLEMENT_DYNCREATE(OpTextPaste, OpPaste)
00189 CC_IMPLEMENT_DYNCREATE(OpTogglePrintTextAsShapes, Operation)
00190 CC_IMPLEMENT_DYNCREATE(OpTextCaret, Operation)
00191 CC_IMPLEMENT_DYNCREATE(OpTextSelection, Operation)
00192 #endif
00193 CC_IMPLEMENT_DYNCREATE(OpApplyGlobalAffect, Operation)
00194 #if !defined(EXCLUDE_FROM_RALPH)
00195 CC_IMPLEMENT_DYNCREATE(OpApplyJustificationToStory, Operation)
00196 CC_IMPLEMENT_DYNCREATE(OpApplyLeftJustifyToStory, OpApplyJustificationToStory)
00197 CC_IMPLEMENT_DYNCREATE(OpApplyCentreJustifyToStory, OpApplyJustificationToStory)
00198 CC_IMPLEMENT_DYNCREATE(OpApplyRightJustifyToStory, OpApplyJustificationToStory)
00199 CC_IMPLEMENT_DYNCREATE(OpApplyFullJustifyToStory, OpApplyJustificationToStory)
00200 #endif
00201 CC_IMPLEMENT_DYNCREATE(OpAffectFontChange, OpApplyGlobalAffect)
00202 
00203 CC_IMPLEMENT_DYNCREATE(PrePostTextAction, Action)
00204 CC_IMPLEMENT_DYNCREATE(ToggleAutoKerningAction, Action)
00205 #if !defined(EXCLUDE_FROM_RALPH)
00206 CC_IMPLEMENT_DYNCREATE(PositionCaretAction, Action)
00207 CC_IMPLEMENT_DYNCREATE(StorePathIndentAction, Action)
00208 #endif
00209 
00210 // Must come after the last CC_IMPLEMENT.. macro
00211 #define new CAM_DEBUG_NEW     
00212 
00213 
00214 // The following define attempts to put in a fix to allow pasted in unformatted text
00215 // to take on the attributes of the current Caret position...
00216 // (Doesn't work reliably...)
00217 //#define PHIL_ATTRS_FIX
00218 
00219 // Define this to ensure that the caret is always selected, even when other characters
00220 // are selected. This allows the caret to take part in attribute applciation and
00221 // optimisation correctly
00222 #define SELECT_CARET_AND_CHARS
00223 
00224 #if !defined(EXCLUDE_FROM_RALPH)
00225 // statics
00226 VerticalInsetStore OpTextCaret::VertInset;
00227 
00228     
00230 // OpTextUndoable methods
00231 
00232 /********************************************************************************************
00233 >   OpTextUndoable::OpTextUndoable()
00234 
00235     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
00236     Created:    24/3/95
00237     Purpose:    OpTextUndoable constructor
00238 ********************************************************************************************/
00239 
00240 OpTextUndoable::OpTextUndoable()
00241 {
00242 }
00243 
00244 
00245 /********************************************************************************************
00246 >   virtual BOOL OpTextUndoable::DoStartTextOp(TextStory* pTxtStory, BOOL StartBlobs=TRUE, BOOL EndBlobs=TRUE)
00247 
00248     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
00249     Created:    24/3/95
00250     Inputs:     pTextStory - The TextStory to operate on
00251                 StartBlobs - wether to render object blobs at the start of the op
00252                 EndBlobs   - wether to render object blobs at the end of the op
00253     Returns:    TRUE if successful 
00254                 FALSE if the operation should be aborted (TIDYUP THEN CALL End()!)
00255     Purpose:    This function should get called at the start of each undoable text operation.
00256                 All common actions are done here
00257 ********************************************************************************************/
00258 
00259 BOOL OpTextUndoable::DoStartTextOp(TextStory* pTxtStory, BOOL StartBlobs, BOOL EndBlobs)
00260 {
00261 //  BeginSlowJob();     // text ops only work on one object at a time so are never slow!?
00262 
00263     // Record the selection state
00264     return DoStartSelOp(StartBlobs, EndBlobs);
00265 }
00266 
00267 
00269 // OpCreateTextObject methods
00270 
00271 /********************************************************************************************
00272 >   OpCreateTextObject::OpCreateTextObject()
00273 
00274     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
00275     Created:    01/02/95                          
00276     Purpose:    OpCreateTextObject constructor
00277 ********************************************************************************************/
00278 
00279 OpCreateTextObject::OpCreateTextObject()
00280 {
00281 }
00282 
00283 
00284 /********************************************************************************************
00285 >   void OpCreateTextObject::DoImmediate( Spread* pSpread, DocCoord Anchor, NodePath* pPath,
00286                     ClickModifiers ClickMods, DocCoord StoryWidth = DocCoord(MinDocCoord,MinDocCoord))
00287     
00288     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com> (From Peter code) 
00289     Created:    09/03/95
00290     Inputs:     pSpread - The spread that the click happened on
00291                 The Anchor and pPath inputs are mutually exclusive. if one is specified
00292                 the other should not be. 
00293                 When the user clicks on a spread, the Anchor will be the click pos
00294                 When the user clicks on a path the pPath will be a pointer to this path.
00295                 ClickMods - the current click modifiers
00296                 StoryWidth - Another point specifing width and angle of story.
00297                             Specify MinDocCoord,MinDocCoord for a non-wrapping story
00298     Purpose:    This function starts and ends the operation of inserting a text object onto
00299                 a spread. It is the DO function.
00300 ********************************************************************************************/
00301 void OpCreateTextObject::DoImmediate( Spread* pSpread, DocCoord Anchor, NodePath* pPath,
00302                                         ClickModifiers ClickMods, DocCoord StoryWidth)
00303 {
00304     // Snap the click point
00305     DocView::SnapCurrent(pSpread, &Anchor);
00306     
00307     // create the text object
00308     TextStory* pTextStory = NULL; 
00309     ALLOC_WITH_FAIL(pTextStory, (TextStory::CreateTextObject(Anchor)), this); 
00310     BOOL ok = (pTextStory != NULL);
00311 
00312     // Create a word-wrapping story on a path
00313     if (ok && pPath==NULL && StoryWidth!=DocCoord(MinDocCoord,MinDocCoord))
00314     {
00315         // Set the wrapping width
00316         pTextStory->SetWordWrapping(TRUE);
00317         pTextStory->SetStoryWidth((MILLIPOINT)Anchor.Distance(StoryWidth));
00318 
00319         // Set the angle on the story too.
00320         double StoryRot = atan2((double)StoryWidth.y-Anchor.y, (double)StoryWidth.x-Anchor.x)*(180/PI);
00321 
00322         // Construct a rotation matrix and apply to the story
00323         Trans2DMatrix StoryRotMat(Matrix(Anchor, StoryRot));
00324         pTextStory->Transform(StoryRotMat);
00325     }
00326 
00327     // Start the operation
00328     if (ok)
00329         ok = DoStartSelOp(FALSE, TRUE, TRUE, FALSE);
00330 
00331     // Hide any existing empty text story
00332     if (ok)
00333         ok = OpDeleteTextStory::RemoveEmptyFocusStory(this);
00334 
00335     // Apply the current attributes to the TextStory
00336     if (ok)
00337         ok = pOurDoc->GetAttributeMgr().ApplyCurrentAttribsToNode((NodeRenderableInk*)pTextStory);
00338 
00339     // Add it to the document and invalidate the region
00340     if (ok)
00341     {
00342         if (pPath == NULL)
00343             ok = DoInsertNewNode(pTextStory, pSpread, FALSE, TRUE, FALSE);
00344         else
00345             ok = DoInsertNewNode(pTextStory, pPath, NEXT, FALSE, TRUE, FALSE);
00346     }
00347 
00348     // Move the path into it if required
00349     if (ok && pPath!=NULL)
00350     {
00351         // Work out how far along the line the click was, CTRL is position at left
00352         MILLIPOINT KernDist = 0;
00353         if (!ClickMods.Constrain)
00354         {
00355             double dist = 0.0;
00356             ok = pPath->InkPath.DistanceTo(Anchor, &dist);
00357             if (ok)
00358                 KernDist = (MILLIPOINT)dist;
00359         }
00360 
00361         // If SHIFT click or path is closed then we don't want the story to wrap
00362         BOOL PathIsClosed = pPath->InkPath.IsSubPathClosed(pPath->InkPath.GetNumCoords()-1);
00363         if (ClickMods.Adjust || PathIsClosed)
00364         {
00365             pTextStory->SetWordWrapping(FALSE);
00366             pTextStory->SetLeftIndent(0);
00367 
00368             // Old style indent via a kern at the start
00369             // Get the size of an em at the caret
00370             CaretNode* pCaret = pTextStory->GetCaret();
00371             if (ok && (pCaret != NULL) && (KernDist > 0))
00372             {
00373                 FormatRegion FRegion;
00374                 ok = FRegion.Init(pCaret);
00375 
00376                 CharMetrics CharMet;
00377                 if (ok)
00378                     ok = FRegion.GetCharMetrics(&CharMet, FONTEMCHAR);
00379 
00380                 // Insert a kern at the start of the text
00381                 if (ok)
00382                 {
00383                     KernDist = (INT32)((KernDist / (double)CharMet.FontEmWidth)*1000);
00384                     KernCode* pKern = new KernCode();
00385                     if (pKern == NULL)
00386                         ok = FALSE;
00387                     else
00388                     {
00389                         DocCoord kd(KernDist,0);
00390                         pKern->SetValue(kd);
00391                         ok = DoInsertNewNode(pKern,pTextStory->FindFirstChild(CC_RUNTIME_CLASS(TextLine)),
00392                                                                                 FIRSTCHILD,FALSE,FALSE,FALSE);
00393                         // Also insert a zero kern so the caret appears in the correct
00394                         if (ok)
00395                         {
00396                             KernCode* pKern = new KernCode();
00397                             if (pKern == NULL)
00398                                 ok = FALSE;
00399                             else
00400                             {
00401                                 DocCoord kd(0,0);
00402                                 pKern->SetValue(kd);
00403                                 ok = DoInsertNewNode(pKern,pTextStory->FindFirstChild(CC_RUNTIME_CLASS(TextLine)),
00404                                                                                     FIRSTCHILD,FALSE,FALSE,FALSE);
00405                             }
00406                         }
00407                     }
00408                 }    
00409             }       
00410         }
00411         else
00412         {
00413             pTextStory->SetWordWrapping(TRUE);
00414             pTextStory->SetLeftIndent(KernDist);
00415         }
00416 
00417 
00418         //Localise attributes of the story
00419         if (ok) ok = DoLocaliseCommonAttributes(pTextStory); 
00420 
00421         // Move the path into the story
00422         if (ok) ok = DoMoveNode(pPath, pTextStory, FIRSTCHILD);
00423         if (ok) pPath->DeSelect(FALSE);
00424 
00425         // Factor back out the common attributes
00426         if (ok) ok = DoFactorOutCommonChildAttributes(pTextStory);
00427     }
00428 
00429     // Mark the document as modified (as we don't have an opDescriptor)
00430     if (ok)
00431         pOurDoc->SetModified(TRUE); 
00432 
00433     // Now format the new story to position the caret
00434     if (ok)
00435         ok = PrePostTextAction::DoFormatStory(this, pTextStory);
00436 
00437     // Select the caret and give it the focus
00438     if (ok)
00439     {
00440         // Only select caret if we added to ok layer
00441         Layer* pLayer = (Layer*)pTextStory->FindParent(CC_RUNTIME_CLASS(Layer));
00442         if ((pLayer != NULL) && pLayer->IsVisible() && !pLayer->IsLocked())
00443         {
00444             pTextStory->GetCaret()->Select(TRUE);
00445             TextStory::SetFocusStory(pTextStory);
00446             DialogManager::DefaultKeyboardFocus();      // Gives input focus to mainframe
00447         }
00448     }
00449 
00450     // Clear any failure
00451     if (!ok)
00452     {
00453         InformError();
00454         FailAndExecute();
00455     }
00456 
00457     End(); 
00458 }
00459 
00460 
00461 
00462 /********************************************************************************************
00463 >   void OpCreateTextObject::DoDrag( Spread* pSpread, DocCoord Anchor, ClickModifiers ClickMods)
00464 
00465     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
00466     Created:    16/8/96
00467     Inputs:     pSpread - The spread that the drag was started on
00468                 Anchor - starting position of the drag
00469                 ClickMods - the starting click modifiers.
00470     Purpose:    Call to start a drag operation to create a wrapping story with a width.
00471 ********************************************************************************************/
00472 void OpCreateTextObject::DoDrag( Spread* pSpread, DocCoord Anchor, ClickModifiers ClickMods)
00473 {
00474     // Snap the starting point
00475     DocView::SnapCurrent(pSpread, &Anchor);
00476 
00477     // Init member vars
00478     m_AnchorPoint = Anchor;
00479     m_CurrentOtherEnd = Anchor;
00480     m_pStartSpread = pSpread;
00481 
00482     // Eor on the drag blobs for the first time
00483     DocRect EditPathBBox = m_pStartSpread->GetBoundingRect();
00484     RenderDragBlobs(EditPathBBox, m_pStartSpread, FALSE);
00485 
00486     // And tell the Dragging system that we need drags to happen
00487     StartDrag( DRAGTYPE_AUTOSCROLL, NULL, &m_AnchorPoint, FALSE );
00488 }
00489 
00490 
00491 
00492 /********************************************************************************************
00493 >   void OpCreateTextObject::DragPointerMove( DocCoord PointerPos, ClickModifiers ClickMods, Spread* pSpread, BOOL bSolidDrag)
00494 
00495     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
00496     Created:    16/8/96
00497     Inputs:     PointerPos - the current mouse position
00498                 ClickMods - the current click modifiers.
00499                 pSpread - the spread the pointer is over.
00500     Purpose:    Called after the user moves the mouse during a drag operation.  Updates the
00501                 on screen drag indication
00502 ********************************************************************************************/
00503 void OpCreateTextObject::DragPointerMove( DocCoord PointerPos, ClickModifiers ClickMods, Spread* pSpread, BOOL bSolidDrag)
00504 {
00505     // Rub out the old drag blob
00506     DocRect EditPathBBox = m_pStartSpread->GetBoundingRect();
00507     RenderDragBlobs(EditPathBBox, m_pStartSpread, bSolidDrag);
00508 
00509     // Should we ever have multiple spreads this code will prove useful
00510     if (pSpread != m_pStartSpread)
00511         PointerPos = MakeRelativeToSpread(m_pStartSpread, pSpread, PointerPos);
00512 
00513     // Constrain options
00514     // none - horizontal story
00515     // SHIFT - unconstrained
00516     // CTRL - angle constrain
00517     if (!ClickMods.Adjust && !ClickMods.Constrain)
00518         PointerPos.y = m_AnchorPoint.y;
00519     if (ClickMods.Constrain)
00520         DocView::ConstrainToAngle(m_AnchorPoint, &PointerPos);
00521     DocView::SnapCurrent(pSpread, &PointerPos);
00522 
00523     m_CurrentOtherEnd = PointerPos;
00524 
00525     // Put the drag blobs back on in the new position
00526     RenderDragBlobs(EditPathBBox, m_pStartSpread, bSolidDrag);
00527 
00528     // Set the status line to be helpful
00529     String_256 HelpString(_R(IDS_TEXTTOOL_DRAGCREATEPARA));
00530     GetApplication()->UpdateStatusBarText(&HelpString);
00531 }
00532 
00533 
00534 
00535 /********************************************************************************************
00536 >   void OpCreateTextObject::DragFinished( DocCoord PointerPos, ClickModifiers ClickMods, Spread* pSpread, BOOL Success, BOOL bSolidDrag)
00537 
00538     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
00539     Created:    16/8/96
00540     Inputs:     PointerPos - the current mouse position
00541                 ClickMods - the current click modifiers.
00542                 pSpread - the spread the pointer is over.
00543                 Success - FALSE if the user has aborted the drag.
00544     Purpose:    Called at the end of a drag operation.  Creates a text story if the user did
00545                 not cancel the drag.
00546 ********************************************************************************************/
00547 void OpCreateTextObject::DragFinished( DocCoord PointerPos, ClickModifiers ClickMods, Spread* pSpread, BOOL Success, BOOL bSolidDrag)
00548 {
00549     EndDrag();
00550 
00551     // Take off the drag blobs
00552     DocRect EditPathBBox = m_pStartSpread->GetBoundingRect();
00553     RenderDragBlobs(EditPathBBox, m_pStartSpread, bSolidDrag);
00554 
00555     if (Success)
00556     {
00557         // If the drag ended (too) close to the start then we don't want to create a wrapping story
00558         DocRect PointerBlobRect;
00559         GetApplication()->GetBlobManager()->GetBlobRect(m_AnchorPoint, &PointerBlobRect);
00560 
00561         // Call the CreateImmediate function to do all the work.
00562         if (PointerBlobRect.ContainsCoord(m_CurrentOtherEnd))
00563             DoImmediate(m_pStartSpread, m_AnchorPoint, NULL, ClickMods);
00564         else
00565             DoImmediate(m_pStartSpread, m_AnchorPoint, NULL, ClickMods, m_CurrentOtherEnd);
00566     }
00567 }
00568 
00569 
00570 
00571 /********************************************************************************************
00572 >   virtual void OpCreateTextObject::RenderDragBlobs( DocRect Rect, Spread* pSpread, BOOL bSolidDrag )
00573 
00574     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
00575     Created:    16/8/96
00576     Inputs:     Rect - The region that needs the blobs to be drawn
00577                 pSpread - The spread that the drawing will happen on
00578     Purpose:    Draws the drag blobs, in this case a line from the start to the current pos.
00579 ********************************************************************************************/
00580 void OpCreateTextObject::RenderDragBlobs(DocRect EditPathBBox, Spread* pSpread, BOOL bSolidDrag)
00581 {
00582     // If being called from DocView::RenderView, then the spread could be wrong - so
00583     // convert the rectangle if necessary.
00584     if (pSpread != m_pStartSpread)
00585     {
00586         EditPathBBox.lo = MakeRelativeToSpread(m_pStartSpread, pSpread, EditPathBBox.lo);
00587         EditPathBBox.hi = MakeRelativeToSpread(m_pStartSpread, pSpread, EditPathBBox.hi);
00588     }
00589 
00590     // start a rendering loop
00591     RenderRegion* pRegion = DocView::RenderOnTop(NULL, pSpread, ClippedEOR);
00592     while (pRegion)
00593     {
00594         // Draw the line 
00595         pRegion->SetFillColour(COLOUR_NONE);
00596         pRegion->SetLineColour(COLOUR_XORNEW);
00597         pRegion->DrawLine(m_AnchorPoint, m_CurrentOtherEnd);
00598 
00599         // Draw a box at the anchor
00600         pRegion->SetLineColour(COLOUR_BEZIERBLOB);
00601         pRegion->SetFillColour(COLOUR_BEZIERBLOB);
00602         pRegion->DrawBlob(m_AnchorPoint, BT_UNSELECTED);
00603 
00604         // Get the Next render region
00605         pRegion = DocView::GetNextOnTop(NULL);
00606     }
00607 }
00608 
00609 
00610 
00611 /********************************************************************************************
00612 >   void OpCreateTextObject::GetOpName(String_256* OpName)
00613 
00614     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
00615     Created:    7/7/95
00616     Outputs:    OpName: The name of the operation (undo / redo string)
00617     Purpose:    Returns the name of the operation
00618 ********************************************************************************************/
00619 
00620 void OpCreateTextObject::GetOpName(String_256* OpName)
00621 {
00622     *OpName = String_256(_R(IDS_CREATETEXTOBJECT));
00623 }
00624 
00625 
00627 // OpTextFormat methods
00628 
00629 /********************************************************************************************
00630 >   OpTextFormat::OpTextFormat()
00631 
00632     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
00633     Created:    16/02/95
00634     Purpose:    OpTextFormat constructor
00635 ********************************************************************************************/
00636 
00637 OpTextFormat::OpTextFormat()
00638 {
00639 }
00640 
00641 
00642 /********************************************************************************************
00643 >   BOOL OpTextFormat::DoDeleteSelection(TextStory* pTextStory, BOOL ReAttachAttributes,
00644                                             BOOL* AllowOpRefused = NULL)
00645 
00646     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
00647     Created:    10/5/95
00648     Inputs:     pTextStory         - The TextStory to operate on
00649                 ReAttachAttributes - TRUE => reattach caret's attributes after deletion
00650     Outputs:    AllowOpRefused     - TRUE => AllowOp said we could not delete
00651     Returns:    FALSE if failed
00652     Purpose:    Delete any sub-selection within pTextStory (exc caret)
00653 ********************************************************************************************/
00654 
00655 BOOL OpTextFormat::DoDeleteSelection(TextStory* pTextStory, BOOL ReAttachAttributes, BOOL* AllowOpRefused)
00656 {
00657     if (AllowOpRefused != NULL)
00658         *AllowOpRefused = FALSE;
00659 
00660     BOOL ok = TRUE;
00661     TextStory* pStory = TextStory::GetFocusStory();
00662     if (pStory != NULL)
00663     {
00664         BOOL DontDelete = FALSE;
00665         BOOL FoundDeletable = FALSE;
00666         
00667         // Call AllowOp on all the selected characters.
00668         VisibleTextNode* pChar = pStory->FindFirstVTN();
00669         ok = (pChar != NULL);
00670         ObjChangeParam ObjEdit(OBJCHANGE_STARTING,ObjChangeFlags(TRUE),NULL,this);
00671         while (ok && !DontDelete && (pChar != NULL))
00672         {
00673             if (pChar->IsSelected() && !pChar->IsACaret())
00674             {
00675                 FoundDeletable = TRUE;
00676                 DontDelete = !(pChar->AllowOp(&ObjEdit));
00677             }
00678             pChar = pChar->FindNextVTNInStory();
00679         }
00680 
00681         if (DontDelete && (AllowOpRefused != NULL))
00682             *AllowOpRefused = TRUE;
00683 
00684         if (FoundDeletable && !DontDelete)
00685         {
00686             // Get the story to delete the selection - it does all attribute localising and factoring out
00687             if (ok && !DontDelete)
00688                 ok = pStory->DeleteSelectedText(this);
00689 
00690             CaretNode* pCaret = pStory->GetCaret();
00691             if (ok && !DontDelete && (pCaret!=NULL))
00692                 pCaret->SetSelected(TRUE);
00693 
00694             // Attach new attributes to the caret
00695             if (ok && ReAttachAttributes)
00696                 ok = pStory->AttachCaretAttributes();
00697 
00698             // Mark the document as modified (as we don't have an opDescriptor)
00699             if (GetWorkingDoc() != NULL)
00700                 GetWorkingDoc()->SetModified(TRUE);
00701         }
00702     }
00703 
00704     return ok;
00705 }
00706 
00707 
00708 /********************************************************************************************
00709 >   void OpTextFormat::DoInsertChar(WCHAR ch, InsertStatus InsStatus)
00710 
00711     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
00712     Created:    9/7/96
00713     Inputs:     ch        - The character to insert at the caret position
00714                 InsStatus - not yet implemented
00715     Purpose:    see DoInsertCharHelper()
00716 ********************************************************************************************/
00717 
00718 void OpTextFormat::DoInsertChar(WCHAR ch, OpTextFormat::InsertStatus InsStatus)
00719 {
00720     if (!DoInsertCharHelper(ch))
00721         FailAndExecute();
00722     End();
00723 }
00724 
00725 
00726 /********************************************************************************************
00727 >   BOOL OpTextFormat::DoInsertChar(WCHAR ch)
00728 
00729     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
00730     Created:    9/7/96
00731     Inputs:     ch - The character to insert at the caret position
00732     Purpose:    Inserts a new character to the left of the caret
00733                 deleting any selection in the story that exists
00734 ********************************************************************************************/
00735 
00736 BOOL OpTextFormat::DoInsertCharHelper(WCHAR ch)
00737 {
00738     // get ptrs to story and caret
00739     TextStory* pStory = TextStory::GetFocusStory();
00740     ERROR2IF(pStory==NULL,FALSE,"OpTextFormat::DoInsertCharHelper() - no focus story");
00741     CaretNode* pCaret = pStory->GetCaret();
00742     ERROR2IF(pCaret==NULL,FALSE,"OpTextFormat::DoInsertCharHelper() - Story has no caret");
00743 
00744     String_256 MasterText = pStory->GetStoryAsString();
00745 
00746     // start the text op (must be done before AllopOp() which may insert actions)
00747     BOOL ok = DoStartTextOp(pStory);
00748 
00749     // ask caret if it and it's parents allow char to be inserted (allows blend to remap)
00750     ObjChangeParam ObjInsert(OBJCHANGE_STARTING,ObjChangeFlags(),NULL,this);
00751     if (ok) ok = pCaret->AllowOp(&ObjInsert);
00752 
00753     // delete any sub-selection if it exists
00754     if (ok) ok = DoDeleteSelection(pStory,TRUE);
00755 
00756     // Create a new text character node, and insert into tree, and apply caret attrs to it
00757     TextChar* pNewChar = NULL;
00758     if (ok) { ALLOC_WITH_FAIL(pNewChar, (new TextChar()) , this); }
00759     if (ok) ok = (pNewChar!=NULL);
00760     if (ok) pNewChar->SetUnicodeValue(ch); 
00761     if (ok) ok = pNewChar->DoInsertNewNode(this,pCaret,PREV);
00762     if (ok) ok = pCaret->DoApplyAttrsTo(this,pNewChar);
00763 
00764     // Mark the document as modified (as we don't have an opDescriptor)
00765     if (ok && GetWorkingDoc()!=NULL)
00766         GetWorkingDoc()->SetModified(TRUE); 
00767 
00768     // update other textstories that are dependant on this one
00769     SliceHelper::OnTextStoryChanged(pStory, this, &ObjInsert, MasterText);
00770     
00771     // Update all the changed nodes
00772     ObjChangeParam ObjChange(OBJCHANGE_FINISHED,ObjChangeFlags(),NULL,this);
00773     if (ok) ok = UpdateChangedNodes(&ObjChange);
00774 
00775     if (ok) pCaret->ScrollToShow();
00776 
00777     return ok;
00778 }
00779 
00780 
00781 /********************************************************************************************
00782 >   void OpTextFormat::DoDeleteChar(OpTextFormat::DeleteType DelTyp)
00783 
00784     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com> (originally Peter)
00785     Created:    12/2/96
00786     Inputs:     DelTyp - DELETE    => delete char to right of caret
00787                          BACKSPACE => delete char to left  of caret
00788     Purpose:    If there is no sub-selection the char to the right/left of the caret is deleted
00789                 else sub-selection is deleted and caret placed to left of 1st selected char
00790 ********************************************************************************************/
00791 
00792 void OpTextFormat::DoDeleteChar(OpTextFormat::DeleteType DelTyp)
00793 {
00794     TextStory* pStory = TextStory::GetFocusStory();
00795     if (pStory==NULL)
00796         End();  // no focus story so normal node deletion used which does not restore a caret
00797 
00798     String_256 MasterText = pStory->GetStoryAsString();
00799 
00800     BOOL ok = DoStartTextOp(pStory);
00801 
00802     BOOL DeletingAtCaret    = FALSE;    // assume not deleting at caret
00803     BOOL AttachAttrsToCaret = TRUE;     // assume attach carets from char to left after deletion
00804 
00805     // If just the caret is selected then select the character to the left/right
00806     CaretNode* pCaret = pStory->GetCaret();
00807     ERROR3IF(pCaret==NULL,"OpTextFormat::DoDeleteChar() - Focus story had no caret");
00808     VisibleTextNode* pForcedSel = NULL;
00809     // Caret is always selected, so test for subselection using selection end pointer
00810     if (ok && pCaret!=NULL && pStory->GetSelectionEnd()==NULL)
00811     {
00812         DeletingAtCaret = TRUE;
00813         if (DelTyp==OpTextFormat::DEL)
00814         {
00815             pForcedSel = pCaret->FindNextVTNInStory();
00816             ERROR3IF(pForcedSel==NULL,"No VisibleTextNode following the caret");
00817             if (pForcedSel!=NULL && pForcedSel==pStory->FindLastVTN())
00818                 pForcedSel = NULL;      // ensure last EOL in story is not deleted
00819         }
00820         else
00821         {
00822             pForcedSel = pCaret->FindPrevVTNInStory();
00823             if (pForcedSel!=NULL && !pForcedSel->IsAnEOLNode())
00824             {
00825                 pStory->AttachCaretAttributes(); // ensure caret has attrs of node to be deleted
00826                 AttachAttrsToCaret = FALSE;
00827             }
00828                     
00829         }
00830 
00831         // if we have a char to delete, select the char to delete
00832         if (pForcedSel!=NULL)
00833         {
00834             pForcedSel->SetSelected(TRUE);
00835             pCaret->SetSelected(FALSE);
00836         }
00837     }
00838 
00839     // do the deletion
00840     if (!DeletingAtCaret || pForcedSel!=NULL)
00841     {
00842         // Delete the selected character(s)
00843         BOOL AllowOpRefused = TRUE;
00844         ok = DoDeleteSelection(pStory, AttachAttrsToCaret, &AllowOpRefused);
00845 
00846         // if we were deleting at the caret, restore the selection
00847         if (ok && AllowOpRefused && pForcedSel!=NULL)
00848         {
00849             pForcedSel->SetSelected(FALSE);
00850             pCaret->SetSelected(TRUE);
00851         }
00852 
00853         // Mark the document as modified (as we don't have an opDescriptor)
00854         if (GetWorkingDoc()!=NULL)
00855             GetWorkingDoc()->SetModified(TRUE); 
00856         
00857         ObjChangeFlags cFlags; // use these so we have some objchange params to pass to the ontextstory changed fn
00858         ObjChangeParam ObjChange(OBJCHANGE_STARTING,cFlags,NULL,this);
00859 
00860         if (ok)
00861         {
00862             // update other textstories that are dependant on this one
00863             SliceHelper::OnTextStoryChanged(pStory, this, &ObjChange, MasterText);
00864         }
00865 
00866         ObjChange.Define(OBJCHANGE_FINISHED,ObjChangeFlags(TRUE),NULL,this);
00867 
00868 
00869         if (ok) ok = UpdateChangedNodes(&ObjChange);
00870 
00871         if (ok) pCaret->ScrollToShow();
00872     }
00873 
00874     if (!ok)
00875     {
00876         InformError();
00877         FailAndExecute();
00878     }
00879 
00880     End();  
00881 }   
00882 
00883 
00884 /********************************************************************************************
00885 >   BOOL OpTextFormat::DoReturn(OpTextFormat::InsertStatus InsStatus)
00886 
00887     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
00888     Created:    24/6/96
00889     Inputs:     InsStatus - insert or overwrite mode (not yet used)
00890     Returns:    FALSE if fails
00891     Purpose:    Break the text line at the caret with a hard EOL
00892                 For simplicity, a new line is created the word wrapper tidies up
00893 ********************************************************************************************/
00894 
00895 BOOL OpTextFormat::DoReturn(OpTextFormat::InsertStatus InsStatus)
00896 {
00897     ERROR2IF(InsStatus!=OpTextFormat::INSERT,FALSE,"OpTextFormat::DoReturn() - only supports insert mode");
00898     UndoableOperation* pUndoOp = this;
00899 
00900     // Get pointers to focus story, caret, caret line and caret line EOL
00901     TextStory* pStory = TextStory::GetFocusStory();
00902     ERROR2IF(pStory==NULL, FALSE, "OpTextFormat::DoReturn() - No focus story");
00903     CaretNode* pCaret = pStory->GetCaret();
00904     ERROR2IF(pCaret==NULL, FALSE, "OpTextFormat::DoReturn() - Focus story had no caret");
00905     TextLine* pCaretLine = pCaret->FindParentLine();
00906     ERROR2IF(pCaretLine==NULL,FALSE,"OpTextFormat::DoReturn() - caret has no parent");
00907 
00908     String_256 MasterText = pStory->GetStoryAsString();
00909 
00910     // start the text op (must be done before AllopOp() which may insert actions)
00911     BOOL ok = DoStartTextOp(pStory);
00912 
00913     // see if the op is allowed
00914     ObjChangeParam ObjParam(OBJCHANGE_STARTING, ObjChangeFlags(), NULL, pUndoOp);
00915     if (pCaret->AllowOp(&ObjParam)==FALSE)
00916         return TRUE;
00917 
00918     // delete any sub-selection if it exists, then get ptr to last VTN on caret line
00919     if (ok) ok = DoDeleteSelection(pStory, TRUE);
00920 
00921     // After deleting the selection, the caret line may have been deleted
00922     // so we need to get it again in it's new location
00923     pCaret = pStory->GetCaret();
00924     ERROR2IF(pCaret==NULL, FALSE, "OpTextFormat::DoReturn() - Focus story had no caret");
00925     pCaretLine = pCaret->FindParentLine();
00926     ERROR2IF(pCaretLine==NULL,FALSE,"OpTextFormat::DoReturn() - caret has no parent");
00927 
00928     VisibleTextNode* pLastCaretLineVTN = pCaretLine->FindLastVTN();
00929     ERROR2IF(pLastCaretLineVTN==NULL,FALSE,"OpTextFormat::DoReturn() - caret line has no VTN");
00930 
00931     // insert a new EOL on src line before caret with caret's attributes
00932     EOLNode* pNewEOL = NULL;
00933     if (ok) pNewEOL = new EOLNode;
00934     if (ok) ok = (pNewEOL!=NULL);
00935     if (ok) ok = pNewEOL->DoInsertNewNode(pUndoOp,pCaret,PREV);
00936     if (ok) ok = pCaret->DoApplyAttrsTo(pUndoOp,pNewEOL);
00937 
00938     // insert a new line after caret line
00939     TextLine* pNewLine = NULL;
00940     if (ok) pNewLine = new TextLine;
00941     if (ok) ok = (pNewLine!=NULL);
00942     if (ok) ok = pNewLine->DoInsertNewNode(pUndoOp,pCaretLine,NEXT);
00943 
00944     // and move all nodes from the caret upto end of line to the new line
00945     if (ok) ok = pCaretLine->DoLocaliseCommonAttributes(pUndoOp,FALSE,FALSE,NULL);
00946     if (ok) ok = pCaret->DoMoveNodes(pUndoOp,pLastCaretLineVTN,pNewLine,FIRSTCHILD);
00947     if (ok) ok = pCaretLine->DoFactorOutCommonChildAttributes(pUndoOp,FALSE,NULL);
00948     if (ok) ok = pNewLine->DoFactorOutCommonChildAttributes(pUndoOp,FALSE,NULL);
00949 
00950     // update other textstories that are dependant on this one
00951     SliceHelper::OnTextStoryChanged(pStory, this, &ObjParam, MasterText);
00952 
00953     // Update all the changed nodes
00954     ObjChangeParam ObjChange(OBJCHANGE_FINISHED,ObjChangeFlags(),NULL,pUndoOp);
00955     if (ok) ok = UpdateChangedNodes(&ObjChange);
00956 
00957     if (ok) pCaret->ScrollToShow();
00958 
00959     if (!ok) FailAndExecute();
00960     End();
00961 
00962     return ok;
00963 }
00964 
00965 BOOL OpTextFormat::DoTab()
00966 {
00967     UndoableOperation* pUndoOp = this;
00968 
00969     // Get pointers to focus story, caret, caret line and caret line EOL
00970     TextStory* pStory = TextStory::GetFocusStory();
00971     ERROR2IF(pStory==NULL, FALSE, "OpTextFormat::DoTab() - No focus story");
00972     CaretNode* pCaret = pStory->GetCaret();
00973     ERROR2IF(pCaret==NULL, FALSE, "OpTextFormat::DoTab() - Focus story had no caret");
00974     TextLine* pCaretLine = pCaret->FindParentLine();
00975     ERROR2IF(pCaretLine==NULL,FALSE,"OpTextFormat::DoTab() - caret has no parent");
00976 
00977     // start the text op (must be done before AllopOp() which may insert actions)
00978     BOOL ok = DoStartTextOp(pStory);
00979 
00980     // see if the op is allowed
00981     ObjChangeParam ObjParam(OBJCHANGE_STARTING, ObjChangeFlags(), NULL, pUndoOp);
00982     if (pCaret->AllowOp(&ObjParam)==FALSE)
00983         return TRUE;
00984 
00985     // delete any sub-selection if it exists, then get ptr to last VTN on caret line
00986     if (ok) ok = DoDeleteSelection(pStory, TRUE);
00987 
00988     // After deleting the selection, the caret line may have been deleted
00989     // so we need to get it again in it's new location
00990     pCaret = pStory->GetCaret();
00991     ERROR2IF(pCaret==NULL, FALSE, "OpTextFormat::DoTab() - Focus story had no caret");
00992     pCaretLine = pCaret->FindParentLine();
00993     ERROR2IF(pCaretLine==NULL,FALSE,"OpTextFormat::DoTab() - caret has no parent");
00994 
00995     VisibleTextNode* pLastCaretLineVTN = pCaretLine->FindLastVTN();
00996     ERROR2IF(pLastCaretLineVTN==NULL,FALSE,"OpTextFormat::DoTab() - caret line has no VTN");
00997 
00998     // insert a new Tab node before caret with caret's attributes
00999     HorizontalTab* pTab = NULL;
01000     if (ok) pTab = new HorizontalTab;
01001     if (ok) ok = (pTab != NULL);
01002     if (ok) ok = pTab->DoInsertNewNode(pUndoOp,pCaret,PREV);
01003     if (ok) ok = pCaret->DoApplyAttrsTo(pUndoOp,pTab);
01004 
01005     // update other textstories that are dependant on this one
01006     //##SliceHelper::OnTextStoryChanged(pStory, this, &ObjParam, MasterText);
01007 
01008     // Update all the changed nodes
01009     ObjChangeParam ObjChange(OBJCHANGE_FINISHED,ObjChangeFlags(),NULL,pUndoOp);
01010     if (ok) ok = UpdateChangedNodes(&ObjChange);
01011 
01012     if (ok) pCaret->ScrollToShow();
01013 
01014     if (!ok) FailAndExecute();
01015     End();
01016 
01017     return ok;
01018 }
01019 
01020 /********************************************************************************************
01021 >   void OpTextFormat::DoSwapCase()
01022 
01023     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
01024     Created:    05/04/95
01025     Purpose:    The character to the right of the character has its case swapped, and the caret
01026                 is moved right one character
01027 ********************************************************************************************/
01028 
01029 void OpTextFormat::DoSwapCase()
01030 {
01031     // Get pointers
01032     TextStory* pStory = TextStory::GetFocusStory();
01033     ERROR3IF(pStory == NULL, "You attempted to swap a character without a focus story");
01034     CaretNode* pCaret = NULL;
01035     if (pStory != NULL)
01036         pCaret = pStory->GetCaret();
01037     ERROR3IF(pCaret == NULL, "The TextStory didn't have a caret!");
01038     if (pCaret == NULL)
01039         return;
01040 
01041     // If there is a sub-selection then don't do anything
01042     if (pStory->GetSelectionEnd() != NULL)
01043     {
01044         Beep();
01045         return;
01046     }
01047 
01048     // Get a pointer to the character to the right of the caret
01049     VisibleTextNode* pSwapChar = NULL;
01050     pSwapChar = pCaret->FindNextVTNInStory();
01051 
01052     BOOL ok = TRUE;
01053 
01054     if (pSwapChar != NULL)
01055     {
01056         ok = DoStartTextOp(pStory);
01057 
01058         // Tell parent nodes about the op so the story reformats
01059         if (ok)
01060         {
01061             ObjChangeParam ObjInsert(OBJCHANGE_STARTING,ObjChangeFlags(),NULL,this);
01062             if (pSwapChar->AllowOp(&ObjInsert)==FALSE)
01063                 pSwapChar=NULL;
01064         }
01065 
01066         // Swap the case of the found character
01067         if ((pSwapChar != NULL) && ok)
01068         {
01069             if (IS_A(pSwapChar,TextChar))
01070             {
01071                 TextChar* pSwapTextChar = (TextChar*)pSwapChar;
01072                 WCHAR CharValue = pSwapTextChar->GetUnicodeValue();
01073                 CharCase Result = TextManager::ProcessCharCase(&CharValue, Swap);
01074 
01075                 ok = (Result != Failed);
01076 
01077                 if (ok && (Result != UnknownType))
01078                 {
01079                     // Store the characters old UniCode value
01080                     ok = StoreCharCodeAction::DoStoreCharacterCode( this, &UndoActions, pSwapTextChar);
01081 
01082                     // Set it to the new value
01083                     if (ok)
01084                         pSwapTextChar->SetUnicodeValue(CharValue);
01085                 }
01086             }
01087 
01088             // advance the caret to the next character
01089             if (ok)
01090             {
01091                 pStory->MoveCaretRightAChar();
01092                 pStory->GetCaret()->HasMoved();
01093                 pStory->GetCaret()->ScrollToShow();
01094             }
01095         }
01096     }
01097 
01098     // Update all the changed nodes, i.e tell all the parents of the children that have been effected
01099     if (ok)
01100     {
01101         ObjChangeParam ObjChange(OBJCHANGE_FINISHED,ObjChangeFlags(),NULL,this);
01102         ok = UpdateChangedNodes(&ObjChange);
01103     }
01104 
01105     if (!ok)
01106     {
01107         InformError();
01108         FailAndExecute();
01109     }
01110 
01111     End();
01112 }
01113 
01114 
01115 /********************************************************************************************
01116 >   void OpTextFormat::GetOpName(String_256* OpName)
01117 
01118     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
01119     Created:    7/7/95
01120     Outputs:    OpName: The name of the operation (undo / redo string)
01121     Purpose:    Returns the name of the operation
01122 ********************************************************************************************/
01123 
01124 void OpTextFormat::GetOpName(String_256* OpName)
01125 {
01126     *OpName = String_256(_R(IDS_TYPING));
01127 }
01128 
01129 
01131 // PositionCaretAction
01132 
01133 /********************************************************************************************
01134 >   PositionCaretAction::PositionCaretAction()
01135 
01136     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
01137     Created:    11/4/95
01138     Purpose:    Constructor for PositionCaretAction.  Initialises the member variables  
01139 ********************************************************************************************/
01140 
01141 PositionCaretAction::PositionCaretAction()
01142 {
01143     pTextStory = NULL;
01144     pNearNode = NULL;
01145     AttachDirection = NEXT;
01146 }
01147 
01148 
01149 /********************************************************************************************
01150 >   PositionCaretAction::~PositionCaretAction()
01151 
01152     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
01153     Created:    11/4/95
01154     Purpose:    Destructor for PositionCaretAction.  
01155 ********************************************************************************************/
01156 
01157 PositionCaretAction::~PositionCaretAction()
01158 {
01159 }
01160 
01161 
01162 /********************************************************************************************
01163 >   ActionCode PositionCaretAction::Execute()
01164 
01165     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
01166     Created:    11/4/95
01167     Returns:    ActionCode, either AC_OK, AC_NORECORD or AC_FAIL
01168     Purpose:    This action inserts the opposite PositionCaretAction for redo purposes, then
01169                 moves the CaretNode within the TextStory.
01170 ********************************************************************************************/
01171 
01172 ActionCode PositionCaretAction::Execute()
01173 {
01174     ERROR2IF(pTextStory==NULL,AC_FAIL,"PositionCaretAction::Execute() - pTextStory==NULL");
01175     ERROR2IF( pNearNode==NULL,AC_FAIL,"PositionCaretAction::Execute() - pNearNode==NULL");
01176     CaretNode* pCaret = pTextStory->GetCaret();
01177     ERROR2IF(    pCaret==NULL,AC_FAIL,"PositionCaretAction::Execute() - pCaret==NULL");
01178     
01179     // create redo action to position caret where it is currently
01180     BaseTextClass*      pReDoAttachNode = NULL;
01181     AttachNodeDirection ReDoAttachDir   = NEXT;
01182     if (!GetCaretNeighbour(pTextStory, &pReDoAttachNode, &ReDoAttachDir))
01183         return AC_FAIL;
01184     if (PositionCaretAction::Init(pOperation,pOppositeActLst,pTextStory,pReDoAttachNode,ReDoAttachDir)==AC_FAIL)
01185         return AC_FAIL;
01186 
01187     // ensure FIRSTCHILD actually means first child object
01188     Node*               pAttachNode = pNearNode;
01189     AttachNodeDirection  AttachDir  = AttachDirection;
01190     if (AttachDirection==FIRSTCHILD)
01191         pNearNode->GetAttachNodeAndDirectionToAttachFirstChildObject(&pAttachNode,&AttachDir);
01192 
01193     // Move caret, deselect in old pos and reselect in new pos to maintain ParentOfSelected
01194     BOOL CaretWasSelected = pCaret->IsSelected();
01195     if (CaretWasSelected)
01196         pCaret->SetSelected(FALSE);
01197     pCaret->MoveNode(pAttachNode, AttachDir);
01198     if (CaretWasSelected)
01199         pCaret->SetSelected(TRUE);
01200 
01201     pTextStory->AttachCaretAttributes();            // ensure caret has attrs of its context
01202     pCaret->ScrollToShow();                         // ensure caret on screen
01203 
01204     return AC_OK;
01205 }
01206 
01207 
01208 /********************************************************************************************
01209 >   static ActionCode PositionCaretAction::Init(Operation* pOp,
01210                                                 ActionList* pActionList,
01211                                                 TextStory* pStory,
01212                                                 BaseTextClass* pAttachNode,
01213                                                 AttachNodeDirection Direction)
01214 
01215     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
01216     Created:    11/4/95
01217     Inputs:     pOp is the pointer to the operation to which this action belongs
01218                 pActionList is the action list to which this action should be added
01219                 pStory - pointer to the text story to apply this action to
01220                 pAttachNode is the node the caret should be attached to
01221                 Direction is the direction of the attachment (either NEXT or PREV)
01222     Returns:    ActionCode, one of AC_OK, AC_NO_RECORD or AC_FAIL
01223     Purpose:    Call this function to cause the move of caret a TextStory during an
01224                 undo/redo set of operations.  In your Do function move the caret, then
01225                 call this function to insert an undo record for that deletion.
01226     SeeAlso:    Action::Init(), PositionCaretAction::Execute()
01227 ********************************************************************************************/
01228 
01229 ActionCode PositionCaretAction::Init(Operation* pOp,
01230                                     ActionList* pActionList,
01231                                     TextStory* pStory,
01232                                     BaseTextClass* pAttachNode,
01233                                     AttachNodeDirection Direction)
01234 {
01235     UINT32 ActSize = sizeof(PositionCaretAction);
01236 
01237     PositionCaretAction* pNewAction = NULL;
01238 
01239     ActionCode Ac = Action::Init( pOp, pActionList, ActSize, CC_RUNTIME_CLASS(PositionCaretAction), (Action**)&pNewAction);
01240     if ((Ac == AC_OK) && (pNewAction != NULL))
01241     {
01242         ERROR2IF(pStory == NULL, AC_FAIL, "TextStory pointer was NULL");
01243         ERROR2IF(pAttachNode == NULL, AC_FAIL, "Attachment node pointer was NULL");
01244         pNewAction->pTextStory = pStory;
01245         pNewAction->pNearNode = pAttachNode;
01246         pNewAction->AttachDirection = Direction;
01247     }
01248 
01249     return Ac;
01250 }
01251 
01252 
01253 /********************************************************************************************
01254 >   static BOOL PositionCaretAction::DoStoreCaretPosition(Operation* pOp, TextStory* pStory)
01255 
01256     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com> (originally Peter)
01257     Created:    15/2/96
01258     Inputs:     pOp    - operation to which this action belongs (NULL if not undoable)
01259                 pStory - pointer to textstory to which this action applies
01260     Returns:    FALSE if fails
01261     Purpose:    Call this function to restore the position of the caret in an undo list
01262 ********************************************************************************************/
01263 
01264 BOOL PositionCaretAction::DoStoreCaretPosition(Operation* pOp, TextStory* pStory)
01265 {
01266     ERROR2IF(pStory==NULL,FALSE,"PositionCaretAction::DoStoreCaretPosition() - pStory==NULL");
01267     ERROR2IF(pOp   ==NULL,FALSE,"PositionCaretAction::DoStoreCaretPosition() - pOp==NULL");
01268 
01269     // Get a pointer to the node to reattach the caret to
01270     CaretNode* pCaret = pStory->GetCaret();
01271     ERROR2IF(pCaret==NULL,FALSE,"PositionCaretAction::DoStoreCaretPosition() - pCaret==NULL");
01272 
01273     ActionList* pActionList = pOp->GetUndoActionList();
01274     ERROR2IF(pActionList==NULL,FALSE,"PositionCaretAction::DoStoreCaretPosition() - pActionList==NULL");
01275 
01276     // get a neighbour for the caret, and its direction
01277     BaseTextClass* pReattachNode = NULL;
01278     AttachNodeDirection AttachDir = NEXT;
01279     if (GetCaretNeighbour(pStory, &pReattachNode, &AttachDir)==FALSE)
01280         return FALSE;
01281 
01282     // Create an action for this
01283     if (PositionCaretAction::Init(pOp, pActionList, pStory, pReattachNode, AttachDir)==AC_FAIL)
01284         return FALSE;
01285 
01286     return TRUE;
01287 }
01288 
01289 
01290 /********************************************************************************************
01291 >   BOOL PositionCaretAction::GetCaretNeighbour(TextStory* pStory, BaseTextClass** ppNeighbour,
01292                                                 AttachNodeDirection* pDirection)
01293 
01294     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
01295     Created:    12/4/95
01296     Inputs:     pStory      - points the TextStory with the caret
01297     Outputs:    ppNeighbour - ptr to node the caret should be attached to 
01298                 pDirection  - direction of the attachment
01299     Returns:    FALSE if fails
01300     Purpose:    Gets a pointer to the node that execute function should attach the caret to.
01301 ********************************************************************************************/
01302 
01303 BOOL PositionCaretAction::GetCaretNeighbour(TextStory* pStory, BaseTextClass** ppNeighbour,
01304                                             AttachNodeDirection* pDirection)
01305 {
01306     ERROR2IF(     pStory== NULL,FALSE,"PositionCaretAction::GetCaretNeighbour() - pStory==NULL");
01307     ERROR2IF(ppNeighbour== NULL,FALSE,"PositionCaretAction::GetCaretNeighbour() - ppNeighbour==NULL");
01308     ERROR2IF( pDirection== NULL,FALSE,"PositionCaretAction::GetCaretNeighbour() - pDirection==NULL");
01309 
01310     // Get a pointer to the node to reattach the caret to
01311     CaretNode* pCaret = pStory->GetCaret();
01312     ERROR2IF(pCaret==NULL,FALSE,"PositionCaretAction::GetCaretNeighbour() - story has no caret");
01313     *ppNeighbour = pCaret->FindPrevVTNInLine();
01314     *pDirection  = NEXT;
01315     if (*ppNeighbour==NULL)
01316     {
01317         *ppNeighbour = pCaret->FindNextVTNInLine();
01318         *pDirection  = PREV;
01319     }
01320     if (*ppNeighbour==NULL)
01321     {
01322         *ppNeighbour = pCaret->FindParentLine();
01323         ERROR2IF(*ppNeighbour==NULL,FALSE,"PositionCaretAction::GetCaretNeighbour() - caret has no parent TExtLine");
01324         *pDirection  = FIRSTCHILD;
01325     }
01326 
01327     return TRUE;
01328 }
01329 
01330 
01332 // OpTextKern methods
01333 
01334 /********************************************************************************************
01335 >   OpTextKern::OpTextKern() 
01336 
01337     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
01338     Created:    29/03/95
01339     Purpose:    OpTextKern constructor
01340 ********************************************************************************************/
01341             
01342 OpTextKern::OpTextKern(): OpTextUndoable()                              
01343 {                              
01344 }
01345 
01346 
01347 /********************************************************************************************
01348 >   BOOL OpTextKern::Init()
01349 
01350     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
01351     Created:    09/03/95
01352     Returns:    TRUE if the operation could be successfully initialised 
01353                 FALSE if no more memory could be allocated 
01354     Purpose:    OpTextKern initialiser method
01355     Errors:     ERROR will be called if there was insufficient memory to allocate the 
01356                 operation.
01357 ********************************************************************************************/
01358 
01359 BOOL OpTextKern::Init()
01360 {
01361     return (RegisterOpDescriptor(0,
01362                             _R(IDS_KERNOP),
01363                             CC_RUNTIME_CLASS(OpTextKern),
01364                             OPTOKEN_KERNTEXT,
01365                             OpTextKern::GetState,
01366                             0,                          // help ID 
01367                             0,                          // Bubble help
01368                             0,
01369                             0,
01370                             SYSTEMBAR_ILLEGAL,          // For now !
01371                             TRUE,                       // Receive messages
01372                             FALSE,
01373                             FALSE,
01374                             0,
01375                             GREY_WHEN_NO_CURRENT_DOC | GREY_WHEN_NO_SELECTION | DONT_GREY_WHEN_SELECT_INSIDE
01376            )); 
01377 }               
01378 
01379     
01380 /********************************************************************************************
01381 >   OpState OpTextKern::GetState(String_256*, OpDescriptor*)
01382 
01383     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
01384     Created:    09/03/95
01385     Returns:    The state of the OpTextKern
01386     Purpose:    For finding OpTextKern's state. The Op is greyed if there is no selected
01387                 caret in the focus story.
01388 ********************************************************************************************/
01389 
01390 OpState OpTextKern::GetState(String_256* UIDescription, OpDescriptor*)
01391 {
01392     OpState OpSt;
01393     OpSt.Greyed = TRUE;
01394 
01395     TextStory* pStory = TextStory::GetFocusStory();
01396     if (pStory != NULL)
01397     {
01398         CaretNode* pCaret = pStory->GetCaret();
01399         if (pCaret != NULL) 
01400         {
01401             if (pCaret->IsSelected() && pStory->GetSelectionEnd() == NULL)
01402                 OpSt.Greyed = FALSE;
01403         }
01404     }
01405 
01406     return OpSt;   
01407 }
01408 
01409 
01410 /********************************************************************************************
01411 >   void OpTextKern::DoWithParam(OpDescriptor*, OpParam* pOpParam)
01412 
01413     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
01414     Created:    09/03/95
01415     Inputs:     OpDescriptor (unused)
01416                 pOpParam->Param1 = XKernValue
01417                 pOpParam->Param2 = YKernValue
01418     Purpose:    If the Caret is selected in the focus story then the kern is inserted to the 
01419                 right of the caret. 
01420                 If there is a sub-selection then a kern is inserted before the first selected
01421                 character and a negative kern after the last selected character.  
01422     SeeAlso:    OpTextKern::DoInsertKernWithMerge
01423 ********************************************************************************************/
01424     
01425 void OpTextKern::DoWithParam(OpDescriptor*, OpParam* pOpParam)
01426 {   
01427     // Extract the parameter
01428     DocCoord KernValue(pOpParam->Param1, pOpParam->Param2);
01429 
01430     // get the focus story and caret
01431     TextStory* pFocusStory=TextStory::GetFocusStory();
01432     CaretNode* pCaret = NULL;
01433     ERROR3IF(pFocusStory==NULL,"OpTextKern::DoWithParam() - The focus story is missing");
01434     if (pFocusStory != NULL)
01435         pCaret = pFocusStory->GetCaret();
01436     ERROR3IF(pCaret==NULL,"OpTextKern::DoWithParam() - The text story's caret is missing");
01437     BOOL ok = ((pFocusStory != NULL) && (pCaret != NULL));
01438 
01439     // Start the TextOp - removes blobs and restores them at the end for us
01440     if (ok)
01441         ok = DoStartTextOp(pFocusStory);
01442     
01443     // if no selection insert kern before caret (else insert one at start and reverse at end of selection)
01444     if ( ok && (pFocusStory->GetSelectionEnd() == NULL))
01445     {
01446         ok = DoInsertKernWithMerge(pCaret, KernValue);
01447     }
01448 
01449     // Update all the changed nodes, i.e tell all the parents of the children that have been effected
01450     if (ok)
01451     {
01452         ObjChangeParam ObjChange(OBJCHANGE_FINISHED,ObjChangeFlags(),NULL,this);
01453         ok = UpdateChangedNodes(&ObjChange);
01454     }
01455 
01456     GetApplication()->UpdateSelection();
01457 
01458     if (!ok)
01459         FailAndExecute();
01460 
01461     End(); 
01462 }           
01463 
01464 
01465 /********************************************************************************************
01466 >   BOOL OpTextKern::DoInsertKernWithMerge(VisibleTextNode* TTContextNode, DocCoord& Value)
01467 
01468     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
01469     Created:    30/3/95
01470     Inputs:     TTContextNode: A kern will be Inserted before this node
01471                 Value:         The kern's X,Y offset value
01472     Returns:    TRUE if it worked
01473                 FALSE otherwise, Call End()
01474     Purpose:    This Do function inserts a Kern before TTContextNode and merges adjacent
01475                 kerns.
01476     SeeAlso:    OpTextKern::DoWithParam
01477 ********************************************************************************************/
01478 
01479 BOOL OpTextKern::DoInsertKernWithMerge(VisibleTextNode* TTContextNode, DocCoord& Value)
01480 {
01481     ERROR2IF(TTContextNode == NULL, FALSE, "TTContextNode is NULL");
01482     DocCoord KernValue = Value;
01483     AbstractTextChar* pPrevATC = (AbstractTextChar*)TTContextNode->FindPrevious(CC_RUNTIME_CLASS(AbstractTextChar));
01484     AbstractTextChar* pNextATC = (AbstractTextChar*)TTContextNode->FindNext(CC_RUNTIME_CLASS(AbstractTextChar));
01485     BOOL ok = TRUE;
01486 
01487     // if there is a kern to the right hide it and merge its value into the new one
01488     ObjChangeParam ObjDelete(OBJCHANGE_STARTING,ObjChangeFlags(TRUE),NULL,this);
01489     if (ok && (pNextATC!=NULL) && IS_A(pNextATC, KernCode))
01490     {
01491         KernValue = KernValue + ((KernCode*)pNextATC)->GetValue();
01492         ok = pNextATC->AllowOp(&ObjDelete);
01493         if (ok)
01494             ok = DoHideNode(pNextATC, TRUE);
01495     }
01496 
01497     // Hide the old kern code if the new values are zero
01498     if (ok && (pPrevATC!=NULL) && IS_A(pPrevATC, KernCode) && (KernValue.x==0) && (KernValue.y==0))
01499     {
01500         ok = pPrevATC->AllowOp(&ObjDelete);
01501         if (ok)
01502             ok = DoHideNode(pPrevATC, TRUE);
01503         pPrevATC = NULL;
01504     }
01505 
01506     // if the new kern will have an effect, set its value and insert it into the tree
01507     if (ok && (KernValue.x!=0 || KernValue.y!=0))
01508     {
01509         // Create a new kern if required, otherwise update the existing one
01510         if ((pPrevATC == NULL) || !IS_A(pPrevATC,KernCode) )
01511         {
01512             // Create the new Kern code
01513             KernCode* pKernCode = NULL;
01514             ALLOC_WITH_FAIL(pKernCode, (new KernCode()), this);
01515             ok = (pKernCode != NULL);
01516 
01517             // Now copy the attributes from the previous character to the kern
01518             if (ok)
01519             {
01520                 AbstractTextChar* pAttrChar = pPrevATC;
01521                 if (pAttrChar == NULL)
01522                     pAttrChar = pNextATC;
01523                 if (pAttrChar != NULL)
01524                     ok = pAttrChar->CopyChildrenTo(pKernCode);
01525                 // BODGE TEXT - should only copy children attributes
01526             }
01527 
01528             // Insert it in the tree
01529             if (ok)
01530             {
01531                 pKernCode->SetValue(KernValue);
01532                 ObjChangeParam ObjInsert(OBJCHANGE_STARTING,ObjChangeFlags(),NULL,this);
01533                 ok = TTContextNode->AllowOp(&ObjInsert);
01534                 if (ok)
01535                     ok = pKernCode->DoInsertNewNode(this,TTContextNode,PREV);
01536             }
01537         }
01538         else
01539         {
01540             // Call AllowOp to mark the kern code as changed
01541             ObjChangeParam ObjEdit(OBJCHANGE_STARTING,ObjChangeFlags(),NULL,this);
01542             if (pPrevATC->AllowOp(&ObjEdit))
01543             {
01544                 // Store the current code value
01545                 ok = StoreKernCodeAction::DoStoreKernValue(this, &UndoActions, (KernCode*)pPrevATC);
01546 
01547                 // set it to the new value
01548                 if (ok)
01549                     ((KernCode*)pPrevATC)->SetValue(KernValue);
01550             }
01551         }
01552     }
01553 
01554     return ok;
01555 }   
01556 
01557 
01558 /********************************************************************************************
01559 >   virtual void OpTextKern::PerformMergeProcessing()
01560 
01561     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
01562     Created:    14/8/95
01563     Purpose:    This virtual function gets called to allow for the merging of text kern ops
01564     Errors:     [Not allowed to fail!]
01565     SeeAlso:    UndoableOperation::PerformMergeProcessing
01566 ********************************************************************************************/
01567 
01568 void OpTextKern::PerformMergeProcessing()
01569 {    
01570     // Obtain a pointer to the operation history for the current document
01571     OperationHistory* pOpHist = &pOurDoc->GetOpHistory();
01572     ERROR3IF(pOpHist->FindLastOp() != this, "Last Op should be this op"); 
01573     
01574     // Is operation performed before this a OpTextKern?
01575     Operation* pPrevOp = pOpHist->FindPrevToLastOp();
01576 
01577     if ((pPrevOp != NULL) && IS_A(pPrevOp, OpTextKern))
01578     {
01579         // We can merge with the previous kern if the two ops both have a StoreKernCodeAction
01580         // And they both reference the same (document) kern code
01581         // We also need to access the invalid rectangles within the action list
01582         StoreKernCodeAction* pThisStoreKern = (StoreKernCodeAction*)GetUndoActionList()->FindActionOfClass(CC_RUNTIME_CLASS(StoreKernCodeAction));  
01583         StoreKernCodeAction* pPrevStoreKern = (StoreKernCodeAction*)pPrevOp->GetUndoActionList()->FindActionOfClass(CC_RUNTIME_CLASS(StoreKernCodeAction));
01584         InvalidateArbitaryAction* pThisInvalidRect = (InvalidateArbitaryAction*)GetUndoActionList()->FindActionOfClass(CC_RUNTIME_CLASS(InvalidateArbitaryAction));
01585         InvalidateArbitaryAction* pPrevInvalidRect = (InvalidateArbitaryAction*)pPrevOp->GetUndoActionList()->FindActionOfClass(CC_RUNTIME_CLASS(InvalidateArbitaryAction));
01586     
01587         if ((pThisStoreKern != NULL) && (pPrevStoreKern != NULL) && 
01588                 (pThisInvalidRect != NULL) && (pPrevInvalidRect != NULL) && 
01589                 (pThisStoreKern->GetKernCode() == pPrevStoreKern->GetKernCode()) &&
01590                 (pThisInvalidRect->GetSpread() == pPrevInvalidRect->GetSpread()))
01591         {
01592             // In order to merge these two ops just throw away this op!
01593             // The kern code already has the new value, on undo the (correct) stored value is restored
01594             // However, we must merge the two redraw rectangles
01595             DocRect AllInvalid = pThisInvalidRect->GetRect().Union(pPrevInvalidRect->GetRect());
01596             pPrevInvalidRect->SetRect(AllInvalid);
01597 
01598             // This op is no longer required, so let's vape it
01599             pOpHist->DeleteLastOp(); 
01600         }
01601     }
01602 }
01603 
01605 // OpTextAutoKern methods
01606 
01607 /********************************************************************************************
01608 >   OpTextAutoKern::OpTextAutoKern() 
01609 
01610     Author:     Jonathan_Payne (Xara Group Ltd) <camelotdev@xara.com>
01611     Created:    27/10/2000
01612     Purpose:    OpTextAutoKern constructor
01613 ********************************************************************************************/
01614 OpTextAutoKern::OpTextAutoKern(): OpTextUndoable()                              
01615 {                              
01616 }
01617 
01618 
01619 /********************************************************************************************
01620 >   BOOL OpTextAutoKern::Init()
01621 
01622     Author:     Jonathan_Payne (Xara Group Ltd) <camelotdev@xara.com>
01623     Created:    27/10/2000
01624     Returns:    TRUE if the operation could be successfully initialised 
01625                 FALSE if no more memory could be allocated 
01626     Purpose:    OpTextKern initialiser method
01627     Errors:     ERROR will be called if there was insufficient memory to allocate the 
01628                 operation.
01629 ********************************************************************************************/
01630 BOOL OpTextAutoKern::Init()
01631 {
01632     return (RegisterOpDescriptor(0,                                 // Tool ID
01633                             _R(IDS_AUTOKERNOP),                         // Resource ID
01634                             CC_RUNTIME_CLASS(OpTextAutoKern),       // Runtime class
01635                             OPTOKEN_AUTOKERNTEXT,                   // Token string
01636                             OpTextAutoKern::GetState,               // Get state function
01637                             0,                                      // Help ID
01638                             0,                                      // Bubble help
01639                             0,                                      // Resource ID
01640                             0,                                      // Control ID
01641                             SYSTEMBAR_ILLEGAL,                      // System bar group ID
01642                             TRUE,                                   // Receive messages
01643                             FALSE,                                  // Smart
01644                             FALSE,                                  // Clean
01645                             0,                                      // OneOpenInstID
01646                             GREY_WHEN_NO_CURRENT_DOC | GREY_WHEN_NO_SELECTION | DONT_GREY_WHEN_SELECT_INSIDE    // Auto state flags
01647            )); 
01648 }               
01649 
01650     
01651 /********************************************************************************************
01652 >   OpState OpTextAutoKern::GetState(String_256*, OpDescriptor*)
01653 
01654     Author:     Jonathan_Payne (Xara Group Ltd) <camelotdev@xara.com>
01655     Created:    27/10/2000
01656     Returns:    The state of the OpTextKern
01657     Purpose:    For finding OpTextKern's state. The Op is greyed if there is no selected
01658                 caret in the focus story.
01659 ********************************************************************************************/
01660 OpState OpTextAutoKern::GetState(String_256* UIDescription, OpDescriptor*)
01661 {
01662     OpState OpSt;
01663 
01664     if (TextStory::GetFocusStory())
01665         OpSt.Greyed = FALSE;
01666     else
01667         OpSt.Greyed = TRUE;
01668 
01669     return OpSt;   
01670 }
01671 
01672 
01673 /********************************************************************************************
01674 >   void OpTextAutoKern::DoWithParam(OpDescriptor*, OpParam* pOpParam)
01675 
01676     Author:     Jonathan_Payne (Xara Group Ltd) <camelotdev@xara.com>
01677     Created:    27/10/2000
01678     Inputs:     OpDescriptor (unused)
01679                 pOpParam->Param1 = Autokerning?
01680                 pOpParam->Param2 = (unused)
01681     Purpose:    Set the selected story to use/not use auto kerning
01682 ********************************************************************************************/
01683 void OpTextAutoKern::DoWithParam(OpDescriptor*, OpParam* pOpParam)
01684 {
01685     bool NewIsAutoKerning = pOpParam->Param1 != 0; // avoids warning casting to bool
01686 
01687     TextStory *pTextStory = TextStory::GetFocusStory();
01688     BOOL ok = pTextStory != 0;
01689 
01690     // causes shadows, contours & bevels to regenerate.
01691     ObjChangeFlags ChangeFlags;
01692     ChangeFlags.RegenerateNode = TRUE;
01693 
01694     // Start the TextOp - removes blobs and restores them at the end for us
01695     if (ok) ok = DoStartTextOp(pTextStory);
01696 
01697     if (ok)
01698     {
01699         ObjChangeParam ObjChange(OBJCHANGE_STARTING, ChangeFlags, pTextStory, this);
01700         ok = pTextStory->AllowOp(&ObjChange);
01701     }
01702 
01703     if (ok) ok = DoInvalidateNodeRegion(pTextStory, TRUE, FALSE);
01704 
01705     if (ok) ok = ToggleAutoKerningAction::Init(this, &UndoActions, pTextStory, NewIsAutoKerning) != AC_FAIL;
01706 
01707     if (ok) ok = DoInvalidateNodeRegion(pTextStory, TRUE, FALSE);
01708 
01709     // Inform all changed nodes that we have finished
01710     // Update all the changed nodes, i.e tell all the parents of the children that have been affected
01711     if (ok)
01712     {
01713         ObjChangeParam ObjChange(OBJCHANGE_FINISHED, ChangeFlags, pTextStory, this);
01714         ok = UpdateChangedNodes(&ObjChange);
01715     }
01716 
01717     if (!ok)
01718         FailAndExecute();
01719 
01720     End();
01721 }
01722 
01723 
01725 // OpFitTextToCurve methods
01726             
01727 /********************************************************************************************
01728 >   OpFitTextToCurve::OpFitTextToCurve() 
01729 
01730     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
01731     Created:    09/03/95
01732     Purpose:    OpFitTextToCurve constructor
01733 ********************************************************************************************/
01734             
01735 OpFitTextToCurve::OpFitTextToCurve(): OpTextUndoable()                              
01736 {                              
01737 }
01738 
01739 
01740 /********************************************************************************************
01741 >   BOOL OpFitTextToCurve::Init()
01742 
01743     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
01744     Created:    09/03/95
01745     Returns:    TRUE if the operation could be successfully initialised 
01746                 FALSE if no more memory could be allocated 
01747     Purpose:    OpFitTextToCurve initialiser method
01748     Errors:     ERROR will be called if there was insufficient memory to allocate the 
01749                 operation.
01750 ********************************************************************************************/
01751 
01752 BOOL OpFitTextToCurve::Init()
01753 {
01754     return (RegisterOpDescriptor(0,
01755                             _R(IDS_FITTEXTTOPATHOP),
01756                             CC_RUNTIME_CLASS(OpFitTextToCurve),
01757                             OPTOKEN_FITTEXTTOPATH,
01758                             OpFitTextToCurve::GetState,
01759                             0,                          // help ID 
01760                             _R(IDBBL_FITTEXTTOCURVE),       // Bubble help
01761                             _R(IDD_BARCONTROLSTORE),
01762                             _R(IDC_FITTEXTTOCURVE),
01763                             SYSTEMBAR_EDIT,             // Bar ID
01764                             TRUE,                       // Receive messages
01765                             FALSE,
01766                             FALSE,
01767                             0,
01768                             GREY_WHEN_NO_CURRENT_DOC | GREY_WHEN_NO_SELECTION | DONT_GREY_WHEN_SELECT_INSIDE
01769            )); 
01770 }               
01771 
01772     
01773 /********************************************************************************************
01774 >   OpState OpFitTextToCurve::GetState(String_256*, OpDescriptor*)
01775 
01776     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
01777     Created:    09/03/95
01778     Returns:    The state of the OpFitTextToCurve
01779     Purpose:    For finding OpFitTextToCurve's state.  It is greyed unless there is just one
01780                 path and one text line selected. 
01781 ********************************************************************************************/
01782 
01783 OpState OpFitTextToCurve::GetState(String_256* UIDescription, OpDescriptor*)
01784 {
01785     OpState OpSt;
01786 
01787     NodeRenderableInk* pPath = NULL;
01788     TextStory* pStory = NULL;
01789     UINT32 IDS = 0;
01790 
01791     // BODGE TEXT remove should work with a story with a caret on a path
01792     // Grey the op if we can't do it
01793     if (!FindPathAndText(&pPath, &pStory, NULL, &IDS))
01794     {
01795         OpSt.Greyed = TRUE;
01796         if (IDS == 0)
01797             IDS = _R(IDS_NOTPATHANDTEXTSEL);    
01798     }
01799     else
01800     {
01801         // Set the UI string for fit text / remove text
01802         if (pPath == NULL)
01803             IDS = _R(IDS_REMOVETEXTFROMPATHOP);
01804     }
01805 
01806     if (IDS != 0)
01807         *UIDescription = String_256(IDS);           
01808 
01809     return(OpSt);   
01810 }
01811 
01812 
01813 /********************************************************************************************
01814 >   void OpFitTextToCurve::Do(OpDescriptor*)
01815 
01816     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
01817     Created:    09/03/95
01818     Inputs:     OpDescriptor (unused)
01819     Purpose:    move selected path into selected text story or remove the path from the story
01820 ********************************************************************************************/
01821 
01822 void OpFitTextToCurve::Do(OpDescriptor*)
01823 {   
01824     // get ptrs to path and text story
01825     NodeRenderableInk* pObject = NULL;
01826     TextStory* pStory = NULL;
01827     UINT32 dummy = 0;
01828     BOOL ok=FindPathAndText(&pObject, &pStory, this, &dummy);
01829 
01830     // Start the op by saving the selection state
01831     if (ok) ok = DoStartSelOp(TRUE, TRUE);
01832 
01833     BeginSlowJob();     // Ended by Operation::End();
01834 
01835     // Inform the story so it reformats
01836     if (ok)
01837     {
01838         ObjChangeParam ObjInsert(OBJCHANGE_STARTING,ObjChangeFlags(FALSE,FALSE,FALSE,FALSE,FALSE,TRUE),NULL,this);
01839         ok = pStory->AllowOp(&ObjInsert);
01840     }
01841 
01842     // Inform the path if we are going to move it into the story
01843     if (ok && (pObject != NULL))
01844     {
01845         ObjChangeParam ObjDelete(OBJCHANGE_STARTING,ObjChangeFlags(TRUE),NULL,this);
01846         ObjChangeParam ObjMove(OBJCHANGE_STARTING,ObjChangeFlags(FALSE, FALSE, TRUE),NULL,this);
01847         if (!IS_A(pObject,NodePath))
01848             ok = pObject->AllowOp(&ObjDelete);
01849         else
01850             ok = pObject->AllowOp(&ObjMove);
01851     }
01852 
01853     // Stop now if the AllowOps failed
01854     if (!ok)
01855     {
01856         FailAndExecute();
01857         End();
01858         return;
01859     }
01860         
01861     // Localise attributes of the story
01862     if (ok) ok = DoLocaliseCommonAttributes(pStory); 
01863 
01864     // we need to remove any controller nodes from the path, and
01865     // from the text
01866     // so we can add them later
01867     //
01868     // New method (Phil 07/08/2005):
01869     // If we have pObject we are always going to end up with a text story containing a NodePath
01870     // That text story may be inside a stack of contollers/effects or not
01871     // So, one stack of controllers/effects may need to be removed
01872     //
01873     if (ok && pObject && pStory)
01874     {
01875         Node* pCompoundAboveObj = NULL;
01876         Node* pCompoundAboveText = NULL;
01877 
01878 PORTNOTE("other", "Removed live effects usage from text");
01879 #ifndef EXCLUDE_FROM_XARALX
01880         // Find controller/effect stacks above the object and the text story
01881         ListRange* pStack = EffectsStack::GetEffectsStackFromNode(pObject, FALSE, TRUE, TRUE);
01882         if (pStack)
01883         {
01884             pCompoundAboveObj = pStack->FindLast();
01885             delete pStack;
01886         }
01887 
01888         pStack = EffectsStack::GetEffectsStackFromNode(pStory, FALSE, TRUE, TRUE);
01889         if (pStack)
01890         {
01891             pCompoundAboveText = pStack->FindLast();
01892             delete pStack;
01893         }
01894 
01895         // If both things have compound stacks get rid of the one on the object
01896         if (pCompoundAboveObj!=NULL && pCompoundAboveText!=NULL)
01897         {
01898             OpLiveEffect::DoDeleteAllPostProcessors(this, pObject, TRUE, TRUE);
01899             pCompoundAboveObj = NULL;
01900         }
01901 #endif
01902 
01903         // We want to translate the story so it sits near the start of the path
01904         // so that attributes and effects are in roughly the right position.
01905         // Do it before the text node is moved inside the object's effects stack
01906         // because DoTransformNode transforms all applied effects.
01907         DocRect ObjectBounds = pObject->GetBoundingRect();
01908         DocRect StoryBounds = pStory->GetBoundingRect();
01909 
01910         Trans2DMatrix* pTransMat = new Trans2DMatrix(Matrix(ObjectBounds.lo.x-StoryBounds.lo.x, ObjectBounds.lo.y-StoryBounds.lo.y));
01911         ok = (pTransMat!=NULL);
01912         if (ok)
01913             ok = DoTransformNode(pStory, pTransMat);
01914 
01915         // If the object has a compound stack but the text doesn't then we want the text
01916         // to take on the effects of the object
01917         if (pCompoundAboveObj!=NULL && pCompoundAboveText==NULL)
01918         {
01919             DoMoveNode(pStory, pObject, NEXT);
01920         }
01921     }
01922 
01923     if (pObject == NULL)
01924     {
01925         // If the text story is already fitted to a path
01926         // Then remove the path...
01928         if (ok) ok = MatrixFitToPathAction::DoMatrixRemoveFromPath(this, &UndoActions, pStory);
01929 
01930         // Remove the path from the story
01931         pObject = pStory->GetTextPath();
01932         if (pObject != NULL)
01933         {
01934             // set story width to path length (minus physical indents), this is the simplest way
01935             // BODGE WORDWRAP - does this need to account for x scaling due to matrix?
01936             TextStoryInfo StoryInfo;
01937             if (ok) ok = pStory->CreateUntransformedPath(&StoryInfo);
01938             if (ok)
01939             {
01940                 delete StoryInfo.pPath;
01941                 if (StoryInfo.WordWrapping)
01942                     pStory->SetStoryWidth(StoryInfo.PathLength - StoryInfo.LeftPathIndent - StoryInfo.RightPathIndent);
01943 
01944                 // Move path outside text object (but not outside any controllers that may be applied)
01945                 ok = DoMoveNode(pObject, pStory, NEXT);
01946             }
01947 
01948             if (ok) pObject->SetSelected(FALSE);
01949             if (ok) pStory->SetSelected(TRUE);
01950         }
01951     }
01952     else
01953     {
01954         // move the node into the TextStory
01955         if (ok) pObject->SetSelected(FALSE);
01956         if (ok) ok = DoMoveNode(pObject,pStory,FIRSTCHILD);
01957 
01958         // convert the object to a path if it isn't
01959         if (ok && !IS_A(pObject,NodePath))
01960         {
01961             Range pThing(pObject, pObject, RangeControl(TRUE, TRUE));
01962             ok = DoMakeShapes(pThing);
01963         }
01964 
01965         if (ok) ok = MatrixRemoveFromPathAction::DoMatrixFitToPath(this, &UndoActions, pStory);
01966         if (ok) pStory->SetSelected(TRUE);
01967     }
01968 
01969     // Factor back out the story attributes
01970     if (ok) ok = DoFactorOutCommonChildAttributes(pStory);
01971     
01972     // Tell the parents of the edited nodes
01973     if (ok)
01974     {
01975         ObjChangeParam ObjChange(OBJCHANGE_FINISHED,ObjChangeFlags(FALSE,FALSE,FALSE,FALSE,FALSE,TRUE),NULL,this);
01976         ok = UpdateChangedNodes(&ObjChange);
01977     }
01978 
01979     if (!ok)
01980     {
01981         FailAndExecute();
01982         InformError();
01983     }
01984 
01985     End();
01986 }       
01987 
01988 /********************************************************************************************
01989 >   BOOL OpFitTextToCurve::FindPathAndText(NodeRenderableInk** pPath, TextStory** pStory, UndoableOperation* pOp, UINT32* ID)
01990 
01991     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
01992     Created:    09/03/95
01993     Inputs:     pPath: (See below)
01994                 pStory: (See below)
01995                 pOp: the paramater to pass to ObjChangeParam
01996     Outputs:    pPath points to the path to fit the text to (is NULL is pStory has a path)
01997                 pStory points to the text story to use (is NULL if no stories found)
01998     Returns:    TRUE if there is a selected path and text story OR a selected story with a path
01999                 FALSE if there isn't
02000     Purpose:    Gets the text story and path to fit.  Used by the Do and the GetState functions
02001     SeeAlso:    OpFitTextToCurve::Do, OpFitTextToCurve::GetState
02002 ********************************************************************************************/
02003 
02004 BOOL OpFitTextToCurve::FindPathAndText(NodeRenderableInk** pPath, TextStory** pStory, UndoableOperation* pOp, UINT32* ID)
02005 {
02006     OpState OpSt;
02007 
02008 //  Range* pSelRange = GetApplication()->FindSelection(); 
02009     *pStory = NULL;
02010     *pPath= NULL;
02011 
02012     // Set up the ObjChangeParam so we can ask the selected nodes if they mind being deleted
02013     ObjChangeParam ObjDeleteChange(OBJCHANGE_STARTING,ObjChangeFlags(TRUE),NULL,pOp);
02014     ObjChangeParam ObjNotChange(OBJCHANGE_STARTING,ObjChangeFlags(FALSE),NULL,pOp);
02015 
02016     BOOL FoundOnePath = FALSE;
02017     BOOL FoundOneTextStory = FALSE;
02018     BOOL FoundMultiple = FALSE;
02019     BOOL StoryHadPath = FALSE;
02020 
02021     SelRange* Selected = GetApplication()->FindSelection();
02022     Node* pNode = Selected->FindFirst();
02023 
02024     BOOL bFoundTextStoryWithController = FALSE;
02025     BOOL bFoundPathWithController = FALSE;
02026     
02027     BecomeA TestBecomeAPath(BECOMEA_TEST, CC_RUNTIME_CLASS(NodePath));
02028 
02029     while (pNode && !FoundMultiple)
02030     {
02031         // check for needs parent nodes too
02032         if (pNode->NeedsParent(NULL))
02033         {
02034             // find out if we have a base text class node here, in this hierarchy
02035             *pStory = (TextStory *)pNode->FindNext(CC_RUNTIME_CLASS(BaseTextClass));
02036 
02037             if ((*pStory) == NULL)
02038             {
02039                 *pStory = (TextStory *)pNode->FindPrevious(CC_RUNTIME_CLASS(BaseTextClass));
02040             }
02041 
02042             // have we found the text story ? then dont bother trying to find it again
02043             if ((*pStory) != NULL)
02044                 bFoundTextStoryWithController = TRUE;
02045 
02046             // have we found an object though ?
02047             if (!bFoundTextStoryWithController)
02048             {
02049                 *pPath = (NodeRenderableInk *)pNode->FindNext(CC_RUNTIME_CLASS(NodeRenderableInk));
02050 
02051                 if ((*pPath) == NULL)
02052                 {
02053                     *pPath = (NodeRenderableInk *)pNode->FindPrevious(CC_RUNTIME_CLASS(NodeRenderableInk));
02054                 }
02055 
02056                 if ((*pPath) != NULL && (*pPath)->NeedsParent(NULL))
02057                 {
02058                     *pPath = NULL;
02059                 }
02060             }
02061 
02062             if ((*pPath) != NULL)
02063             {
02064                 bFoundPathWithController = TRUE;
02065             }
02066         }
02067 
02068         // check for controller nodes selected with text children
02069         if (pNode->IsCompound())
02070         {
02071             if (pNode->IsController())
02072             {
02073                 // try to find the text node
02074                 *pStory = (TextStory *)pNode->FindFirstChild(CC_RUNTIME_CLASS(BaseTextClass));
02075                 
02076                 if ((*pStory) != NULL)              
02077                     bFoundTextStoryWithController = TRUE;
02078 
02079                 if (!bFoundTextStoryWithController)
02080                 {
02081                     // try to find the shape node
02082                     *pPath = (NodeRenderableInk *)pNode->FindFirstChild(CC_RUNTIME_CLASS(NodeRenderableInk));
02083 
02084                     while (*pPath && (*pPath)->NeedsParent(NULL))
02085                     {
02086                         *pPath = (NodeRenderableInk *)(*pPath)->FindNext(CC_RUNTIME_CLASS(NodeRenderableInk));
02087                     }
02088                 }
02089             }
02090 
02091             if ((*pPath) != NULL)
02092             {
02093                 bFoundPathWithController = TRUE;
02094             }
02095         }
02096 
02097         if ((pNode->IS_KIND_OF(BaseTextClass) || bFoundTextStoryWithController)
02098             && (*pStory) != pNode) // make sure we won't discount a node we've already found
02099         {
02100             // only do this once
02101             bFoundTextStoryWithController = FALSE;
02102             
02103             TextStory* pTS = NULL;
02104             
02105             if (*(pStory) == NULL)
02106             {
02107                 if (IS_A(pNode,TextStory))
02108                     pTS = (TextStory*)pNode;
02109                 else
02110                     pTS = (TextStory*)pNode->FindParent(CC_RUNTIME_CLASS(TextStory));
02111             }
02112             else
02113             {
02114                 pTS = *pStory;
02115             }
02116 
02117             if (pTS != NULL)
02118             {
02119                 if (FoundOneTextStory && (pTS != *pStory))
02120                     FoundMultiple = TRUE;
02121                 else
02122                 {
02123                     // We should check here to see if the TextStory is already on a path
02124                     if (pTS->AllowOp(&ObjNotChange,FALSE))
02125                     {
02126                         FoundOneTextStory = TRUE;
02127                         *pStory = pTS;
02128                         if ((*pStory)->GetTextPath() != NULL)
02129                         {
02130                             FoundOnePath = TRUE;
02131                             StoryHadPath = TRUE;
02132                         }
02133                     }
02134                 }
02135             }
02136         }
02137         else if (pNode->IsAnObject() &&
02138                 !pNode->IsCompound() &&
02139                 ((NodeRenderableInk*)pNode)->CanBecomeA(&TestBecomeAPath) ||
02140                 bFoundPathWithController && (*pPath) != pNode
02141                 )
02142         {
02143             if (FoundOnePath)
02144                 FoundMultiple = TRUE;
02145             else
02146             {
02147                 if (bFoundPathWithController)
02148                 {
02149                     if (!(*pPath)->AllowOp(&ObjDeleteChange, FALSE))
02150                     {
02151                         *pPath = NULL;
02152                     }
02153                     else
02154                     {
02155                         FoundOnePath = TRUE;
02156                     }
02157                 }
02158                 else if (pNode->AllowOp(&ObjDeleteChange,FALSE))
02159                 {
02160                     FoundOnePath = TRUE;
02161                     *pPath = (NodePath*)pNode;
02162                 }
02163             }
02164         }
02165 
02166         pNode = Selected->FindNext(pNode);
02167     }
02168 
02169     // If there's a sub-selection the use the focus story
02170     if (!FoundOneTextStory && (TextStory::GetFocusStory() != NULL))
02171         FoundOneTextStory = TRUE;
02172 
02173     *ID = ObjDeleteChange.GetReasonForDenial();
02174 
02175     if (StoryHadPath)
02176         *pPath = NULL;
02177 
02178     return !FoundMultiple && ((FoundOnePath && FoundOneTextStory) || (StoryHadPath && FoundOneTextStory));
02179 }
02180 
02181 
02183 // OpReverseStoryPath
02184 
02185 /********************************************************************************************
02186 >   static BOOL OpReverseStoryPath::Init()
02187 
02188     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
02189     Created:    15/5/95
02190     Returns:    TRUE/FALSE for successful initialisation of this operation
02191     Purpose:    Registers the ReversStoryPath operation
02192 ********************************************************************************************/
02193 
02194 BOOL OpReverseStoryPath::Init()
02195 {
02196     return (RegisterOpDescriptor(0,
02197                             _R(IDS_REVERSEPATHOP),
02198                             CC_RUNTIME_CLASS(OpReverseStoryPath),
02199                             OPTOKEN_REVERSESTORYPATH,
02200                             OpReverseStoryPath::GetState)); 
02201 }
02202 
02203 
02204 /********************************************************************************************
02205 >   static OpState OpReverseStoryPath::GetState(String_256*, OpDescriptor*)
02206 
02207     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
02208     Created:    15/5/95
02209     Inputs:     (unused)
02210     Returns:    The tick/grey state of this operation
02211     Purpose:    The op is greyed if there are no selected "Text on a path" TextStories
02212 ********************************************************************************************/
02213 
02214 OpState OpReverseStoryPath::GetState(String_256*, OpDescriptor*)
02215 {
02216     OpState State;
02217     State.Greyed = FALSE;
02218 
02219     SelRange* Selection = GetApplication()->FindSelection();
02220     Node* pNode = Selection->FindFirst();
02221 
02222     while (pNode != NULL)
02223     {
02224         if (IS_A(pNode,TextStory))
02225         {
02226             TextStory* pTS = (TextStory*)pNode;
02227             if (pTS->GetTextPath()==NULL /*|| pTS->IsWordWrapping()*/)
02228             {
02229                 State.Greyed = TRUE;
02230                 break;
02231             }
02232         }
02233 
02234         pNode = Selection->FindNext(pNode);
02235     }
02236 
02237     return State;
02238 }
02239 
02240 
02241 /********************************************************************************************
02242 >   virtual void OpReverseStoryPath::Do(OpDescriptor*)
02243 
02244     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
02245     Created:    15/5/95
02246     Inputs:     (Unused)
02247     Purpose:    Called when the user selects the "Reverse TextStory path" option.  Reverses
02248                 the path for all selected TextStories on a path, so it (and the text) goes
02249                 the other way
02250 ********************************************************************************************/
02251 
02252 void OpReverseStoryPath::Do(OpDescriptor*)
02253 {
02254     SelRange* pSelection = GetApplication()->FindSelection();
02255     Node* pNode = pSelection->FindFirst();
02256     BOOL ok = TRUE;
02257 
02258     if (ok && (pNode != NULL))
02259         ok = DoStartTextOp(NULL);
02260 
02261     while (ok && (pNode != NULL))
02262     {
02263         if (IS_A(pNode,TextStory))
02264         {
02265             NodePath* pPath = ((TextStory*)pNode)->GetTextPath();
02266 
02267             if (pPath != NULL)
02268             {
02269                 ObjChangeParam ObjEdit(OBJCHANGE_STARTING,ObjChangeFlags(),NULL,this);
02270                 if (pNode->AllowOp(&ObjEdit))
02271                     ok = ReversePathAction::DoReversePath(this, &UndoActions, (TextStory*)pNode); 
02272             }
02273         }
02274 
02275         pNode = pSelection->FindNext(pNode);
02276     }
02277 
02278     // Tell the parents of the edited nodes
02279     if (ok)
02280     {
02281         ObjChangeParam ObjChange(OBJCHANGE_FINISHED,ObjChangeFlags(),NULL,this);
02282         ok = UpdateChangedNodes(&ObjChange);
02283     }
02284 
02285     // Do a SelChange so the text tool updates it's blobs
02286     if (ok)
02287         pSelection->Update();
02288 
02289 
02290     if (!ok)
02291     {
02292         FailAndExecute();
02293         InformError();
02294     }
02295 
02296     End();
02297 }
02298 
02299 #endif
02300 
02302 // The PrePostTextAction class
02303 
02304 /********************************************************************************************
02305 >   PrePostTextAction::PrePostTextAction()
02306 
02307     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
02308     Created:    11/7/94
02309     Purpose:    Constructor for the text reformat action
02310 ********************************************************************************************/
02311 
02312 PrePostTextAction::PrePostTextAction()
02313 {
02314     HasEffect            = TRUE;
02315     pTextStoryToReformat = NULL;
02316     CaretContextNode     = NULL;
02317     CaretAttachDir       = PREV;
02318 }
02319 
02320 
02321 /********************************************************************************************
02322 >   PrePostTextAction::PrePostTextAction()
02323 
02324     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
02325     Created:    11/7/94
02326     Purpose:    Destructor for the text reformat action
02327 ********************************************************************************************/
02328 
02329 PrePostTextAction::~PrePostTextAction()
02330 {
02331 }
02332 
02333 
02334 /********************************************************************************************
02335 >   static ActionCode PrePostTextAction::Init(Operation* pOp,
02336                                               ActionList* pActionList,
02337                                               TextStory* pStory,
02338                                               BOOL ThisActionReformats,
02339                                               Action** NewAction)
02340 
02341     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
02342     Created:    14/3/95
02343     Inputs:     pOp is the pointer to the operation to which this action belongs
02344                 pActionList is the action list to which this action should be added
02345                 pStory - pointer to the text story to reformat
02346                 ThisActionReformats -   TRUE if this action will cause a reformat
02347                                         FALSE if it is dormant
02348     Returns:    ActionCode, one of AC_OK, AC_NO_RECORD or AC_FAIL
02349     Purpose:    This is the function which creates an instance of this action.  There should
02350                 be two actions in the ActionList for each operation, one at the start lying
02351                 idle, and the one at the end of the action list causing a reformat.  The
02352                 execute function should toggle the idle state of the action.  When inserting
02353                 actions into the list in the Do function of your operation the first one should
02354                 be active and the second one idle.
02355     SeeAlso:    Action::Init(), PrePostTextAction::Execute()
02356 ********************************************************************************************/
02357 
02358 ActionCode PrePostTextAction::Init( Operation* pOp,
02359                                     ActionList* pActionList,
02360                                     TextStory* pStory,
02361                                     BOOL ThisActionReformats,
02362                                     BOOL ToTail)
02363 {
02364     UINT32 ActSize = sizeof(PrePostTextAction);
02365     PrePostTextAction* pNewAction;
02366     ActionCode Ac = Action::Init( pOp, pActionList, ActSize, CC_RUNTIME_CLASS(PrePostTextAction), (Action**)&pNewAction);
02367 
02368     if (Ac==AC_OK && pNewAction!=NULL)
02369     {
02370         pNewAction->m_bToTail = ToTail;
02371         
02372         pNewAction->HasEffect            = ThisActionReformats;
02373         pNewAction->pTextStoryToReformat = pStory;
02374 
02375         // Now store a Context node and direction so the caret's position can be restored
02376         // must be on the same line, otherwise losts of grief!
02377         CaretNode* pCaret = pStory->GetCaret();
02378         ERROR2IF(pCaret==NULL,AC_FAIL,"PrePostTextAction::Init() - story has no caret");
02379         pNewAction->CaretAttachDir   = PREV;
02380         pNewAction->CaretContextNode = pCaret->FindNextVTNInLine();
02381         if (pNewAction->CaretContextNode==NULL)
02382         {
02383             pNewAction->CaretAttachDir   = NEXT;
02384             pNewAction->CaretContextNode = pCaret->FindPrevVTNInLine();
02385             ERROR2IF(pNewAction->CaretContextNode==NULL,AC_FAIL,"PrePostTextAction::Init() - could find no context node for caret!");
02386         }
02387 
02388         // reformat the text (if the tail flag is set)
02389         if (ToTail)
02390         {
02391             pStory->FlagNodeAndDescendantsModifiedByOpAndParentsHaveDescendantModifiedByOp();
02392             pStory->FormatAndChildren(NULL,FALSE);
02393 
02394             // remove the new action from the list, and add it to the head of
02395             // the action list
02396             pActionList->RemoveItem(pNewAction);
02397             pActionList->AddHead(pNewAction);
02398         }
02399     }
02400 
02401     return Ac;
02402 }
02403 
02404 
02405 /********************************************************************************************
02406 >   static BOOL PrePostTextAction::DoFormatStory(UndoableOperation* pUndoOp, TextStory* pStory)
02407 
02408     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
02409     Created:    21/4/95
02410     Inputs:     pUndoOp - pointer to an undoable operation
02411                 pStory  - points to the story to reform
02412     Returns:    FALSE if fails
02413     Purpose:    Calls FormatAndChildren() then inserts a dormant action to reformat the story.
02414 ********************************************************************************************/
02415 
02416 BOOL PrePostTextAction::DoFormatStory(UndoableOperation* pUndoOp, TextStory* pStory,
02417                                       BOOL ToTail)
02418 {
02419     ERROR2IF(pUndoOp==NULL,FALSE,"PrePostTextAction::DoFormatStory() - NULL operation pointer");
02420     ERROR2IF( pStory==NULL,FALSE,"PrePostTextAction::DoFormatStory() - NULL story pointer");
02421     
02422     // Format the story
02423     BOOL ok = pStory->FormatAndChildren(pUndoOp,!ToTail);
02424 
02425     // Insert dormant reformat action
02426     ActionList* pActionList = pUndoOp->GetUndoActionList();
02427     ERROR2IF(pActionList==NULL,FALSE,"PrePostTextAction::DoFormatStory() - pActionList==NULL");
02428     if (ok) ok = (Init(pUndoOp, pActionList, pStory, FALSE, ToTail) != AC_FAIL);
02429 
02430     return ok;
02431 }
02432 
02433 
02434 /********************************************************************************************
02435 >   ActionCode PrePostTextAction::Execute()
02436 
02437     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
02438     Created:    14/3/95
02439     Returns:    ActionCode, either AC_OK, AC_NORECORD or AC_FAIL
02440     Purpose:    If this action is active then it calls the text story to reformat itself.  In
02441                 both cases (active and idle) the opposite action created has the opposite state.
02442     Errors:     ERROR3 if the text story pointer is NULL
02443 ********************************************************************************************/
02444 
02445 ActionCode PrePostTextAction::Execute()
02446 {
02447     ERROR3IF(pTextStoryToReformat == NULL, "The TextStory pointer is NULL");
02448     
02449     // Create a redo action for this action, which is also a PrePostTextAction
02450     ActionCode Act;
02451     Act = PrePostTextAction::Init(pOperation, 
02452                                   pOppositeActLst, 
02453                                   pTextStoryToReformat,
02454                                   !HasEffect,
02455                                   m_bToTail);         // Toggle
02456     
02457     if (Act == AC_FAIL)
02458         return AC_FAIL;
02459 
02460     if (pTextStoryToReformat == NULL)
02461         return Act;
02462 
02463     if (pTextStoryToReformat->FindParent() == NULL) // The TextStory is not in the tree, 
02464                                                   // it's probably hidden
02465         return Act;
02466 
02467     // Cause the reformat if required.
02468     if (HasEffect && pTextStoryToReformat!=NULL)
02469     {
02470         // Restore the caret first
02471         pTextStoryToReformat->MoveCaretToCharacter(CaretContextNode, CaretAttachDir);
02472 
02473         if (!pTextStoryToReformat->FormatAndChildren(NULL,FALSE,FALSE))
02474             return AC_FAIL;
02475     }
02476     return Act;
02477 }
02478 
02479 #if !defined(EXCLUDE_FROM_RALPH)
02480 
02482 // OpTextCaret methods
02483 
02484 // Word movement rules - taken from Word 6
02485 // Cursor left, right, up and down simply moves the caret.
02486 // CTRL- left & right always puts caret at start of previous/next word.
02487 // CTRL- up & down moves to start and end of paragraph.
02488 // Home and end for start & end of line.  CTRL for start/end of story
02489 // Movement with SHIFT always selects.
02490 
02491 /********************************************************************************************
02492 >   void OpTextCaret::DoMoveCaretLeft(BOOL WithSelect, BOOL AWord)
02493 
02494     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
02495     Created:    25/3/95 - rewritten 29/8/95
02496     Inputs:     WithSelect - movement should select words
02497                 AWord - move the caret a word at a time
02498     Purpose:    Cursor left key operation - see movement rules above
02499     Errors:     Story and caret pointers are checked for NULL
02500 ********************************************************************************************/
02501 
02502 void OpTextCaret::DoMoveCaretLeft(BOOL WithSelect, BOOL AWord)
02503 {
02504     // Initialise
02505     BOOL Success = TRUE;
02506     if (!PreCaretMove())
02507     {
02508         Success = FALSE;
02509         goto DoneMovement;
02510     }
02511 
02512     if (WithSelect)
02513     {
02514         BOOL MoveSelection = TRUE;
02515         VisibleTextNode* pSelEnd = pCaret;
02516         VisibleTextNode* pSelStart = pOtherSelEnd;
02517 
02518         // Get the new selection end.  Move the current end to the left, either by one word or one character
02519         if (AWord)                         
02520         {
02521             // Special case so we skip back over selected EOLs like Word
02522             if (SelToRightOfCaret && pSelStart->IsAnEOLNode())
02523             {
02524                 pSelStart = pSelStart->FindPrevVTNInStory();
02525                 MoveSelection = FALSE;
02526             }
02527             else
02528                 pSelStart = pStory->GetPrevWordChar(pSelStart);
02529         }
02530         else
02531         {
02532             VisibleTextNode* pTemp = pSelStart->FindPrevVTNInStory();
02533             if (pTemp != NULL)
02534                 pSelStart = pTemp;
02535         }
02536 
02537         ERROR3IF(pSelStart == NULL, "Failed to find selection start");
02538         ERROR3IF(pSelEnd == NULL, "Failed to find selection end (where's the caret!)");
02539         VisibleTextNode* pScrollTo = pSelStart;
02540         if ((pSelStart != NULL) && (pSelEnd != NULL))
02541         {
02542             // Ensure that start is before end.  For left movement see if the character
02543             // before the new start is selected and swap if it is
02544             VisibleTextNode* pPrev = pSelStart->FindPrevVTNInStory();
02545             if ((pPrev != NULL) && (pSelStart != pSelEnd) && (pPrev->IsSelected() || pPrev->IsACaret()))
02546             {
02547                 VisibleTextNode* pTemp = pSelEnd;
02548                 pSelEnd = pSelStart;
02549                 pSelStart = pTemp;
02550                 pTemp = pSelEnd->FindPrevVTNInStory();
02551                 if (AWord && MoveSelection && (pTemp != NULL) && !pSelEnd->IsAnEOLNode())
02552                     pSelEnd = pTemp;
02553                 pScrollTo = pSelEnd;
02554             }
02555 
02556             // Select the characters and scroll to show moving end
02557             pScrollTo->ScrollToShow();      
02558             OpTextCaret::SelectStoryCharacters(pSelStart, pSelEnd, pStory, pCaret);
02559         }
02560     }
02561     else
02562     {
02563         // If there is a selected region move caret to its end, else move caret
02564         if (pStory->GetSelectionEnd() != NULL)
02565             MoveCaretToSelectionEnd(TRUE);
02566         else
02567         {
02568             if (pCaret->FindPrevVTNInStory() != NULL)
02569             {
02570                 NodeRenderableInk::DeselectAll(TRUE, FALSE);
02571                 if (AWord)
02572                     Success = pStory->MoveCaretLeftAWord();
02573                 else
02574                     Success = pStory->MoveCaretLeftAChar();
02575                 pCaret->ScrollToShow();
02576                 pCaret->Select(TRUE);
02577             }
02578         }
02579     }
02580 
02581 DoneMovement:
02582     if (!Success)
02583     {
02584         FailAndExecute();
02585         InformError();
02586     }
02587     
02588     CompleteOperation();
02589 }
02590 
02591 
02592 /********************************************************************************************
02593 >   void OpTextCaret::DoMoveCaretRight(BOOL AWord = FALSE)
02594 
02595     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
02596     Created:    25/3/95
02597     Inputs:     pTextStory: The TextStory to perform the operation on
02598                 AWord:      Move the caret a word at a time
02599     Purpose:    Cursor right key operation. If the caret is selected then the caret moves
02600                 one position to the right.  If there is a sub-selection then the caret will
02601                 move to the right of the last selected character and become selected. The
02602                 subselection is cleared.
02603 ********************************************************************************************/
02604 
02605 void OpTextCaret::DoMoveCaretRight(BOOL WithSelect, BOOL AWord)
02606 {
02607     BOOL Success = TRUE;
02608     if (!PreCaretMove())
02609     {
02610         Success = FALSE;
02611         goto DoneMovement;
02612     }
02613 
02614     if (WithSelect)
02615     {
02616         VisibleTextNode* pSelEnd = pCaret;
02617         VisibleTextNode* pSelStart = pOtherSelEnd;
02618 
02619         // Get the new selection end.  Move the current end to the right, either by one word or one character
02620         if (AWord)
02621         {
02622             // If the sel end is at the end of a line then skip to the start of the new line
02623             // then to the end of the first word on the line
02624             if (pSelStart->IsAnEOLNode() && SelToRightOfCaret)
02625                 pSelStart = pStory->GetNextWordChar(pSelStart);
02626             pSelStart = pSelStart->FindNextVTNInStory();
02627             pSelStart = pStory->GetNextWordChar(pSelStart);
02628         }
02629         else
02630             pSelStart = pSelStart->FindNextVTNInStory();
02631 
02632         ERROR3IF(pSelStart == NULL, "Failed to find slection start");
02633         ERROR3IF(pSelEnd == NULL, "Failed to find slection end (where's the caret!)");
02634         VisibleTextNode* pScrollTo = pSelStart;
02635         if ((pSelStart != NULL) && (pSelEnd != NULL))
02636         {
02637             // Ensure that start is before end.  For right movement see if the character
02638             // after the new start is selected, swap if it isn't
02639             VisibleTextNode* pNext = pSelStart->FindNextVTNInStory();
02640             if ((pNext == NULL) || (pNext != NULL) && (pSelStart != pSelEnd) && (pNext != pSelEnd) && !pNext->IsSelected())
02641             {
02642                 VisibleTextNode* pTemp = pSelEnd;
02643                 pSelEnd = pSelStart;
02644                 pSelStart = pTemp;
02645                 if (AWord && (pSelEnd->FindPrevVTNInStory() != NULL) && (pSelEnd->FindNext() != pStory->FindLastVTN()))
02646                     pSelEnd = pSelEnd->FindPrevVTNInStory();
02647                 pScrollTo = pSelEnd;
02648             }
02649 
02650             // Select the characters and scroll to show moving end
02651             pScrollTo->ScrollToShow();      
02652             OpTextCaret::SelectStoryCharacters(pSelStart, pSelEnd, pStory, pCaret);
02653         }
02654     }
02655     else
02656     {
02657         // If there is a selected region move caret to its end, else move caret
02658         if (pStory->GetSelectionEnd() != NULL)
02659             MoveCaretToSelectionEnd(FALSE);
02660         else
02661         {
02662             // Do simple caret moving
02663             if (pCaret->FindNextVTNInStory() != pStory->FindLastVTN())
02664             {
02665                 NodeRenderableInk::DeselectAll(TRUE,FALSE);
02666                 if (AWord)
02667                     pStory->MoveCaretRightAWord();
02668                 else
02669                     pStory->MoveCaretRightAChar();
02670                 pCaret->ScrollToShow();
02671                 pCaret->Select(TRUE);
02672             }
02673         }
02674     }
02675 
02676 DoneMovement:
02677     if (!Success)
02678     {
02679         FailAndExecute();
02680         InformError();
02681     }
02682     
02683     CompleteOperation();
02684 }
02685     
02686 
02687 /********************************************************************************************
02688 >   void OpTextCaret::DoMoveCaretUp(BOOL WithSelect)
02689 
02690     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
02691     Created:    19/4/95
02692     Inputs:     WithSelect - TRUE if characters should be selected
02693     Purpose:    Moves the caret up one line
02694 ********************************************************************************************/
02695 
02696 void OpTextCaret::DoMoveCaretUp(BOOL WithSelect)
02697 {
02698     BOOL Success = TRUE;
02699     if (!PreCaretMove())
02700     {
02701         Success = FALSE;
02702         goto DoneMovement;
02703     }
02704         
02705     if (WithSelect)
02706     {
02707         VisibleTextNode* pSelEnd = pCaret;
02708         VisibleTextNode* pSelStart = pOtherSelEnd;
02709 
02710         // Move the current selection end up a line
02711         BOOL SelToLeftOfNewEnd = TRUE;
02712         pSelStart = GetCharacterLineChange(pSelStart, SelToRightOfCaret, &SelToLeftOfNewEnd, TRUE);
02713 
02714         if (pSelStart == NULL)
02715         {
02716             DoMoveCaretHome(TRUE, TRUE);
02717             return; // We have already End()ed
02718         }
02719         else
02720         {
02721             ERROR3IF(pSelEnd == NULL, "Failed to find slection end (where's the caret!)");
02722             if ((pSelStart != NULL) && (pSelEnd != NULL))
02723             {
02724                 if (!SelToLeftOfNewEnd && (pSelStart->FindNextVTNInStory() != NULL))
02725                     pSelStart = pSelStart->FindNextVTNInStory();
02726                 VisibleTextNode* pScrollTo = pSelStart;
02727                 // Ensure that start is before end.  For up movement see if the character
02728                 // before the new start is selected and swap if it is
02729                 VisibleTextNode* pPrev = pSelStart->FindPrevVTNInStory();
02730                 if ((pPrev != NULL) && (pSelStart != pSelEnd) && (pPrev->IsSelected() || pPrev->IsACaret()))
02731                 {
02732                     VisibleTextNode* pTemp = pSelEnd;
02733                     pSelEnd = pSelStart;
02734                     pSelStart = pTemp;
02735                     if (pSelEnd->FindPrevVTNInStory() != NULL)
02736                         pSelEnd = pSelEnd->FindPrevVTNInStory();
02737                     pScrollTo = pSelEnd;
02738                 }
02739 
02740                 // Select the characters and scroll to show moving end
02741                 pScrollTo->ScrollToShow();      
02742                 OpTextCaret::SelectStoryCharacters(pSelStart, pSelEnd, pStory, pCaret);
02743             }
02744         }
02745     }
02746     else
02747     {
02748         if (pStory->GetSelectionEnd() != NULL)
02749             MoveCaretToSelectionEnd(TRUE);
02750         else
02751         {
02752             BOOL ToLeft = FALSE;
02753             VisibleTextNode* pNewPos = GetCharacterLineChange(pCaret, TRUE, &ToLeft, TRUE);
02754 
02755             if (pNewPos != NULL)
02756             {
02757                 NodeRenderableInk::DeselectAll(TRUE,FALSE);
02758                 pStory->MoveCaretToCharacter(pNewPos, ToLeft?PREV:NEXT);
02759                 pCaret->ScrollToShow();
02760                 pCaret->Select(TRUE);
02761             }
02762         }
02763     }
02764 
02765 DoneMovement:
02766     if (!Success)
02767     {
02768         FailAndExecute();
02769         InformError();
02770     }
02771     
02772     CompleteOperation();
02773 }
02774 
02775 
02776 /********************************************************************************************
02777 >   void OpTextCaret::DoMoveCaretDown(BOOL WithSelect)
02778 
02779     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
02780     Created:    19/4/95
02781     Inputs:     WithSelect - TRUE if characters should be selected
02782                 Scroll - TRUE to scroll the screen
02783     Purpose:    Moves the caret down one line, dealing with selection if needed
02784 ********************************************************************************************/
02785 
02786 void OpTextCaret::DoMoveCaretDown(BOOL WithSelect)
02787 {
02788     BOOL Success = TRUE;
02789     if (!PreCaretMove())
02790     {
02791         Success = FALSE;
02792         goto DoneMovement;
02793     }
02794         
02795     if (WithSelect)
02796     {
02797         // get the 'other' end of the currect selection (the caret is at non-moving end)
02798         VisibleTextNode* pSelEnd = pCaret;
02799         VisibleTextNode* pSelStart = pOtherSelEnd;
02800         VisibleTextNode* pOldSelStart = pSelStart;
02801         BOOL SelToLeftOfNewEnd = TRUE;
02802         pSelStart = GetCharacterLineChange(pSelStart, SelToRightOfCaret, &SelToLeftOfNewEnd, FALSE);
02803 
02804         if (pSelStart == NULL)
02805         {
02806             DoMoveCaretEnd(TRUE, TRUE);
02807             return; // We have already End()ed
02808         }
02809         else
02810         {
02811             ERROR3IF(pSelEnd == NULL, "Failed to find selection end (where's the caret!)");
02812             if ((pSelStart != NULL) && (pSelEnd != NULL))
02813             {
02814                 if (!SelToLeftOfNewEnd && (pSelStart->FindNextVTNInStory() != NULL))
02815                     pSelStart = pSelStart->FindNextVTNInStory();
02816                 VisibleTextNode* pScrollTo = pSelStart;
02817 
02818                 // Ensure that start is before end.  For down movement see if the character
02819                 // after the new start is selected, swap if it isn't
02820                 VisibleTextNode* pNext = pSelStart->FindNextVTNInStory();
02821                 if ((pNext == NULL) || ((pNext != NULL) && (pSelStart != pSelEnd) && (pNext != pSelEnd) && !pNext->IsSelected()))
02822                 {
02823                     VisibleTextNode* pTemp = pSelEnd;
02824                     pSelEnd = pSelStart;
02825                     pSelStart = pTemp;
02826                     VisibleTextNode *pPrev = pSelEnd->FindPrevVTNInStory();
02827                     if ((pPrev != NULL) && (pPrev != pOldSelStart))
02828                         pSelEnd = pPrev;
02829                     pScrollTo = pSelEnd;
02830                 }
02831 
02832                 if (pScrollTo->IsAnEOLNode() && (pScrollTo->FindNextVTNInStory() != NULL))
02833                     pScrollTo = pScrollTo->FindNextVTNInStory();
02834 
02835                 // Select the characters and scroll to show moving end
02836                 pScrollTo->ScrollToShow();      
02837                 OpTextCaret::SelectStoryCharacters(pSelStart, pSelEnd, pStory, pCaret);
02838             }
02839         }
02840     }
02841     else
02842     {
02843         if (pStory->GetSelectionEnd() != NULL)
02844             MoveCaretToSelectionEnd(FALSE);
02845         else
02846         {
02847             BOOL ToLeft = FALSE;
02848             VisibleTextNode* pNewPos = GetCharacterLineChange(pCaret, TRUE, &ToLeft, FALSE);
02849 
02850             if (pNewPos != NULL)
02851             {
02852                 NodeRenderableInk::DeselectAll(TRUE,FALSE);
02853                 pStory->MoveCaretToCharacter(pNewPos, ToLeft?PREV:NEXT);
02854                 pCaret->ScrollToShow();
02855                 pCaret->Select(TRUE);
02856             }
02857         }
02858     }
02859 
02860 DoneMovement:
02861     if (!Success)
02862     {
02863         FailAndExecute();
02864         InformError();
02865     }
02866     
02867     CompleteOperation();
02868 }
02869 
02870 
02871 /********************************************************************************************
02872 >   void OpTextCaret::DoMoveCaretHome(BOOL WithSelect, BOOL ToStoryStart)
02873 
02874     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
02875     Created:    25/3/95
02876     Inputs:     WithSelect - true if characters from the current caret position to the home
02877                 position should be selected
02878     Purpose:    Moves the caret to the left of the first character on the line, or to the 
02879                 start of the TextStory
02880 ********************************************************************************************/
02881 
02882 void OpTextCaret::DoMoveCaretHome(BOOL WithSelect, BOOL ToStoryStart)
02883 {
02884     if (!PreCaretMove())
02885     {
02886         FailAndExecute();
02887         InformError();
02888         End();
02889         return;
02890     }
02891 
02892     VisibleTextNode* pMoveChar = pStory->GetSelectionEnd();
02893     if (ToStoryStart)
02894         pMoveChar = pStory->FindFirstVTN();
02895     else
02896     {
02897         if (pMoveChar == NULL)
02898             pMoveChar = GetLineStartCharacter(pCaret);
02899         else
02900             pMoveChar = GetLineStartCharacter(pMoveChar);
02901     }
02902 
02903     if (pMoveChar == NULL)
02904         return ;
02905 
02906     if (WithSelect)
02907     {
02908         // Get a pointer to the first and last selectable chars
02909         VisibleTextNode* pSelEndChar = pCaret;
02910         VisibleTextNode* pSelStartChar = (VisibleTextNode*)pMoveChar;
02911         if ((pSelEndChar != NULL) && (pSelStartChar != NULL))
02912         {
02913             // Its possible that the end character is in front of the start
02914             if (pSelStartChar != pSelEndChar)
02915             {
02916                 VisibleTextNode* pChar = pSelEndChar;
02917                 while (pChar != NULL)
02918                 {
02919                     if (pChar == pSelStartChar)
02920                     {
02921                         pSelStartChar = pCaret->FindNextVTNInStory();
02922                         pSelEndChar = pChar->FindPrevVTNInStory();
02923                         break;
02924                     }
02925 
02926                     pChar = pChar->FindNextVTNInStory();
02927                 }
02928 
02929                 if (pChar==NULL)
02930                     pSelEndChar = pCaret->FindPrevVTNInStory();
02931             }               
02932 
02933             if (pSelStartChar != NULL)
02934                 pSelStartChar->ScrollToShow();
02935             SelectStoryCharacters(pSelStartChar, pSelEndChar, pStory, pCaret);
02936         }
02937     }
02938     else
02939     {
02940         NodeRenderableInk::DeselectAll(TRUE,FALSE);
02941         if (pMoveChar != pCaret)
02942             pStory->MoveCaretToCharacter(pMoveChar, PREV);
02943         pCaret->ScrollToShow();
02944         pCaret->Select(TRUE);
02945     }
02946 
02947     CompleteOperation();
02948 }
02949 
02950 
02951 /********************************************************************************************
02952 >   void OpTextCaret::DoMoveCaretEnd(BOOL WithSelect, BOOL ToStoryEnd)
02953 
02954     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
02955     Created:    25/3/95
02956     Inputs:     WithSelect - true if characters from the current caret position to the end
02957                 of the line should be selected
02958     Purpose:    Moves the caret to the right of the last character on the line
02959 ********************************************************************************************/
02960 
02961 void OpTextCaret::DoMoveCaretEnd(BOOL WithSelect, BOOL ToStoryEnd)  
02962 {
02963     if (!PreCaretMove())
02964     {
02965         FailAndExecute();
02966         InformError();
02967         End();
02968         return;
02969     }
02970 
02971     VisibleTextNode* pMoveChar = pStory->GetSelectionEnd();
02972     if (ToStoryEnd)
02973         pMoveChar = pStory->FindLastVTN();
02974     else
02975     {
02976         if (pMoveChar == NULL)
02977             pMoveChar = GetLineEndCharacter(pCaret);
02978         else
02979             pMoveChar = GetLineEndCharacter(pMoveChar);
02980     }
02981 
02982     if (pMoveChar == NULL)
02983         return ;
02984 
02985     if (WithSelect)
02986     {
02987         // Get a pointer to the first and last selectable chars
02988         VisibleTextNode* pSelStartChar = pCaret;
02989         VisibleTextNode* pSelEndChar = pMoveChar->FindPrevVTNInStory();
02990         if (pSelEndChar == NULL)
02991             pSelEndChar = pMoveChar;
02992 
02993         if (pSelStartChar != pSelEndChar)
02994         {
02995             // Its possible that the end character is in front of the start
02996             VisibleTextNode* pChar = pSelEndChar;
02997             while (pChar != NULL)
02998             {
02999                 if (pChar == pSelStartChar)
03000                 {
03001                     pSelStartChar = pSelEndChar->FindNextVTNInStory();
03002                     pSelEndChar = pCaret->FindPrevVTNInStory();
03003                     break;
03004                 }
03005 
03006                 pChar = pChar->FindNextVTNInStory();
03007             }
03008             if (pChar == NULL)
03009                 pSelStartChar = pCaret->FindNextVTNInStory();
03010         }
03011 
03012         if (pSelEndChar != NULL)
03013             pSelEndChar->ScrollToShow();
03014 
03015         SelectStoryCharacters(pSelStartChar, pSelEndChar, pStory, pCaret);
03016     }
03017     else
03018     {
03019         NodeRenderableInk::DeselectAll(TRUE,FALSE);
03020         if (pMoveChar != pCaret)
03021         {
03022             if (pMoveChar->IsAnEOLNode())
03023                 pStory->MoveCaretToCharacter(pMoveChar, PREV);
03024             else
03025                 pStory->MoveCaretToCharacter(pMoveChar, NEXT);
03026         }
03027         pCaret->ScrollToShow();
03028         pCaret->Select(TRUE);
03029     }
03030 
03031     CompleteOperation();
03032 }
03033 
03034 
03035 /********************************************************************************************
03036 >   BOOL OpTextCaret::PreCaretMove()
03037 
03038     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
03039     Created:    29/08/95
03040     Returns:    TRUE for success, FALSE if op should not continue
03041     Purpose:    Carries out common pre operation code for caret movement ops
03042     Errors:     Checks story and caret pointers for NULL
03043 ********************************************************************************************/
03044 
03045 BOOL OpTextCaret::PreCaretMove()
03046 {
03047     // Pointer checks on the ops member variables
03048     ERROR2IF(pStory == NULL, FALSE, "(OpTextCaret::DoMoveCaretLeft) pStory was NULL");
03049     ERROR2IF(pCaret == NULL, FALSE, "(OpTextCaret::DoMoveCaretLeft) pCaret was NULL");
03050 
03051     GetApplication()->FindSelection()->SetGag(TRUE);
03052 
03053     return TRUE;
03054 }
03055 
03056 
03057 /********************************************************************************************
03058 >   void OpTextCaret::DoSelectWordAtCaret()
03059 
03060     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
03061     Created:    05/04/95
03062     Purpose:    Moves the caret to the start of the word that is is currently at, then selects
03063                 the characters to the end of the word 
03064     Errors:     Checks story and caret pointers for NULL
03065 ********************************************************************************************/
03066 
03067 void OpTextCaret::DoSelectWordAtCaret()
03068 {
03069     // Validate the pointers
03070     if (!PreCaretMove())
03071     {
03072         InformError();
03073         FailAndExecute();
03074         End();
03075         return;
03076     }
03077     
03078     NodeRenderableInk::DeselectAll(TRUE,FALSE);
03079     DialogManager::DefaultKeyboardFocus();
03080 
03081     // find the start of the previous word and move the caret there
03082     VisibleTextNode* pPrevWordStart = pStory->GetPrevWordChar(pCaret);
03083     if (pPrevWordStart==NULL)
03084     {
03085         ERROR3("OpTextCaret::DoSelectWordAtCaret() - failed to find start of previous word!");
03086         return;
03087     }
03088     if (pPrevWordStart!=pCaret)
03089         pStory->MoveCaretToCharacter(pPrevWordStart, PREV);
03090 
03091     // find the start of the next word
03092     VisibleTextNode* pNextWordStart = pStory->GetNextWordChar(pPrevWordStart);
03093     if (pNextWordStart==NULL)
03094     {
03095         ERROR3("OpTextCaret::DoSelectWordAtCaret() - failed to find start of next word!");
03096         return;
03097     }
03098 
03099 #ifdef SELECT_CARET_AND_CHARS
03100     // Select the caret
03101     pCaret->Select(TRUE);
03102 #endif
03103 
03104     // select all chars between the start of the last word to the start of the next word
03105     VisibleTextNode* pVTN = pPrevWordStart;
03106     while (pVTN!=NULL && pVTN!=pNextWordStart)
03107     {
03108         if (!pVTN->IsAnEOLNode())
03109             pVTN->Select(TRUE);
03110         pVTN = pVTN->FindNextVTNInStory();
03111         ERROR3IF(pVTN==NULL,"OpTextCaret::DoSelectWordAtCaret() - never found pNextWordStart");
03112     }
03113 
03114     GetApplication()->FindSelection()->SetGag(FALSE);
03115     CompleteOperation();
03116 }
03117 
03118   
03119 /********************************************************************************************
03120 >   OpTextCaret::OpTextCaret()
03121 
03122     Author:     Simon_Maneggio (Xara Group Ltd) <camelotdev@xara.com>
03123     Created:    11/7/94
03124     Purpose:    OpTextCaret constructor.  Sets the member variables
03125 ********************************************************************************************/
03126 
03127 OpTextCaret::OpTextCaret()
03128 {
03129     // Get pointers to the focus story.  There should be one!
03130     pStory = TextStory::GetFocusStory();
03131     ERROR3IF(pStory == NULL, "OpTextCaret created without a focus story");
03132 
03133     // Get pointer to caret and other end of selection
03134     if (pStory != NULL)
03135     {
03136         pCaret = pStory->GetCaret();
03137         pOtherSelEnd = pStory->GetSelectionEnd(&SelToRightOfCaret);
03138 
03139         // If no selection then set pointer to the caret
03140         if (pOtherSelEnd == NULL)
03141             pOtherSelEnd = pCaret;
03142     }
03143     else
03144     {
03145         pCaret = NULL;
03146         pOtherSelEnd = NULL;
03147     }
03148 
03149     // Reset the vertical move inset
03150     PreviousVerticalInset = VertInset;
03151     VertInset.LastInset = VERTICALMOVEINSET_NONE;
03152     VertInset.HoldingInset = VERTICALMOVEINSET_NONE;
03153 }
03154 
03155 
03156 /********************************************************************************************
03157 >   OpTextCaret::MoveCaretToSelectionEnd(BOOL MoveToLeft)
03158 
03159     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
03160     Created:    28/03/95
03161     Inputs:     MoveToLeft, TRUE if caret should go to left side of selection, FALSE for right side
03162     Purpose:    This function should be called to move the caret to the end of the selected text.
03163                 It also selectes the caret, deselecting the other characters
03164 ********************************************************************************************/
03165 
03166 void OpTextCaret::MoveCaretToSelectionEnd(BOOL MoveToLeft)
03167 {
03168     ERROR3IF(pStory == NULL, "pStory was NULL");
03169     ERROR3IF(pCaret == NULL, "pCaret was NULL");
03170 
03171     if ((pStory != NULL) && (pCaret != NULL) )
03172     {
03173         BOOL SelToRightOfCaret = TRUE;
03174         VisibleTextNode* pSelEnd = pStory->GetSelectionEnd(&SelToRightOfCaret);
03175         ERROR3IF(pSelEnd == NULL, "Who cleared the selection before calling MoveCaretToSelectionEnd?");
03176 
03177         // Reposition the caret at right end of the selected region
03178         NodeRenderableInk::DeselectAll(TRUE,FALSE);
03179         if ((pSelEnd != NULL) && (MoveToLeft != SelToRightOfCaret))
03180         {
03181             if (pSelEnd->IsAnEOLNode())
03182                 pStory->MoveCaretToCharacter(pSelEnd, PREV);
03183             else
03184                 pStory->MoveCaretToCharacter(pSelEnd, MoveToLeft?PREV:NEXT);
03185         }
03186         pCaret->ScrollToShow();
03187         pCaret->Select(TRUE);
03188     }
03189 }
03190 
03191 
03192 /********************************************************************************************
03193 >   OpTextCaret::CompleteOperation()
03194 
03195     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
03196     Created:    28/03/95
03197     Purpose:    This function performs common things done at the end of all operations
03198                 It calls the End() method so don't do anything else except return 
03199                 after calling it.
03200 ********************************************************************************************/
03201 
03202 void OpTextCaret::CompleteOperation()
03203 {
03204     GetApplication()->UpdateSelection();
03205 
03206     End();
03207 }
03208 
03209 
03210 
03211 /********************************************************************************************
03212 >   static void OpTextCaret::SelectStoryCharacters(VisibleTextNode* pFirstToSel, VisibleTextNode* pLastToSel,
03213                                                     TextStory* pStory, CaretNode* pCaret = NULL);
03214 
03215     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
03216     Created:    28/03/95
03217     Inputs:     pFirstToSel - points to the first character to select
03218                 pLastToSel - points to the last character to select
03219                 pStory - points to the story to operate in
03220                 pCaret - points to the caret in the TextStory, pass in NULL if you don't have it
03221     Purpose:    This function selects characters between the two pointers (inclusive), deselecting
03222                 all other characters in the story.
03223 ********************************************************************************************/
03224 
03225 void OpTextCaret::SelectStoryCharacters(VisibleTextNode* pFirstToSel, VisibleTextNode* pLastToSel,
03226                                                             TextStory* pStory, CaretNode* pCaret)
03227 {
03228     ERROR3IF(pStory == NULL, "Story pointer was NULL");
03229     if ((pFirstToSel == NULL) || (pLastToSel == NULL) || (pStory == NULL))
03230         return;
03231 
03232     // Get required pointers
03233     if (pCaret == NULL)
03234         pCaret = pStory->GetCaret();
03235 
03236     BlobManager* pBlobMgr = GetApplication()->GetBlobManager();
03237     if (pBlobMgr == NULL)
03238         return;
03239 
03240     // Adjust the selection bounds so caret is not included.
03241     if (pFirstToSel != pLastToSel)
03242     {
03243         if (pFirstToSel->IsACaret())
03244             pFirstToSel = pFirstToSel->FindNextVTNInStory();
03245         else if (pLastToSel->IsACaret())
03246             pLastToSel = pLastToSel->FindPrevVTNInStory();
03247     }
03248 
03249     // Check that we haven't just swapped the start and end
03250     if (pLastToSel == pFirstToSel->FindPrevVTNInStory())
03251     {
03252         VisibleTextNode* pTemp = pFirstToSel;
03253         pFirstToSel = pLastToSel;
03254         pLastToSel = pTemp;
03255     }
03256 
03257     Spread* pSpread = pStory->FindParentSpread();
03258 
03259     // We only need to use the Blob manager, if there are some pending render regions
03260     RenderRegionList* pRegionList = GetApplication()->GetRegionList();
03261     BOOL UseBlobMgr = !pRegionList->IsEmpty();
03262 
03263     RenderRegion* pRegion = NULL;
03264 
03265     // Select characters using just one render region
03266     if (!UseBlobMgr)
03267         pRegion = DocView::RenderOnTop(NULL, pSpread, ClippedEOR );
03268 
03269     while ( UseBlobMgr || pRegion != NULL )
03270     {
03271         // Loop through all the characters in the story
03272         BOOL Select = FALSE;
03273         VisibleTextNode* pChar = pStory->FindFirstVTN();
03274         VisibleTextNode* pNextChar;
03275         while (pChar != NULL)
03276         {
03277             if (pChar == pFirstToSel)
03278                 Select = TRUE;
03279 
03280             // We don't want the very last character to be selectable
03281             pNextChar = pChar->FindNextVTNInStory();
03282             if (pNextChar == NULL)
03283                 Select = FALSE;
03284 
03285             if (Select)
03286             {
03287                 if (!pChar->IsSelected())
03288                 {
03289                     pChar->SetSelected(TRUE);
03290 
03291                     if (UseBlobMgr)
03292                         pBlobMgr->RenderObjectBlobsOn(NULL, pSpread, pChar);
03293                     else
03294                         pChar->RenderObjectBlobs(pRegion);
03295                 }
03296             }
03297             else
03298             {
03299                 if (pChar->IsSelected())
03300                 {
03301                     if (UseBlobMgr)
03302                         pBlobMgr->RenderObjectBlobsOff(NULL, pSpread, pChar);
03303                     else
03304                         pChar->RenderObjectBlobs(pRegion);
03305 
03306                     pChar->SetSelected(FALSE);
03307                 }
03308             }
03309 
03310             if (pChar == pLastToSel)
03311                 Select = FALSE;
03312 
03313             pChar = pNextChar;
03314 
03315         }
03316 
03317         // Get the Next render region
03318         if (UseBlobMgr)
03319             UseBlobMgr = FALSE;
03320         else
03321             pRegion = DocView::GetNextOnTop(NULL);
03322     }
03323 
03324     GetApplication()->UpdateSelection();
03325 
03326 #ifdef SELECT_CARET_AND_CHARS
03327     // Ensure the caret is always selected
03328     pCaret->Select(TRUE);
03329 #else
03330     // Now that we have finished selecting characters, select the caret if there is no selected chars
03331     if (pStory->GetSelectionEnd() == NULL)
03332         pCaret->Select(TRUE);
03333 #endif
03334 
03335 }
03336 
03337 
03338 /********************************************************************************************
03339 >   VisibleTextNode* OpTextCaret::GetCharacterLineChange(   VisibleTextNode* pCharacter,
03340                                                             BOOL ToLeftOfChar,
03341                                                             BOOL* NewToLeft,
03342                                                             BOOL MoveUp)
03343     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
03344     Created:    19/04/95
03345     Inputs:     pCharacter - points to the character to look from
03346                 ToLeftOfChar - TRUE/FALSE if current pos is to left/right of pCharacter
03347                 NewToLeft - pointer to a BOOL to output a return value
03348                 MoveUp - TRUE to look up a line, FALSE to look down
03349     Outputs:    ToLeft - TRUE to position the caret to the left of the return character
03350                          FALSE to position the caret to the right of the return character
03351     Returns:    A pointer to the character on the line above/below pCharacter, or NULL if there
03352                 isn't a character.
03353     Purpose:    This function returns a pointer to the character on the line above or below
03354                 the given character or position.  If there is a previous vertical movement
03355                 position then this is used, rather then the supplied character.
03356 ********************************************************************************************/
03357 
03358 VisibleTextNode* OpTextCaret::GetCharacterLineChange(VisibleTextNode* pCharacter, BOOL ToLeftOfChar,
03359                                                         BOOL* NewToLeft,  BOOL MoveUp)
03360 {
03361     ERROR2IF(pCharacter == NULL, NULL, "NULL entry param");
03362 
03363     // Get a pointer to the current and next TextLine
03364     TextLine* pCurrentLine = pCharacter->FindParentLine();
03365     ERROR2IF(pCurrentLine == NULL, NULL, "Character didn't have a parent text line");
03366     TextLine* pNewLine = NULL;
03367     if (MoveUp)
03368         pNewLine = pCurrentLine->FindPrevLine();
03369     else
03370         pNewLine = pCurrentLine->FindNextLine();
03371 
03372     // If there is a new line then find the character on it
03373     VisibleTextNode* pResult = NULL;
03374     if (pNewLine != NULL)
03375     {
03376         // Get the current distance along the line, using the previous vertical move if there was one
03377         MILLIPOINT CurrentDistance = pCharacter->CalcCharDistAlongLine(ToLeftOfChar);
03378         if (PreviousVerticalInset.HoldingInset != VERTICALMOVEINSET_NONE &&
03379             PreviousVerticalInset.LastInset == CurrentDistance &&
03380             CurrentDistance != -1)
03381         {
03382             CurrentDistance = PreviousVerticalInset.HoldingInset;
03383         }
03384         else
03385         {
03386             PreviousVerticalInset.HoldingInset = VERTICALMOVEINSET_NONE;
03387         }
03388 
03389         // Get the character that distance along the new line
03390         if (CurrentDistance != -1)
03391             pResult = pNewLine->FindCharAtDistAlongLine(CurrentDistance, NewToLeft);
03392 
03393         // Update the vertical move memory
03394         if (pResult != NULL)
03395         {
03396             if (PreviousVerticalInset.HoldingInset != VERTICALMOVEINSET_NONE)
03397                 VertInset.HoldingInset = PreviousVerticalInset.HoldingInset;
03398             else
03399                 VertInset.HoldingInset = CurrentDistance;
03400             VertInset.LastInset = pResult->CalcCharDistAlongLine(!*NewToLeft);
03401         }
03402     }
03403 
03404     return pResult;
03405 }
03406 
03407 
03408 /********************************************************************************************
03409 >   VisibleTextNode* OpTextCaret::GetLineStartCharacter(VisibleTextNode* pCharacter)
03410 
03411     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
03412     Created:    19/04/95
03413     Inputs:     pCharacter - points to the character to look from
03414     Returns:    A pointer to the first VisibleTextNode character on the line pCharacter is on,
03415                 NULL if there is isn't one (this is BAD!)
03416     Purpose:    This function returns a pointer to the VisibleTextNode at the start of a line
03417 ********************************************************************************************/
03418 
03419 VisibleTextNode* OpTextCaret::GetLineStartCharacter(VisibleTextNode* pCharacter)
03420 {
03421     ERROR2IF(pCharacter == NULL, NULL, "pCharacter was NULL");
03422     VisibleTextNode* pResult = NULL;
03423     
03424     TextLine* pParent = pCharacter->FindParentLine();
03425     ERROR2IF(pParent == NULL, NULL, "Character didn't have a parent TextLine");
03426     pResult = pParent->FindFirstVTN();
03427 
03428     ERROR2IF(pResult == NULL, NULL, "Line didn't have a VTN");
03429 
03430     return pResult;
03431 }
03432 
03433 
03434 /********************************************************************************************
03435 >   VisibleTextNode* OpTextCaret::GetLineEndCharacter(VisibleTextNode* pCharacter)
03436 
03437     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
03438     Created:    19/04/95
03439     Inputs:     pCharacter - points to the character to look from
03440     Returns:    A pointer to the last VisibleTextNode character on the line pCharacter is on,
03441                 NULL if there is isn't one (this is BAD!)
03442     Purpose:    This function returns a pointer to the VisibleTextNode at the end of a line,
03443                 this should be an EOLNode.
03444 ********************************************************************************************/
03445 
03446 VisibleTextNode* OpTextCaret::GetLineEndCharacter(VisibleTextNode* pCharacter)
03447 {
03448     ERROR2IF(pCharacter == NULL, NULL, "pCharacter was NULL");
03449     VisibleTextNode* pResult = NULL;
03450     
03451     TextLine* pParent = pCharacter->FindParentLine();
03452     ERROR2IF(pParent == NULL, NULL, "Character didn't have a parent TextLine");
03453     pResult = pParent->FindLastVTN();
03454 
03455     ERROR2IF(pResult == NULL, NULL, "Line didn't have a VTN");
03456 
03457     return pResult;
03458 }
03459 
03460 
03462 //  The OpTextSelection Operation
03463 
03464 /********************************************************************************************
03465 >   OpTextSelection::OpTextSelection()
03466 
03467     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
03468     Created:    03/04/95
03469     Purpose:    Constructor.  Initialises members
03470 ********************************************************************************************/
03471 
03472 OpTextSelection::OpTextSelection()
03473 {
03474     StartSpread = NULL;
03475     pSelectionStory = NULL;
03476 }
03477 
03478 
03479 /********************************************************************************************
03480 >   BOOL OpTextSelection::Init()
03481 
03482     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
03483     Created:    22/7/94
03484     Returns:    TRUE if all went OK, False otherwise
03485     Purpose:    Adds the operation to the list of all known operations
03486 ********************************************************************************************/
03487 
03488 BOOL OpTextSelection::Init()
03489 {
03490     return (RegisterOpDescriptor(0,
03491                                     _R(IDS_OPTEXTSELECTION),
03492                                     CC_RUNTIME_CLASS(OpTextSelection),
03493                                     OPTOKEN_TEXTSELECTION,
03494                                     OpTextSelection::GetState,
03495                                     0,                          // help ID 
03496                                     0,                          // Bubble help
03497                                     0,
03498                                     0,
03499                                     SYSTEMBAR_ILLEGAL,          // For now !
03500                                     TRUE,                       // Receive messages
03501                                     FALSE,
03502                                     FALSE,
03503                                     0,
03504                                     GREY_WHEN_NO_CURRENT_DOC
03505            )); 
03506 }               
03507 
03508 
03509 /********************************************************************************************
03510 >   OpState OpTextSelection::GetState(String_256* Description, OpDescriptor*)
03511 
03512     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
03513     Created:    29/6/94
03514     Outputs:    Description - GetState fills this string with an approriate description
03515                 of the current state of the push tool
03516     Returns:    The state of the operation, so that menu items (ticks and greying can be
03517                 done properly
03518     Purpose:    Find out the state of the operation at the specific time
03519 ********************************************************************************************/
03520 
03521 OpState OpTextSelection::GetState(String_256* Description, OpDescriptor*)
03522 {
03523     OpState Blobby;
03524     
03525     return Blobby;
03526 }
03527 
03528 
03529 /********************************************************************************************
03530 >   BOOL OpTextSelection::DoDrag( DocCoord Anchor, Spread *pSpread, ClickModifiers ClickMods,
03531                                     Node* pClickNode)
03532 
03533     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
03534     Created:    03/04/95
03535     Inputs:     Anchor  - The position of the mouse at the start of the Drag
03536                 pSpread - The spread that the drag started on
03537                 ClickMods are the modifiers at the start of the operaration
03538                 pClickNode - the node the click happened on
03539     Purpose:    Initially inserts the caret into the TextStory at the position of the click
03540 ********************************************************************************************/
03541 
03542 BOOL OpTextSelection::DoDrag( DocCoord Anchor, Spread *pSpread, ClickModifiers ClickMods,
03543                                     Node* pClickNode)
03544 {
03545     // Validate clicked character and story
03546     ERROR2IF(pClickNode == NULL, FALSE, "Pointer to clicked node was NULL!");
03547     ERROR2IF(!pClickNode->IsAVisibleTextNode(), FALSE, "Clicked node was not a text character");
03548     VisibleTextNode* pClickChar = (VisibleTextNode*)pClickNode;
03549     pSelectionStory = (TextStory*)(pClickNode->FindParent(CC_RUNTIME_CLASS(TextStory)));
03550     ERROR2IF(pSelectionStory == NULL, FALSE, "Clicked character didn't have a parent TextStory");
03551     pCaret = pSelectionStory->GetCaret();
03552     ERROR2IF(pCaret == NULL, FALSE, "TextStory didn't have a caret");
03553 
03554     // Record the initial parameters for later use
03555     StartPoint = Anchor;
03556     StartSpread = pSpread;
03557 
03558     BOOL Success = TRUE;
03559 
03560     // Give the mainframe input focus
03561     DialogManager::DefaultKeyboardFocus();
03562 
03563     if (ClickMods.Adjust && !ClickMods.Constrain)
03564     {
03565         // SHIFT click to extend selection
03566         Success = ExtendSelection(pClickChar, Anchor);
03567         End();
03568         return Success;
03569     }
03570     else
03571     {
03572         if (ClickMods.Constrain)
03573         {
03574             // CTRL click to select line
03575             Success = InitialCaretPosition(pClickChar) && SelectLine();
03576             End();
03577             return Success;
03578         }
03579         else
03580         {
03581             // Normal click to position caret and then drag selection
03582             Success = InitialCaretPosition(pClickChar);
03583         }
03584     }
03585 
03586     if (Success)
03587         Success = StartDrag( DRAGTYPE_AUTOSCROLL );
03588 
03589     return Success;
03590 }
03591 
03592 
03593 /********************************************************************************************
03594 >   void OpTextSelection::DragPointerMove( DocCoord PointerPos, ClickModifiers ClickMods, 
03595                                   Spread *pSpread, BOOL bSolidDrag)
03596 
03597     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
03598     Created:    03/04/95
03599     Inputs:     PointerPos - The current position of the mouse in Doc Coords
03600                 ClickMods - Which key modifiers are being pressed
03601                 pSpread - The spread that the mouse is moving over
03602     Purpose:    Adds or removes characters from the text story selection
03603 ********************************************************************************************/
03604 
03605 void OpTextSelection::DragPointerMove( DocCoord PointerPos, ClickModifiers ClickMods, 
03606                                           Spread *pSpread, BOOL bSolidDrag)
03607 {
03608     ERROR3IF(((pSelectionStory == NULL) || (pCaret == NULL)), "A pointer was NULL");
03609     BOOL Success = TRUE;
03610 
03611     // If drag has moved onto a different spread, convert the coord to be relative to the
03612     // original spread.
03613     if (pSpread != StartSpread)
03614         PointerPos = MakeRelativeToSpread(StartSpread, pSpread, PointerPos);
03615 
03616     // Do different things depending wether you are on a path or not
03617     VisibleTextNode* pFirstToSel = NULL;
03618     VisibleTextNode* pLastToSel = NULL;
03619     BOOL Ignore = FALSE;
03620     if (pSelectionStory->GetTextPath() != NULL)
03621         Success = GetNewSelEndsOnPath(PointerPos, &pFirstToSel, &pLastToSel, &Ignore);
03622     else
03623         Success = GetNewSelEndsInStory(PointerPos, &pFirstToSel, &pLastToSel);
03624 
03625     // Run through all the characters, selecting and deselecting
03626     if (!Success || (pFirstToSel == NULL) || (pLastToSel == NULL))
03627     {
03628         if ((!pCaret->IsSelected() || (pSelectionStory->GetSelectionEnd() != NULL)) && !Ignore)
03629         {
03630             NodeRenderableInk::DeselectAll(TRUE,FALSE);
03631             pCaret->Select(TRUE);
03632         }
03633     }
03634     else
03635     {
03636         OpTextCaret::SelectStoryCharacters(pFirstToSel, pLastToSel, pSelectionStory, pCaret);
03637     }
03638 
03639     if (!Success)
03640     {
03641         InformError();
03642         FailAndExecute();
03643         EndDrag();
03644         End();
03645     }
03646 }
03647 
03648 
03649 /********************************************************************************************
03650 >   void OpTextSelection::DragFinished( DocCoord PointerPos, ClickModifiers ClickMods,
03651                                Spread *pSpread, BOOL Success, BOOL bSolidDrag)
03652 
03653     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
03654     Created:    29/6/94
03655     Inputs:     PointerPos - The position of the mouse at the end of the drag
03656                 ClickMods - the key modifiers being pressed
03657                 pSpread - The spread that the drag finished on
03658                 Success - TRUE if the drag was terminated properly, FALSE if it
03659                 was ended with the escape key being pressed
03660     Purpose:    Handles the drag finishing.  Clears the selection if ESCAPE was pressed
03661 ********************************************************************************************/
03662 
03663 void OpTextSelection::DragFinished( DocCoord PointerPos, ClickModifiers ClickMods,
03664                                Spread *pSpread, BOOL Success, BOOL bSolidDrag)
03665 {
03666     BOOL Worked = TRUE;
03667     
03668     // End the Drag
03669     if (Worked)
03670         Worked = EndDrag();
03671 
03672     // Select the caret if they pressed ESCAPE to end the drag, or there is no remaing selection
03673     BOOL Direction;
03674     if ((!Success) || (pSelectionStory->GetSelectionEnd(&Direction) == NULL) )
03675     {
03676         if ((pSelectionStory->GetCaret() != NULL) && !pSelectionStory->GetCaret()->IsSelected())
03677         {
03678             NodeRenderableInk::DeselectAll(TRUE,FALSE);
03679             pSelectionStory->GetCaret()->Select(TRUE);
03680         }
03681     }
03682 
03683     GetApplication()->FindSelection()->Update();
03684 
03685     End();
03686 }
03687 
03688 
03689 /********************************************************************************************
03690 >   BOOL OpTextSelection::GetNewSelEndsOnPath(DocCoord Point, VisibleTextNode** pSelStart,
03691                                                     VisibleTextNode** pSelEnd, BOOL* pIgnore)
03692 
03693     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
03694     Created:    18/05/95
03695     Inputs:     Point - the current pointer position
03696     Outputs:    pSelStart - points to the first character to select (or NULL)
03697                 pSelEnd - points to the last character to select (or NULL)
03698                 pIgnore - TRUE if the pointer was not over any text
03699     Returns:    TRUE/FALSE for Success/Failure
03700     Purpose:    For finding the bounds of the selection when the TextStory is on a path
03701     Errors:     Checks on the story
03702     SeeAlso:    OpTextSelection::GetNewSelEndsInStory
03703 ********************************************************************************************/
03704 
03705 BOOL OpTextSelection::GetNewSelEndsOnPath(DocCoord Point, VisibleTextNode** pSelStart, VisibleTextNode** pSelEnd, BOOL* pIgnore)
03706 {
03707     // Find the first character in the story
03708     ERROR2IF(((pSelectionStory == NULL) || (pCaret == NULL)), FALSE, "Story or caret pointer was NULL");
03709     VisibleTextNode* pFirstChar = pSelectionStory->FindFirstVTN();
03710     ERROR2IF(pFirstChar == NULL, FALSE, "Selection Story had no characters");
03711 
03712     // Find the character the pointer is currently over
03713     BOOL Success = TRUE;
03714     DocRect CharBoundsInDoc;
03715     DocRect CharBoundsInChar;
03716     BOOL ToLeftOfCaret = TRUE;
03717     OpTextSelection::SelectionPos CharSelPos = NOT_NEAR;
03718     VisibleTextNode* pCurrent = pFirstChar;
03719     VisibleTextNode* pChar = NULL;
03720     while ((pCurrent != NULL) && Success)
03721     {       
03722         if (pCurrent->IsAnAbstractTextChar())
03723         {
03724             // Do a test on the bounds of the character matrix
03725             Success = ((AbstractTextChar*)pCurrent)->GetMetricsRectBounds(&CharBoundsInDoc);
03726 
03727             // Now test the metric rect of the character
03728             if (Success)
03729             {
03730                 CharSelPos = IsClickToLeftHalfOfChar((AbstractTextChar*)pCurrent, Point, FALSE);
03731 
03732                 if (CharSelPos != NOT_NEAR)
03733                 {
03734                     pChar = (AbstractTextChar*)pCurrent;
03735                     break;
03736                 }
03737             }
03738         }
03739 
03740         if (pCurrent == (VisibleTextNode*)pCaret)
03741             ToLeftOfCaret = FALSE;
03742 
03743         pCurrent = pCurrent->FindNextVTNInStory();
03744     }
03745 
03746     // Generate first and last selection characters
03747     if (Success && (pChar != NULL))
03748     {
03749         *pIgnore = FALSE;
03750         Success = GetNewSelBoundsChars((CharSelPos == TO_LEFT), ToLeftOfCaret, pChar, pSelStart, pSelEnd);
03751     }
03752     else
03753     {
03754         *pIgnore = TRUE;
03755         pSelStart = NULL;
03756         pSelEnd = NULL;
03757     }
03758 
03759     return Success;
03760 }
03761 
03762 
03763 /********************************************************************************************
03764 >   BOOL OpTextSelection::GetNewSelEndsInStory(DocCoord Point, VisibleTextNode** pSelStart, VisibleTextNode* pSelEnd)
03765 
03766     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
03767     Created:    18/05/95
03768     Inputs:     Point - the current pointer position
03769     Outputs:    pSelStart - points to the first character to select (or NULL)
03770                 pSelEnd - points to the last character to select (or NULL)
03771     Returns:    TRUE/FALSE for Success/Failure
03772     Purpose:    For finding the bounds of the selection when the TextStory is not on a path
03773     Errors:     Checks on the story
03774     SeeAlso:    OpTextSelection::GetNewSelEndsOnPath
03775 ********************************************************************************************/
03776 
03777 BOOL OpTextSelection::GetNewSelEndsInStory(DocCoord Point, VisibleTextNode** pSelStart, VisibleTextNode** pSelEnd)
03778 {
03779     // Check members
03780     ERROR2IF(((pSelectionStory == NULL) || (pCaret == NULL)), FALSE, "Story or caret pointer was NULL");
03781 
03782     BOOL ToLeftPosChar = TRUE;
03783     VisibleTextNode* pPointerChar = NULL;
03784     if (!pSelectionStory->GetCharacterAtPoint(FALSE, Point, &pPointerChar, &ToLeftPosChar))
03785         return FALSE;
03786     ERROR2IF(pPointerChar == NULL, FALSE, "GetCharacterAtPoint failed to find a character");
03787 
03788     // Now scan through the Story to see wether the char is to the left or right of the caret
03789     VisibleTextNode* pChar = pSelectionStory->FindFirstVTN();
03790     ERROR2IF(pChar == NULL, FALSE, "Selection Story had no characters");
03791     BOOL ToLeftOfCaret = TRUE;
03792     while (pChar != NULL)
03793     {
03794         if (pChar == pCaret)
03795         {
03796             ToLeftOfCaret = FALSE;
03797             break;
03798         }
03799 
03800         if (pChar == pPointerChar)
03801         {
03802             break;
03803         }
03804 
03805         pChar = pChar->FindNextVTNInStory();
03806     }
03807 
03808     // Generate first and last selection characters
03809     return GetNewSelBoundsChars(ToLeftPosChar, ToLeftOfCaret, pPointerChar, pSelStart, pSelEnd);
03810 }
03811 
03812 
03813 /********************************************************************************************
03814 >   BOOL OpTextSelection::GetNewSelBoundsChars( BOOL ToLeftOfChar,
03815                                                 BOOL ToLeftOfCaret,
03816                                                 VisibleTextNode* pFoundChar,
03817                                                 VisibleTextNode** pSelStart,
03818                                                 VisibleTextNode** pSelEnd)
03819     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
03820     Created:    18/05/95
03821     Inputs:     ToLeftOfChar - TRUE if the position is to the left of pFoundChar, FALSE to the right
03822                 ToLeftOfCaret - TRUE if pFoundChar comes before the caret in the story
03823                 pFoundChar - the character that is to become one end of the selection,
03824                 the caret is at the other end
03825     Outputs:    pSelStart - points to the first character to select (or NULL)
03826                 pSelEnd - points to the last character to select (or NULL)
03827     Returns:    TRUE/FALSE for Success/Failure
03828     Purpose:    Having found the character that will form the other end of the story selection,
03829                 call this function to find the first and last chars to select.
03830     Errors:     Assorted checks on pointers
03831 ********************************************************************************************/
03832 
03833 BOOL OpTextSelection::GetNewSelBoundsChars(BOOL ToLeftOfChar, BOOL ToLeftOfCaret, VisibleTextNode* pFoundChar,
03834                                         VisibleTextNode** pSelStart, VisibleTextNode** pSelEnd)
03835 {
03836     ERROR2IF(((pFoundChar == NULL) || (pCaret == NULL)), FALSE, "NULL Pointer found");
03837     *pSelStart = NULL;
03838     *pSelEnd = NULL;
03839 
03840     // Work out wether to include the character in the selection
03841     if (ToLeftOfCaret)
03842     {
03843         // Selection goes from the left/right of pFoundChar to left of caret
03844         if (ToLeftOfChar)
03845             *pSelStart = pFoundChar;
03846         else
03847         {
03848             *pSelStart = pFoundChar->FindNextVTNInStory();
03849             if (*pSelStart == pCaret)
03850                 *pSelStart = NULL;
03851         }
03852 
03853         *pSelEnd = pCaret->FindPrevVTNInStory();
03854     }
03855     else
03856     {
03857         // Selection goes from right of caret to left/right of pChar
03858         if (!ToLeftOfChar)
03859             *pSelEnd = pFoundChar;
03860         else
03861         {
03862             *pSelEnd = pFoundChar->FindPrevVTNInStory();
03863             if (*pSelEnd == pCaret)
03864                 *pSelEnd = NULL;
03865         }
03866 
03867         *pSelStart = pCaret->FindNextVTNInStory();
03868     }
03869 
03870     return TRUE;
03871 }
03872 
03873 
03874 /********************************************************************************************
03875 >   BOOL OpTextSelection::InitialCaretPosition(VisibleTextNode* pSelectChar)
03876 
03877     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
03878     Created:    04/04/95
03879     Inputs:     pSelectChar - points to the character that was clicked on
03880     Outputs:    TransStartPoint is made character relative.
03881     Returns:    TRUE/FALSE for Success/Failure
03882     Purpose:    The caret is positioned either to the left or right of the character clicked
03883                 upon.
03884 ********************************************************************************************/
03885 
03886 BOOL OpTextSelection::InitialCaretPosition(VisibleTextNode* pSelectChar)
03887 {
03888     BOOL Success = TRUE;
03889     
03890     // Deselect ALL objects in the tree
03891     NodeRenderableInk::DeselectAll(TRUE,FALSE);
03892     if (pSelectionStory!=TextStory::GetFocusStory())
03893         if (!OpDeleteTextStory::RemoveEmptyFocusStory())
03894             return FALSE;
03895 
03896     // Position the caret to the left or right of the character, depending on the click
03897     OpTextSelection::SelectionPos ClickPos = TO_LEFT;
03898     if (pSelectChar->IsAnAbstractTextChar())
03899         ClickPos = IsClickToLeftHalfOfChar((AbstractTextChar*)pSelectChar, StartPoint, TRUE);
03900     ERROR3IF(ClickPos == NOT_NEAR,"Selection point was not near the character claimed");
03901     if (ClickPos == TO_LEFT)
03902         pSelectionStory->MoveCaretToCharacter(pSelectChar, PREV);
03903     if (ClickPos == TO_RIGHT)
03904         pSelectionStory->MoveCaretToCharacter(pSelectChar, NEXT);
03905 
03906     // Select the new Caret
03907     TextStory::SetFocusStory(pSelectionStory);
03908     pSelectionStory->GetCaret()->HasMoved();
03909     pSelectionStory->GetCaret()->ScrollToShow();
03910     pSelectionStory->GetCaret()->Select(TRUE);
03911 
03912     return Success;
03913 }                                                                             
03914 
03915 
03916 /********************************************************************************************
03917 >   SelectionPos OpTextSelection::IsClickToLeftHalfOfChar(AbstractTextChar* pClickChar,
03918                                                         DocCoord ClickPoint, BOOL MustChoose)
03919 
03920     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
03921     Created:    10/04/95
03922     Inputs:     pClickChar - points to the character clicked on
03923                 ClickPoint - the actual position of the click
03924                 MustChose - TRUE if left or right should be returned, ignoring NOT_NEAR
03925     Returns:    Wether the click is to the left (TO_LEFT), to the right (TO_RIGHT), or not
03926                 near the character (NOT_NEAR)
03927     Purpose:    To see if a click is to the left or right of the middle of a character
03928 ********************************************************************************************/
03929 
03930 OpTextSelection::SelectionPos OpTextSelection::IsClickToLeftHalfOfChar(AbstractTextChar* pClickChar,
03931                                                             DocCoord ClickPoint, BOOL MustChoose)
03932 {
03933     ERROR3IF(pSelectionStory == NULL, "pSelectionStory is NULL");
03934     ERROR3IF(pClickChar == NULL, "pClickChar is NULL");
03935 
03936     SelectionPos Result = NOT_NEAR;
03937 
03938     if ((pSelectionStory != NULL) && (pClickChar != NULL))
03939     {
03940         // Transform the mouse position through the two matrices so it is character relative
03941         Matrix CharMat(0,0);
03942         if (pClickChar->GetStoryAndCharMatrix(&CharMat)!=FALSE)
03943             CharMat=CharMat.Inverse();
03944         else
03945             ERROR3("GetStoryAndCharMatrix() failed");
03946 
03947         // Transform the mouse click position into character space
03948         CharMat.transform(&ClickPoint);
03949 
03950         // Get the character rectangle
03951         DocRect CharRect;
03952         if (pClickChar->GetMetricsRect(&CharRect))
03953         {
03954             if (CharRect.ContainsCoord(ClickPoint) || MustChoose)
03955             {
03956                 if (ClickPoint.x > ((CharRect.lo.x+CharRect.hi.x)/2))
03957                     Result = TO_RIGHT;
03958                 else
03959                     Result = TO_LEFT;
03960             }
03961         }
03962         else
03963             ERROR3("GetMetricsRectInStory failed");
03964     }
03965 
03966     return Result;
03967 }
03968 
03969 
03970 /********************************************************************************************
03971 >   BOOL OpTextSelection::SelectLine()
03972 
03973     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
03974     Created:    04/04/95
03975     Returns:    TRUE/FALSE for Success/Failure
03976     Purpose:    The caret is positioned at the start of the line and all AbstractCharacters on
03977                 the line are selected.
03978     SeeAlso:    OpTextSelection::DoSelectTextLine
03979 ********************************************************************************************/
03980 
03981 BOOL OpTextSelection::SelectLine()
03982 {
03983     ERROR2IF(pSelectionStory == NULL, FALSE, "Selection story was NULL");
03984     CaretNode* pCaret = pSelectionStory->GetCaret();
03985     ERROR2IF(pCaret == NULL, FALSE, "Selection story didn't have a caret");
03986     TextLine* pLine = pCaret->FindParentLine();
03987     ERROR2IF(pLine == NULL, FALSE, "Caret didn't have a parent TextLine");
03988 
03989     // Move the caret to the start of the line
03990     NodeRenderableInk::DeselectAll(TRUE,FALSE);
03991     pSelectionStory->MoveCaretToStartOfLine();
03992 
03993     // Get pointer to first and last char in line
03994     VisibleTextNode* pFirstChar = pCaret->FindNextVTNInStory();
03995     VisibleTextNode* pLastChar = pLine->FindLastVTN();
03996 
03997     // Select the characters
03998     OpTextCaret::SelectStoryCharacters(pFirstChar, pLastChar, pSelectionStory, pCaret);
03999 
04000     return TRUE;
04001 }
04002 
04003 
04004 /********************************************************************************************
04005 >   BOOL OpTextSelection::ExtendSelection(VisibleTextNode* pClickChar)
04006 
04007     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
04008     Created:    04/04/95
04009     Inputs:     pClickChar - the character that was clicked on
04010     Returns:    TRUE/FALSE for Success/Failure
04011     Purpose:    If the story clicked on was not the focus story the the caret is positioned
04012                 at the click point
04013                 If the story clicked on was the focus story, but there was no text selection
04014                 then a selection is created between the caret and the click point
04015                 If the story clicked on was the focus story and there was no text selection
04016                 the selection is extended, using the caret as the anchor point.
04017 ********************************************************************************************/
04018 
04019 BOOL OpTextSelection::ExtendSelection(VisibleTextNode* pClickChar, DocCoord ClickPoint)
04020 {
04021     BOOL Success = TRUE;
04022     
04023     // Is the selection story the one with the input focus?
04024     if (pSelectionStory != TextStory::GetFocusStory())
04025     {
04026         Success = InitialCaretPosition(pClickChar);
04027     }
04028     else
04029     {
04030 //      BOOL CurrentForwardsDirection = TRUE;
04031         BOOL NewForwardsDirection = TRUE;
04032 //      VisibleTextNode* pSelEnd = pSelectionStory->GetSelectionEnd(&CurrentForwardsDirection);
04033         CaretNode* pCaret = pSelectionStory->GetCaret();
04034         ERROR2IF(pCaret == NULL, FALSE, "Caret not found");
04035         
04036         // Scan from the caret to the right to find the direction of pClickChar
04037         // BODGE TEXT - should be a formatty thing, rather than scanning chars
04038         VisibleTextNode* pChar = pCaret->FindNextVTNInStory();
04039         while ((pChar != NULL) && (pChar != pClickChar))
04040         {
04041             pChar = pChar->FindNextVTNInStory();
04042         }
04043         if (pChar != NULL)
04044             NewForwardsDirection = TRUE;        // The clicked char is to the right of the caret
04045         else
04046             NewForwardsDirection = FALSE;       // The clicked char is to the left of the caret
04047 
04048         // Get the selection end char
04049         VisibleTextNode* pNewSelEndChar = pClickChar;
04050         SelectionPos ClickPos = TO_LEFT;
04051         if (pClickChar->IsAnAbstractTextChar())
04052             ClickPos = IsClickToLeftHalfOfChar((AbstractTextChar*)pClickChar, ClickPoint, TRUE);
04053         if (NewForwardsDirection && (ClickPos==TO_LEFT))
04054         {
04055             // The selection goes from the caret to the left of the click char
04056             pNewSelEndChar = pClickChar->FindPrevVTNInStory();
04057         }
04058         if (!NewForwardsDirection && (ClickPos==TO_RIGHT))
04059         {
04060             // The selection goes from the caret to the right of the click char
04061             pNewSelEndChar = pClickChar->FindNextVTNInStory();
04062         }
04063         if (pNewSelEndChar->IsACaret())
04064             pNewSelEndChar = NULL;
04065 
04066         // Get the selection start character
04067         VisibleTextNode* pSelStartChar = NULL;
04068         if (NewForwardsDirection)
04069             pSelStartChar = pCaret->FindNextVTNInStory();
04070         else
04071         {
04072             pSelStartChar = pNewSelEndChar;
04073             pNewSelEndChar = pCaret->FindPrevVTNInStory();
04074         }
04075 
04076         // Do the selection
04077         if ((pSelStartChar == NULL) || (pNewSelEndChar == NULL))
04078         {
04079             NodeRenderableInk::DeselectAll(TRUE,FALSE);
04080             pCaret->Select(TRUE);
04081         }
04082         else
04083         {
04084             OpTextCaret::SelectStoryCharacters(pSelStartChar, pNewSelEndChar, pSelectionStory, pCaret);
04085             pNewSelEndChar->ScrollToShow();
04086         }
04087     }
04088 
04089     GetApplication()->UpdateSelection();
04090     return Success;
04091 }
04092 
04093 
04094 /********************************************************************************************
04095 >   BOOL OpTextSelection::DoSelectAllText(TextStory* pStory)
04096 
04097     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
04098     Created:    24/04/95
04099     Inputs:     pStory - points to a text story
04100     Returns:    TRUE/FALSE for Success/Failure
04101     Purpose:    Selects all characters in the given story
04102     Errors:     Checks on story pointer and the caret in the story
04103     SeeAlso:    OpTextSelection::DoSelectLineText
04104 ********************************************************************************************/
04105 
04106 BOOL OpTextSelection::DoSelectAllText(TextStory* pStory)
04107 {
04108     ERROR2IF(pStory == NULL, FALSE, "Story pointer was NULL");
04109     CaretNode* pCaret = pStory->GetCaret();
04110     ERROR2IF(pCaret == NULL, FALSE, "Story didn't have a caret");
04111     BOOL ok = TRUE;
04112 
04113     NodeRenderableInk::DeselectAll(TRUE, FALSE);
04114 
04115     // Move the caret to the start of the text story
04116     VisibleTextNode* pStartChar = pStory->FindFirstVTN();
04117     if (ok && (pStartChar != pCaret))
04118         ok = pStory->MoveCaretToCharacter(pStartChar, PREV);
04119     
04120     // Get the first and last char in story
04121     pStartChar = pStory->FindFirstVTN();
04122     VisibleTextNode* pEndChar = pStory->FindLastVTN();
04123     if ((pStartChar == NULL) || (pEndChar == NULL))
04124         ok = FALSE;
04125     
04126     if (ok)
04127     {
04128         OpTextCaret::SelectStoryCharacters(pStartChar, pEndChar, pStory, pCaret);
04129     }
04130     else
04131     {
04132         FailAndExecute();
04133         InformError();
04134     }
04135 
04136     End();
04137     
04138     return ok;
04139 }
04140 
04141 
04142 /********************************************************************************************
04143 >   BOOL OpTextSelection::DoSelectLineText()
04144 
04145     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
04146     Created:    24/04/95
04147     Inputs:     -
04148     Returns:    TRUE/FALSE for Success/Failure
04149     Purpose:    Selects all VTN's in the given text line, clearing all other selections
04150     Errors:     Check on line pointer
04151     SeeAlso:    OpTextSelection::DoSelectAllText
04152 ********************************************************************************************/
04153 
04154 BOOL OpTextSelection::DoSelectLineText()
04155 {
04156     // Set pointers
04157     pSelectionStory = TextStory::GetFocusStory();;
04158     ERROR2IF(pSelectionStory == NULL, FALSE, "No focus text story");
04159     pCaret = pSelectionStory->GetCaret();
04160     ERROR2IF(pCaret == NULL, FALSE, "TextStory didn't have a caret");
04161 
04162     BOOL ok = SelectLine();
04163 
04164     if (!ok)               
04165     {
04166         FailAndExecute();
04167         InformError();
04168     }
04169 
04170     End();
04171     
04172     return ok;
04173 }
04174 
04175 
04176 /********************************************************************************************
04177 >   BOOL OpDeleteTextStory::Init()
04178 
04179     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
04180     Created:    25/04/95
04181     Returns:    TRUE/FALSE for Success/Failure
04182     Purpose:    Initialalises the DeleteATextStory operation
04183 ********************************************************************************************/
04184 
04185 BOOL OpDeleteTextStory::Init()
04186 {
04187     return RegisterOpDescriptor(0,
04188                                 _R(IDS_DELETETEXTSTORY),
04189                                 CC_RUNTIME_CLASS(OpDeleteTextStory),
04190                                 OPTOKEN_DELETESTORY,
04191                                 OpDeleteTextStory::GetState);
04192 }               
04193 
04194 
04195 /********************************************************************************************
04196 >   OpState OpDeleteTextStory::GetState(String_256*, OpDescriptor*)
04197 
04198     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
04199     Created:    25/04/95
04200     Returns:    The state of the OpDeleteTextStory
04201     Purpose:    For finding OpDeleteTextStory's state. Always available
04202 ********************************************************************************************/
04203 
04204 OpState OpDeleteTextStory::GetState(String_256* UIDescription, OpDescriptor*)
04205 {
04206     OpState OpSt;
04207 
04208     return OpSt;
04209 }
04210 
04211 
04212 
04213 /********************************************************************************************
04214 >   OpState OpDeleteTextStory::GetState(DoWithParam(OpDescriptor* pOpDesc, OpParam* pParam))
04215 
04216     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
04217     Created:    25/04/95
04218     Inputs:     pOpDesc - points to the OpDescriptor (unused)
04219                 pParam - Param1 points to the TextStory to delete 
04220     Purpose:    For finding OpDeleteTextStory's state. Always available
04221 ********************************************************************************************/
04222 
04223 void OpDeleteTextStory::DoWithParam(OpDescriptor* pOpDesc, OpParam* pParam)
04224 {
04225     TextStory* pStory = (TextStory*) (void *) pParam->Param1;
04226     if (pStory == NULL)
04227     {
04228         ERROR3("Story pointer was NULL");
04229         return;
04230     }
04231     ERROR3IF(!IS_A(pStory,TextStory),"Story pointer didn't point to a TextStory!");
04232 
04233     BOOL ok = DoStartSelOp(TRUE, TRUE);
04234 
04235     if (ok)
04236         ok = DoActions(this, pStory);
04237 
04238     if (ok)
04239     {
04240         ObjChangeParam ObjChange(OBJCHANGE_FINISHED,ObjChangeFlags(TRUE), pStory, this);
04241         ok = UpdateChangedNodes(&ObjChange);
04242     }
04243         
04244     if (!ok)
04245     {
04246         FailAndExecute();
04247         InformError();
04248     }
04249 
04250     End();
04251 }
04252 
04253 
04254 /********************************************************************************************
04255 >   static BOOL OpDeleteTextStory::DoActions(UndoableOperation* pOp, TextStory* pEmptyStory)
04256 
04257     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
04258     Created:    17/05/95
04259     Inputs:     pOp - points to the current operation
04260                 pEmptyStory - points to the text story to delete
04261     Returns:    TRUE/FALSE for success/failure
04262     Purpose:    Hides an empty text story, inserting the required undo actions into the operation
04263     Errors:     Checks on the arguments
04264 ********************************************************************************************/
04265 
04266 BOOL OpDeleteTextStory::DoActions(UndoableOperation* pOp, TextStory* pEmptyStory)
04267 {
04268     ERROR2IF(pEmptyStory == NULL, FALSE, "Story pointer was NULL");
04269 
04270     BOOL ok = TRUE;
04271 
04272     ObjChangeParam ObjDeleteChange(OBJCHANGE_STARTING, ObjChangeFlags(TRUE), pEmptyStory, pOp);
04273     if (ok && pEmptyStory->AllowOp(&ObjDeleteChange))
04274     {
04275         if (pEmptyStory->GetCaret() != NULL)
04276             pEmptyStory->GetCaret()->DeSelect(TRUE);
04277 
04278         if (ok)
04279             ok = pOp->DoInvalidateNodeRegion(pEmptyStory, TRUE, FALSE);
04280 
04281         // If the story is on a path then we want the path to remain
04282         if (ok && (pEmptyStory->GetTextPath() != NULL))
04283         {
04284             NodePath* pTextPath = pEmptyStory->GetTextPath();
04285 
04286             //Localise attributes of the story
04287             if (ok) ok = pOp->DoLocaliseCommonAttributes(pEmptyStory); 
04288             if (ok) ok = pOp->DoMoveNode(pTextPath,pEmptyStory,NEXT);
04289             if (ok) pTextPath->DeSelect(FALSE);
04290 
04291             // Factor back out the common attributes
04292             if (ok)
04293                 ok = pOp->DoFactorOutCommonChildAttributes(pEmptyStory);
04294         }
04295 
04296         // Hide the story
04297         if (ok)
04298             ok = pOp->DoHideNode(pEmptyStory, TRUE);
04299 
04300         if (ok && (TextStory::GetFocusStory() == pEmptyStory))
04301             TextStory::SetFocusStory(NULL);
04302 
04303         GetApplication()->UpdateSelection();
04304     }
04305 
04306     return ok;
04307 }
04308 
04309 
04310 /********************************************************************************************
04311 >   static BOOL OpDeleteTextStory::RemoveEmptyFocusStory(UndoableOperation* pOp = NULL)
04312 
04313     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
04314     Created:    26/04/95
04315     Inputs:     pOp - points to the current operation (if any)
04316     Returns:    TRUE/FALSE for success/failure
04317     Purpose:    If the focus story has no characters in it and the caret in it is not selected
04318                 then the story is hidden and the focus story is cleared.
04319 ********************************************************************************************/
04320 
04321 BOOL OpDeleteTextStory::RemoveEmptyFocusStory(UndoableOperation* pOp)
04322 {
04323     BOOL ok = TRUE;
04324     TextStory* pTS = TextStory::GetFocusStory();
04325     CaretNode* pCaret = NULL;
04326     if (pTS != NULL)
04327         pCaret = pTS->GetCaret();
04328     if ((pTS != NULL) && (pCaret != NULL))
04329     {
04330         // Get a pointer to the first character in the story
04331         VisibleTextNode* pFirstChar = pTS->FindFirstVTN();
04332         if (pFirstChar == NULL)
04333             InformError();
04334         else
04335         {
04336             if (!IS_A(pFirstChar,TextChar))
04337                 pFirstChar = pFirstChar->FindNextTextCharInStory();
04338         }
04339 
04340         // Remove the story if it contains no text chars
04341         if (pFirstChar == NULL)
04342         {
04343             // If no op is running then invoke on to do the delete, otherwise append delete actions to running op 
04344             if (pOp == NULL)
04345             {
04346                 OpDescriptor* OpDesc = OpDescriptor::FindOpDescriptor(CC_RUNTIME_CLASS(OpDeleteTextStory));
04347                 if (OpDesc != NULL)
04348                 {
04349                     OpParam p((void*)pTS,0);
04350                     OpDesc->Invoke(&p);
04351                 }
04352                 else
04353                     ok = FALSE;
04354             }
04355             else
04356                 ok = OpDeleteTextStory::DoActions(pOp, pTS);
04357         }
04358     }
04359 
04360     return ok;
04361 }
04362 
04363 void OpDeleteTextStory::PerformMergeProcessing()
04364 {
04365     MergeWithPrevious();
04366 }
04367 
04368 
04370 // Justification operations
04371 
04372 /********************************************************************************************
04373 >   static BOOL OpApplyJustificationToStory::Init()
04374 
04375     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
04376     Created:    27/04/95
04377     Returns:    TRUE/FALSE for success/failure
04378     Purpose:    Initialises all the justification options on the TextStory pop-up menu
04379 ********************************************************************************************/
04380 
04381 BOOL OpApplyJustificationToStory::Init()
04382 {
04383     BOOL ok = TRUE;
04384 
04385     ok = ok && RegisterOpDescriptor(0,
04386                                     _R(IDS_APPLYLEFTJUSTIFY),
04387                                     CC_RUNTIME_CLASS(OpApplyLeftJustifyToStory),
04388                                     OPTOKEN_APPLYLEFTJUSTIFY,
04389                                     OpApplyJustificationToStory::GetState);
04390     ok = ok && RegisterOpDescriptor(0,
04391                                     _R(IDS_APPLYCENTREJUSTIFY),
04392                                     CC_RUNTIME_CLASS(OpApplyCentreJustifyToStory),
04393                                     OPTOKEN_APPLYCENTREJUSTIFY,
04394                                     OpApplyJustificationToStory::GetState);
04395     ok = ok && RegisterOpDescriptor(0,
04396                                     _R(IDS_APPLYRIGHTJUSTIFY),
04397                                     CC_RUNTIME_CLASS(OpApplyRightJustifyToStory),
04398                                     OPTOKEN_APPLYRIGHTJUSTIFY,
04399                                     OpApplyJustificationToStory::GetState);
04400     ok = ok && RegisterOpDescriptor(0,
04401                                     _R(IDS_APPLYFULLJUSTIFY),
04402                                     CC_RUNTIME_CLASS(OpApplyFullJustifyToStory),
04403                                     OPTOKEN_APPLYFULLJUSTIFY,
04404                                     OpApplyJustificationToStory::GetState);
04405 
04406     return ok;
04407 }
04408 
04409 
04410 /********************************************************************************************
04411 >   static OpState  OpApplyJustificationToStory::GetState(String_256*, OpDescriptor*)
04412 
04413     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
04414     Created:    27/04/95
04415     Returns:    Tick/Grey state for the op
04416     Purpose:    Returns the greying state of the operation, greyed if there are no text
04417                 stories selected.
04418 ********************************************************************************************/
04419 
04420 OpState OpApplyJustificationToStory::GetState(String_256* pUIDesc, OpDescriptor* pOpDesc)
04421 {
04422     OpState OpSt;
04423 
04424     SelRange* pSelection = GetApplication()->FindSelection();
04425     Node* pNode = pSelection->FindFirst();
04426     
04427     // Search the SelRange for a selected TextStory
04428     BOOL FoundSelStory = FALSE;
04429     while (pNode != NULL)
04430     {
04431         if (IS_A(pNode, TextStory))
04432         {
04433             FoundSelStory = TRUE;
04434             break;
04435         }
04436 
04437         pNode = pSelection->FindNext(pNode);
04438     }
04439 
04440     OpSt.Greyed = !FoundSelStory;
04441 
04442     return OpSt;
04443 }       
04444 
04445 
04446 /********************************************************************************************
04447 >   virtual void OpApplyLeftJustifyToStory::Do(OpDescriptor*)
04448 
04449     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
04450     Created:    27/04/95
04451     Inputs:     OpDesc - unused
04452     Purpose:    Applies left justification to selected text stories.
04453 ********************************************************************************************/
04454 
04455 void OpApplyLeftJustifyToStory::Do(OpDescriptor* pOpDesc)
04456 {
04457     AttrTxtJustification* ApplyAttrib = new AttrTxtJustification();
04458 
04459     if (ApplyAttrib != NULL)
04460     {
04461         ApplyAttrib->Value.justification = JLEFT;
04462         AttributeManager::AttributeSelected(ApplyAttrib, NULL);
04463     }
04464 }
04465 
04466 
04467 /********************************************************************************************
04468 >   virtual void OpApplyCentreJustifyToStory::Do(OpDescriptor*)
04469 
04470     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
04471     Created:    27/04/95
04472     Inputs:     OpDesc - unused
04473     Purpose:    Applies centre justification to selected text stories.
04474 ********************************************************************************************/
04475 
04476 void OpApplyCentreJustifyToStory::Do(OpDescriptor* pOpDesc)
04477 {
04478     AttrTxtJustification* ApplyAttrib = new AttrTxtJustification();
04479 
04480     if (ApplyAttrib != NULL)
04481     {
04482         ApplyAttrib->Value.justification = JCENTRE;
04483         AttributeManager::AttributeSelected(ApplyAttrib, NULL);
04484     }
04485 }
04486 
04487 
04488 /********************************************************************************************
04489 >   virtual void OpApplyRightJustifyToStory::Do(OpDescriptor*)
04490 
04491     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
04492     Created:    27/04/95
04493     Inputs:     OpDesc - unused
04494     Purpose:    Applies right justification to selected text stories.
04495 ********************************************************************************************/
04496 
04497 void OpApplyRightJustifyToStory::Do(OpDescriptor* pOpDesc)
04498 {
04499     AttrTxtJustification* ApplyAttrib = new AttrTxtJustification();
04500 
04501     if (ApplyAttrib != NULL)
04502     {
04503         ApplyAttrib->Value.justification = JRIGHT;
04504         AttributeManager::AttributeSelected(ApplyAttrib, NULL);
04505     }
04506 }
04507 
04508 
04509 /********************************************************************************************
04510 >   virtual void OpApplyFullJustifyToStory::Do(OpDescriptor*)
04511 
04512     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
04513     Created:    27/04/95
04514     Inputs:     OpDesc - unused
04515     Purpose:    Applies full justification to selected text stories.
04516 ********************************************************************************************/
04517 
04518 void OpApplyFullJustifyToStory::Do(OpDescriptor* pOpDesc)
04519 {
04520     AttrTxtJustification* ApplyAttrib = new AttrTxtJustification();
04521 
04522     if (ApplyAttrib != NULL)
04523     {
04524         ApplyAttrib->Value.justification = JFULL;
04525         AttributeManager::AttributeSelected(ApplyAttrib, NULL);
04526     }
04527 }
04528 
04529 
04531 // OpTextPaste
04532 
04533 /********************************************************************************************
04534 >   OpTextPaste::OpTextPaste() 
04535 
04536     Author:     Mike_Kenny (Xara Group Ltd) <camelotdev@xara.com>
04537     Created:    09/05/95
04538     Purpose:    Constructor for paste text operation
04539 ********************************************************************************************/
04540 
04541 OpTextPaste::OpTextPaste()
04542 {
04543     // Dummy constructor
04544 }
04545 
04546 
04547 /********************************************************************************************
04548 >   BOOL OpTextPaste::Init()
04549 
04550     Author:     Mike_Kenny (Xara Group Ltd) <camelotdev@xara.com>
04551     Created:    25/01/95
04552     Returns:    TRUE if the op is correctly registered.
04553     Purpose:    Registers the specilised text paste operation
04554 ********************************************************************************************/
04555 
04556 BOOL OpTextPaste::Init()
04557 {
04558     return RegisterOpDescriptor(0, 
04559                                 _R(IDS_TEXTPASTEOP),
04560                                 CC_RUNTIME_CLASS(OpTextPaste),
04561                                 OPTOKEN_TEXTPASTE, 
04562                                 OpTextPaste::GetState,
04563                                 0,
04564                                 _R(IDBBL_TEXTPASTEOP));
04565 }
04566 
04567 
04568 /********************************************************************************************
04569 >   OpState OpTextPaste::GetState(String_256*, OpDescriptor*)
04570 
04571     Author:     Mike_Kenny (Xara Group Ltd) <camelotdev@xara.com>
04572     Created:    27/01/95
04573     Returns:    Returns a default OpState.
04574     Purpose:    Controls whether the text paste operation is available or not.
04575                 Currently, is always available.
04576 ********************************************************************************************/
04577 
04578 OpState OpTextPaste::GetState(String_256*, OpDescriptor*)
04579 {
04580     OpState os;
04581     return os;
04582 }
04583 
04584 
04585 /********************************************************************************************
04586 > void OpTextPaste::Do(OpDescriptor* pOpDesc)
04587 
04588     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>        
04589     Created:    21/7/96
04590     Inputs:     pOpDesc - unused
04591     Purpose:    see OpTextPaste::DoPasteTextHelper()
04592 ********************************************************************************************/
04593 
04594 void OpTextPaste::Do(OpDescriptor*)
04595 {
04596     BeginSlowJob();
04597     if (!DoPasteTextHelper())
04598         FailAndExecute();
04599     End();
04600 }
04601 
04602 
04603 /********************************************************************************************
04604 > BOOL OpTextPaste::DoPasteTextHelper()
04605 
04606     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>        
04607     Created:    21/7/96
04608     Returns:    FALSE if fails
04609     Purpose:    If there is a single TextStory on clipboard and a focus story in the doc,
04610                 the story on the clipboard is pasted at caret in focus story,
04611                 else the normal paste method is used
04612 ***************************************************************