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 ********************************************************************************************/
04613 
04614 BOOL OpTextPaste::DoPasteTextHelper()
04615 {
04616     // get a ptr to the clipbaord and prepare it for a paste
04617     BOOL ExternalData = FALSE;
04618     if (!InternalClipboard::PrepareForPaste(&ExternalData)) return FALSE;
04619 
04620     // determine how many objects and text stories on the clipboard, keeping ptr to 1st text story
04621     INT32 NumObjects = 0;
04622     INT32 NumStories = 0;
04623     TextStory* pFirstStory = NULL;
04624     Node* pNode = InternalClipboard::GetInsertionLayer()->FindFirstChild();
04625     while (pNode!=NULL)
04626     {
04627         NumObjects += 1;
04628         if (IS_A(pNode,TextStory))
04629         {
04630             NumStories += 1;
04631             if (pFirstStory==NULL)
04632                 pFirstStory = (TextStory*)pNode;
04633         }
04634         pNode = pNode->FindNext();
04635     }
04636 
04637     // if no focus story or not a single text story on the clipboard, do a normal paste op
04638     TextStory* pFocusStory = TextStory::GetFocusStory();
04639     if (NumStories!=1 || NumObjects!=1 || pFocusStory==NULL)
04640         return DoPasteStandard(FALSE,ExternalData);
04641 
04642     if (!DoStartSelOp(TRUE,TRUE))  
04643         return FALSE;
04644 
04645     // create a range of selected nodes in the focus story and call AllowOp() on them
04646     VisibleTextNode* pFirstVTN = pFocusStory->FindFirstVTN();
04647     VisibleTextNode* pLastVTN  = pFocusStory->FindLastVTN();
04648     ERROR2IF(pFirstVTN==NULL || pLastVTN==NULL, FALSE,"OpTextPaste::PasteTextHelper() - Focus story has no VTN");
04649     Range pFocusStorySelection(pFirstVTN,pLastVTN,RangeControl(TRUE));
04650     ObjChangeParam ObjChange(OBJCHANGE_STARTING,ObjChangeFlags(),NULL,this);
04651     if (!pFocusStorySelection.AllowOp(&ObjChange))
04652         //ERROR2(FALSE,"OpTextPaste::PasteTextHelper() - op should have been greyed!");
04653         return DoPasteStandard(FALSE,ExternalData); // doesn't allow me to paste in - but we need to do something! so we do a general paste to app.
04654 
04655     String_256 MasterText = pFocusStory->GetStoryAsString();
04656 
04657     // delete any selected text
04658     if (!pFocusStory->DeleteSelectedText(this))
04659         return FALSE;
04660 
04661     // After op ends need to inform all DocComponents in dest doc of the outcome
04662     BaseDocument* pDoc = pFocusStory->FindOwnerDoc();
04663     ERROR2IF(pDoc==NULL,FALSE,"OpTextPaste::PasteTextHelper() - Focus story has no parent document!");
04664 
04665     // Ensure that EndComponentCopy and/or AbortComponentCopy will be called when the Op ends
04666     InformDocComponentsOfOperationsOutcome(pDoc); 
04667 
04668     // paste story into focus story at the caret, ensuring dest doc component data up to date
04669     BOOL ok = TRUE;
04670     CALL_WITH_FAIL((pDoc->StartComponentCopy()),this, ok)
04671     if (!ok) return FALSE;
04672     Range PasteRange;
04673     if (!DoPasteText(pFirstStory,pFocusStory,&PasteRange))
04674         return FALSE;
04675     CALL_WITH_FAIL((PasteRange.CopyComponentDataToDoc(InternalClipboard::Instance(), pDoc)), this, ok);
04676     if (!ok) return FALSE;
04677     
04678     // inform the clipboard the paste has finished
04679     if (!InternalClipboard::PasteCompleted()) return FALSE;
04680 
04681     CaretNode* pCaret = pFocusStory->GetCaret();
04682     if (!DoSelectNode(pCaret,NULL)) return FALSE;
04683 
04684     // update other textstories that are dependant on this one
04685     SliceHelper::OnTextStoryChanged(pFocusStory, this, &ObjChange, MasterText);
04686         
04687     // Update all the changed nodes, i.e. tell all the parents of the children that have been effected
04688     ObjChange.Define(OBJCHANGE_FINISHED,ObjChangeFlags(),NULL,this);
04689     if (!UpdateChangedNodes(&ObjChange))
04690         return FALSE;
04691 
04692     // Make sure the caret is still in view
04693     pCaret->ScrollToShow();
04694     return TRUE;
04695 }
04696 
04697 
04698 /********************************************************************************************
04699 >   BOOL OpTextPaste::DoPasteText(TextStory *pPasteStory, TextStory *pFocusStory, Range* pPasteRange)
04700 
04701     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>        
04702     Created:    21/7/96
04703     Inputs:     pPasteStory - story to paste
04704                 pFocusStory - story to paste into
04705     Outputs:    pPasteRange - Range of nodes pasted into focus story
04706     Returns:    FALSE if fails
04707     Purpose:    Paste a story into the Focus story at the caret
04708 ********************************************************************************************/
04709 
04710 BOOL OpTextPaste::DoPasteText(TextStory* pPasteStory, TextStory* pFocusStory, Range* pPasteRange)
04711 {
04712     BaseDocument* pDoc = pFocusStory->FindOwnerDoc();
04713     ERROR2IF(pPasteStory==NULL, FALSE, "Null paste story passed to PasteText()");
04714     ERROR2IF(pFocusStory==NULL, FALSE, "Null focus story passed to PasteText()");
04715     UndoableOperation* pUndoOp = this;
04716 
04717     // get some useful pointers ...
04718     CaretNode* pCaret = pFocusStory->GetCaret();
04719     ERROR2IF(pCaret==NULL,FALSE,"OpTextPaste::DoPasteText() - focus story has no caret!");
04720     TextLine* pCaretLine = pCaret->FindParentLine();
04721     ERROR2IF(pCaretLine==NULL,FALSE,"OpTextPaste::DoPasteText() - caret in dest story has no parent TextLine");
04722     TextLine* pFirstSrcLine = pPasteStory->FindFirstLine();
04723     ERROR2IF(pFirstSrcLine==NULL,FALSE,"OpTextPaste::DoPasteText() - story to paste has no lines");
04724     VisibleTextNode* pCaretLineLastVTN = pCaretLine->FindLastVTN();
04725     ERROR2IF(pCaretLineLastVTN==NULL,FALSE,"OpTextPaste::DoPasteText() - Caret line has no VTN!");
04726     
04727     // get the set of non line level attrs on both stories
04728     AttrTypeSet NonLineLevelAttrs;
04729     BOOL    ok = pPasteStory->AddNonLineLevelDescendantAttrsToSet(&NonLineLevelAttrs);
04730     if (ok) ok = pFocusStory->AddNonLineLevelDescendantAttrsToSet(&NonLineLevelAttrs);
04731 
04732     // ptrs to create a range of node pasted
04733     VisibleTextNode* pFirstPastedVTN = NULL;
04734     VisibleTextNode* pLastPastedVTN  = NULL;
04735 
04736     // copy all VTN on the first line to be pasted before the caret, noting the first copied
04737     // - Localise/FactorOut to/from chars global as attrs on whole story may change
04738     // - Localise/FactorOut to/from chars excludes LineLevelAttrs ensuring LLA merged correctly
04739     // - Localise/FactorOut to/from chars on clipboard not done undoably
04740     if (ok) ok = pCaretLine->DoLocaliseCommonAttributes(pUndoOp,FALSE,TRUE,&NonLineLevelAttrs);
04741     if (ok) ok = pFirstSrcLine->DoLocaliseCommonAttributes(NULL,FALSE,TRUE,&NonLineLevelAttrs);
04742     VisibleTextNode* pSrcVTN = pFirstSrcLine->FindFirstVTN();
04743     while (ok && pSrcVTN!=NULL)
04744     {
04745         if (!pSrcVTN->IsACaret())   // don't paste caret
04746         {
04747             VisibleTextNode* pVTNCopy = NULL;
04748             CALL_WITH_FAIL(pSrcVTN->NodeCopy((Node**)&pVTNCopy), pUndoOp, ok);
04749             if (ok) pVTNCopy->ClearSelection(TRUE);     // ensure new node not selected
04750             if (ok) ok = pVTNCopy->DoInsertNewNode(pUndoOp,pCaret,PREV);
04751 
04752             if (ok)
04753             {
04754                 // Ensure component references are up to date /before/ we start to optimise
04755                 // attributes!
04756                 Node* pNode = pVTNCopy->FindFirstDepthFirst();
04757                 while (ok && pNode && pNode!=pVTNCopy)
04758                 {
04759                     if (pNode->IsRenderable())
04760                         ok = ((NodeRenderable*)pNode)->CopyComponentData(InternalClipboard::Instance(), pDoc);
04761 
04762                     pNode = pNode->FindNextDepthFirst(pVTNCopy);
04763                 }
04764                 if (!ok)
04765                     pDoc->AbortComponentCopy(); // Cancel all data which has been copied
04766             }
04767 
04768 #ifdef PHIL_ATTRS_FIX
04769             // Apply any missing attributes to this node (leaving original copies alone)
04770             if (ok) ok = pCaret->DoApplyAttrsTo(pUndoOp, pVTNCopy, FALSE);
04771 #endif
04772 
04773             if (pFirstPastedVTN==NULL)
04774                 pFirstPastedVTN = pVTNCopy;
04775         }
04776         pSrcVTN = pSrcVTN->FindNextVTNInLine();
04777     }
04778     if (ok) ok = pFirstSrcLine->DoFactorOutCommonChildAttributes(NULL,TRUE,&NonLineLevelAttrs);
04779     if (ok) ok = pCaretLine->DoFactorOutCommonChildAttributes(pUndoOp,TRUE,&NonLineLevelAttrs);
04780 
04781     // then move all nodes from caret to last VTN on line to a new line after the caret line
04782     // - Localise/FactorOut to/from chars local as attrs on story unchanged (only moving nodes)
04783     // - Localise/FactorOut to/from chars includes LineLevelAttrs as new line needs them
04784     TextLine* pNewLine = NULL;
04785     if (ok) pNewLine = new TextLine;
04786     if (ok) ok = (pNewLine!=NULL);
04787     if (ok) ok = pNewLine->DoInsertNewNode(pUndoOp,pCaretLine,NEXT);
04788     if (ok) ok = pCaretLine->DoLocaliseCommonAttributes(pUndoOp,FALSE,FALSE,NULL);
04789     if (ok) ok = pCaret->DoMoveNodes(pUndoOp,pCaretLineLastVTN,pNewLine,FIRSTCHILD);
04790     if (ok) ok = pCaretLine->DoFactorOutCommonChildAttributes(pUndoOp,FALSE,NULL);
04791     if (ok) ok = pNewLine->DoFactorOutCommonChildAttributes(pUndoOp,FALSE,NULL);
04792     pCaretLine = pNewLine;
04793 
04794     // insert all remaining lines to be pasted before the (new) caret line
04795     // - Localise/FactorOut to/from lines global as attrs on whole story may change (inc LLA)
04796     // - Localise/FactorOut to/from lines on clipboard not done undoably
04797     TextLine* pSrcLine = pFirstSrcLine->FindNextLine();
04798     if (pSrcLine!=NULL)
04799     {
04800         if (ok) ok = pFocusStory->DoLocaliseCommonAttributes(pUndoOp,FALSE,TRUE,NULL);
04801         if (ok) ok = pPasteStory->DoLocaliseCommonAttributes(NULL,FALSE,TRUE,NULL);
04802         while (ok && pSrcLine!=NULL)
04803         {
04804             TextLine* pLineCopy = NULL;
04805             CALL_WITH_FAIL(pSrcLine->NodeCopy((Node**)&pLineCopy), pUndoOp, ok);
04806             pLineCopy->ClearSelection(TRUE);        // ensure new node not selected
04807 
04808 #ifndef PHIL_ATTRS_FIX
04809             CaretNode* pCaret = ((TextLine*)pLineCopy)->FindCaret();
04810             if (pCaret!=NULL)   // remove any caret pasted
04811             {
04812                 pCaret->CascadeDelete();
04813                 delete pCaret;
04814             }
04815             if (ok) ok = pLineCopy->DoInsertNewNode(pUndoOp,pCaretLine,PREV);
04816 #else
04817             CaretNode* pCopyCaret = ((TextLine*)pLineCopy)->FindCaret();
04818             if (pCopyCaret!=NULL)   // remove any caret pasted
04819             {
04820                 pCopyCaret->CascadeDelete();
04821                 delete pCopyCaret;
04822                 pCopyCaret = NULL;
04823             }
04824             if (ok) ok = pLineCopy->DoInsertNewNode(pUndoOp,pCaretLine,PREV);
04825 
04826             // Apply any missing attributes to the nodes in this line (leaving original copies alone)
04827             VisibleTextNode* pLineNode = pLineCopy->FindFirstVTN();
04828             if (ok) ok = pLineCopy->DoLocaliseCommonAttributes(pUndoOp,FALSE,FALSE,NULL);
04829             if (ok) ok = pCaretLine->DoLocaliseCommonAttributes(pUndoOp,FALSE,FALSE,NULL);
04830             while (ok && pLineNode)
04831             {
04832                 if (ok) ok = pCaret->DoApplyAttrsTo(pUndoOp, pLineNode, FALSE);
04833                 pLineNode = pLineNode->FindNextVTNInLine();
04834             }
04835             if (ok) ok = pCaretLine->DoFactorOutCommonChildAttributes(pUndoOp,FALSE,NULL);
04836             if (ok) ok = pLineCopy->DoFactorOutCommonChildAttributes(pUndoOp,FALSE,NULL);
04837 #endif
04838             pSrcLine = pSrcLine->FindNextLine();
04839         }
04840         if (ok) ok = pFocusStory->DoFactorOutCommonChildAttributes(pUndoOp,TRUE,NULL);
04841         if (ok) ok = pPasteStory->DoFactorOutCommonChildAttributes(NULL,TRUE,NULL);
04842     }
04843 
04844     // if last EOL pasted virtual, delete it and append (new) caret line to prev line
04845     // - Localise/FactorOut to/from chars local as attrs on story unchanged (only moving nodes)
04846     // - Localise/FactorOut to/from chars excludes LineLevelAttrs ensuring LLA merged correctly
04847     if (ok)
04848     {
04849         TextLine* pLastPastedLine = pCaretLine->FindPrevLine();
04850         ERROR2IF(pLastPastedLine==NULL,FALSE,"OpTextPaste::DoPasteText() - could not find last line pasted!");
04851         EOLNode* pLastPastedEOL = pLastPastedLine->FindEOLNode();
04852         ERROR2IF(pLastPastedEOL==NULL,FALSE,"OpTextPaste::DoPasteText() - story to paste has no final EOL");
04853         pLastPastedVTN = pLastPastedEOL;
04854         if (pLastPastedEOL->IsVirtual())
04855         {
04856             pLastPastedVTN = pLastPastedVTN->FindPrevVTNInStory();
04857             ERROR2IF(pLastPastedVTN==NULL,FALSE,"OpTextPaste::DoPasteText() - no chars on pasted line which ended in a virtual EOL");
04858             if (ok) ok = pLastPastedLine->DoLocaliseCommonAttributes(pUndoOp,FALSE,FALSE,&NonLineLevelAttrs);
04859             if (ok) ok =      pCaretLine->DoLocaliseCommonAttributes(pUndoOp,FALSE,FALSE,&NonLineLevelAttrs);
04860             if (ok) ok = pLastPastedEOL->DoHideNode(pUndoOp);
04861             if (ok) ok = pCaret->DoMoveNodes(pUndoOp,pCaretLineLastVTN,pLastPastedLine,LASTCHILD);
04862             if (ok) ok = pCaretLine->DoHideNode(pUndoOp);   // delete the empty line
04863             if (ok) ok = pLastPastedLine->DoFactorOutCommonChildAttributes(pUndoOp,FALSE,&NonLineLevelAttrs);
04864         }
04865     }
04866 
04867     // create a range of nodes which have been pasted
04868     if (ok)
04869     {
04870         Range r(pFirstPastedVTN, pLastPastedVTN, RangeControl(TRUE,TRUE,FALSE));
04871         *pPasteRange = r;
04872     }
04873     return ok;
04874 }
04875 
04876 
04878 // OpTogglePrintTextAsShapes
04879 
04880 /********************************************************************************************
04881 >   BOOL OpTogglePrintTextAsShapes::Init()
04882 
04883     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
04884     Created:    25/01/95
04885     Returns:    TRUE if the op is correctly registered.
04886     Purpose:    Registers this op
04887 ********************************************************************************************/
04888 
04889 BOOL OpTogglePrintTextAsShapes::Init()
04890 {
04891     return RegisterOpDescriptor(0, 
04892                                 _R(IDS_OPTOGGLEPRINTTEXTASCURVES),
04893                                 CC_RUNTIME_CLASS(OpTogglePrintTextAsShapes),
04894                                 OPTOKEN_TOGGLEPRINTASSHAPES, 
04895                                 OpTogglePrintTextAsShapes::GetState);
04896 }
04897 
04898 
04899 /********************************************************************************************
04900 >   OpState OpTogglePrintTextAsShapes::GetState(String_256*, OpDescriptor*)
04901 
04902     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
04903     Created:    31/05/95
04904     Inputs:     Unused
04905     Returns:    Returns the state of the operation, greying and ticking
04906     Purpose:    The state of the operation
04907 ********************************************************************************************/
04908 
04909 OpState OpTogglePrintTextAsShapes::GetState(String_256*, OpDescriptor*)
04910 {
04911     OpState os;
04912 
04913     SelRange* pSelection = GetApplication()->FindSelection();
04914     TextStory* pStory = (TextStory*)pSelection->FindFirstObject(CC_RUNTIME_CLASS(TextStory));
04915 
04916     if (pStory != NULL)
04917     {
04918         os.Greyed = FALSE;
04919         os.Ticked = pStory->IsPrintingAsShapes();
04920     }
04921     else
04922     {
04923         os.Greyed = TRUE;
04924         os.Ticked = FALSE;
04925     }
04926 
04927     return os;
04928 }
04929 
04930 
04931 /********************************************************************************************
04932 >   virtual void OpTogglePrintTextAsShapes::Do(OpDescriptor*)
04933 
04934     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
04935     Created:    31/05/95
04936     Inputs:     Unused
04937     Purpose:    Toggles the state of PrintAsShapes flag of the first selected TextStory, and
04938                 sets the state of the remaining selected stories to the same.
04939 ********************************************************************************************/
04940 
04941 void OpTogglePrintTextAsShapes::Do(OpDescriptor*)
04942 {
04943     SelRange* pSelection = GetApplication()->FindSelection();
04944     TextStory* pStory = (TextStory*)pSelection->FindFirstObject(CC_RUNTIME_CLASS(TextStory));
04945 
04946     if (pStory != NULL)
04947     {
04948         BOOL NewState = !pStory->IsPrintingAsShapes();
04949 
04950         while (pStory != NULL)
04951         {
04952             pStory->SetPrintingAsShapes(NewState);
04953 
04954             pStory = (TextStory*)pSelection->FindNextObject(CC_RUNTIME_CLASS(TextStory), pStory);
04955         }
04956     }
04957 }
04958 
04959 
04960 /********************************************************************************************
04961 >   OpDragStoryIndent::OpDragStoryIndent()
04962 
04963     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
04964     Created:    18/12/95
04965     Purpose:    Constructor - inits member variables
04966 ********************************************************************************************/
04967 
04968 OpDragStoryIndent::OpDragStoryIndent()
04969 {
04970     mpStartSpread = NULL;
04971     mpDragStory = NULL;
04972     mpStoryPath = NULL;
04973 }
04974 
04975 
04976 /********************************************************************************************
04977 >   OpDragStoryIndent::~OpDragStoryIndent()
04978 
04979     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
04980     Created:    18/12/95
04981     Purpose:    Destructor
04982 ********************************************************************************************/
04983 
04984 OpDragStoryIndent::~OpDragStoryIndent()
04985 {
04986 
04987 }
04988 
04989 
04990 /********************************************************************************************
04991 >   OpState OpDragStoryIndent::GetState(String_256* Description, OpDescriptor*)
04992 
04993     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
04994     Created:    18/12/95
04995     Inputs:     unused
04996     Returns:    Current state of the operation
04997     Purpose:    Get state function.  Never ticked, Never greyed.
04998 ********************************************************************************************/
04999 
05000 OpState OpDragStoryIndent::GetState(String_256* Description, OpDescriptor*)
05001 {
05002     OpState apple;
05003     return apple;
05004 }
05005 
05006 
05007 /********************************************************************************************
05008 >   BOOL OpDragStoryIndent::DoDrag( DocCoord Anchor, Spread* pSpread, ClickModifiers ClickMods, TextStory* pStory)
05009 
05010     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
05011     Created:    18/12/95
05012     Inputs:     Anchor - the start point of the drag
05013                 pSpread - the spread Anchor is on
05014                 ClickMods - the click modifiers at the start of the drag
05015                 pStory - pointer to the text story to edit.
05016     Returns:    TRUE if the drag started ok, FALSE if an error occured
05017     Purpose:    Starts a drag operation to move the path indent position for a text story
05018 ********************************************************************************************/
05019 BOOL OpDragStoryIndent::DoDrag( DocCoord Anchor, Spread* pSpread, ClickModifiers ClickMods, TextStory* pStory)
05020 {
05021     ERROR2IF((pSpread == NULL) || (pStory == NULL), FALSE, "NULL entry param");
05022     ERROR3IF(pSpread != pStory->FindParentSpread(), "Spread pointer didn't point to the spread the story was on. Is this bad?");
05023     BOOL ok = TRUE;
05024 
05025     // Record the initial parameters for later use
05026     mStartPoint = Anchor;
05027     mpStartSpread = pSpread;
05028     mpDragStory = pStory;
05029     mpStoryPath = pStory->GetTextPath();
05030 
05031     // Get the initial indent along the line
05032     if (ok)
05033     {
05034         SetCurrentPos(mStartPoint, ClickMods);
05035         mStartPoint = mCurrentBlobPos;
05036     }
05037 
05038     // Put the drag blobs on for the first time
05039     DocRect Empty;
05040     Empty.MakeEmpty();
05041     RenderDragBlobs(Empty, mpStartSpread, FALSE);
05042     
05043     // Start the drag going
05044     if (ok)
05045         ok = StartDrag( DRAGTYPE_AUTOSCROLL );
05046 
05047     return ok;
05048 }
05049 
05050 
05051 /********************************************************************************************
05052 >   virtual void OpDragStoryIndent::DragPointerMove( DocCoord PointerPos, ClickModifiers ClickMods, Spread *pSpread, BOOL bSolidDrag)
05053 
05054     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
05055     Created:    18/12/95
05056     Inputs:     PointerPos - the new mouse position
05057                 ClickMods - the current click modifiers
05058                 pSpread - the spread the mouse position is on
05059     Purpose:    Starts a drag operation to move the path indent position for a text story
05060 ********************************************************************************************/
05061 void OpDragStoryIndent::DragPointerMove( DocCoord PointerPos, ClickModifiers ClickMods, Spread *pSpread, BOOL bSolidDrag)
05062 {
05063     // Salary check
05064     if ((mpStartSpread == NULL) || (mpDragStory == NULL))
05065     {
05066         ERROR3("A pointer was NULL");
05067         return;
05068     }
05069 
05070     // If drag has moved onto a different spread, convert the coord to be relative to the original spread.
05071     if (pSpread != mpStartSpread)
05072         PointerPos = MakeRelativeToSpread(mpStartSpread, pSpread, PointerPos);
05073 
05074     // Take the previous blobs off
05075     DocRect Empty;
05076     Empty.MakeEmpty();
05077     RenderDraggingBlobs(Empty, mpStartSpread);
05078 
05079     // Get the new distance along the path
05080     SetCurrentPos(PointerPos, ClickMods);
05081 
05082     // Put the blob back on
05083     RenderDraggingBlobs(Empty, mpStartSpread);
05084 
05085     // Set the status line to be helpful
05086     if (mpStoryPath==NULL)
05087     {
05088         String_256 HelpString(_R(IDS_TEXTTOOL_DRAGSTORYHANDLE));
05089         GetApplication()->UpdateStatusBarText(&HelpString);
05090     }
05091 }
05092 
05093 
05094 /********************************************************************************************
05095 >   virtual void OpDragStoryIndent::DragFinished( DocCoord PointerPos, ClickModifiers ClickMods, Spread *pSpread, BOOL Success, BOOL bSolidDrag)
05096 
05097     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
05098     Created:    18/12/95
05099     Inputs:     PointerPos - the mouse position at the end of the drag
05100                 ClickMods - the current click modifiers
05101                 pSpread - the spread the mouse position is on
05102                 Success - FALSE if the drag was aborted by the user
05103     Purpose:    Completes the drag operation, stores the new path indent in the story
05104 ********************************************************************************************/
05105 void OpDragStoryIndent::DragFinished( DocCoord PointerPos, ClickModifiers ClickMods, Spread *pSpread, BOOL Success, BOOL bSolidDrag)
05106 {
05107     BOOL ok = TRUE;
05108 
05109     // Take the drag blob off
05110     DocRect Empty;
05111     Empty.MakeEmpty();
05112     RenderDragBlobs(Empty, mpStartSpread, bSolidDrag);
05113 
05114     BeginSlowJob();
05115     EndDrag();
05116 
05117     if (Success)
05118     {
05119         if (ok)
05120             ok = DoStartTextOp(mpDragStory, TRUE, TRUE);
05121 
05122         // Ask the story if it minds being altered (also does reformatting for us)
05123         if (ok)
05124         {
05125             ObjChangeParam ObjInsert(OBJCHANGE_STARTING, ObjChangeFlags(), NULL, this);
05126             ok = mpDragStory->AllowOp(&ObjInsert);
05127         }
05128 
05129         // Store the current indent in undo history
05130         if (ok)
05131             ok = StorePathIndentAction::DoStoreIndent(this, &UndoActions, mpDragStory);
05132 
05133         if (ok)
05134             ok = DoIndentChange();
05135 
05136         GetApplication()->FindSelection()->Update();
05137 
05138         // Tell the story we have finished fiddling with it (reformats for us)
05139         if (ok)
05140         {
05141             ObjChangeParam ObjChange(OBJCHANGE_FINISHED, ObjChangeFlags(), NULL, this);
05142             ok = UpdateChangedNodes(&ObjChange);
05143         }
05144     }
05145         
05146     if (!ok)
05147         FailAndExecute();
05148     
05149     End();
05150 }
05151 
05152 
05153 /********************************************************************************************
05154 >   virtual void OpDragStoryIndent::RenderDragBlobs(DocRect ClipRect, Spread* pSpread, BOOL bSolidDrag)
05155 
05156     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
05157     Created:    18/12/95
05158     Inputs:     ClipRect - a clipping rectangle
05159                 pSpread - the spread to render on
05160     Purpose:    Renders the EOR drag blobs, either putting them on or taking them off.
05161                 Also renders the story's object blobs
05162 ********************************************************************************************/
05163 void OpDragStoryIndent::RenderDragBlobs(DocRect ClipRect, Spread* pSpread, BOOL bSolidDrag)
05164 {
05165     DocCoord OtherIndent = GetOtherIndent();
05166         
05167     if (ClipRect.IsEmpty())
05168         TextToolBlobPosList::RenderBlob(pSpread, NULL, OtherIndent);
05169     else
05170         TextToolBlobPosList::RenderBlob(pSpread, &ClipRect, OtherIndent);
05171 
05172     RenderDraggingBlobs(ClipRect, pSpread);
05173 }
05174 
05175 
05176 /********************************************************************************************
05177 >   void OpDragStoryIndent::RenderDraggingBlobs(DocRect ClipRect, Spread* pSpread)
05178 
05179     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
05180     Created:    18/12/95
05181     Inputs:     ClipRect - a clipping rectangle
05182                 pSpread - the spread to render on
05183     Outputs:    -
05184     Returns:    -
05185     Purpose:    Renders the EOR drag blob for the margin being dragged.
05186 ********************************************************************************************/
05187 void OpDragStoryIndent::RenderDraggingBlobs(DocRect ClipRect, Spread* pSpread)
05188 {
05189     if (ClipRect.IsEmpty())
05190         TextToolBlobPosList::RenderBlob(pSpread, NULL, mCurrentBlobPos);
05191     else
05192         TextToolBlobPosList::RenderBlob(pSpread, &ClipRect, mCurrentBlobPos);
05193 
05194     // Render a line connecting the blobs if not on a path
05195     if (mpStoryPath==NULL)
05196     {
05197         DocRect* pClipper = &ClipRect;
05198         if (ClipRect.IsEmpty())
05199             pClipper = NULL;
05200     
05201         RenderRegion* pRegion = DocView::RenderOnTop(pClipper, pSpread, ClippedEOR);
05202         while (pRegion != NULL)
05203         {
05204             // Draw a Cross Hair
05205             pRegion->SetLineColour(COLOUR_XOREDIT);
05206             pRegion->SetFillColour(COLOUR_TRANS);
05207             pRegion->DrawLine(mCurrentBlobPos, GetOtherIndent());
05208 
05209             // Get the next region in the list
05210             pRegion = DocView::GetNextOnTop(pClipper);
05211         }
05212     }
05213 }
05214 
05215 
05216 
05217 /********************************************************************************************
05218 >   BOOL OpDragStoryIndent::GetLeftHandLength(MILLIPOINT* pResult)
05219 
05220     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
05221     Created:    2/9/96
05222     Inputs:     -
05223     Outputs:    pResult - Length along the path from the start
05224     Returns:    TRUE/FALSE for success/failure
05225     Purpose:    Gets the length along the path from the start of the path to the current pos
05226 ********************************************************************************************/
05227 BOOL OpDragStoryIndent::GetLeftHandLength(MILLIPOINT* pResult)
05228 {
05229     return GetLeftHandLength(mCurrentBlobPos, mpStoryPath, pResult);
05230 }
05231 
05232 
05233     
05234 /********************************************************************************************
05235 >   BOOL OpDragStoryIndent::GetRightHandLength(MILLIPOINT* pResult)
05236 
05237     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
05238     Created:    2/9/96
05239     Inputs:     -
05240     Outputs:    TRUE/FALSE for success/failure
05241     Returns:    Length along the path from the end
05242     Purpose:    Gets the length along the path from the end of the path to the current pos
05243 ********************************************************************************************/
05244 BOOL OpDragStoryIndent::GetRightHandLength(MILLIPOINT* pResult)
05245 {
05246     return GetRightHandLength(mCurrentBlobPos, mpStoryPath, pResult);
05247 }
05248 
05249 
05250 
05251 /********************************************************************************************
05252 >   BOOL OpDragStoryIndent::GetLeftHandLength(DocCoord Coord, NodePath* pPath, MILLIPOINT* pResult)
05253 
05254     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
05255     Created:    2/9/96
05256     Inputs:     Coord - coord of point
05257                 pPath - pointer to a path
05258     Outputs:    pResult - Length along the path from the start to Coord
05259     Returns:    TRUE/FALSE for success/failure
05260     Purpose:    Gets the length along the path from the start of the path to the given point
05261 ********************************************************************************************/
05262 BOOL OpDragStoryIndent::GetLeftHandLength(DocCoord Coord, NodePath* pPath, MILLIPOINT* pResult)
05263 {
05264     ERROR2IF(pResult==NULL, FALSE, "NULL output parameter");
05265     *pResult = 0;
05266     ERROR2IF(pPath==NULL, FALSE, "NULL path pointer");
05267 
05268     return pPath->InkPath.GetDistanceToPoint(Coord, pResult);
05269 }
05270 
05271 
05272     
05273 /********************************************************************************************
05274 >   BOOL OpDragStoryIndent::GetRightHandLength(DocCoord Coord, NodePath* pPath, MILLIPOINT* pResult)
05275 
05276     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
05277     Created:    2/9/96
05278     Inputs:     Coord - coord of point
05279                 pPath - pointer to a path
05280     Outputs:    pResult - Length along the path from the end to Coord
05281     Returns:    TRUE/FALSE for success/failure
05282     Purpose:    Gets the length along the path from the end of the path to the given point
05283 ********************************************************************************************/
05284 BOOL OpDragStoryIndent::GetRightHandLength(DocCoord Coord, NodePath* pPath, MILLIPOINT* pResult)
05285 {
05286     ERROR2IF(pResult==NULL, FALSE, "NULL output parameter");
05287     *pResult = 0;
05288     ERROR2IF(pPath==NULL, FALSE, "NULL path pointer");
05289 
05290     // Get the length of the whole path
05291     MILLIPOINT TotalLength = (MILLIPOINT)(pPath->InkPath.GetPathLength());
05292 
05293     // Get the distance from the start of the path to where the drag ended
05294     MILLIPOINT Distance = 0;
05295     BOOL ok = pPath->InkPath.GetDistanceToPoint(Coord, &Distance);
05296 
05297     *pResult = TotalLength - Distance;
05298         
05299     return ok;
05300 }
05301 
05302 
05303 
05304 /********************************************************************************************
05305 >   BOOL OpDragStoryPathLeftIndent::Init()
05306 
05307     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
05308     Created:    19/8/96
05309     Returns:    TRUE for succesful init, FALSE if init failed
05310     Purpose:    Initialises the operation; registers its op descriptor
05311 ********************************************************************************************/
05312 BOOL OpDragStoryPathLeftIndent::Init()
05313 {
05314     return (RegisterOpDescriptor(   0,
05315                                     _R(IDS_OPTEXT_DRAGINDENT),
05316                                     CC_RUNTIME_CLASS(OpDragStoryPathLeftIndent),
05317                                     OPTOKEN_TEXTDRAGLEFTPATHINDENT,
05318                                     OpDragStoryIndent::GetState,
05319                                     0,                          // help ID 
05320                                     0,                          // Bubble help
05321                                     0,
05322                                     0,
05323                                     SYSTEMBAR_ILLEGAL,          // For now !
05324                                     TRUE,                       // Receive messages
05325                                     FALSE,
05326                                     FALSE,
05327                                     0,
05328                                     GREY_WHEN_NO_CURRENT_DOC | GREY_WHEN_NO_SELECTION | DONT_GREY_WHEN_SELECT_INSIDE
05329            )); 
05330 }
05331 
05332 
05333 /********************************************************************************************
05334 >   BOOL OpDragStoryPathRightIndent::Init()
05335 
05336     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
05337     Created:    19/8/96
05338     Returns:    TRUE for succesful init, FALSE if init failed
05339     Purpose:    Initialises the operation; registers its op descriptor
05340 ********************************************************************************************/
05341 BOOL OpDragStoryPathRightIndent::Init()
05342 {
05343     return (RegisterOpDescriptor(   0,
05344                                     _R(IDS_OPTEXT_DRAGINDENT),
05345                                     CC_RUNTIME_CLASS(OpDragStoryPathRightIndent),
05346                                     OPTOKEN_TEXTDRAGRIGHTPATHINDENT,
05347                                     OpDragStoryIndent::GetState,
05348                                     0,                          // help ID 
05349                                     0,                          // Bubble help
05350                                     0,
05351                                     0,
05352                                     SYSTEMBAR_ILLEGAL,          // For now !
05353                                     TRUE,                       // Receive messages
05354                                     FALSE,
05355                                     FALSE,
05356                                     0,
05357                                     GREY_WHEN_NO_CURRENT_DOC | GREY_WHEN_NO_SELECTION | DONT_GREY_WHEN_SELECT_INSIDE
05358            )); 
05359 }
05360 
05361 
05362 /********************************************************************************************
05363 >   BOOL OpDragStoryNonPathLeftIndent::Init()
05364 
05365     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
05366     Created:    19/8/96
05367     Returns:    TRUE for succesful init, FALSE if init failed
05368     Purpose:    Initialises the operation; registers its op descriptor
05369 ********************************************************************************************/
05370 BOOL OpDragStoryNonPathLeftIndent::Init()
05371 {
05372     return (RegisterOpDescriptor(   0,
05373                                     _R(IDS_OPTEXT_DRAGINDENT),
05374                                     CC_RUNTIME_CLASS(OpDragStoryNonPathLeftIndent),
05375                                     OPTOKEN_TEXTDRAGLEFTNONPATHINDENT,
05376                                     OpDragStoryIndent::GetState,
05377                                     0,                          // help ID 
05378                                     0,                          // Bubble help
05379                                     0,
05380                                     0,
05381                                     SYSTEMBAR_ILLEGAL,          // For now !
05382                                     TRUE,                       // Receive messages
05383                                     FALSE,
05384                                     FALSE,
05385                                     0,
05386                                     GREY_WHEN_NO_CURRENT_DOC | GREY_WHEN_NO_SELECTION | DONT_GREY_WHEN_SELECT_INSIDE
05387            )); 
05388 }
05389 
05390 
05391 /********************************************************************************************
05392 >   BOOL OpDragStoryNonPathRightIndent::Init()
05393 
05394     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
05395     Created:    19/8/96
05396     Returns:    TRUE for succesful init, FALSE if init failed
05397     Purpose:    Initialises the operation; registers its op descriptor
05398 ********************************************************************************************/
05399 BOOL OpDragStoryNonPathRightIndent::Init()
05400 {
05401     return (RegisterOpDescriptor(   0,
05402                                     _R(IDS_OPTEXT_DRAGINDENT),
05403                                     CC_RUNTIME_CLASS(OpDragStoryNonPathRightIndent),
05404                                     OPTOKEN_TEXTDRAGRIGHTNONPATHINDENT,
05405                                     OpDragStoryIndent::GetState,
05406                                     0,                          // help ID 
05407                                     0,                          // Bubble help
05408                                     0,
05409                                     0,
05410                                     SYSTEMBAR_ILLEGAL,          // For now !
05411                                     TRUE,                       // Receive messages
05412                                     FALSE,
05413                                     FALSE,
05414                                     0,
05415                                     GREY_WHEN_NO_CURRENT_DOC | GREY_WHEN_NO_SELECTION | DONT_GREY_WHEN_SELECT_INSIDE
05416            )); 
05417 }
05418 
05419 
05420 
05421 /********************************************************************************************
05422 >   BOOL OpDragStoryPathLeftIndent::DoIndentChange()
05423 
05424     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
05425     Created:    19/8/96
05426     Returns:    TRUE/FALSE for success/failure
05427     Purpose:    Called to perform the change to the story required at the completion of the
05428                 blob drag
05429 ********************************************************************************************/
05430 BOOL OpDragStoryPathLeftIndent::DoIndentChange()
05431 {
05432     ERROR2IF(mpDragStory==NULL, FALSE, "NULL story pointer");
05433 
05434     MILLIPOINT Distance = 0;
05435     BOOL ok = TRUE;
05436 
05437     if (mpDragStory->IsTextOnPathReversed())
05438         ok = GetRightHandLength(&Distance);
05439     else
05440         ok = GetLeftHandLength(&Distance);
05441 
05442     if (ok)
05443         mpDragStory->SetLeftIndent(Distance);
05444 
05445     return ok;
05446 }
05447 
05448 
05449 /********************************************************************************************
05450 >   BOOL OpDragStoryPathRightIndent::DoIndentChange()
05451 
05452     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
05453     Created:    19/8/96
05454     Returns:    TRUE/FALSE for success/failure
05455     Purpose:    Called to perform the change to the story required at the completion of the
05456                 blob drag
05457 ********************************************************************************************/
05458 BOOL OpDragStoryPathRightIndent::DoIndentChange()
05459 {
05460     ERROR2IF(mpDragStory==NULL, FALSE, "NULL story pointer");
05461 
05462     MILLIPOINT Distance = 0;
05463     BOOL ok = TRUE;
05464 
05465     if (mpDragStory->IsTextOnPathReversed())
05466         ok = GetLeftHandLength(&Distance);
05467     else
05468         ok = GetRightHandLength(&Distance);
05469 
05470     if (ok)
05471         mpDragStory->SetRightIndent(Distance);
05472 
05473     return ok;
05474 }
05475 
05476 
05477 /********************************************************************************************
05478 >   BOOL OpDragStoryNonPathLeftIndent::DoIndentChange()
05479 
05480     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
05481     Created:    19/8/96
05482     Returns:    TRUE/FALSE for success/failure
05483     Purpose:    Called to perform the change to the story required at the completion of the
05484                 blob drag
05485 ********************************************************************************************/
05486 BOOL OpDragStoryNonPathLeftIndent::DoIndentChange()
05487 {
05488     // Set the story width
05489     DocCoord LeftIndentPos = GetThisIndent();
05490     DocCoord RightIndentPos = GetOtherIndent();
05491     mpDragStory->SetStoryWidth((INT32)mCurrentBlobPos.Distance(RightIndentPos));
05492 
05493     // We also want to rotate about the right indent position
05494     double OldAngle = atan2((double)(LeftIndentPos.y-RightIndentPos.y), (double)(LeftIndentPos.x-RightIndentPos.x));
05495     double NewAngle = atan2((double)(mCurrentBlobPos.y-RightIndentPos.y), (double)(mCurrentBlobPos.x-RightIndentPos.x));
05496     ANGLE RotAngle = (NewAngle-OldAngle)*(180/PI);
05497     Matrix RMatrix(RightIndentPos, RotAngle);
05498     Trans2DMatrix* pTransMat = new Trans2DMatrix(RMatrix);
05499     BOOL ok = pTransMat!=NULL;
05500     if (ok)
05501         ok = DoTransformNode(mpDragStory, pTransMat);
05502 
05503     // Now we need to translate the story so the left blob is where the user ended the drag
05504     if (ok)
05505     {
05506         LeftIndentPos = GetThisIndent();
05507         DocCoord TransOffset = mCurrentBlobPos - LeftIndentPos;
05508         Matrix TMatrix(TransOffset.x, TransOffset.y);
05509         Trans2DMatrix* pTransMat = new Trans2DMatrix(TMatrix);
05510         ok = pTransMat!=NULL;
05511         if (ok)
05512             ok = DoTransformNode(mpDragStory, pTransMat);
05513     }
05514 
05515     return ok;
05516 }
05517 
05518 
05519 /********************************************************************************************
05520 >   BOOL OpDragStoryNonPathRightIndent::DoIndentChange()
05521 
05522     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
05523     Created:    19/8/96
05524     Returns:    TRUE/FALSE for success/failure
05525     Purpose:    Called to perform the change to the story required at the completion of the
05526                 blob drag
05527 ********************************************************************************************/
05528 BOOL OpDragStoryNonPathRightIndent::DoIndentChange()
05529 {
05530     // Set the story width
05531     mpDragStory->SetStoryWidth((INT32)mCurrentBlobPos.Distance(GetOtherIndent()));
05532 
05533     // Rotate the story about the left indent pos.
05534     DocCoord LeftIndentPos = GetOtherIndent();
05535     DocCoord OldRightIndentPos = GetThisIndent();
05536 
05537     double OldAngle = atan2((double)(OldRightIndentPos.y-LeftIndentPos.y), (double)(OldRightIndentPos.x-LeftIndentPos.x));
05538     double NewAngle = atan2((double)(mCurrentBlobPos.y-LeftIndentPos.y), (double)(mCurrentBlobPos.x-LeftIndentPos.x));
05539     ANGLE RotAngle = (NewAngle-OldAngle)*(180/PI);
05540 
05541     Matrix RotMatrix(LeftIndentPos, RotAngle);
05542 
05543     // Must not create a transform matrix on the stack as the operation keeps a pointer to it!
05544     Trans2DMatrix* pTransMat = new Trans2DMatrix(RotMatrix);
05545 
05546     if (pTransMat == NULL)
05547         return FALSE;
05548     else
05549         return DoTransformNode(mpDragStory, pTransMat);
05550 }
05551 
05552 
05553 
05554 /********************************************************************************************
05555 >   DocCoord OpDragStoryPathLeftIndent::GetThisIndent()
05556 
05557     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
05558     Created:    19/8/96
05559     Returns:    The coordinate of the indent begin dragged (where it was before the drag started)
05560     Purpose:    Gets the original position of the dragged indent
05561 ********************************************************************************************/
05562 DocCoord OpDragStoryPathLeftIndent::GetThisIndent()
05563 {
05564     return mpDragStory->GetLeftIndentPos();
05565 }
05566 
05567 
05568 /********************************************************************************************
05569 >   DocCoord OpDragStoryPathRightIndent::GetThisIndent()
05570 
05571     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
05572     Created:    19/8/96
05573     Returns:    The coordinate of the indent begin dragged (where it was before the drag started)
05574     Purpose:    Gets the original position of the dragged indent
05575 ********************************************************************************************/
05576 DocCoord OpDragStoryPathRightIndent::GetThisIndent()
05577 {
05578     return mpDragStory->GetRightIndentPos();
05579 }
05580 
05581 
05582 /********************************************************************************************
05583 >   DocCoord OpDragStoryNonPathLeftIndent::GetThisIndent()
05584 
05585     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
05586     Created:    19/8/96
05587     Returns:    The coordinate of the indent begin dragged (where it was before the drag started)
05588     Purpose:    Gets the original position of the dragged indent
05589 ********************************************************************************************/
05590 DocCoord OpDragStoryNonPathLeftIndent::GetThisIndent()
05591 {
05592     const Matrix* pStoryMatrix = mpDragStory->GetpStoryMatrix();
05593     DocCoord LeftIndent(0,0);
05594     pStoryMatrix->transform(&LeftIndent);
05595 
05596     return LeftIndent;
05597 }
05598 
05599 
05600 /********************************************************************************************
05601 >   DocCoord OpDragStoryNonPathRightIndent::GetThisIndent()
05602 
05603     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
05604     Created:    19/8/96
05605     Returns:    The coordinate of the indent begin dragged (where it was before the drag started)
05606     Purpose:    Gets the original position of the dragged indent
05607 ********************************************************************************************/
05608 DocCoord OpDragStoryNonPathRightIndent::GetThisIndent()
05609 {
05610     const Matrix* pStoryMatrix = mpDragStory->GetpStoryMatrix();
05611     DocCoord RightIndent(mpDragStory->GetStoryWidth(),0);
05612     pStoryMatrix->transform(&RightIndent);
05613 
05614     return RightIndent;
05615 }
05616 
05617 
05618 
05619 /********************************************************************************************
05620 >   DocCoord OpDragStoryPathLeftIndent::GetOtherIndent()
05621 
05622     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
05623     Created:    19/8/96
05624     Returns:    The coordinate of the 'other' indent, not the one being dragged
05625     Purpose:    Gets the original position of the stationary indent
05626 ********************************************************************************************/
05627 DocCoord OpDragStoryPathLeftIndent::GetOtherIndent()
05628 {
05629     return mpDragStory->GetRightIndentPos();
05630 }
05631 
05632 
05633 /********************************************************************************************
05634 >   DocCoord OpDragStoryPathRightIndent::GetOtherIndent()
05635 
05636     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
05637     Created:    19/8/96
05638     Returns:    The coordinate of the 'other' indent, not the one being dragged
05639     Purpose:    Gets the original position of the stationary indent
05640 ********************************************************************************************/
05641 DocCoord OpDragStoryPathRightIndent::GetOtherIndent()
05642 {
05643     return mpDragStory->GetLeftIndentPos();
05644 }
05645 
05646 
05647 /********************************************************************************************
05648 >   DocCoord OpDragStoryNonPathLeftIndent::GetOtherIndent()
05649 
05650     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
05651     Created:    19/8/96
05652     Returns:    The coordinate of the 'other' indent, not the one being dragged
05653     Purpose:    Gets the original position of the stationary indent
05654 ********************************************************************************************/
05655 DocCoord OpDragStoryNonPathLeftIndent::GetOtherIndent()
05656 {
05657     const Matrix* pStoryMatrix = mpDragStory->GetpStoryMatrix();
05658     DocCoord RightIndent(mpDragStory->GetStoryWidth(),0);
05659     pStoryMatrix->transform(&RightIndent);
05660 
05661     return RightIndent;
05662 }
05663 
05664 
05665 /********************************************************************************************
05666 >   DocCoord OpDragStoryNonPathRightIndent::GetOtherIndent()
05667 
05668     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
05669     Created:    19/8/96
05670     Returns:    The coordinate of the 'other' indent, not the one being dragged
05671     Purpose:    Gets the original position of the stationary indent
05672 ********************************************************************************************/
05673 DocCoord OpDragStoryNonPathRightIndent::GetOtherIndent()
05674 {
05675     const Matrix* pStoryMatrix = mpDragStory->GetpStoryMatrix();
05676     DocCoord LeftIndent(0,0);
05677     pStoryMatrix->transform(&LeftIndent);
05678 
05679     return LeftIndent;
05680 }
05681 
05682 
05683 
05684 /********************************************************************************************
05685 >   DocCoord OpDragStoryPathLeftIndent::InternalConstrain(DocCoord Current, ClickModifiers ClickMods)
05686 
05687     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
05688     Created:    19/8/96
05689     Inputs:     Current - the current mouse position
05690                 ClickMods - the current click modifiers
05691     Returns:    The suitably constrained mouse position
05692     Purpose:    Constrains the drag of the left indent on the path
05693 ********************************************************************************************/
05694 DocCoord OpDragStoryPathLeftIndent::InternalConstrain(DocCoord Current, ClickModifiers ClickMods)
05695 {
05696     ERROR2IF(mpDragStory==NULL || mpStoryPath==NULL, DocCoord(0,0), "NULL pointer");
05697 
05698     DocView::SnapCurrent(mpStartSpread, &Current);
05699 
05700     INT32 NearEl=0;
05701     double mu=0.0;
05702     mpStoryPath->InkPath.SqrDistanceToPoint(Current, &NearEl, &mu);
05703     DocCoord LinePoint = mpStoryPath->InkPath.ClosestPointTo(mu, NearEl);
05704 
05705     // We need to stop the left hand blob going past the right hand one.
05706     MILLIPOINT TotalLineLength = (MILLIPOINT)mpStoryPath->InkPath.GetPathLength();
05707     MILLIPOINT LeftIndentLength = 0;
05708     BOOL ok = TRUE;
05709     if (mpDragStory->IsTextOnPathReversed())
05710         ok = GetRightHandLength(LinePoint, mpStoryPath, &LeftIndentLength);
05711     else
05712         ok = GetLeftHandLength(LinePoint, mpStoryPath, &LeftIndentLength);
05713 
05714     if (((TotalLineLength-LeftIndentLength) < mpDragStory->GetRightIndent()) && ok) 
05715         return mpDragStory->GetRightIndentPos();
05716     else
05717         return LinePoint;
05718 }
05719 
05720 
05721 /********************************************************************************************
05722 >   DocCoord OpDragStoryPathRightIndent::InternalConstrain(DocCoord Current, ClickModifiers ClickMods)
05723 
05724     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
05725     Created:    19/8/96
05726     Inputs:     Current - the current mouse position
05727                 ClickMods - the current click modifiers
05728     Returns:    The suitably constrained mouse position
05729     Purpose:    Constrains the drag of the right indent on the path
05730 ********************************************************************************************/
05731 DocCoord OpDragStoryPathRightIndent::InternalConstrain(DocCoord Current, ClickModifiers ClickMods)
05732 {
05733     DocView::SnapCurrent(mpStartSpread, &Current);
05734     
05735     INT32 NearEl=0;
05736     double mu=0.0;
05737     mpStoryPath->InkPath.SqrDistanceToPoint(Current, &NearEl, &mu);
05738     DocCoord LinePoint = mpStoryPath->InkPath.ClosestPointTo(mu, NearEl);
05739 
05740     // We need to stop the right hand blob going past the left hand one.
05741     MILLIPOINT TotalLineLength = (MILLIPOINT)mpStoryPath->InkPath.GetPathLength();
05742     MILLIPOINT RightIndentLength = 0;
05743     BOOL ok = TRUE;
05744     if (mpDragStory->IsTextOnPathReversed())
05745         ok = GetLeftHandLength(LinePoint, mpStoryPath, &RightIndentLength);
05746     else
05747         ok = GetRightHandLength(LinePoint, mpStoryPath, &RightIndentLength);
05748 
05749     if (((TotalLineLength-RightIndentLength) < mpDragStory->GetLeftIndent()) && ok) 
05750         return mpDragStory->GetLeftIndentPos();
05751     else
05752         return LinePoint;
05753 }
05754 
05755 
05756 /********************************************************************************************
05757 >   DocCoord OpDragStoryNonPathIndent::InternalConstrain(DocCoord Current, ClickModifiers ClickMods)
05758 
05759     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
05760     Created:    19/8/96
05761     Inputs:     Current - the current mouse position
05762                 ClickMods - the current click modifiers
05763     Returns:    The suitably constrained mouse position
05764     Purpose:    Constrains the drag of indents not on a path
05765 ********************************************************************************************/
05766 DocCoord OpDragStoryNonPathIndent::InternalConstrain(DocCoord Current, ClickModifiers ClickMods)
05767 {
05768     // By default drag along the baseline
05769     if (!ClickMods.Adjust && !ClickMods.Constrain)
05770     {
05771         const double OrigLength = GetThisIndent().Distance(GetOtherIndent());
05772         double Hypot = GetOtherIndent().Distance(Current);
05773         double Oppo = DocCoord::DistanceFromLine(GetThisIndent(), GetOtherIndent(), Current);
05774         double Adj = sqrt(Hypot*Hypot - Oppo*Oppo);
05775         double Ratio = 1.0;
05776         if (OrigLength != 0.0)
05777             Ratio = Adj/(OrigLength);
05778 
05779         const DocCoord JoinPoint = GetOtherIndent(); 
05780         const DocCoord OtherJoinEnd = GetThisIndent(); 
05781         const DocCoord cCurrent = Current; 
05782 
05783         if (DocCoord::IsJoinAcute(&JoinPoint, &OtherJoinEnd, &cCurrent))
05784             return DocCoord::PositionPointFromRatio(JoinPoint, OtherJoinEnd, Ratio);
05785         else
05786             return DocCoord::PositionPointFromRatio(JoinPoint, OtherJoinEnd, -Ratio);
05787     }
05788     else
05789     {
05790         // CTRL is angle constrain, // SHIFT is unconstrained
05791         if (ClickMods.Constrain)
05792         {
05793             DocCoord Anchor = GetOtherIndent();
05794             DocView::ConstrainToAngle(Anchor, &Current);
05795         }
05796 
05797         DocView::SnapCurrent(mpStartSpread, &Current);
05798     }
05799 
05800     return Current;
05801 }
05802 
05803 
05804 
05805 /********************************************************************************************
05806 >   BOOL OpDragStoryPathIndent::SetCurrentPos(DocCoord MousePos, ClickModifiers ClickMods)
05807 
05808     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
05809     Created:    19/8/96
05810     Inputs:     MousePos - a coordinate
05811                 ClickMods - the current click modifiers
05812     Outputs:    Member variable mCurrentBlobPos is set from MousePos
05813     Returns:    TRUE/FALSE for success failure
05814     Purpose:    Given a position works out the nearest position on the path to it, and sets
05815                 mCurrentBlobPos to it.
05816 ********************************************************************************************/
05817 BOOL OpDragStoryPathIndent::SetCurrentPos(DocCoord MousePos, ClickModifiers ClickMods)
05818 {
05819     ERROR2IF(mpStoryPath==NULL, FALSE, "NULL story pointer");
05820 
05821     mCurrentBlobPos = InternalConstrain(MousePos, ClickMods);
05822 
05823     return TRUE;
05824 }
05825 
05826 
05827 /********************************************************************************************
05828 >   BOOL OpDragStoryNonPathIndent::SetCurrentPos(DocCoord MousePos, ClickModifiers ClickMods)
05829 
05830     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
05831     Created:    18/12/95
05832     Inputs:     MousePos - a coordinate
05833                 ClickMods - the current click modifiers
05834     Outputs:    Member variable mCurrentBlobPos is set from MousePos
05835     Returns:    TRUE/FALSE for success/failure
05836     Purpose:    Given a position sets mCurrentBlobPos to it.
05837 ********************************************************************************************/
05838 BOOL OpDragStoryNonPathIndent::SetCurrentPos(DocCoord MousePos, ClickModifiers ClickMods)
05839 {
05840     ERROR3IF(mpStoryPath!=NULL, "Story is on a path, you don't want to use this function");
05841 
05842     mCurrentBlobPos = InternalConstrain(MousePos, ClickMods);
05843 
05844     return TRUE;
05845 }
05846 
05847 
05848 
05849 
05850 
05851 
05852 
05853 
05854 
05855 
05856 
05857 
05858 
05859 /********************************************************************************************
05860 >   StorePathIndentAction::StorePathIndentAction()
05861 
05862     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
05863     Created:    18/12/95
05864     Purpose:    Constructor for StorePathIndentAction.  Initialises the member variables  
05865 ********************************************************************************************/
05866 
05867 StorePathIndentAction::StorePathIndentAction()
05868 {
05869     mpTextStory = NULL;
05870     mLeftIndentLength = 0;
05871     mRightIndentLength = 0;
05872     mStoryWidth = 0;
05873 }
05874 
05875 
05876 /********************************************************************************************
05877 >   StorePathIndentAction::~StorePathIndentAction()
05878 
05879     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
05880     Created:    18/12/95
05881     Purpose:    Destructor for StorePathIndentAction.  
05882 ********************************************************************************************/
05883 
05884 StorePathIndentAction::~StorePathIndentAction()
05885 {
05886 }
05887 
05888 
05889 /********************************************************************************************
05890 >   ActionCode StorePathIndentAction::Execute()
05891 
05892     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
05893     Created:    12/18/95
05894     Returns:    ActionCode, either AC_OK, AC_NORECORD or AC_FAIL
05895     Purpose:    This action inserts the opposite StorePathIndentAction for redo purposes, then
05896                 set the indent in the TextStory to the stored value.
05897     Errors:     Checks on pointers
05898 ********************************************************************************************/
05899 
05900 ActionCode StorePathIndentAction::Execute()
05901 {
05902     // Salary checks
05903     ERROR2IF(mpTextStory == NULL, AC_FAIL, "The TextStory pointer is NULL");
05904     
05905     // Create a redo action for this action, which is also a StorePathIndentAction
05906     ActionCode Act;
05907     Act = StorePathIndentAction::Init(pOperation, 
05908                                         pOppositeActLst, 
05909                                         mpTextStory,
05910                                         mpTextStory->GetLeftIndent(),
05911                                         mpTextStory->GetRightIndent(),
05912                                         mpTextStory->GetStoryWidth());
05913 
05914     // Now undo
05915     if (Act != AC_FAIL)
05916     {
05917         mpTextStory->SetLeftIndent(mLeftIndentLength);
05918         mpTextStory->SetRightIndent(mRightIndentLength);
05919         mpTextStory->SetStoryWidth(mStoryWidth);
05920     }
05921 
05922     return Act;
05923 }
05924 
05925 
05926 /********************************************************************************************
05927 >   static ActionCode StorePathIndentAction::Init(Operation* pOp,
05928                                                 ActionList* pActionList,
05929                                                 TextStory* pStory,
05930                                                 MILLIPOINT LeftIndent,
05931                                                 MILLIPOINT RightIndent
05932                                                 MILLIPOINT StoryWidth)
05933 
05934     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
05935     Created:    18/12/95
05936     Inputs:     pOp is the pointer to the operation to which this action belongs
05937                 pActionList is the action list to which this action should be added
05938                 pStory - pointer to the text story to apply this action to
05939                 LeftIndent - the left path indent value
05940                 RightIndent - the right path indent value
05941                 StoryWidth - the width of the story
05942     Returns:    ActionCode, one of AC_OK, AC_NO_RECORD or AC_FAIL
05943     Purpose:    Sets up the action to store a text story path indent
05944     SeeAlso:    Action::Init(), StorePathIndentAction::Execute()
05945 ********************************************************************************************/
05946 
05947 ActionCode StorePathIndentAction::Init(Operation* pOp,
05948                                     ActionList* pActionList,
05949                                     TextStory* pStory,
05950                                     MILLIPOINT LeftIndent,
05951                                     MILLIPOINT RightIndent,
05952                                     MILLIPOINT StoryWidth)
05953 {
05954     // Salary check
05955     ERROR2IF(pStory == NULL, AC_FAIL, "TextStory pointer was NULL");
05956 
05957     // Create the new action
05958     UINT32 ActSize = sizeof(StorePathIndentAction);
05959     StorePathIndentAction* pNewAction = NULL;
05960     ActionCode Ac = Action::Init( pOp, pActionList, ActSize, CC_RUNTIME_CLASS(StorePathIndentAction), (Action**)&pNewAction);
05961 
05962     // Fill in its data
05963     if ((Ac == AC_OK) && (pNewAction != NULL))
05964     {
05965         pNewAction->mpTextStory = pStory;
05966         pNewAction->mLeftIndentLength = LeftIndent;
05967         pNewAction->mRightIndentLength = RightIndent;
05968         pNewAction->mStoryWidth = StoryWidth;
05969     }
05970 
05971     return Ac;
05972 }
05973 
05974 
05975 /********************************************************************************************
05976 >   static BOOL StorePathIndentAction::DoStoreIndent(Operation* pOp,
05977                                                      ActionList* pActionList,
05978                                                      TextStory* pStory)
05979 
05980     Author:     Peter_Arnold (Xara Group Ltd) <camelotdev@xara.com>
05981     Created:    18/12/95
05982     Inputs:     pOp is the pointer to the operation to which this action belongs
05983                 pActionList is the action list to which this action should be added
05984                 pStory - pointer to the text story to apply this action to
05985     Returns:    TRUE/FALSE for success/failure
05986     Purpose:    Call this function to store the current text story path indent value before
05987                 changing it
05988     SeeAlso:    StorePathIndentAction::Init()
05989 ********************************************************************************************/
05990 
05991 BOOL StorePathIndentAction::DoStoreIndent( Operation* pOp, ActionList* pActionList, TextStory* pStory)
05992 {
05993     // Salary check
05994     ERROR2IF(pStory == NULL, FALSE, "The TextStory pointer is NULL");
05995     
05996     // Create an action for this
05997     ActionCode Act;
05998     Act = StorePathIndentAction::Init(pOp, pActionList, pStory, pStory->GetLeftIndent(), pStory->GetRightIndent(), pStory->GetStoryWidth());
05999 
06000     return (Act != AC_FAIL);
06001 }
06002 
06003 
06004 #endif
06005 
06006 
06008 // OpApplyGlobalAffect
06009 
06010 /********************************************************************************************
06011 >   void OpApplyGlobalAffect::DoAffectChange(ObjChangeParam* pObjChange, Document* pSearchDoc=NULL)
06012 
06013     Author:     Mike_Kenny (Xara Group Ltd) <camelotdev@xara.com>
06014     Created:    17/01/96
06015     Inputs:     pObjChange  = a pointer to a user change parameter
06016     Purpose:    Scans through all object nodes of all object trees in all documents, passing
06017                 the user object change parameter to every node it comes accross. The tree
06018                 scans are depth first. This is a useful operation for other ops to derive
06019                 themselves off.
06020 ********************************************************************************************/
06021 
06022 void OpApplyGlobalAffect::DoAffectChange(ObjChangeParam* pObjChange, Document* pSearchDoc)
06023 {
06024     // Get a pointer to the StartDocument 
06025     ERROR3IF(pObjChange == NULL, "DoAffectChange called with a NULL pObjChange param");
06026     if (pObjChange==NULL)
06027         return;
06028 
06029     Document* pDocument = pSearchDoc;
06030     if (!pDocument)
06031         pDocument = (Document*) Camelot.Documents.GetHead();
06032 
06033 #if !defined(EXCLUDE_FROM_RALPH)
06034     // render blobs off (only in selected doc)
06035     if (Camelot.FindSelection()!=NULL)
06036     {
06037         Node* SelNode = Camelot.FindSelection()->FindFirst();
06038         if (SelNode!=NULL)
06039         {
06040             Spread* SelSpread = (Spread*)SelNode->FindParent(CC_RUNTIME_CLASS(Spread));
06041             if (SelSpread!=NULL)
06042                 Camelot.GetBlobManager()->RenderOff(NULL, SelSpread);
06043         }
06044     }
06045 #endif
06046 
06047     // scan through all documents
06048     while (pDocument != NULL)
06049     {
06050         // Get a pointer to first node
06051         Node* pNode = Node::DocFindFirstDepthFirst(pDocument);
06052         // Scan through the tree, on a depth first traversal affecting all nodes.
06053         while (pNode != NULL)
06054         {
06055             AffectNode(pDocument, pNode, pObjChange);
06056             // Move onto the next node in the tree
06057             pNode = pNode->DocFindNextDepthFirst(); 
06058         }
06059         // make sure we update all of this document
06060         pDocument->FlushRedraw();
06061         
06062         if (pSearchDoc==NULL)
06063             pDocument = (Document*) Camelot.Documents.GetNext(pDocument);
06064         else
06065             pDocument = NULL;
06066     }
06067 
06068     // Now for the important bit, make sure we update all the changed nodes.
06069     pObjChange->Define(OBJCHANGE_FINISHED,pObjChange->GetChangeFlags(),NULL,NULL);
06070     UpdateAllChangedNodes(pObjChange);
06071 
06072 #if !defined(EXCLUDE_FROM_RALPH)
06073     // render blobs back on again!
06074     if (Camelot.FindSelection()!=NULL)
06075     {
06076         Node* SelNode = Camelot.FindSelection()->FindFirst();
06077         if (SelNode!=NULL)
06078         {
06079             Spread* SelSpread = (Spread*)SelNode->FindParent(CC_RUNTIME_CLASS(Spread));
06080             if (SelSpread!=NULL)
06081                 Camelot.GetBlobManager()->RenderOff(NULL, SelSpread);
06082         }
06083     }
06084 #endif
06085 }
06086 
06087 
06088 /********************************************************************************************
06089 >   virtual void OpApplyGlobalAffect::AffectNode(Document* pDocument,
06090                                                 Node* pNode,
06091                                                 ObjChangeParam* pObjChange)
06092 
06093     Author:     Mike_Kenny (Xara Group Ltd) <camelotdev@xara.com>
06094     Created:    17/01/96
06095     Inputs:     pDocument   = a pointer to the document currently being scanned
06096                 pNode       = a pointer to the node the scan is at (going up the tree)
06097                 pObjChange  = a pointer to a user change parameter
06098     Purpose:    This function checks for text story nodes. If the entry node is one, the
06099                 node will be reformated and redrawn.
06100 ********************************************************************************************/
06101 
06102 void OpApplyGlobalAffect::AffectNode(Document* pDocument, Node* pNode, ObjChangeParam* pObjChange)
06103 {
06104 }
06105 
06106 
06107 /********************************************************************************************
06108 >   BOOL OpAffectFontChange::Init()
06109 
06110     Author:     Mike_Kenny (Xara Group Ltd) <camelotdev@xara.com>
06111     Created:    17/01/96
06112     Returns:    TRUE if the operation could be successfully initialised 
06113                 FALSE if no more memory could be allocated 
06114     Purpose:    OpApplyGlobalAffect initialiser method
06115     Errors:     ERROR will be called if there was insufficient memory to allocate the 
06116                 operation.
06117 ********************************************************************************************/
06118 
06119 BOOL OpAffectFontChange::Init()
06120 {
06121     return (RegisterOpDescriptor(   0,
06122                                     0,
06123                                     CC_RUNTIME_CLASS(OpAffectFontChange),
06124                                     OPTOKEN_AFFECTFONTCHANGE,
06125                                     OpAffectFontChange::GetState,
06126                                     0,  /* help ID */
06127                                     0,
06128                                     0   /* bitmap ID */));
06129 }               
06130     
06131 
06132 /********************************************************************************************
06133 >   OpState OpAffectFontChange::GetState(String_256*, OpDescriptor*)
06134 
06135     Author:     Mike_Kenny (Xara Group Ltd) <camelotdev@xara.com>
06136     Created:    17/01/96
06137     Returns:    The state of the OpApplyGlobalAffect operation
06138     Purpose:    For finding the OpApplyGlobalAffect's state. 
06139 ********************************************************************************************/
06140 
06141 OpState OpAffectFontChange::GetState(String_256* UIDescription, OpDescriptor*)
06142 {
06143     OpState OpSt;
06144     return (OpSt);
06145 }
06146 
06147 
06148 /********************************************************************************************
06149 >   void OpAffectFontChange::DoAffectFontChange()
06150 
06151     Author:     Mike_Kenny (Xara Group Ltd) <camelotdev@xara.com>
06152     Created:    17/01/96
06153     Inputs:     -
06154     Purpose:    Scans all documents, asking all text objects to reformat themselves on
06155                 this font change. This is performed as a none undoable operation so that
06156                 the allow op system can be used to update all parents.
06157 ********************************************************************************************/
06158 
06159 void OpAffectFontChange::DoAffectFontChange()
06160 {
06161     ObjChangeFlags cFlags;
06162     cFlags.Attribute = TRUE;
06163     ObjChangeParam ObjChange(OBJCHANGE_STARTING,cFlags,NULL,NULL);
06164 
06165     BeginSlowJob();
06166     DoAffectChange(&ObjChange);
06167     EndSlowJob();
06168     End();
06169 }
06170 
06171 
06172 /********************************************************************************************
06173 >   void OpAffectFontChange::AffectNode(Document* pDocument,
06174                                         Node* pNode,
06175                                         ObjChangeParam* pObjChange)
06176 
06177     Author:     Ed_Cornes (Xara Group Ltd) <camelotdev@xara.com>
06178     Created:    11/8/96
06179     Inputs:     pDocument   = a pointer to the document currently being scanned
06180                 pNode       = a pointer to the node the scan is at (going up the tree)
06181                 pObjChange  = a pointer to a user change parameter
06182     Purpose:    Throw away undo/redo history unless the document is known not to be affected
06183                 by the font substitution (this can only be ascertained by the font gallery)
06184                 Also, call AllowOp() on TextStories to let them know of the potential change
06185 ********************************************************************************************/
06186 
06187 void OpAffectFontChange::AffectNode(Document* pDocument, Node* pNode, ObjChangeParam* pObjChange)
06188 {
06189     // throw away undo for all docs
06190     if (IS_A(pNode,NodeDocument))
06191     {
06192         pDocument->GetOpHistory().DeleteUndoableOps();
06193         pDocument->GetOpHistory().DeleteRedoableOps();
06194     }
06195 
06196     if (IS_A(pNode,TextStory))
06197         ((TextStory*)pNode)->AllowOp(pObjChange);
06198 }
06199 
06200 // ------------------------------------------------------------------------------------------
06201 // ToggleAutoKerningAction
06202 
06203 /********************************************************************************************
06204 
06205 >   ToggleAutoKerningAction::ToggleAutoKerningAction()
06206 
06207     Author:     Jonathan_Payne (Xara Group Ltd) <camelotdev@xara.com>
06208     Created:    27/10/2000
06209     Purpose:    Constructor for the action
06210 
06211 ********************************************************************************************/
06212 ToggleAutoKerningAction::ToggleAutoKerningAction()
06213 {
06214     pTextStory          = 0;
06215     OldIsAutoKerning    = FALSE;
06216 }
06217 
06218 
06219 /********************************************************************************************
06220 
06221 >   ActionCode ToggleAutoKerningAction::Init(   Operation*  pOp,
06222                                                 ActionList* pActionList,
06223                                                 TextStory*  pStory,
06224                                                 bool        NewIsAutoKerning);
06225 
06226     Author:     Jonathan_Payne (Xara Group Ltd) <camelotdev@xara.com>
06227     Created:    27/10/2000
06228     Inputs:     pOp                 = ptr to the operation to which this action belongs
06229                 pActionList         = ptr to action list to which this action should be added
06230                 pStory              = ptr to TextStory to change 
06231                 NewIsAutoKerning    = as the name says
06232     Returns:    ActionCode, one of AC_OK, AC_NO_RECORD or AC_FAIL
06233     Purpose:    This is the function which creates an instance of this action. If there is no room 
06234                 in the undo buffer (which is determined by the base class Init function called within)
06235                 the function will either return AC_NO_RECORD which means the operation can continue, 
06236                 but no undo information needs to be stored, or AC_OK which means the operation should
06237                 continue AND record undo information. If the function returns AC_FAIL, there was not 
06238                 enough memory to record the undo information, and the user has decided not to continue
06239                 with the operation.
06240     SeeAlso:    Action::Init()
06241 
06242 ********************************************************************************************/
06243 ActionCode ToggleAutoKerningAction::Init(Operation* pOp,
06244                                         ActionList* pActionList,
06245                                         TextStory*  pStory,
06246                                         BOOL        NewIsAutoKerning)
06247 {
06248     ERROR2IF(pStory==0,AC_FAIL,"pStory was NULL");
06249 
06250     UINT32 ActSize = sizeof(ToggleAutoKerningAction);
06251 
06252     ToggleAutoKerningAction* pNewAction = NULL;
06253 
06254     ActionCode Ac = Action::Init(   pOp,
06255                                     pActionList,
06256                                     ActSize,
06257                                     CC_RUNTIME_CLASS(ToggleAutoKerningAction),
06258                                     (Action**)&pNewAction);
06259 
06260     if ((Ac != AC_FAIL) && (pNewAction != NULL))
06261     {
06262         ERROR2IF(pStory == NULL,AC_FAIL,"TextStory pointer was NULL");
06263         pNewAction->pTextStory          = pStory;
06264         pNewAction->OldIsAutoKerning    = !NewIsAutoKerning;
06265 
06266         pStory->SetAutoKerning(NewIsAutoKerning);
06267         pStory->FlagNodeAndDescendantsModifiedByOpAndParentsHaveDescendantModifiedByOp();
06268         pStory->FormatAndChildren(NULL,FALSE);
06269     }
06270 
06271     return Ac;
06272 }
06273 
06274 /********************************************************************************************
06275 
06276 >   ActionCode ToggleAutoKerningAction::Execute();
06277 
06278     Author:     Jonathan_Payne (Xara Group Ltd) <camelotdev@xara.com>
06279     Created:    27/10/2000
06280     Returns:    ActionCode, one of AC_OK, AC_NO_RECORD or AC_FAIL
06281     Purpose:    Executes the action.  Uses Init() to undo/redo the action.
06282     SeeAlso:    Action::Init()
06283 
06284 ********************************************************************************************/
06285 ActionCode ToggleAutoKerningAction::Execute()
06286 {
06287     ActionCode Act;
06288 
06289     Act = ToggleAutoKerningAction::Init(pOperation, 
06290                                         pOppositeActLst,
06291                                         pTextStory,
06292                                         OldIsAutoKerning);
06293 
06294     return Act;
06295 }

Generated on Sat Nov 10 03:48:00 2007 for Camelot by  doxygen 1.4.4