home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 2 / Apprentice-Release2.iso / Source Code / Think Class Libraries / CPStyleText / CPStyleText.c < prev    next >
Encoding:
C/C++ Source or Header  |  1994-11-30  |  130.0 KB  |  5,624 lines  |  [TEXT/KAHL]

  1. /******************************************************************************
  2.  CPStyleText.c
  3.  
  4.                                The PStyleText Class
  5.                                         by:
  6.                                 John A. Love, III
  7.                                 
  8.                             NO Copyright — it's free!
  9.                (mentioning my name would be VERY MUCH appreciated)
  10.      
  11.      
  12.                              SUPERCLASS = CPEditText
  13.                                         by:
  14.                               Christopher R. Wysocki
  15.                       Copyright © 1992, All Rights Reserved
  16.  
  17.  
  18.  
  19.                              ---- DESCRIPTION ----
  20.  
  21.     CPStyleText is a class for version 1.1.x of Symantec's THINK Class
  22.     Library that implements a styled text editing pane.  It can be used
  23.     as a direct replacement for the standard TCL CStyleText class without
  24.     TextEdit's limitation to a maximum 32k of text.
  25.     
  26.     
  27.                            ---- VERSION HISTORY ----
  28.     
  29.         • 1.3b1 (25 September 1993)
  30.             - Started working and working and working and ...
  31.     
  32.         • 1.3b2 (28 December 1993)
  33.             - Introduced left and right margins
  34.             - Reinserted center and right text alignment
  35.               ( Chris dropped these in his version 1.2 of CPEditText )
  36.             - Introduced word-wrap:
  37.               (a) to window width
  38.               (b) to line width
  39.               (c) to left and right margins
  40.     
  41.         • 1.3b3 ( 6 January 1994)
  42.             - Introduced full justify text alignment.  Created
  43.               a CStylePStyleTask class to help effect this.
  44.     
  45.         • 1.3b4 (13 January 1994)
  46.             - Modified Chris' ScrollToSelection and ScrollToOffset methods
  47.             - Changed my WrapLineStarts method to
  48.               accomodate breaking SUPER long words
  49.     
  50.         • 1.3b5 (28 January 1994)
  51.             - Added an autoscroll capability when the user presses the
  52.               <Option> key while clicking the mouse in the text pane.
  53.               Idea came from my use of Symantec's "THINK Reference" app.
  54.               This autoscrolling scrolls just one line at a time.
  55.     
  56.         • 1.3b6 ( 6 February 1994)
  57.             - Added another autoscroll capability effected when the
  58.               user presses the <Option> key while dragging the thumb
  59.               in either scroll bar.  Created the CPStyleScrollPane and
  60.               CPStyleScrollBar classes to make this happen.  Idea came
  61.               from my use of Jersey Scientific's "CMaster".  The user
  62.               would implement this type of autoscrolling when s/he
  63.               wishes to scroll VERY fast since the scrolling speed is
  64.               limited only by the speed of the mouse movement.
  65.     
  66.         • 1.3b7 (10 February 1994)
  67.             - Modified Chris' DrawCaret method for drawing the caret
  68.               at the end of a "sub-line" of a word-wrapped line.
  69.               Chris' DrawCaret method is declared NON-virtual within
  70.               his "CPEditText.h" interface file.  So the most simple!
  71.               way I can override it for both THINK C and Symantec C++
  72.               is to declare Chris' DrawCaret a virtual method within
  73.               that interface file.
  74.     
  75.         • 1.3    (26 February 1994)
  76.             - Started the 'mother' of all additions ... style info ...
  77.               Before this addition is finished, it'll change many, many
  78.               times.  For the moment, I have presented just a shell for
  79.               us to chew on.
  80.     
  81.         • 1.4b1    (?????)
  82.             - Another BIG challenge is to significantly speed things
  83.               up for super large files (> 100k), for example, reading
  84.               them in, displaying them, word-wrap, scrolling, etc.
  85.             
  86.  
  87.                           ---- LICENSE AGREEMENT ----
  88.     
  89.     The source code unique to CPStyleText was written by and is the property
  90.     of John A. Love, III.  Any users of CPStyleText are legally bound by the
  91.     LICENSE AGREEMENT authored by Christopher R. Wysocki.  No other Copyright
  92.     is stated or implied.
  93.     
  94.                       ---- ELECTRONIC MAIL ADDRESSES ----
  95.     
  96.         America Online:        John Love
  97.  
  98.                                ---- OTHER INFO ----
  99.     
  100.         Telephone:            (703) 569-2294        <your nickel>
  101.         
  102.         Letter:                6413 Wyngate Drive
  103.                             Springfield, Virginia   22152
  104.  ******************************************************************************/
  105.  
  106.  
  107.  
  108.  
  109. /** Includes **/
  110.  
  111. #include <Commands.h>
  112. #include <Constants.h>
  113. #include <CBartender.h>
  114. #include <CClipboard.h>
  115. #include <CSizeBox.h>
  116. #include <OSChecks.h>
  117. #include <Script.h>
  118. #include <TBUtilities.h>
  119. #include <TCLUtilities.h>
  120.  
  121. #include "CPEditScrollPane.h"
  122. #include "CPStyleText.h"
  123.  
  124.  
  125. /** TCL Global Variables **/
  126.  
  127. extern CBartender    *gBartender;
  128. extern CClipboard    *gClipboard;
  129. extern short        gClicks;
  130.  
  131.  
  132. /**                Added Constant                     **/
  133. /**                                                 **/
  134. /** New resources in my "CPStyleText.rsrc" file. **/
  135. /** 'CURS' and 'crsr' resources adapted from     **/
  136. /** Symantec's "THINK Reference" application.     **/
  137.  
  138. #define rAutoScrollCursor    4000
  139.  
  140.  
  141. /**             Speaking of resources ...                           **/
  142. /**                                                               **/
  143. /** This is the ID of my 'Estr' resource that the TCL uses       **/
  144. /** for Error Handling when you call:                           **/
  145. /**                                                               **/
  146. /** if ( MemError() )                                           **/
  147. /**     FailOSErr( FileTooBig )                                   **/
  148. /**                                                               **/
  149. /** This is clearly more specific than calling:                   **/
  150. /**                                                               **/
  151. /**        FailMemError();                                           **/
  152. /**                                                               **/
  153. /** Not only does this isolate the problem for the programmer, **/
  154. /** but it also gives the user specific information rather       **/
  155. /** than the statement that "there was not enough memory".       **/
  156. /**                                                               **/
  157. /** This resource is included within "CPStyleText.rsrc" and       **/
  158. /** has the following "THINK Rez" source:                       **/
  159. /**                                                               **/
  160. /** resource    'Estr'    (-26000, "Text Size Error", purgeable) **/
  161. /** {                                                           **/
  162. /**        "the file is too big -- 156,000 bytes maximum"           **/
  163. /** }                                                           **/
  164.  
  165. enum
  166. {
  167.     FileTooBig = -26000
  168. };
  169.  
  170.  
  171. /*
  172.     Initialize the Class Variables:
  173.     
  174.     Idea for cursors generated by my use of
  175.     Symantec's "THINK Reference" application.
  176. */
  177.  
  178. CursHandle        CPStyleText::cAutoScrollCursor = NULL;
  179. CCrsrHandle        CPStyleText::cAutoScrollCRSR   = NULL;
  180. Boolean            CPStyleText::cHasTrueType       = TrapAvailable( 0xA854 );
  181.  
  182.  
  183.  
  184. /******************************************************************************
  185.  IsWordBreakChar
  186.  
  187.         Method to determine if a character is a word-break character.
  188.  ******************************************************************************/
  189.  
  190.     static long        wordBreakFlags[8] =
  191.     {
  192.         /*
  193.             Packed Boolean flags from Chris' WordBreakHook method.
  194.             Note that if a character is represented here, it is
  195.             NOT a word-break character.  Conversely, if a character
  196.             is NOT here, it IS a word-break character.
  197.         */
  198.         
  199.         0x00000000, 0x0000FFC0,
  200.         0x7FFFFFE1, 0x7FFFFFE0,
  201.         0x00000000, 0x00000000,
  202.         0x00000000, 0x00000000
  203.     };
  204.     
  205.     
  206.     
  207.     static Boolean        IsWordBreakChar (unsigned char ch)
  208.     {
  209.         /*
  210.             Note the NOT transposition.  Remember, if a
  211.             character is NOT in the above packed Boolean
  212.             array, it IS a word-break character.
  213.         */
  214.             
  215.         
  216.         return    (
  217.                   !(
  218.                       ((long*) wordBreakFlags)[ch >> 5] & (0x80000000U >> (ch & 0x1F))
  219.                    )
  220.                 );
  221.  
  222.     }    /* IsWordBreakChar */
  223.  
  224.  
  225.  
  226. /**** C O N S T R U C T I O N / D E S T R U C T I O N   M E T H O D S ****/
  227.  
  228.  
  229. void    CPStyleText::IPStyleText (CView *anEnclosure, CBureaucrat *aSupervisor,
  230.                                   short aWidth, short aHeight,
  231.                                   short aHEncl, short aVEncl,
  232.                                   SizingOption aHSizing, SizingOption aVSizing,
  233.                                   short aLineWidth)
  234. {
  235.  
  236.     CPEditText::IPEditText( anEnclosure, aSupervisor,
  237.                             aWidth, aHeight, aHEncl, aVEncl,
  238.                             aHSizing, aVSizing );
  239.  
  240.     /*
  241.         Unfortunately, IPEditText does not accept a passed
  242.         line width as IAbstractText does ... a shame because
  243.         CAbstractText is CPEditText's superclass ... but
  244.         this IS consistent with version 1.2 of Chris'
  245.         CPEditText which does NOT support word-wrapping.
  246.         So Chris just passes:
  247.         
  248.             aLineWidth = kDefaultBoundsWidth
  249.             
  250.         to IAbstractText within his IPEditText method.
  251.         
  252.         Of course, if all this were false, then the
  253.         following statement would NOT be required
  254.         because IAbstractText would do it for us.
  255.     */
  256.     lineWidth = aLineWidth;
  257.  
  258.     IPStyleTextX();
  259.                                                 
  260. }    /* IPStyleText */
  261.  
  262.  
  263.  
  264. /* OVERRIDE: */
  265. void    CPStyleText::IViewTemp (CView *anEnclosure, CBureaucrat *aSupervisor,
  266.                                 Ptr viewData)
  267. {
  268.  
  269.     CPEditText::IViewTemp( anEnclosure, aSupervisor, viewData );
  270.     
  271.  /*
  272.     IViewTemp reads the line width
  273.     from the viewData template:
  274.     
  275.     lineWidth = aLineWidth;
  276.  */
  277.  
  278.     IPStyleTextX();
  279.                                                 
  280. }    /* IViewTemp */
  281.  
  282.  
  283.  
  284. void    CPStyleText::IPStyleTextX (void)
  285. {
  286.         LongRect    theInterior;
  287.         CWindow        *theWindow;
  288.         Boolean        savedAlloc;
  289.         CArray        *theLines = NULL;
  290.         
  291.     
  292.  /* CPEditText::IPEditTextX();  --  already called by IPEditText or by IViewTemp. */
  293.  
  294.     autoRefresh = fWordWrap = lineWidth <= 0;    /* Examine IEditTextX ...          */
  295.                                                 /* Changed later by SetWordWrap.  */
  296.                                                 
  297.  /* Change CAbstractText's instance variable: */
  298.  
  299.     fixedLineHeights = FALSE;
  300.     /*
  301.         Because we change fixedLineHeights, we need to re-call
  302.         SetWholeLines whose response is a function of fixedLineHeights:
  303.     */
  304.  /* wholeLines = TRUE;            --  set by IPEditTextX.                              */
  305.     SetWholeLines( wholeLines );
  306.     
  307.  /* Change a couple of Chris' instance variables: */
  308.  
  309.     fOutlineHilite = TRUE;
  310.     fUseItalicCaret = TRUE;
  311.      
  312.  /* Initialize my added instance variables: */
  313.  
  314.     itsAlignCmd = cmdAlignLeft;
  315.     itsLeftMargin = 0L;
  316.     itsRightMargin = MAXINT;
  317.     itsViewRect = aperture;
  318.     GetInterior( &theInterior );
  319.     itsDestRect = theInterior;
  320.     itsDestRect.bottom = (long) MAXINT;            /* Will NEVER change --              */
  321.                                                 /* ONLY the bounds will              */
  322.     fOldEndWrapLineCaret = FALSE;
  323.     fNewEndWrapLineCaret = FALSE;
  324.     /*
  325.         The parent CPane of my CPStyleText may
  326.         simply scroll to home via:
  327.         
  328.             GetHomePosition( &homePos );
  329.             ScrollTo( &homePos, TRUE );
  330.         
  331.         rather than call:
  332.             
  333.             SetSelection (0L, 0L, TRUE);
  334.             ScrollToSelection();
  335.             
  336.         SetSelection is where I set my caret
  337.         flag = fEndWrapLine.  Therefore, I
  338.         better turn it off here in case
  339.         SetSelection is NOT called as I have
  340.         just described.
  341.     */
  342.     fEndWrapLine = FALSE;
  343.     
  344.     /*
  345.         Word-wrap becomes a very slow process for long docs
  346.         if the wrap width becomes too narrow.  Therefore,
  347.         override the TCL's default value of 100 pixels:
  348.     */
  349.     
  350.     theWindow = GetWindow();
  351.     theWindow->sizeRect.left = Max( theWindow->sizeRect.left + (short)itsLeftMargin,
  352.                                     MIN_WINDOW_WIDTH );
  353.  
  354.     /* The BIGGEE !!! */
  355.     
  356.     theLines = new (CArray);
  357.     theLines->IArray( sizeof(LineRecord) );
  358.     itsLineInfo = theLines;
  359.     AddLine( atEnd );
  360.     
  361.     /* Various Class Variables ... */
  362.     
  363.     if (!gSystem.hasColorQD)
  364.     {
  365.         if (cAutoScrollCursor == NULL)
  366.         {
  367.             savedAlloc = SetAllocation( kAllocCanFail );
  368.             cAutoScrollCursor = GetCursor( rAutoScrollCursor );
  369.             SetAllocation( savedAlloc );
  370.             FailNILRes( cAutoScrollCursor );
  371.             HNoPurge( (Handle)cAutoScrollCursor );
  372.             ;
  373.             cAutoScrollCRSR = NULL;
  374.             
  375.         }    /* NOT previously initialized */
  376.         
  377.     }    /* "Yucky" black-and-white */
  378.     
  379.     else
  380.     {
  381.         if (cAutoScrollCRSR == NULL)
  382.         {
  383.             savedAlloc = SetAllocation( kAllocCanFail );
  384.             cAutoScrollCRSR = GetCCursor( rAutoScrollCursor );
  385.             SetAllocation( savedAlloc );
  386.             FailNILRes( cAutoScrollCRSR );
  387.             HPurge( (Handle)cAutoScrollCRSR );            /* See IM 5. */
  388.             ;
  389.             cAutoScrollCursor = NULL;
  390.             
  391.         }    /* NOT previously initialized */
  392.         
  393.     }    /* color Quickdraw */
  394.         
  395. }    /* IPStyleTextX */
  396.  
  397.  
  398.  
  399. /* OVERRIDE: */
  400. void    CPStyleText::Dispose (void)
  401. {
  402.         register long    line, numLines;
  403.         LineRecord        lineInfo;
  404.         
  405.         
  406.     /*
  407.         Need to dump each line's component StyleRecord
  408.         CArray before we dispose of the master LineRecord:
  409.     */
  410.     
  411.     numLines = itsLineInfo->GetNumItems();
  412.     ;
  413.     for (line = 1; line <= numLines; ++line)
  414.     {
  415.         itsLineInfo->GetItem( &lineInfo, line );        /* 1-based */
  416.         ForgetObject( lineInfo.lStyles );
  417.     }
  418.     ;
  419.     ForgetObject( itsLineInfo );
  420.     
  421.     CPEditText::Dispose();
  422.     
  423. }    /* Dispose */
  424.  
  425.  
  426.  
  427. /**** D I S P L A Y   M E T H O D S ****/
  428.  
  429.  
  430. /* OVERRIDE: */
  431. void    CPStyleText::Draw (Rect *area)
  432. {
  433.         LongRect        longArea;
  434.         register long    numLines  = itsNumLines;
  435.         register long    startPointHt, endPointHt;
  436.         register long    cumLineHt;
  437.         register long    startLine, endLine,
  438.                         vertInset = VertTopInset();
  439.     
  440.     
  441.         /*
  442.             Compute and draw the visible text lines.  Added
  443.             for-loops for eventual incorporation of styled
  444.             text wherein each line can have a unique height.
  445.         */
  446.     
  447.     QDToFrameR( area, &longArea );
  448.     ;
  449.     cumLineHt = 0;
  450.     startPointHt = longArea.top - vertInset;
  451.     for (startLine = 0; startLine < numLines; startLine++)
  452.     {
  453.      /*
  454.         Because this for-loop consumes a lot of time,
  455.         every time-savings helps.  Calling Get1Height
  456.         just calls GetHeight with identical lines:
  457.  
  458.         cumLineHt += Get1Height( startLine );
  459.      */
  460.         
  461.         cumLineHt += GetHeight( startLine, startLine );
  462.         if (cumLineHt > startPointHt)
  463.             break;
  464.     }
  465.     startLine = Min( startLine, numLines - 1 );
  466.     ;
  467.  /* cumLineHt = 0;  --  start where you left off */
  468.     endPointHt = longArea.bottom - vertInset;
  469.     for (endLine = startLine + 1; endLine < numLines; endLine++)
  470.     {
  471.         /*
  472.             cumLineHt above includes the line
  473.             height of startLine, so start this
  474.             2nd for-loop at startLine + 1.
  475.         */
  476.         
  477.         cumLineHt += GetHeight( endLine, endLine );
  478.         if (cumLineHt >= endPointHt)
  479.             break;
  480.     }
  481.     endLine = Min( endLine, numLines - 1 );
  482.     
  483.     DrawLineRange( startLine, endLine, 0, kDontEraseText );
  484.     
  485.         /*
  486.             If printing is not in progress, highlight the current
  487.             selection or draw the insertion caret, as appropriate
  488.         */
  489.     if (
  490.          !this->printing                        &&
  491.          this->wantsClicks                        &&
  492.          (fReallyActive || fOutlineHilite)
  493.        )
  494.     {
  495.         if (itsSelStart != itsSelEnd)
  496.             HiliteTextRange( itsSelStart, itsSelEnd );
  497.         else if (fCaretVisible)
  498.             DrawCaret();
  499.     }
  500.     
  501. }    /* Draw */
  502.  
  503.  
  504.  
  505. /* OVERRIDE: */
  506. void    CPStyleText::SetSelection (long selStart, long selEnd, Boolean fRedraw)
  507. {
  508.     /*
  509.         Overridden for special caret placement at the end
  510.         of a "sub-line" of a longer word-wrapped line:
  511.     */
  512.     
  513.         enum
  514.         {
  515.             /*
  516.                 These dudes are in Chris' "CPEditText.c"
  517.                 and so must be repeated here:
  518.             */
  519.             
  520.             kUnhideSelection = FALSE,
  521.             kHideSelection     = TRUE
  522.         };
  523.  
  524.     
  525.     selStart = Max( selStart, 0 );                /* Ensure positions are valid ...  */
  526.     selStart = Min( selStart, itsTextLength );
  527.     selEnd = Max( selEnd, 0 );
  528.     selEnd = Min( selEnd, itsTextLength );
  529.     selEnd = Max( selEnd, selStart );
  530.             
  531.     /*
  532.         Unlike Chris, I do NOT take a quick exit if the
  533.         selection range has stayed the same.  The reason
  534.         is that the selection at the end of a "sub-line"
  535.         of a longer word-wrapped line is actually the
  536.         beginning of the next "sub-line".
  537.         
  538.         I use this overridden SetSelection for special
  539.         caret placement.  Clicking at the end of a
  540.         "sub-line" demands different caret placement
  541.         than clicking at the far left of the next
  542.         "sub-line", even tho` the selection is identical.
  543.     */
  544.     
  545.         /* Unhilite the old selection range: */
  546.         
  547.     fEndWrapLine = fOldEndWrapLineCaret;
  548.     HideSelection( kHideSelection, fRedraw );
  549.         /*
  550.             Once you've erased the old caret, you're done with
  551.             it until you click somewhere else or press a key:
  552.         */
  553.     fOldEndWrapLineCaret = FALSE;
  554.             
  555.     itsSelStart = selStart;                    /* Update our instance variables ...   */
  556.     itsSelEnd = selEnd;
  557.     fUpDownArrow = FALSE;
  558.     
  559.         /* Ensure that the anchor point is one of the selection endpoints: */
  560.         
  561.     if ( (itsSelAnchor != selStart) && (itsSelAnchor != selEnd) )
  562.         itsSelAnchor = selStart;
  563.         
  564.         /* Hilite the new selection range: */
  565.         
  566.     if ( editable || (selStart != selEnd) )
  567.     {
  568.         fEndWrapLine = fNewEndWrapLineCaret;
  569.         HideSelection( kUnhideSelection, fRedraw );
  570.     }
  571.     
  572. }    /* SetSelection */
  573.  
  574.  
  575.  
  576. /**** C A L I B R A T I O N   M E T H O D S ****/
  577.  
  578.  
  579. /* OVERRIDE: */
  580. void    CPStyleText::UpdateMenus (void)
  581. {
  582.     /*
  583.         If wrapping to the window width, the various
  584.         alignments do NOT make much sense.
  585.  
  586.         Within your CApplication::SetUpMenus method,
  587.         you could designate all the alignments as
  588.         items of the main MENU, in which case the
  589.         "else" clause to follow is NOT required.
  590.         However, if you designated these alignments
  591.         as items of a hierarchical MENU, you would
  592.         probably specify "dimNONE" as the dimming
  593.         option for the parent MENU item.  In this
  594.         last instance, "if-else" are required.
  595.     */
  596.         
  597.  
  598.     CPEditText::UpdateMenus();
  599.     
  600.     if (lineWidth != -1)
  601.     {
  602.         gBartender->EnableCmd( cmdAlignLeft );
  603.         gBartender->EnableCmd( cmdAlignCenter );
  604.         gBartender->EnableCmd( cmdAlignRight );
  605.         gBartender->EnableCmd( cmdJustify );
  606.     }
  607.     else
  608.     {
  609.         gBartender->DisableCmd( cmdAlignLeft );
  610.         gBartender->DisableCmd( cmdAlignCenter );
  611.         gBartender->DisableCmd( cmdAlignRight );
  612.         gBartender->DisableCmd( cmdJustify );
  613.     }
  614.     
  615. }    /* UpdateMenus */
  616.  
  617.  
  618.  
  619. /* OVERRIDE: */
  620. void    CPStyleText::ResizeFrame (Rect *delta)
  621. {        
  622.     
  623.     CPEditText::ResizeFrame( delta );            /* Calls ForceNextPrepare() */
  624.     
  625.     itsDestRect.left += delta->left;
  626.     itsDestRect.top += delta->top;
  627.  
  628.     CalcPERects();
  629.     
  630.     /*
  631.         If wrapping by the window width, recalculate line starts.
  632.         RecalcLineStarts calls WrapLineStarts and AdjustBounds().
  633.         The latter sets the bounds width of the CPanorama to the
  634.         width of itsDestRect and the bounds height to itsNumLines:
  635.     */
  636.     if (lineWidth == -1)
  637.         if (delta->left || delta->right)
  638.         {
  639.             /* Recalculate ONLY with horizontal changes: */
  640.             
  641.             RecalcLineStarts();
  642.         }
  643.     
  644. }    /* ResizeFrame */
  645.  
  646.  
  647.  
  648. /* OVERRIDE: */
  649. void    CPStyleText::SetWholeLines (Boolean aWholeLines)
  650. {
  651.     /*
  652.         Overridden to ONLY change the frame size when
  653.         there are more lines than frame to draw them in.
  654.     */
  655.     
  656.         LongPt        charPos;
  657.         long        charOffset, topLine, bottomLine, totalHeight;
  658.         
  659.         
  660.     frame.bottom = frame.top + height;
  661.  
  662.     wholeLines = aWholeLines;
  663.  
  664.     if (wholeLines)
  665.     {
  666.         if (fixedLineHeights)
  667.         {
  668.             /* Use the height of the first line as the standard height: */
  669.             
  670.                 short lineHeight = GetHeight( 1, 1 );    
  671.             
  672.             if (lineHeight > 0)
  673.                 frame.bottom = frame.top + lineHeight * (height / lineHeight);
  674.         }
  675.         else
  676.         {
  677.             /* Find the line at top of frame: */
  678.             
  679.             SetLongPt( &charPos,  frame.left + 1, frame.top + 1 );
  680.             charOffset = GetCharOffset( &charPos );
  681.             topLine = FindLine( charOffset ) ;
  682.             
  683.             /* Find the line at bottom of frame: */
  684.             
  685.             SetLongPt( &charPos, frame.left + 1, frame.top + height - 1 );
  686.             charOffset = GetCharOffset( &charPos );
  687.             
  688.          /*
  689.             Chris' <even CEditText's> FindLine addresses
  690.             the case charOffset = 0.  Besides, the "else"
  691.             clause should read:
  692.             
  693.                 bottomLine = GetNumLines() - 1;
  694.          
  695.             if (charOffset > 0)
  696.                 bottomLine = FindLine( charOffset );    
  697.             else
  698.                 bottomLine = GetNumLines();
  699.          */
  700.                 
  701.             bottomLine = FindLine( charOffset );    
  702.  
  703.             if (bottomLine > topLine)
  704.             {
  705.                 totalHeight = GetHeight( topLine, bottomLine + 1 );
  706.                 
  707.                 if (totalHeight > height)
  708.                 {
  709.                     /*
  710.                         Mess with the frame size ONLY when there
  711.                         are more lines than frame to draw them in.
  712.                         In this way, the frame stays put if we've
  713.                         only got a few lines of text.
  714.                         
  715.                         Also, if the frame stays put, the cursor
  716.                         won't change from the I-beam as long as
  717.                         the Mouse is inside the text area.  Before,
  718.                         it changed to an Arrow in the bottom part
  719.                         of the text area because with just a few
  720.                         lines of text, the frame's bottom was very
  721.                         close to the window's top.
  722.                     */
  723.                     
  724.                     while ( (totalHeight > height) && (bottomLine > topLine) )        
  725.                     {
  726.                         totalHeight = GetHeight( topLine, bottomLine-- );
  727.                     }
  728.         
  729.                     /*
  730.                         We now know how many full lines will fit.  Resize frame,
  731.                         while ensuring that at least one line is visible:
  732.                     */
  733.     
  734.                     if (totalHeight < height)
  735.                     {
  736.                         frame.bottom = frame.top + totalHeight;
  737.                     }
  738.                     
  739.                 }    /* more lines than frame */
  740.                 
  741.             }    /* bottomLine > topLine */
  742.             
  743.         }    /* NOT fixedLineHeights */
  744.         
  745.     }    /* if (wholeLines) */
  746.  
  747.     CalcAperture();    
  748.  
  749. }    /* SetWholeLines */
  750.  
  751.  
  752.  
  753. /**** A C C E S S I N G   M E T H O D S ****/
  754.  
  755.  
  756. /* OVERRIDE: */
  757. void    CPStyleText::SetBounds (LongRect *aBounds)
  758. {
  759.     /*
  760.         CPEditText's SetBounds does some preliminary scrolling
  761.         which generates an update Event.  SetBounds is called
  762.         by AdjustBounds which, in turn, is called by a bunch
  763.         of methods, most notably, ResizeFrame.  Chris' update
  764.         amounts to a SECOND update in addition to the one to be
  765.         generated after CPanorama's ChangeSize calls ResizeFrame.
  766.         Because of this duplication, the screen flashes.
  767.         
  768.         In addition, CPanorama's SetBounds calls:
  769.         
  770.             ASSERT( position.v <= bounds.bottom );
  771.  
  772.         which presents a problem while you are calling CPane's
  773.         ChangeSize for positioning your various CPanes within
  774.         your CDocument's BuildWindow method.  This is why
  775.         Chris did the preliminary scrolling.
  776.         
  777.         Note that CEditText (no "P") does not override CPanorama's
  778.         SetBounds and does not call it.  Instead, CEditText's
  779.         AdjustBounds does everything that CPanorama's SetBounds
  780.         does, but withOUT the above assertion check.
  781.         
  782.         So, do the same here by duplicating CPanorama's SetBounds,
  783.         but withOUT this check.
  784.     */
  785.  
  786.  
  787.     bounds = *aBounds;
  788.     
  789.     if (itsScrollPane != NULL)
  790.         itsScrollPane->AdjustScrollMax();
  791.     
  792. }    /* SetBounds */
  793.  
  794.  
  795.  
  796. /* OVERRIDE: */
  797. long    CPStyleText::GetHeight (long startLine, long endLine)
  798. {
  799.     /*
  800.         Will eventually incorporate styled text
  801.         wherein each line can have a unique height.
  802.         
  803.         I used to have range checking here, such as:
  804.         
  805.             0 => startLine, endLine <= itsNumLines - 1
  806.             
  807.         However, all the methods which call GetHeight
  808.         accomplish their own range checking beforehand,
  809.         so I just call the inherited method (so far!).
  810.     */
  811.     
  812.     
  813.     return    ( CPEditText::GetHeight(startLine, endLine) );
  814.         
  815. }    /* GetHeight */
  816.  
  817.  
  818.  
  819. /* OVERRIDE: */
  820. long    CPStyleText::GetCharOffset (LongPt *aPt)
  821. {
  822.         register long        numLines = itsNumLines;
  823.         register long        line, cumLineHt, pointHt;
  824.         register short        offset;
  825.         register short        lineLen;
  826.         register short        width, lastWidth;
  827.         register short        horiz;
  828.         register short        *widthsP;
  829.         ShortHandle            widthsH;
  830.     
  831.     
  832.     Prepare();
  833.     
  834.     /*
  835.         Initialize at the beginning just in case we
  836.         encounter any of the special cases to follow.
  837.     */
  838.     
  839.     fNewEndWrapLineCaret = FALSE;
  840.  
  841.         /*
  842.             Determine which line the point lies on.
  843.             Implemented for-loop to find line number
  844.             because eventually I will incorporate
  845.             styled text wherein each line will have
  846.             a unique height.
  847.         */
  848.         
  849.     pointHt = aPt->v - VertTopInset();
  850.     if (pointHt < 0L)                /* Handle special case of above the first line. */
  851.         return    (0L);
  852.     ;
  853.     cumLineHt = 0;
  854.     for (line = 0; line < numLines; line++)
  855.     {
  856.         /* See comments within Draw method: */
  857.         
  858.         cumLineHt += GetHeight( line, line );
  859.         if (cumLineHt > pointHt)
  860.             break;
  861.     }
  862.     if (line > numLines - 1)        /* Handle special case of below the last line. */
  863.         return    (itsTextLength);
  864.     
  865.         /* Get the number of characters in the line */
  866.         
  867.     lineLen = GetLineLength( line ) ;
  868.     
  869.         /*
  870.             Determine pixel width that is available for text.
  871.             This available space appears AFTER the left margin
  872.             and goes out to the passed point = the point where
  873.             the user clicked (in frame coordinates):
  874.         */
  875.     
  876.     horiz = aPt->h - HorizLeftInset();
  877.     
  878.         /* Calculate character pixel offsets for the given line: */
  879.         
  880.     if (horiz <= itsLeftMargin)        offset = 0;    
  881.     else
  882.     {
  883.         widthsH = MeasureLineWidths( line );
  884.         widthsP = *widthsH;
  885.         
  886.             /*
  887.                 NON-left text alignments eliminated from version 1.2:
  888.                 
  889.                 For all text alignments, decrease the available
  890.                 width.  This decrease should match the corresponding
  891.                 increase by GetCharPoint(...).  As stated above,
  892.                 this width appears AFTER the left margin.
  893.             */
  894.         
  895.         if (itsAlignCmd == cmdAlignLeft)
  896.             horiz -= itsLeftMargin;
  897.         else if (itsAlignCmd == cmdAlignRight)
  898.             horiz -= HorizPixelExtent() - widthsP[lineLen];
  899.         else if (itsAlignCmd == cmdAlignCenter)
  900.             /*
  901.                 Center between left margin and bounds' right edge:
  902.             */
  903.             horiz -= itsLeftMargin +
  904.                         (HorizPixelExtent() - itsLeftMargin - widthsP[lineLen]) / 2;
  905.         else
  906.         {
  907.                 /*
  908.                     For full justify alignment, the "widthsH"
  909.                     Handle addresses the point displacement:
  910.                 */
  911.             
  912.             horiz -= itsLeftMargin;
  913.         }
  914.         
  915.         offset = lastWidth = 0;
  916.         
  917.         while (
  918.                 (offset                  < lineLen)    &&
  919.                 ((width = *++widthsP) < horiz  )
  920.               )
  921.         {
  922.             lastWidth = width;
  923.             ++offset;
  924.         }
  925.         
  926.         DisposHandle( (Handle)widthsH );
  927.         
  928.         if (offset < lineLen)
  929.         {
  930.             /*
  931.                 If we clicked somewhere ON a character, check
  932.                 whether on its first or last half.  If the last
  933.                 half, then bump the offset to the NEXT character.
  934.                 
  935.                 Do this first in case the offset is bumped beyond
  936.                 a word-break character:
  937.             */
  938.             
  939.             if (width > lastWidth)
  940.                 if (horiz > (lastWidth + width) / 2)
  941.                     ++offset;
  942.                     
  943.         }    /* offset < lineLen */
  944.         
  945.         if (offset == lineLen)
  946.         {
  947.             /* We have clicked to the FAR right of the line's end: */
  948.             
  949.                 long        endOfLine = (*itsLineStarts)[line] + lineLen;
  950.                 tCharBuf    theChar;
  951.                 
  952.             GetCharBefore( &endOfLine, theChar );
  953.             if ( theChar[0] )
  954.             {
  955.                     register Byte    ch = theChar[1];
  956.                     
  957.                 if (ch == kReturn)
  958.                 {
  959.                     --offset;
  960.                     /*
  961.                         Special case where we click to the far right
  962.                         of the last line which is empty, i.e., the
  963.                         user pressed <CR> after the last full line
  964.                         and stopped typing.
  965.                     */
  966.                     offset = Max( offset, 0 );
  967.                 }
  968.                 else
  969.                     fNewEndWrapLineCaret = IsWordBreakChar( ch );
  970.             }
  971.             
  972.         }    /* offset = lineLen */
  973.         
  974.     }    /* horiz > itsLeftMargin */
  975.  
  976.     return    ( (*itsLineStarts)[line] + offset );
  977.     
  978. }    /* GetCharOffset */
  979.  
  980.  
  981.  
  982. /* OVERRIDE: */
  983. void    CPStyleText::GetCharPoint (long charOffset, LongPt *aPt)
  984. {
  985.         long            line;
  986.         ShortHandle        widthsH;
  987.     
  988.     
  989.         /* Ensure that the offset is within bounds: */
  990.     
  991.     charOffset = Max( charOffset, 0 );
  992.     charOffset = Min( charOffset, itsTextLength );
  993.         
  994.         /*
  995.             Determine which line the given character offset is
  996.             on and calculate the pixel widths for that line:
  997.         */
  998.     
  999.     line = FindLine( charOffset );
  1000.     widthsH = MeasureLineWidths( line );
  1001.     
  1002.         /* Compute the pixel coordinates for the character: */
  1003.     
  1004.     aPt->v = VertTopInset() + GetHeight( 0, line - 1 ) + itsFontAscent;
  1005.     aPt->h = HorizLeftInset() + (*widthsH)[charOffset - (*itsLineStarts)[line]];
  1006.         
  1007.         /*
  1008.             NON-left text alignments eliminated from version 1.2:
  1009.                 
  1010.             For all text alignments, increase horizontal coordinate.
  1011.             This addition should include the left margin.  For
  1012.             further info, see comments within GetCharOffset(...).
  1013.         */
  1014.      
  1015.     if (itsAlignCmd == cmdAlignLeft)
  1016.         aPt->h += itsLeftMargin;
  1017.     else if (itsAlignCmd == cmdAlignRight)
  1018.         aPt->h += HorizPixelExtent() - (*widthsH)[GetLineLength(line)];
  1019.     else if (itsAlignCmd == cmdAlignCenter)
  1020.         aPt->h += itsLeftMargin + (
  1021.                                     HorizPixelExtent() - itsLeftMargin -
  1022.                                     (*widthsH)[GetLineLength(line)]
  1023.                                   ) / 2;
  1024.     else
  1025.     {
  1026.             /*
  1027.                 For full justify alignment, the "widthsH"
  1028.                 Handle addresses the point displacement:
  1029.             */
  1030.  
  1031.         aPt->h += itsLeftMargin;
  1032.     }
  1033.     
  1034.         /* Dispose of the pixel widths handle: */
  1035.     
  1036.     DisposHandle( (Handle) widthsH );
  1037.     
  1038. }    /* GetCharPoint */
  1039.  
  1040.  
  1041.  
  1042. /* OVERRIDE: */
  1043. void    CPStyleText::SetMargins (long leftMargin, long rightMargin)
  1044. {
  1045.     /*
  1046.         Calls to SetWordWrap and
  1047.         DoWordWrap should follow:
  1048.         
  1049.         ( Passed parms are in pixels )
  1050.     */
  1051.         
  1052.     itsLeftMargin = Max( leftMargin, 0L );                    /* Store new value        */
  1053.  
  1054.     itsRightMargin = Max( rightMargin, 0L );                /* Store new value        */
  1055.     if (itsRightMargin == 0L)    itsRightMargin = 576L;        /* Default = 8.0 inches    */
  1056.     
  1057. }    /* SetMargins */
  1058.  
  1059.  
  1060.  
  1061. /* OVERRIDE: */
  1062. void    CPStyleText::GetMargins (long *leftMargin, long *rightMargin)
  1063. {
  1064.  
  1065.     *leftMargin = itsLeftMargin;
  1066.     *rightMargin = itsRightMargin;
  1067.     
  1068. }    /* GetMargins */
  1069.  
  1070.  
  1071.  
  1072. /**** M O U S E   A N D   K E Y S T R O K E   M E T H O D S ****/
  1073.  
  1074.  
  1075. /* OVERRIDE: */
  1076. void    CPStyleText::DoClick (Point hitPt, short modifierKeys, long when)
  1077. {
  1078.     /*
  1079.         Override to select the end of a "sub-line" of a word-wrapped line.
  1080.         Also, to enable autoscrolling when the user presses the <Option>
  1081.         key while clicking the mouse in the text pane.
  1082.     */
  1083.     
  1084.         LongPt        framePoint, autoScrollPoint;
  1085.         long        selStart, selEnd;
  1086.         long        charOffset, origCharOffset, lastCharOffset;
  1087.         long        newSelStart, newSelEnd;
  1088.         tCharBuf    breakChar;
  1089.         /* <Option> scrolling ... */
  1090.         Boolean        optionKeyDown  = ( (modifierKeys & optionKey) != 0 );
  1091.         long        saveSelStart, saveSelEnd;
  1092.         Point        movedPt;
  1093.         LongPt        framePt;
  1094.         enum
  1095.         { 
  1096.             /*
  1097.                 These dudes are in Chris' "CPEditText.c"
  1098.                 and so must be repeated here:
  1099.             */
  1100.             
  1101.             kUnhideSelection       = FALSE,
  1102.             kHideSelection           = TRUE
  1103.         };
  1104.         
  1105.         
  1106.     fOldEndWrapLineCaret = fNewEndWrapLineCaret;
  1107.     fNewEndWrapLineCaret = FALSE;                    /* Assume NOT !!! */
  1108.     
  1109.     if (gClicks == 3)
  1110.     {
  1111.         /* Use Chris'method for paragraph selection: */
  1112.         
  1113.         CPEditText::DoClick( hitPt, modifierKeys, when );
  1114.     }
  1115.     
  1116.     else if (gClicks == 2)
  1117.     {
  1118.         /* First, get the current selection range for word selection: */
  1119.         
  1120.         GetSelection( &selStart, &selEnd );
  1121.     
  1122.         /* Then, determine which character was clicked on: */
  1123.         
  1124.         QDToFrame( hitPt, &framePt );
  1125.         charOffset = GetCharOffset( &framePt );
  1126.  
  1127.         /*
  1128.             Finally, determine which word ... the word at
  1129.             the end of a "sub-line" of a word-wrapped line
  1130.             or a regular word.  The fNewEndWrapLineCaret
  1131.             Boolean was determined by GetCharOffset above:
  1132.         */
  1133.         
  1134.         if (fNewEndWrapLineCaret)    --charOffset;
  1135.         GetWordBounds( charOffset, &selStart, &selEnd );
  1136.         SetSelection( selStart, selEnd, kRedraw );
  1137.         
  1138.     }    /* double-click */
  1139.     
  1140.     else if (optionKeyDown)
  1141.     {
  1142.         /*
  1143.             Autoscrolling ...
  1144.             
  1145.             a gadget whose idea came from my use of
  1146.             Symantec's "THINK Reference" application.
  1147.         */
  1148.     
  1149.         /* Unhilite the current selection range: */
  1150.         
  1151.         HideSelection( kHideSelection, TRUE );
  1152.         
  1153.         if (itsSelStart == itsSelEnd)
  1154.         {
  1155.             saveSelStart = saveSelEnd = itsSelStart;
  1156.         }
  1157.         else    /* finite range */
  1158.         {
  1159.             /*
  1160.                 Save selection range for restoration later.
  1161.                 Then, set new selection with zero range.
  1162.                 Otherwise, when you scroll beyond the prior
  1163.                 selection range and subsequently scroll it back
  1164.                 into view, it's still hilited.
  1165.             */
  1166.             
  1167.             saveSelStart = itsSelStart;
  1168.             saveSelEnd = itsSelEnd;
  1169.             SetSelection( itsSelStart, itsSelStart, FALSE );
  1170.         }
  1171.         
  1172.         /*
  1173.             Nifty Cursor adapted from Symantec's
  1174.             "THINK Reference" application:
  1175.         */
  1176.         
  1177.         if (gSystem.hasColorQD)
  1178.         {
  1179.             /* May have been purged: */
  1180.             LoadResource( (Handle)cAutoScrollCRSR );
  1181.             SetCCursor( cAutoScrollCRSR );
  1182.         }
  1183.         else
  1184.         {
  1185.             SetCursor( *cAutoScrollCursor );
  1186.         }
  1187.  
  1188.         while ( WaitMouseUp() )
  1189.         {            
  1190.             /* Get the current mouse position in frame coordinates: */
  1191.             
  1192.             GetMouse( &movedPt );
  1193.             QDToFrame( movedPt, &autoScrollPoint );
  1194.         
  1195.             /*
  1196.                 Scroll the text automatically while
  1197.                 the user is holding down the Mouse:
  1198.             */
  1199.             (void) AutoScroll( &autoScrollPoint );
  1200.  
  1201.         }    /* while loop */
  1202.         
  1203.         /* Rehilite the current selection range: */
  1204.         
  1205.         if (saveSelStart != saveSelEnd)
  1206.         {
  1207.             SetSelection( saveSelStart, saveSelEnd, FALSE );
  1208.         }
  1209.         HideSelection( kUnhideSelection, TRUE );
  1210.             
  1211.     }    /* Autoscrolling with <Option> key down + just one click */
  1212.     
  1213.     else
  1214.     {
  1215.         GetSelection( &selStart, &selEnd );
  1216.     
  1217.         /* Determine which character was clicked on: */
  1218.     
  1219.         QDToFrame( hitPt, &framePoint );
  1220.         charOffset = GetCharOffset( &framePoint );
  1221.         origCharOffset = lastCharOffset = charOffset;
  1222.     
  1223.         /* Determine the new selection range: */
  1224.     
  1225.         if (modifierKeys & shiftKey)
  1226.         {
  1227.             /* If the Shift key is down, extend the selection: */
  1228.             
  1229.             origCharOffset = lastCharOffset = itsSelAnchor;
  1230.         }
  1231.         else
  1232.             SetSelection( charOffset, charOffset, kRedraw );    
  1233.  
  1234.         /* Ensure that the insertion caret is visible: */
  1235.     
  1236.         if (selStart == selEnd)
  1237.             ShowCaret();
  1238.     
  1239.         /* Track the selection while the mouse is down: */
  1240.     
  1241.         while ( StillDown() )
  1242.         {
  1243.             /* Get the current mouse position in frame coordinates: */
  1244.         
  1245.             GetMouse( &hitPt );
  1246.             QDToFrame( hitPt, &framePoint );
  1247.             autoScrollPoint = framePoint;
  1248.         
  1249.             /* Check if the mouse has moved to a new character: */
  1250.         
  1251.             PinInRect( &frame, &framePoint );
  1252.             charOffset = GetCharOffset( &framePoint );
  1253.             if (charOffset != lastCharOffset)
  1254.             {
  1255.                 /* Remember the current selection range: */
  1256.             
  1257.                 selStart = itsSelStart;
  1258.                 selEnd = itsSelEnd;
  1259.             
  1260.                 /* Determine the new selection range: */
  1261.             
  1262.                 newSelStart = Min( origCharOffset, charOffset );
  1263.                 newSelEnd = Max( origCharOffset, charOffset );
  1264.  
  1265.                 /* Adjust the hilited text: */
  1266.             
  1267.                 if (
  1268.                       (selStart == selEnd)        &&
  1269.                       fCaretVisible                &&
  1270.                       ((selStart != newSelStart) || (selStart != newSelEnd))
  1271.                    )
  1272.                     HideCaret();
  1273.                 
  1274.                 if (selStart != newSelStart)
  1275.                     HiliteTextRange( Min(selStart, newSelStart),
  1276.                                      Max(selStart, newSelStart) );
  1277.                 if (selEnd != newSelEnd)
  1278.                     HiliteTextRange( Min(selEnd, newSelEnd),
  1279.                                      Max(selEnd, newSelEnd) );
  1280.             
  1281.                 SetSelection( newSelStart, newSelEnd, kNoRedraw );
  1282.             
  1283.                 if (newSelStart == newSelEnd)
  1284.                     ShowCaret();
  1285.             
  1286.                 lastCharOffset = charOffset;    /* Remember current offset. */
  1287.                 
  1288.             }    /* charOffset ≠ lastCharOffset */
  1289.         
  1290.             /* Scroll text automatically while the user is selecting text: */
  1291.             
  1292.             AutoScroll( &autoScrollPoint );
  1293.         
  1294.         }    /* while StillDown() */
  1295.     
  1296.         /* Determine the anchor point for the selection: */
  1297.     
  1298.         itsSelAnchor = origCharOffset;
  1299.  
  1300.         /*
  1301.             For non-editable but selectable text, we want to display a selection
  1302.             range so the user can copy, but we don't want to display a caret:
  1303.         */
  1304.         if ( !this->editable && (itsSelStart == itsSelEnd) )
  1305.             HideCaret();
  1306.             
  1307.         /* Notify dependents that the selection has changed: */
  1308.         
  1309.         SelectionChanged();
  1310.     
  1311.     }    /* no <Option> key + gClicks = 1 */
  1312.     
  1313. }    /* DoClick */
  1314.  
  1315.  
  1316.  
  1317. /* OVERRIDE: */
  1318. void    CPStyleText::DoKeyDown (char theChar, Byte keyCode, EventRecord *macEvent)
  1319. {
  1320.     /*
  1321.         DoKeyDown is the common denominator even if the
  1322.         <CMD> key is pressed.  If the <CMD> key is pressed,
  1323.         CSwitchboard's DoKeyEvent will scan the MENU list
  1324.         for <CMD> key equivalents by calling _MenuKey
  1325.         and if it finds none will send a DoKeyDown message
  1326.         to the gGopher which, of course, is us.
  1327.         
  1328.         As a result, Chris' DoKeyDown is called below.  If we
  1329.         pressed a <CMD> Arrow key, Chris calls DoArrowKey.
  1330.         If not a <CMD> Arrow key, then he calls CAbstractText's
  1331.         DoKeyDown.  If an unmodified or <Option>/<Shift> Arrow
  1332.         key, then the latter method will call TypeChar.
  1333.         
  1334.         The point of all this is that since DoKeyDown is
  1335.         the common denominator as mentioned above, then here
  1336.         is where we must set up the special Caret flags.
  1337.         We also do it for mouse clicks within DoClick, of course.
  1338.     */
  1339.     
  1340.     
  1341.     fOldEndWrapLineCaret = fNewEndWrapLineCaret;
  1342.     fNewEndWrapLineCaret = FALSE;                    /* Assume NOT !!! */
  1343.  
  1344.     if (theChar == kEnterKey)
  1345.     {
  1346.         ScrollToSelection();
  1347.     }
  1348.     else
  1349.         CPEditText::DoKeyDown( theChar, keyCode, macEvent );
  1350.     
  1351. }    /* DoKeyDown */
  1352.  
  1353.  
  1354.  
  1355. /* OVERRIDE: */
  1356. void    CPStyleText::TypeChar (char theChar, short theModifiers)
  1357. {
  1358.         
  1359.     if ( IsArrowKey(theChar) )
  1360.     {
  1361.         /*
  1362.             Chris removed the call to SelectionChanged from
  1363.             DoArrowKey.  To compensate, he added this call
  1364.             to CPEditText's DoKeyDown, but seemingly forgot
  1365.             to do likewise within his TypeChar.
  1366.             
  1367.             WRONG !!!!!
  1368.             
  1369.             Look at CPEditText's DoKeyDown(…) and notice that
  1370.             if the <CMD> key is not pressed then CAbstractText's
  1371.             DoKeyDown is called.  The latter calls our TypeChar
  1372.             and follows with a call to SelectionChanged.  In
  1373.             short, the TCL calls SelectionChanged for us.
  1374.         */
  1375.         
  1376.         DoArrowKey( theChar, theModifiers );
  1377.     }
  1378.     
  1379.     else
  1380.     {
  1381.         /*
  1382.             DeleteTextRange, ReplaceSelection and InsertTextPtr
  1383.             eventually call my AdjustLineStarts.  The latter
  1384.             calls my WrapLineStarts which will re-draw text
  1385.             lines that were wrapped.
  1386.         */
  1387.         
  1388.             long        selStart = itsSelStart;
  1389.             long        selEnd     = itsSelEnd;
  1390.             Boolean        beyondWordWrapEdge;
  1391.             LongPt        selPt;
  1392.         
  1393.         /* Obscure the cursor so it is hidden until the mouse is moved */
  1394.         ObscureCursor();
  1395.         
  1396.         /* Now perform the typing */
  1397.         if (theChar == kBackspace)
  1398.         {
  1399.             if (selStart == selEnd && selStart > 0)
  1400.             {
  1401.                 HideCaret();
  1402.                 DeleteTextRange( selStart - 1, selStart, kRedraw );
  1403.                 ShowCaret();
  1404.             }
  1405.             else
  1406.                 DeleteTextRange( selStart, selEnd, kRedraw );
  1407.         }
  1408.         else
  1409.         {
  1410.             GetCharPoint( selStart, &selPt );
  1411.             beyondWordWrapEdge = ( selPt.h + CharWidth(theChar) > itsDestRect.right );
  1412.             if (selStart != selEnd)
  1413.             {
  1414.                 ReplaceSelection( &theChar, sizeof(char) );
  1415.                 
  1416.                 /*
  1417.                     ReplaceSelection passes FALSE to all its methods
  1418.                     for drawing, so make up for it here if required:
  1419.                 */
  1420.                 
  1421.                 if (beyondWordWrapEdge)
  1422.                 {
  1423.                     RefreshTextAfter( selStart, FALSE /* refreshOnlyLine */ );
  1424.                     
  1425.                 }    /* word-wrap needed */
  1426.                 
  1427.             }
  1428.             else
  1429.             {
  1430.                 /* InsertTextPtr addresses re-drawing for us: */
  1431.                 
  1432.                 InsertTextPtr( &theChar, sizeof(char), beyondWordWrapEdge );
  1433.             }
  1434.             
  1435.         }    /* NOT a backspace */
  1436.         
  1437.         /* Ensure that the insertion point is visible: */
  1438.         
  1439.         ScrollToOffset( itsSelStart );
  1440.         
  1441.     }    /* NOT an Arrow */
  1442.     
  1443. }    /* TypeChar */
  1444.  
  1445.  
  1446.  
  1447. /******************************************************************************
  1448.  DoArrowKey {OVERRIDE}
  1449.  
  1450.      Handle a cursor key.  The behavior is as follows:
  1451.          
  1452.                               modifier key
  1453.                               ============
  1454.                      <none>         Option         Command
  1455.                      ------         ------         -------
  1456.                          
  1457.         up            prev line    prev page    first line
  1458.         down        next line    next page    last line
  1459.         left        prev char    prev word    line start
  1460.         right        next char    next word    line end
  1461.           
  1462.     Override to select the end of a "sub-line" of a word-wrapped line.
  1463.          
  1464.  ******************************************************************************/
  1465.  
  1466. /* OVERRIDE: */
  1467. void    CPStyleText::DoArrowKey (char theChar, short theModifiers)
  1468. {
  1469.         Boolean            commandKeyDown = ( (theModifiers & cmdKey    ) != 0 ),
  1470.                         optionKeyDown  = ( (theModifiers & optionKey) != 0 ),
  1471.                         shiftKeyDown   = ( (theModifiers & shiftKey    ) != 0 ),
  1472.                         isInsertion, isUpDownArrow;
  1473.         register long    nonAnchor;
  1474.         long            selChar;
  1475.         long            numLines, selLine;
  1476.         long            selStart, selEnd;
  1477.         LongPt            charPt;
  1478.         tCharBuf        breakChar;
  1479.     
  1480.     
  1481.     if (optionKeyDown || shiftKeyDown)
  1482.     {
  1483.         CPEditText::DoArrowKey( theChar, theModifiers );
  1484.     }
  1485.     
  1486.     else if (theChar == kUpCursor || theChar == kDownCursor)
  1487.     {
  1488.         if (commandKeyDown)
  1489.         {
  1490.             CPEditText::DoArrowKey( theChar, theModifiers );
  1491.         }
  1492.         
  1493.         else
  1494.         {
  1495.             GetSelection( &selStart, &selEnd );
  1496.             numLines = GetNumLines();
  1497.     
  1498.             /* Determine if we have an insertion point or not */
  1499.     
  1500.             isInsertion = (selStart == selEnd);
  1501.     
  1502.             /* Reset the selection anchor position if necessary */
  1503.     
  1504.             if (isInsertion)
  1505.                 itsSelAnchor = selStart;
  1506.             else if (itsSelAnchor < 0)
  1507.             {
  1508.                 if (theChar == kUpCursor)
  1509.                     itsSelAnchor = selEnd;
  1510.                 else
  1511.                     itsSelAnchor = selStart;
  1512.             }
  1513.     
  1514.             /* Determine the non-anchored selection position */
  1515.     
  1516.             nonAnchor = (itsSelAnchor == selEnd ? selStart : selEnd);
  1517.     
  1518.             /* Now handle the keystroke */
  1519.     
  1520.             selLine = FindLine( nonAnchor );
  1521.         
  1522.             if ( (theChar == kUpCursor) && (selLine == 0) )
  1523.             {
  1524.                 selChar = 0;
  1525.                 ;
  1526.                 isUpDownArrow = FALSE;
  1527.             }
  1528.             
  1529.             else if ( (theChar == kDownCursor) && (selLine == numLines - 1) )
  1530.             {
  1531.                 selChar = itsTextLength;
  1532.                 ;
  1533.                 isUpDownArrow = FALSE;
  1534.             }
  1535.             
  1536.             else
  1537.             {
  1538.                 /* Somewhere in the middle of the text. */
  1539.                     
  1540.                 if (fOldEndWrapLineCaret)
  1541.                 {
  1542.                     /*
  1543.                         At end of a word-wrapped "sub-line".
  1544.                     
  1545.                         Further down we call GetCharOffset which sets the
  1546.                         fNewEndWrapLineCaret flag depending on whether or
  1547.                         not we're at the end of a wrapped "sub-line".
  1548.                         Upon re-entry to DoKeyDown, fOldEndWrapLineCaret
  1549.                         is set equal to fNewEndWrapLineCaret.
  1550.                     
  1551.                         See additional comments below when <CMD>-Right Arrow:
  1552.                     */
  1553.                     
  1554.                     if ( !fUpDownArrow )
  1555.                     {
  1556.                         /*
  1557.                             This instance Boolean = FALSE when the:
  1558.                             
  1559.                                 last Arrow keypress was a Right or Left Arrow    OR
  1560.                                 last Up Arrow after already at the top            OR
  1561.                                 last Down Arrow after already at the bottom        OR
  1562.                                 user clicked elsewhere                            OR
  1563.                                 user typed a non-Arrow character
  1564.                         
  1565.                             Note that "itsUpDownHOffset" stays defined as here
  1566.                             until we satisfy one of the five above conditions
  1567.                             AND eventually press an un-modified Up/Down Arrow.
  1568.                             Only then do we come back here to re-define this
  1569.                             instance variable and start fresh with a new value.
  1570.                         */
  1571.                     
  1572.                         selChar = nonAnchor - 1;
  1573.                         GetCharPoint( selChar, &charPt );
  1574.                         GetCharAfter( &selChar, breakChar );
  1575.                         itsUpDownHOffset = charPt.h + CharWidth( breakChar[1] );
  1576.  
  1577.                     }    /* !fUpDownArrow */
  1578.                     
  1579.                     if (theChar == kUpCursor)
  1580.                     {
  1581.                         /*
  1582.                             Chris' GetLineStart below will
  1583.                             compensate if we back up too far:
  1584.                         */
  1585.                         
  1586.                         selLine -= 2;
  1587.                     }
  1588.                     else
  1589.                     {
  1590.                      /*
  1591.                         ++selLine;  --  already there
  1592.                      */
  1593.                     }
  1594.  
  1595.                 }    /* fOldEndWrapLineCaret */
  1596.                 
  1597.                 else
  1598.                 {
  1599.                     if ( !fUpDownArrow )
  1600.                     {
  1601.                         GetCharPoint( nonAnchor, &charPt );
  1602.                         itsUpDownHOffset = charPt.h;
  1603.                     }
  1604.                 
  1605.                     if (theChar == kUpCursor)
  1606.                         --selLine;
  1607.                     else
  1608.                         ++selLine;
  1609.  
  1610.                 }    /* not at end of a wrapped "sub-line" */
  1611.  
  1612.                 GetCharPoint( GetLineStart(selLine), &charPt );
  1613.                 charPt.h = (long)itsUpDownHOffset;
  1614.                 selChar = GetCharOffset( &charPt );
  1615.  
  1616.                 isUpDownArrow = TRUE;
  1617.                     
  1618.             }    /* in the middle of the text */
  1619.         
  1620.             SetSelection( selChar, selChar, kRedraw );
  1621.  
  1622.             ScrollToOffset( selChar );
  1623.     
  1624.             fUpDownArrow = isUpDownArrow;
  1625.             
  1626.         }    /* unmodified kUpCursor or kDownCursor */
  1627.  
  1628.     }    /* kUpCursor || kDownCursor */
  1629.     
  1630.     else
  1631.     {
  1632.         GetSelection( &selStart, &selEnd );
  1633.         numLines = GetNumLines();
  1634.     
  1635.         /* Determine if we have an insertion point or not: */
  1636.     
  1637.         isInsertion = (selStart == selEnd);
  1638.     
  1639.         /* Reset the selection anchor position if necessary: */
  1640.     
  1641.         if (isInsertion)
  1642.             itsSelAnchor = selStart;
  1643.         else if (itsSelAnchor < 0)
  1644.         {
  1645.             if (theChar == kLeftCursor)
  1646.                 itsSelAnchor = selEnd;
  1647.             else
  1648.                 itsSelAnchor = selStart;
  1649.         }
  1650.     
  1651.         /*
  1652.             Determine the non-anchored selection
  1653.             position and its corresponding line:
  1654.         */
  1655.     
  1656.         nonAnchor = (itsSelAnchor == selEnd ? selStart : selEnd);
  1657.         selLine = FindLine( nonAnchor );
  1658.     
  1659.         /* Now handle the keystroke: */
  1660.     
  1661.         if (commandKeyDown)
  1662.         {            
  1663.             if (theChar == kLeftCursor)
  1664.             {
  1665.                 if (fOldEndWrapLineCaret)
  1666.                 {
  1667.                     /*
  1668.                         Former caret position at end of THIS "sub-line".
  1669.                         Remember, the end of a "sub-line" is really
  1670.                         the beginning of the next "sub-line".  See
  1671.                         comments about the selection point below:
  1672.                     */
  1673.                     
  1674.                     selLine = Max( selLine - 1, 0 );
  1675.                 }
  1676.                 
  1677.                 selChar = GetLineStart( selLine );
  1678.                 
  1679.             }    /* kLeftCursor */
  1680.             
  1681.             else
  1682.             {
  1683.                 selChar = GetLineEnd( selLine );
  1684.                 GetCharBefore( &selChar, breakChar );
  1685.                 if ( breakChar[0] )
  1686.                 {
  1687.                         unsigned char    ch = breakChar[1];
  1688.                         
  1689.                     if (ch != kReturn)
  1690.                     {
  1691.                         /*
  1692.                             Note that I do NOT count a <CR> as a word break
  1693.                             character.  This is because for a <CR> the
  1694.                             selection is BEFORE the <CR> and for this there
  1695.                             is no special Caret placement.
  1696.                             
  1697.                             Chris, however, does count the <CR> because
  1698.                             when double clicking on the last word of a line
  1699.                             ending in a <CR>, he doesn't want to select the
  1700.                             <CR> itself.  The only way, therefore, to delete
  1701.                             a <CR> is to select the beginning of the NEXT
  1702.                             line and then backspace.
  1703.                         */
  1704.                     
  1705.                         fNewEndWrapLineCaret = IsWordBreakChar( ch );
  1706.                         
  1707.                         /*
  1708.                             Note that GetCharBefore decrements selChar.
  1709.                             selChar should remain decremented for a line
  1710.                             ending with a <CR>.  Otherwise, we need to
  1711.                             increment the selection point back to beyond
  1712.                             any other character so both backspacing and
  1713.                             my DrawCaret method function properly:
  1714.                         */
  1715.                         ++selChar;
  1716.                         
  1717.                     }    /* not a <CR> */
  1718.                 }
  1719.                 
  1720.             }    /* kRightCursor */
  1721.             
  1722.         }    /* <CMD> key */
  1723.         
  1724.         /*
  1725.             We've eliminated the <Option> and <Shift> keys by
  1726.             going directly to Chris' method above, so what's
  1727.             left must be just the plain, un-modified Arrow key:
  1728.         */
  1729.             
  1730.         else if (isInsertion)
  1731.         {
  1732.             if (theChar == kLeftCursor)
  1733.             {
  1734.                 selChar = Min( Max(nonAnchor - 1, 0), itsTextLength );
  1735.             }
  1736.             
  1737.             else
  1738.             {
  1739.                 /*
  1740.                     Here we're going PAST the line's end whereas with
  1741.                     <CMD>-Right Arrow above we went TO the line's end:
  1742.                 */
  1743.                 
  1744.                 selChar = Min( Max(nonAnchor + 1, 0), itsTextLength );
  1745.                 if (selChar == GetLineEnd(selLine) )
  1746.                 {
  1747.                     GetCharBefore( &selChar, breakChar );
  1748.                     if ( breakChar[0] )
  1749.                     {
  1750.                             unsigned char    ch = breakChar[1];
  1751.                             
  1752.                         fNewEndWrapLineCaret = (ch != kReturn)        &&
  1753.                                                IsWordBreakChar( ch );
  1754.                     }
  1755.                     
  1756.                     /*
  1757.                         Remember that GetCharBefore decrements selChar and
  1758.                         that the selection point is beyond the last char.
  1759.                         
  1760.                         If the last character is a word break character,
  1761.                         the new selection point is really the beginning of
  1762.                         the NEXT "sub-line".  My DrawCaret method, however,
  1763.                         will position the Caret at the end of THIS "sub-line".
  1764.                         
  1765.                         If we're going past a <CR>, the new selection is the
  1766.                         beginning of the next whole line and that's where
  1767.                         the Caret is drawn.
  1768.                     */
  1769.                     
  1770.                     ++selChar;
  1771.                     
  1772.                 }    /* at the end */
  1773.                 
  1774.             }    /* kRightCursor */
  1775.             
  1776.         }    /* isInsertion */
  1777.         
  1778.         else
  1779.             selChar = ((theChar == kLeftCursor) ? selStart : selEnd);
  1780.     
  1781.         SetSelection( selChar, selChar, kRedraw );
  1782.  
  1783.         ScrollToOffset( selChar );
  1784.     
  1785.         fUpDownArrow = FALSE;
  1786.  
  1787.     }    /* left, right Arrow or <CMD> left, right Arrow */
  1788.  
  1789. }    /* DoArrowKey */
  1790.  
  1791.  
  1792.  
  1793. /**** T E X T   S P E C I F I C A T I O N   M E T H O D S ****/
  1794.  
  1795.  
  1796. /* OVERRIDE: */
  1797. void    CPStyleText::SetTextPtr (Ptr textPtr, long numChars)
  1798. {
  1799.     /*
  1800.         SetTextPtr is typically called to install text
  1801.         data into a window.  CAbstractText's SetWholeLines
  1802.         is typically called by its ResizeFrame.  However,
  1803.         until text data is installed, there are NO lines
  1804.         so SetWholeLines does nothing.  Here is our first
  1805.         chance to resize the frame when there ARE lines.
  1806.         
  1807.         One other general series of comments ...
  1808.         
  1809.         A lot of methods call AdjustBounds but withOUT calling
  1810.         SetWholeLines first.  The problem here centers on the
  1811.         fact that SetWholeLines resets the frame size based on
  1812.         line count and height that vary with typing or inserting
  1813.         text which actions eventually call AdjustLineStarts.
  1814.         Line count and height also vary with font size etc. and
  1815.         these actions eventually call CalcLineHeight.  Both of
  1816.         these methods call AdjustBounds.
  1817.         
  1818.         WithOUT calling SetWholeLines first, AdjustBounds calls
  1819.         SetBounds which will call CScrollPane::AdjustScrollMax
  1820.         to adjust the scroll bars maximum scroll position based
  1821.         on an NON-updated frame, thus messing up the works.
  1822.     */
  1823.     
  1824.     
  1825.     CPEditText::SetTextPtr( textPtr, numChars );
  1826.     
  1827.     SetWholeLines( wholeLines );
  1828.     
  1829. }    /* SetTextPtr */
  1830.  
  1831.  
  1832.  
  1833. /* OVERRIDE: */
  1834. void    CPStyleText::ReplaceSelection (Ptr replacePtr, long replaceLen)
  1835. {
  1836.         Boolean        refresh;
  1837.         long        selStart;
  1838.         long        selEnd;
  1839.         long        numReplaceCRs;
  1840.         long        numInsertCRs;
  1841.         long        numLinesDelta;
  1842.     
  1843.     
  1844.     CheckInsertion( replacePtr, replaceLen, kUseSelection,
  1845.                     &numInsertCRs, &numReplaceCRs );
  1846.     
  1847.     /* Delete the current selection, if any */
  1848.     
  1849.     GetSelection( &selStart, &selEnd );
  1850.     if (selStart < selEnd)
  1851.     {
  1852.         DeleteText( selStart, selEnd, numReplaceCRs, kNoRedraw );
  1853.         numLinesDelta = -numReplaceCRs;
  1854.         /*
  1855.             Passing FALSE to DeleteText does
  1856.             NOT call RefreshTextAfter:
  1857.         */
  1858.         refresh = TRUE;
  1859.     }
  1860.     else
  1861.     {
  1862.         numLinesDelta = 0;
  1863.         refresh = FALSE;
  1864.     }
  1865.     
  1866.     /* Insert the new text, if any */
  1867.     
  1868.     if ((replacePtr != NULL) && (replaceLen > 0))
  1869.     {
  1870.         InsertText( replacePtr, replaceLen, numInsertCRs, kNoRedraw );
  1871.      /*
  1872.         Called by InsertText:
  1873.         
  1874.         SetSelection( selStart + replaceLen, selStart + replaceLen, kNoRedraw );
  1875.      */
  1876.         /*
  1877.             Passing FALSE to InsertText
  1878.             DOES call RefreshTextAfter:
  1879.         */
  1880.         refresh = FALSE;
  1881.     }
  1882.     
  1883.     /* Refresh the text if necessary */
  1884.     
  1885.     if (refresh)
  1886.         RefreshTextAfter( selStart, (numLinesDelta == 0) );
  1887.     
  1888. }    /* ReplaceSelection */
  1889.  
  1890.  
  1891.  
  1892. /**** W O R D   W R A P   M E T H O D S ****/
  1893.  
  1894.  
  1895. /* OVERRIDE: */
  1896. void    CPStyleText::SetWordWrap (Boolean doWrap, long lineLengthInChars)
  1897. {
  1898.     /*
  1899.         The calling sequence is:
  1900.         
  1901.             SetMargins(…);
  1902.             SetWordWrap(…);
  1903.             DoWordWrap();
  1904.     */
  1905.     
  1906.         
  1907.     fWordWrap = doWrap;
  1908.     
  1909.     if (doWrap)
  1910.     {
  1911.         /* If we're wrapping to the Window, then the passed length <= 0: */
  1912.         
  1913.         if (lineLengthInChars <= 0)
  1914.         {
  1915.             SetAlignCmd( cmdAlignLeft );    /* See comments within UpdateMenus. */
  1916.             
  1917.             if (lineWidth != -1)
  1918.             {
  1919.                 /*
  1920.                     Previously set to other than wrap-to-window-width.
  1921.                     Scroll home so word wrap can be properly setup:
  1922.                 */
  1923.                 
  1924.                     LongPt        homePos;
  1925.                     
  1926.                 GetHomePosition( &homePos );
  1927.                 ScrollTo( &homePos, TRUE );
  1928.             
  1929.                 /*
  1930.                     SetAlignCmd does nothing if already wrapped to window
  1931.                     width.  So set lineWidth = 1 AFTER SetAlignCmd:
  1932.                 */
  1933.                 lineWidth = -1;
  1934.             }
  1935.         }    
  1936.         else
  1937.         {
  1938.             /* lineLengthInChars > 0 */
  1939.             
  1940.                 FontInfo    fInfo;
  1941.             
  1942.             GetMacFontInfo( &fInfo );
  1943.             lineWidth = lineLengthInChars * fInfo.widMax;
  1944.         }
  1945.     }
  1946.     else
  1947.     {
  1948.         /* Word wrap OFF: */
  1949.         
  1950.         lineWidth = itsRightMargin;
  1951.     }
  1952.     
  1953.     autoRefresh = (lineWidth == -1);
  1954.         
  1955. }    /* SetWordWrap */
  1956.  
  1957.  
  1958.  
  1959. /* OVERRIDE: */
  1960. Boolean        CPStyleText::GetWordWrap (void)
  1961. {
  1962.     /*
  1963.         You can retrieve the wrap line width
  1964.         via CAbstractText's "lineWidth".
  1965.     */
  1966.     
  1967.     
  1968.     return    (fWordWrap);
  1969.     
  1970. }    /* GetWordWrap */
  1971.  
  1972.  
  1973.  
  1974. /* OVERRIDE: */
  1975. void    CPStyleText::DoWordWrap (void)
  1976. {
  1977.     /*
  1978.         The calling sequence is:
  1979.         
  1980.             SetMargins(…);
  1981.             SetWordWrap(…);
  1982.             DoWordWrap();
  1983.     */
  1984.     
  1985.     
  1986.     ForceNextPrepare();
  1987.         
  1988.     /*
  1989.         CalcPERects adjusts the CPStyleText pane's
  1990.         destRect.right to the window's interior if
  1991.         its lineWidth is negative or, if positive,
  1992.         to the specified lineWidth.
  1993.     */
  1994.     CalcPERects();    
  1995.  
  1996.     /*
  1997.         RecalcLineStarts adjusts itsNumLines and the
  1998.         itsLineStarts array if required to effect
  1999.         word-wrap to the new "itsDestRect".  It then
  2000.         determines the new bounds based on these data
  2001.         by calling my AdjustBounds():
  2002.     */
  2003.     RecalcLineStarts();
  2004.     
  2005.     Refresh();
  2006.     
  2007. }    /* DoWordWrap */
  2008.  
  2009.  
  2010.  
  2011. /**** S T Y L I N G   M E T H O D S ****/
  2012.  
  2013.  
  2014. /* OVERRIDE: */
  2015. void    CPStyleText::SetSpacingCmd (long aSpacingCmd )
  2016. {
  2017.     /*
  2018.         CPEditText does NOT support variable spacing,
  2019.         so we need to "roll our own".  Get spacing
  2020.         either for the current paragraph if nothing
  2021.         is selected or for current selection:
  2022.     */
  2023.     
  2024.         long            oldSpacingCmd, startLine, endLine, i,
  2025.                         selStart, selEnd;
  2026.         TEStyleHandle    styleHdl;
  2027.         LHHandle        lineHeightHdl;
  2028.         SignedByte        hState;
  2029.         short            lineHt,
  2030.                         extra;                    /* Extra space between lines. */
  2031.         StScrpHandle    scrapHdl;
  2032.         Handle            theText;
  2033.         
  2034.         
  2035.     CPEditText::SetSpacingCmd( aSpacingCmd );
  2036.  
  2037. }    /* SetSpacingCmd */
  2038.  
  2039.  
  2040.  
  2041. /* OVERRIDE: */
  2042. void    CPStyleText::SetAlignCmd (long anAlignCmd)
  2043. {
  2044.  
  2045.     /* See comments within UpdateMenus: */
  2046.     
  2047.     if (lineWidth == -1)    return;
  2048.     
  2049.     if (itsAlignCmd != anAlignCmd)
  2050.     {
  2051.         itsAlignCmd = anAlignCmd;
  2052.     
  2053.         SetAlignment( (short)anAlignCmd );
  2054.     
  2055.         Refresh();
  2056.     }
  2057.     
  2058. }    /* SetAlignCmd */
  2059.  
  2060.  
  2061.  
  2062. /* OVERRIDE: */
  2063. void    CPStyleText::SetAlignment (short anAlignment )
  2064. {
  2065.     /* Set the alignment ONLY for the current paragraph: */
  2066.     
  2067.         long        selStart, selEnd;
  2068.         
  2069.     
  2070.     switch    (anAlignment)
  2071.     {
  2072.         case cmdAlignRight:
  2073.         
  2074.             break;
  2075.             
  2076.         case cmdAlignCenter:
  2077.         
  2078.             break;
  2079.  
  2080.         case cmdAlignLeft:
  2081.         
  2082.             break;
  2083.         
  2084.         case cmdJustify:
  2085.         
  2086.             break;
  2087.     }
  2088.     
  2089. }    /* SetAlignment */
  2090.  
  2091.  
  2092.  
  2093. /* OVERRIDE: */
  2094. long    CPStyleText::GetAlignCmd (void)
  2095. {
  2096.  
  2097.     return    (itsAlignCmd);
  2098.     
  2099. }    /* GetAlignCmd */
  2100.  
  2101.  
  2102.  
  2103. /* OVERRIDE: */
  2104. StScrpHandle    CPStyleText::GetStyleScrap (void)
  2105. {
  2106.     /*
  2107.         For now, just duplicate CTextStyleTask's SaveStyle:
  2108.     */
  2109.     
  2110.     
  2111.         StScrpHandle        theScrap      = NULL;
  2112.         CTextStyleTask        *theStyleTask = (CTextStyleTask*) itsLastTask;
  2113.         TextStyle            style;
  2114.         short                styleAttrib;
  2115.  
  2116.  
  2117.     styleAttrib = doAll;
  2118.     GetTextStyle( &styleAttrib, &style );
  2119.     theStyleTask->oldStyle = style;
  2120.     
  2121.     return    (theScrap);
  2122.     
  2123. }    /* GetStyleScrap */
  2124.  
  2125.  
  2126.  
  2127. /* OVERRIDE: */
  2128. void    CPStyleText::SetStyleScrap (long rangeStart, long rangeEnd,
  2129.                                     StScrpHandle styleScrap, Boolean redraw)
  2130. {
  2131.     /*
  2132.         For now, just duplicate CTextStyleTask's RestoreStyle:
  2133.     */
  2134.     
  2135.     
  2136.         CTextStyleTask        *theStyleTask = (CTextStyleTask*) itsLastTask;
  2137.         long                alignCmd, spacingCmd; 
  2138.  
  2139.  
  2140.     Prepare();
  2141.  
  2142.     switch (theStyleTask->styleAttribute)
  2143.     {
  2144.         case doFont:
  2145.             SetFontNumber( theStyleTask->oldStyle.tsFont );
  2146.             break;
  2147.             
  2148.         case doFace:
  2149.             SetFontStyle( 1 << (theStyleTask->styleCmd - cmdBold) );
  2150.             break;
  2151.             
  2152.         case doSize:
  2153.             SetFontSize( theStyleTask->oldStyle.tsSize );
  2154.             break;
  2155.             
  2156.         case doAlign:
  2157.             alignCmd = GetAlignCmd();
  2158.             SetAlignCmd( theStyleTask->oldAlignCmd );
  2159.             theStyleTask->oldAlignCmd = alignCmd;
  2160.             break;
  2161.             
  2162.         case doSpacing:
  2163.             spacingCmd = GetSpacingCmd();
  2164.             SetSpacingCmd( theStyleTask->oldSpacingCmd );
  2165.             theStyleTask->oldSpacingCmd = spacingCmd;
  2166.             break;
  2167.  
  2168.     }    /* switch */
  2169.  
  2170.     /*
  2171.         See comments within SetTextPtr method:
  2172.     */
  2173.     
  2174.     SetWholeLines( wholeLines );
  2175.     AdjustBounds();
  2176.  
  2177. }    /* SetStyleScrap */
  2178.  
  2179.  
  2180.  
  2181. /******************************************************************************
  2182.     Note that the line numbers passed to the following six
  2183.     methods are 0-based just as is Chris' "itsLineStarts"
  2184.     Handle.  However, because the various CArray methods
  2185.     such as GetItem and InsertAtIndex are 1-based, so are
  2186.     the passed style indices.
  2187.  ******************************************************************************/
  2188.  
  2189.     static long        Convert (CObject *theObject, long num)
  2190.     {
  2191.     
  2192.         if ( member(theObject, CPStyleText) )
  2193.         {
  2194.             if (num == atBeginning)
  2195.                 return    (0L);
  2196.             else if (num == atEnd)
  2197.                 return    ( ((CPStyleText*) theObject)->itsNumLines - 1 );
  2198.             else
  2199.                 return    (num);
  2200.         }
  2201.         
  2202.         else if ( member(theObject, CArray) )
  2203.         {
  2204.             if (num == atBeginning)
  2205.                 return    (1L);
  2206.             else if (num == atEnd)
  2207.                 return    ( ((CArray*) theObject)->GetNumItems() );
  2208.             else
  2209.                 return    (num);
  2210.         }
  2211.         
  2212.         else
  2213.         {
  2214.             ASSERT(
  2215.                      member(theObject, CPStyleText)
  2216.                             ||
  2217.                      member(theObject, CArray)
  2218.                   );
  2219.         }
  2220.         
  2221.     }    /* Convert */
  2222.     
  2223.     
  2224.     
  2225. /* OVERRIDE */
  2226. void    CPStyleText::AddLine (long afterLine)
  2227. {
  2228.         long            lineNum, styleInLine;
  2229.         CArray            *newStyles = NULL;
  2230.         StyleRecord        newStyleInfo;
  2231.         LineRecord        newLineInfo;
  2232.         
  2233.     
  2234.     ASSERT( itsLineInfo != NULL );
  2235.  
  2236.     ASSERT( itsNumLines < MAX_LINES );
  2237.  
  2238.     /*
  2239.         First fill in the component StyleRecord and then
  2240.         fill in the remaining parts of the LineRecord item.
  2241.     */
  2242.     
  2243.     lineNum = Convert( this, afterLine );
  2244.     
  2245.     newStyles = new (CArray);
  2246.     newStyles->IArray( sizeof(StyleRecord) );
  2247.  
  2248.     if (itsTextLength == 0)
  2249.     {
  2250.         /* A brand new text window: */
  2251.         
  2252.      // newLineInfo.lStyles            = newStyles;  --  later ...
  2253.         newLineInfo.lSpacingCmd        = itsSpacingCmd;
  2254.         newLineInfo.lHeight            = itsLineHeight;
  2255.         newLineInfo.lJustAmount        = macPort->spExtra;
  2256.         newLineInfo.lTabSpaces        = itsTabSpaces;
  2257.         newLineInfo.lTabWidth        = itsTabWidth;
  2258.  
  2259.         newStyleInfo.sStart            = GetLineEnd( lineNum );
  2260.         newStyleInfo.sEnd            = newStyleInfo.sStart;
  2261.         newStyleInfo.sHeight        = itsLineHeight;
  2262.         newStyleInfo.sFont            = itsTextFont;
  2263.         newStyleInfo.sSize            = itsTextSize;
  2264.         newStyleInfo.sFace            = itsTextFace;
  2265.         newStyleInfo.sMode            = itsTextMode;
  2266.         newStyleInfo.sColor.red        = 0;                    /* Black ... */
  2267.         newStyleInfo.sColor.green    = 0;
  2268.         newStyleInfo.sColor.blue    = 0;
  2269.         newStyleInfo.sMaxCharWidth    = itsMaxCharWidth;
  2270.     }
  2271.     
  2272.     else
  2273.     {
  2274.         /*
  2275.             Duplicate either the first line or
  2276.             continue from the previous line ...
  2277.         */
  2278.         
  2279.         /*
  2280.             CArray's GetItem _BlockMove's the
  2281.             array element into our local struct:
  2282.         */
  2283.         itsLineInfo->GetItem( &newLineInfo, lineNum + 1 );
  2284.         if (afterLine == atBeginning)
  2285.             styleInLine = 1;
  2286.         else
  2287.             styleInLine = newLineInfo.lStyles->GetNumItems();
  2288.         newLineInfo.lStyles->GetItem( &newStyleInfo, styleInLine );
  2289.         
  2290.         /* Now, change the needed fields: */
  2291.         
  2292.         if (afterLine == atBeginning)
  2293.             newStyleInfo.sStart    = GetLineStart( lineNum );
  2294.         else
  2295.             newStyleInfo.sStart    = GetLineEnd( lineNum );
  2296.         newStyleInfo.sEnd    = newStyleInfo.sStart;
  2297.         
  2298.     }    /* text length > 0 */
  2299.  
  2300.     /*
  2301.         Now stuff the copies in place ...
  2302.     */
  2303.     
  2304.     newStyles->InsertAtIndex( &newStyleInfo, 1 );        /* Make it the first item. */
  2305.     newLineInfo.lStyles = newStyles;
  2306.     if (afterLine == atBeginning)
  2307.         itsLineInfo->InsertAtIndex( &newLineInfo, 1 );    /* Ditto                   */
  2308.     else
  2309.         itsLineInfo->InsertAtIndex( &newLineInfo, lineNum + 2 );
  2310.     
  2311. }    /* AddLine */
  2312.  
  2313.  
  2314.  
  2315. /* OVERRIDE */
  2316. void    CPStyleText::DeleteLine (long lineNum)
  2317. {
  2318.     /*
  2319.         This method is called when we backspace over a <CR>.
  2320.         We need to merge the styles from the PREVIOUS line,
  2321.         if any, with the styles of THIS line.  The PREVIOUS
  2322.         line is the line whose trailing <CR> we are deleting
  2323.         and THIS line is the line we're deleting.
  2324.         
  2325.         Remember ... itsLineInfo is an ordered array of
  2326.         LineRecords each of which corresponds to the text
  2327.         in a <CR>-delimited line.  In short ... word wrapping
  2328.         does NOT affect this array.
  2329.     */
  2330.     
  2331.         LineRecord        prevLineInfo, thisLineInfo;
  2332.         StyleRecord        styleInfo;
  2333.         long            numStylesInThisLine, styleIndex;
  2334.  
  2335.  
  2336.     ASSERT( itsLineInfo != NULL );
  2337.  
  2338.     lineNum = Convert( this, lineNum );
  2339.     
  2340.     itsLineInfo->GetItem( &thisLineInfo, lineNum + 1 );
  2341.     
  2342.     if (lineNum > 0)
  2343.     {
  2344.         /* There IS a previous line ... */
  2345.         
  2346.         numStylesInThisLine = thisLineInfo.lStyles->numItems;
  2347.         
  2348.         for (styleIndex = 1; styleIndex <= numStylesInThisLine; ++styleIndex)
  2349.         {
  2350.             /* Merge styles in THIS line to those in PREVIOUS line: */
  2351.             
  2352.             GetStyleInfo( lineNum, styleIndex, &styleInfo );    /* THIS line     */
  2353.             AddStyle( lineNum - 1, atEnd, &styleInfo );            /* PREVIOUS line */
  2354.         }
  2355.     }
  2356.     
  2357.     /*
  2358.         Need to dump the LineRecord item's component StyleRecord
  2359.         CArray before we dispose of the entire LineRecord item:
  2360.     */
  2361.  
  2362.     ForgetObject( thisLineInfo.lStyles );
  2363.  
  2364.     itsLineInfo->DeleteItem( lineNum + 1 );
  2365.     
  2366. }    /* DeleteLine */
  2367.  
  2368.  
  2369.  
  2370. /* OVERRIDE */
  2371. void    CPStyleText::AddStyle (long lineNum, long afterStyleIndex,
  2372.                                StyleRecord *styleInfo)
  2373. {
  2374.     /*
  2375.         Remember ...
  2376.         
  2377.         ... lineNum is 0-based and afterStyleIndex is 1-based.
  2378.     */
  2379.     
  2380.         LineRecord        lineInfo;
  2381.         long            numStyles;
  2382.  
  2383.  
  2384.     ASSERT( itsLineInfo != NULL );
  2385.  
  2386.     lineNum = Convert( this, lineNum );
  2387.     itsLineInfo->GetItem( &lineInfo, lineNum + 1 );
  2388.  
  2389.     numStyles = lineInfo.lStyles->GetNumItems();
  2390.     ASSERT( numStyles < MAX_STYLES_PER_LINE );
  2391.  
  2392.  /*
  2393.     Because of the "after" in the passed index,
  2394.     Convert does not QUITE work ...
  2395.     
  2396.     afterStyleIndex = Convert( lineInfo.lStyles, afterStyleIndex );
  2397.  */
  2398.     if (afterStyleIndex == atBeginning)
  2399.         afterStyleIndex = 0;
  2400.     else if (afterStyleIndex == atEnd)
  2401.         afterStyleIndex = numStyles;
  2402.     lineInfo.lStyles->InsertAtIndex( styleInfo, afterStyleIndex + 1 );
  2403.         
  2404. }    /* AddStyle */
  2405.  
  2406.  
  2407.  
  2408. /* OVERRIDE */
  2409. void    CPStyleText::DeleteStyle (long lineNum, long styleIndex)
  2410. {
  2411.         LineRecord        lineInfo;
  2412.  
  2413.  
  2414.     ASSERT( itsLineInfo != NULL );
  2415.  
  2416.     lineNum = Convert( this, lineNum );
  2417.     itsLineInfo->GetItem( &lineInfo, lineNum + 1 );
  2418.     
  2419.     styleIndex = Convert( lineInfo.lStyles, styleIndex );
  2420.     lineInfo.lStyles->DeleteItem( styleIndex );
  2421.         
  2422. }    /* DeleteStyle */
  2423.  
  2424.  
  2425.  
  2426. /* OVERRIDE */
  2427. void    CPStyleText::GetStyleInfo (long lineNum, long styleIndex,
  2428.                                    StyleRecord *styleInfo)
  2429. {
  2430.         LineRecord        lineInfo;
  2431.         
  2432.         
  2433.     ASSERT( itsLineInfo != NULL );
  2434.  
  2435.     lineNum = Convert( this, lineNum );
  2436.     itsLineInfo->GetItem( &lineInfo, lineNum + 1 );            /* 0-based */
  2437.         
  2438.     styleIndex = Convert( lineInfo.lStyles, styleIndex );
  2439.     lineInfo.lStyles->GetItem( styleInfo, styleIndex );        /* 1-based */
  2440.     
  2441. }    /* GetStyleInfo */
  2442.  
  2443.  
  2444.  
  2445. /* OVERRIDE */
  2446. void    CPStyleText::SetStyleInfo (long lineNum, long styleIndex,
  2447.                                    StyleRecord *styleInfo)
  2448. {
  2449.         LineRecord        lineInfo;
  2450.         
  2451.         
  2452.     ASSERT( itsLineInfo != NULL );
  2453.  
  2454.     lineNum = Convert( this, lineNum );
  2455.     itsLineInfo->GetItem( &lineInfo, lineNum + 1 );
  2456.         
  2457.     styleIndex = Convert( lineInfo.lStyles, styleIndex );
  2458.     lineInfo.lStyles->SetItem( styleInfo, styleIndex );
  2459.     
  2460. }    /* SetStyleInfo */
  2461.  
  2462.  
  2463.  
  2464. /* OVERRIDE */
  2465. Boolean        CPStyleText::FindStyle (long startPos, long endPos,
  2466.                                     long *startLine, long *endLine,
  2467.                                     long *startIndex, long *endIndex)
  2468. {
  2469.     /*
  2470.         My overridden methods = SetFontNumber, SetFontSize etc.
  2471.         call FindStyle.  When FindStyle returns, the line numbers
  2472.         and indices corresponding to the passed positions are
  2473.         filled in.  These indices are the indices to the style
  2474.         runs which bracket the passed positions.
  2475.         
  2476.         FindStyle returns TRUE only if the passed positions match
  2477.         the positions of the already stored style runs exactly.
  2478.     */
  2479.     
  2480.         Boolean            foundExactStartIndex = FALSE,
  2481.                         foundExactEndIndex     = FALSE;
  2482.         CArray            *theStyles;
  2483.         LineRecord        lineInfo;
  2484.         StyleRecord        styleInfo;
  2485.         register long    startLn, endLn,
  2486.                         numStyles, styleIndex;
  2487.         
  2488.         
  2489.     ASSERT( itsLineInfo != NULL );
  2490.     
  2491.     startLn = *startLine = FindLine( startPos );
  2492.     endLn = *endLine = FindLine( endPos );
  2493.     
  2494.     itsLineInfo->GetItem( &lineInfo, startLn + 1 );
  2495.     theStyles = lineInfo.lStyles;
  2496.     numStyles = theStyles->GetNumItems();
  2497.     
  2498.     for (styleIndex = 1; styleIndex <= numStyles; ++styleIndex)
  2499.     {
  2500.         theStyles->GetItem( &styleInfo, styleIndex );
  2501.         if ( styleInfo.sStart == startPos )
  2502.         {
  2503.             /* An exact match ... */
  2504.                 
  2505.             *startIndex = styleIndex;            /* Current style run. */
  2506.             foundExactStartIndex = TRUE;
  2507.             break;
  2508.         }
  2509.             
  2510.         else if ( styleInfo.sStart > startPos )
  2511.         {
  2512.             if (styleIndex == 1)
  2513.                 *startIndex = styleIndex;        /*       ?????           */
  2514.             else
  2515.                 *startIndex = styleIndex - 1;    /* Previous style run. */
  2516.             break;
  2517.         }
  2518.  
  2519.     }    /* for-loop */
  2520.         
  2521.     if (styleIndex > numStyles)
  2522.     {
  2523.         *startIndex = atEnd;
  2524.         *endIndex = atEnd;
  2525.         
  2526.         /*
  2527.             No sense worrying about the end position
  2528.             since we can't even bracket the start position:
  2529.         */
  2530.         return    (FALSE);
  2531.     }
  2532.     
  2533.     /*
  2534.         Okay, we've got the startIndex, so
  2535.         let's continue with the endIndex ...
  2536.     */
  2537.     
  2538.     itsLineInfo->GetItem( &lineInfo, endLn + 1 );
  2539.     theStyles = lineInfo.lStyles;
  2540.     numStyles = theStyles->GetNumItems();
  2541.     
  2542.     for (styleIndex = 1; styleIndex <= numStyles; ++styleIndex)
  2543.     {
  2544.         theStyles->GetItem( &styleInfo, styleIndex );
  2545.         if ( styleInfo.sEnd == endPos )
  2546.         {
  2547.             /* An exact match ... */
  2548.                 
  2549.             *endIndex = styleIndex;                /* Current style run. */
  2550.             foundExactEndIndex = TRUE;
  2551.             break;
  2552.         }
  2553.             
  2554.         else if ( styleInfo.sEnd > endPos )
  2555.         {
  2556.             *endIndex = styleIndex;                /* Current style run. */
  2557.             break;
  2558.         }
  2559.  
  2560.     }    /* for-loop */
  2561.     
  2562.     if (styleIndex > numStyles)
  2563.         *endIndex = atEnd;
  2564.         
  2565.     return    ( foundExactStartIndex && foundExactEndIndex );
  2566.  
  2567. }    /* FindStyle */
  2568.  
  2569.  
  2570.  
  2571. /* OVERRIDE */
  2572. void    CPStyleText::SetFontNumber (short aFontNumber)
  2573. {
  2574.         Boolean            foundStyle, twoParts;
  2575.         long            startLine, endLine,
  2576.                         startIndex, endIndex,
  2577.                         saveSelEnd;
  2578.         register long    line, styleIndex, numStyles;
  2579.         LineRecord        lineInfo;
  2580.         CArray            *theStyles;
  2581.         StyleRecord        styleInfo;
  2582.     
  2583.     
  2584.     foundStyle = FindStyle( itsSelStart, itsSelEnd,
  2585.                             &startLine, &endLine,
  2586.                             &startIndex, &endIndex );
  2587.     
  2588.     itsLineInfo->GetItem( &lineInfo, startLine + 1 );
  2589.     theStyles = lineInfo.lStyles;
  2590.         
  2591.     if (startLine == endLine)
  2592.     {
  2593.         if (foundStyle)
  2594.         {
  2595.             /*
  2596.                 Change leading StyleRecord in the returned index
  2597.                 range and then delete the excess that follow, if any:
  2598.             */
  2599.         
  2600.             GetStyleInfo( startLine, startIndex, &styleInfo );
  2601.             styleInfo.sEnd = itsSelEnd;
  2602.             styleInfo.sFont = aFontNumber;
  2603.             SetStyleInfo( startLine, startIndex, &styleInfo );
  2604.         
  2605.             /* Always delete backwards: */
  2606.             for (styleIndex = endIndex; styleIndex >= startIndex + 1; --styleIndex)
  2607.             {                
  2608.                 theStyles->DeleteItem( styleIndex );
  2609.             }
  2610.             
  2611.         }    /* found exact match */
  2612.         
  2613.         else
  2614.         {
  2615.             if (startIndex != atEnd && endIndex != atEnd)
  2616.             {
  2617.                 /*
  2618.                     Change first and last StyleRecords in the returned index
  2619.                     range and then delete those in the middle, if any:
  2620.                 */
  2621.             
  2622.                 GetStyleInfo( startLine, startIndex, &styleInfo );
  2623.                 saveSelEnd = styleInfo.sEnd;
  2624.                 if (styleInfo.sStart != itsSelStart)
  2625.                 {
  2626.                     styleInfo.sEnd = itsSelStart;
  2627.                     SetStyleInfo( startLine, startIndex, &styleInfo );
  2628.                     ;
  2629.                     twoParts = TRUE;
  2630.                 }
  2631.                 else
  2632.                     twoParts = FALSE;
  2633.                 
  2634.                 if (endIndex != startIndex)
  2635.                 {
  2636.                     GetStyleInfo( startLine, endIndex, &styleInfo );
  2637.                     if (styleInfo.sEnd != itsSelEnd)
  2638.                     {
  2639.                         styleInfo.sStart = itsSelEnd;
  2640.                         SetStyleInfo( startLine, endIndex--, &styleInfo );
  2641.                     }
  2642.                     
  2643.                     for (
  2644.                            styleIndex = endIndex;
  2645.                            styleIndex >= startIndex + 1;
  2646.                            --styleIndex
  2647.                         )
  2648.                     {
  2649.                         theStyles->DeleteItem( styleIndex );
  2650.                     }
  2651.                     
  2652.                     GetStyleInfo( startLine, startIndex, &styleInfo );
  2653.                     styleInfo.sStart = itsSelStart;
  2654.                     styleInfo.sEnd = itsSelEnd;
  2655.                     styleInfo.sFont = aFontNumber;
  2656.                     if (twoParts)
  2657.                         AddStyle( startLine, startIndex, &styleInfo );
  2658.                     else
  2659.                         SetStyleInfo( startLine, startIndex, &styleInfo );
  2660.                     
  2661.                 }    /* endIndex ≠ startIndex */
  2662.                 
  2663.                 else
  2664.                 {
  2665.                     if (saveSelEnd != itsSelEnd)
  2666.                     {
  2667.                         styleInfo.sStart = itsSelStart;
  2668.                         styleInfo.sEnd = itsSelEnd;
  2669.                         styleInfo.sFont = aFontNumber;
  2670.                         if (twoParts)
  2671.                             AddStyle( startLine, startIndex++, &styleInfo );
  2672.                         else
  2673.                             SetStyleInfo( startLine, startIndex, &styleInfo );
  2674.                         ;
  2675.                         styleInfo.sStart = itsSelEnd;
  2676.                         styleInfo.sEnd = saveSelEnd;
  2677.                         AddStyle( startLine, startIndex, &styleInfo );
  2678.                     }
  2679.                     else
  2680.                     {
  2681.                         styleInfo.sStart = itsSelStart;
  2682.                      // styleInfo.sEnd = saveSelEnd;  --  already there!
  2683.                         styleInfo.sFont = aFontNumber;
  2684.                         if (twoParts)
  2685.                             AddStyle( startLine, startIndex, &styleInfo );
  2686.                         else
  2687.                             SetStyleInfo( startLine, startIndex, &styleInfo );
  2688.                     }
  2689.                     
  2690.                 }    /* endIndex = startIndex */
  2691.                 
  2692.             }    /* startIndex ≠ atEnd && endIndex ≠ atEnd */
  2693.             
  2694.             else if (startIndex == atEnd)
  2695.             {
  2696.                 GetStyleInfo( startLine, atEnd, &styleInfo );
  2697.                 styleInfo.sStart = itsSelStart;
  2698.                 styleInfo.sEnd = itsSelEnd;
  2699.                 styleInfo.sFont = aFontNumber;
  2700.                 AddStyle( startLine, atEnd, &styleInfo );
  2701.                 
  2702.             }    /* startIndex = atEnd */
  2703.             
  2704.             else
  2705.             {
  2706.                 GetStyleInfo( startLine, startIndex, &styleInfo );
  2707.                 if (styleInfo.sStart != itsSelStart)
  2708.                 {
  2709.                     styleInfo.sEnd = itsSelStart;
  2710.                     SetStyleInfo( startLine, startIndex++, &styleInfo );
  2711.                 }
  2712.                 
  2713.                 GetStyleInfo( startLine, atEnd, &styleInfo );
  2714.                 styleInfo.sStart = itsSelStart;
  2715.                 styleInfo.sEnd = itsSelEnd;
  2716.                 styleInfo.sFont = aFontNumber;
  2717.                 AddStyle( startLine, atEnd, &styleInfo );
  2718.                 
  2719.                 for (
  2720.                         styleIndex = endIndex;
  2721.                         styleIndex >= startIndex;
  2722.                         --styleIndex
  2723.                     )
  2724.                 {
  2725.                     theStyles->DeleteItem( styleIndex );
  2726.                 }
  2727.                 
  2728.             }    /* endIndex = atEnd */
  2729.             
  2730.         }    /* did NOT find exact match */
  2731.         
  2732.     }    /* startLine = endLine */
  2733.     
  2734.     else
  2735.     {
  2736.         /* Selection spans multiple lines: */
  2737.         
  2738.         GetStyleInfo( startLine, startIndex, &styleInfo );
  2739.  
  2740.         if (startIndex != atEnd)
  2741.         {
  2742.             /* We found the starting style ... */
  2743.             
  2744.             if (styleInfo.sStart != itsSelStart)
  2745.             {
  2746.                 styleInfo.sEnd = itsSelStart;
  2747.                 SetStyleInfo( startLine, startIndex--, &styleInfo );
  2748.                 ;
  2749.                 twoParts = TRUE;
  2750.             }
  2751.             else
  2752.                 twoParts = FALSE;
  2753.                 
  2754.             /*
  2755.                 We found at least the starting style, so we need
  2756.                 to delete from here to the end of the line.  Once
  2757.                 again, note we are deleting in reverse:
  2758.             */
  2759.             
  2760.             numStyles = theStyles->GetNumItems();        /* In startLine. */
  2761.             for (styleIndex = numStyles; styleIndex >= startIndex + 1; --styleIndex)
  2762.                 theStyles->DeleteItem( styleIndex );
  2763.         }
  2764.         else
  2765.             twoParts = FALSE;
  2766.         
  2767.         styleInfo.sStart = itsSelStart;
  2768.         styleInfo.sEnd = GetLineEnd( startLine );
  2769.         styleInfo.sFont = aFontNumber;
  2770.         if (twoParts)
  2771.             AddStyle( startLine, startIndex, &styleInfo );
  2772.         else
  2773.             SetStyleInfo( startLine, startIndex, &styleInfo );
  2774.         
  2775.         /* The startLine is now taken care of.  Next is the endLine: */
  2776.         
  2777.         itsLineInfo->GetItem( &lineInfo, endLine + 1 );
  2778.         theStyles = lineInfo.lStyles;
  2779.         
  2780.         GetStyleInfo( endLine, endIndex, &styleInfo );
  2781.         
  2782.         if (endIndex != atEnd)
  2783.         {
  2784.             if (styleInfo.sEnd != itsSelEnd)
  2785.             {
  2786.                 styleInfo.sStart = itsSelEnd;
  2787.                 SetStyleInfo( endLine, endIndex--, &styleInfo );
  2788.                 ;
  2789.                 twoParts = TRUE;
  2790.             }
  2791.             else
  2792.                 twoParts = FALSE;
  2793.  
  2794.             /*
  2795.                 We found at least the ending style, so we need
  2796.                 to delete from here to the start of the line:
  2797.             */
  2798.             
  2799.             for (styleIndex = endIndex; styleIndex >= 1; --styleIndex)
  2800.                 theStyles->DeleteItem( styleIndex );
  2801.         }
  2802.         else
  2803.             twoParts = FALSE;
  2804.         
  2805.         styleInfo.sStart = GetLineStart( startLine );
  2806.         styleInfo.sEnd = itsSelEnd;
  2807.         styleInfo.sFont = aFontNumber;
  2808.         if (twoParts)        AddStyle( startLine, atBeginning, &styleInfo );
  2809.         else                SetStyleInfo( startLine, atBeginning, &styleInfo );
  2810.         
  2811.         /* Finally, address all the lines inbetween: */
  2812.         
  2813.         for (line = startLine + 1; line <= endLine - 1; ++line)
  2814.         {
  2815.             itsLineInfo->GetItem( &lineInfo, line );
  2816.             theStyles = lineInfo.lStyles;
  2817.             numStyles = theStyles->GetNumItems();            /* In this line. */
  2818.             /*
  2819.                 Delete all styles AFTER the first:
  2820.             */
  2821.             for (styleIndex = numStyles; styleIndex >= 2; --styleIndex)
  2822.                 theStyles->DeleteItem( styleIndex );
  2823.             
  2824.             GetStyleInfo( line, 1, &styleInfo );
  2825.             styleInfo.sStart = GetLineStart( line );
  2826.             styleInfo.sEnd = GetLineEnd( line );
  2827.             styleInfo.sFont = aFontNumber;
  2828.             SetStyleInfo( line, 1, &styleInfo );
  2829.             
  2830.         }    /* for lines inbetween */
  2831.         
  2832.     }    /* startLine ≠ endLine */
  2833.     
  2834. }    /* SetFontNumber */
  2835.  
  2836.  
  2837.  
  2838. /* OVERRIDE */
  2839. void    CPStyleText::SetFontSize (short aSize)
  2840. {
  2841. }    /* SetFontSize */
  2842.  
  2843.  
  2844.  
  2845. /* OVERRIDE */
  2846. void    CPStyleText::SetFontStyle (short aStyle)
  2847. {
  2848. }    /* SetFontStyle */
  2849.  
  2850.  
  2851.  
  2852. /* OVERRIDE */
  2853. void    CPStyleText::SetTextMode (short aMode)
  2854. {
  2855. }    /* SetTextMode */
  2856.  
  2857.  
  2858.  
  2859. /**** C O M M A N D   M E T H O D S ****/
  2860.  
  2861.  
  2862. /* OVERRIDE */
  2863. void    CPStyleText::DoCommand (long theCommand)
  2864. {
  2865.  
  2866.     switch (theCommand)
  2867.     {
  2868.         case cmdJustify:
  2869.          // if (makeStyleTask && stylable)    --  which are TRUE !!!
  2870.          // {
  2871.                     CStylePStyleTask    *styleTask;
  2872.                     
  2873.                 itsTypingTask = NULL;
  2874.                 
  2875.                 styleTask = NULL;
  2876.                 styleTask = (CStylePStyleTask*) MakeStyleTask( theCommand );
  2877.                 itsLastTask = styleTask;
  2878.                 itsSupervisor->Notify( styleTask );
  2879.                 styleTask->Do();
  2880.          // }
  2881.  
  2882.             break;
  2883.             
  2884.         default:
  2885.             CPEditText::DoCommand( theCommand);
  2886.             break;
  2887.     }
  2888.  
  2889. }    /* DoCommand */
  2890.  
  2891.  
  2892.  
  2893. /* OVERRIDE */
  2894. void    CPStyleText::PerformEditCommand (long theCommand)
  2895. {
  2896.         Handle        textH;
  2897.         long        selStart;
  2898.         long        selEnd;
  2899.     
  2900.     
  2901.     Prepare();
  2902.     
  2903.     GetSelection( &selStart, &selEnd );
  2904.     
  2905.     /* Copy the selection range to the Clipboard for Cut or Copy */
  2906.     
  2907.     if (
  2908.           (theCommand == cmdCut || theCommand == cmdCopy)    &&
  2909.           (selStart != selEnd)
  2910.        )
  2911.     {
  2912.         textH = CopyTextRange( selStart, selEnd );
  2913.         TRY
  2914.         {
  2915.             gClipboard->EmptyScrap();
  2916.             gClipboard->PutData( 'TEXT', textH );
  2917.             DisposHandle( textH );
  2918.         }
  2919.         CATCH
  2920.         {
  2921.             ForgetHandle( textH );
  2922.         }
  2923.         ENDTRY
  2924.     }
  2925.     
  2926.     /* Delete the selection range for Cut or Clear */
  2927.     
  2928.     if (
  2929.           (theCommand == cmdCut || theCommand == cmdClear)    &&
  2930.           (selStart < selEnd)
  2931.        )
  2932.         DeleteTextRange( selStart, selEnd, kRedraw );
  2933.         
  2934.     /* Replace the current selection with the contents of the Clipboard for Paste */
  2935.     
  2936.     else if (
  2937.               (theCommand == cmdPaste)    &&
  2938.               gClipboard->GetData('TEXT', &textH)
  2939.             )
  2940.     {
  2941.             Boolean        beyondWordWrapEdge;
  2942.             long        txLength, txWidth;
  2943.             LongPt        selPt;
  2944.             
  2945.         /*
  2946.             Get existing point BEFORE we replace the selection.
  2947.             AFTER replacement, this will be different:
  2948.         */
  2949.         GetCharPoint( selStart, &selPt );
  2950.  
  2951.         HLockHi( textH );
  2952.         TRY
  2953.         {
  2954.             txLength = gClipboard->DataSize( 'TEXT' );
  2955.             txWidth = TextWidth( *textH, 0, (short) txLength );
  2956.             ReplaceSelection( *textH, txLength );
  2957.             DisposHandle( textH );
  2958.         }
  2959.         CATCH
  2960.         {
  2961.             ForgetHandle( textH );
  2962.         }
  2963.         ENDTRY
  2964.         
  2965.         beyondWordWrapEdge = ( selPt.h + txWidth > itsDestRect.right );
  2966.         if (beyondWordWrapEdge)
  2967.         {
  2968.             /*
  2969.                 ReplaceSelection only calls RefreshTextAfter ...
  2970.             */
  2971.             
  2972.             Refresh();
  2973.         }
  2974.         
  2975.     }    /* cmdPaste */
  2976.     
  2977.     /* Ensure that the insertion caret is visible */
  2978.     
  2979.     ScrollToOffset( itsSelStart );
  2980.     
  2981. }    /* PerformEditCommand */
  2982.  
  2983.  
  2984.  
  2985. /* OVERRIDE: */
  2986. void    CPStyleText::CaretHook (const Rect *caretRect)
  2987. {
  2988.     /* Change for the inactive case just BECAUSE ... */
  2989.     
  2990.         PenState    prevPenState;
  2991.     
  2992.     
  2993.     GetPenState( &prevPenState );
  2994.     PenMode( patXor );
  2995.     
  2996.     /* Draw the caret in gray if the pane isn't active: */
  2997.     
  2998.     if ( !fReallyActive && fOutlineHilite )
  2999.     {
  3000.         PenPat( gray );
  3001.     }
  3002.     
  3003.     /* Draw the caret: */
  3004.     
  3005.     if ((itsTextFace & italic) && fUseItalicCaret)
  3006.     {
  3007.         PenSize( 1, 1 );
  3008.         MoveTo( caretRect->left, caretRect->bottom );
  3009.         LineTo( caretRect->left + (caretRect->bottom - caretRect->top) / 2,
  3010.                 caretRect->top );
  3011.     }
  3012.     else
  3013.         PaintRect( caretRect );
  3014.     
  3015.     SetPenState( &prevPenState );
  3016.         
  3017. }    /* CaretHook */
  3018.  
  3019.  
  3020.  
  3021. /**** S C R O L L I N G   M E T H O D S ****/
  3022.  
  3023.  
  3024. /* OVERRIDE: */
  3025. void    CPStyleText::Scroll (long hDelta, long vDelta, Boolean redraw)
  3026. {
  3027.         long    hPixels, vPixels;
  3028.     
  3029.     
  3030.     hPixels = hDelta * hScale;
  3031.     vPixels = vDelta * vScale;
  3032.  
  3033.     OffsetLongRect( &itsViewRect, hPixels, vPixels );
  3034.         
  3035.     CPEditText::Scroll( hDelta, vDelta, redraw );
  3036.     
  3037. }    /* Scroll */
  3038.  
  3039.  
  3040.  
  3041. /* OVERRIDE: */
  3042. void    CPStyleText::ScrollToSelection (void)
  3043. {
  3044.     /*
  3045.         Change Chris' algorithm for computing the
  3046.         horizontal coord. of the new scroll position:
  3047.     */
  3048.     
  3049.     
  3050.         long        startLine, endLine;
  3051.         short        hSpan, vSpan;
  3052.         LongPt        selPos, selPt;
  3053.         long        selStart, selEnd;
  3054.     
  3055.     
  3056.     /* Calculate the number of panorama units spanned by the frame: */
  3057.     
  3058.     GetFrameSpan( &hSpan, &vSpan );
  3059.     
  3060.     /* Determine the starting and ending lines for the selection: */
  3061.     
  3062.     GetSelection( &selStart, &selEnd );
  3063.     startLine = FindLine( selStart );
  3064.     endLine = (selStart == selEnd ? startLine : FindLine(selEnd));
  3065.     
  3066.     /*
  3067.         Calculate the new vertical scroll position.
  3068.         This position is close to the vertical middle
  3069.         of the window unless it's already visible:
  3070.     */
  3071.     
  3072.     if (startLine >= position.v + vSpan)
  3073.     {
  3074.         selPos.v = startLine - (vSpan / 2) + 1;
  3075.         selPos.v = Max( selPos.v, 0 );
  3076.         selPos.v = Min( selPos.v, itsNumLines - vSpan );
  3077.     }
  3078.     else if (endLine < position.v)
  3079.     {
  3080.         selPos.v = endLine - (vSpan / 2) + 1;
  3081.         selPos.v = Max( selPos.v, 0 );
  3082.         selPos.v = Min( selPos.v, itsNumLines - vSpan );
  3083.     }
  3084.     else
  3085.     {
  3086.         /* Already visible: */
  3087.         
  3088.         selPos.v = position.v;
  3089.     }
  3090.     
  3091.     /*
  3092.         Calculate the new horizontal scroll position.
  3093.         When horizontal scrolling is permitted, this
  3094.         position is close to the horizontal middle
  3095.         of the window unless it's already visible or
  3096.         we've selected wrap-to-window-width.  In these
  3097.         last cases, do NOT scroll horizontally at all.
  3098.         If wrapping to window width, force the user to
  3099.         grow the window to get the selection in view:
  3100.  
  3101.     */
  3102.     
  3103.     if (!scrollHoriz)
  3104.     {
  3105.         /*
  3106.             Class instance variable set to
  3107.             turn OFF horizontal scrolling:
  3108.         */
  3109.         
  3110.         selPos.h = position.h;
  3111.     }
  3112.     else
  3113.     {
  3114.         /****   C H A N G E D   A L G O R I T H M   ****/
  3115.         
  3116.         if (lineWidth == -1)
  3117.         {
  3118.             /* wrap-to-window-width: */
  3119.             
  3120.             selPos.h = position.h;
  3121.         }
  3122.         else
  3123.         {
  3124.             GetCharPoint( endLine == startLine ? selEnd : (selStart + selEnd) / 2,
  3125.                           &selPt );
  3126.             selPos.h = selPt.h / hScale;
  3127.             if ( (selPos.h >= position.h) && (selPos.h <= position.h + hSpan) )
  3128.             {
  3129.                 /* Already visible, so do NOT scroll horizontally: */
  3130.             
  3131.                 selPos.h = position.h;
  3132.             }
  3133.             else
  3134.             {
  3135.                 /*
  3136.                     Scroll so the selection is close to
  3137.                     the horizontal middle of the window:
  3138.                 */
  3139.             
  3140.                 selPos.h = Max( selPos.h - hSpan / 2, 0 );
  3141.             }
  3142.             
  3143.         }    /* NOT wrapping to window width */
  3144.                 
  3145.     }    /* scrollHoriz is TRUE */
  3146.     
  3147.     /* Scroll the pane if necessary: */
  3148.     
  3149.     if ((selPos.v != position.v) || (selPos.h != position.h))
  3150.         ScrollTo( &selPos, kRedraw );
  3151.         
  3152. }    /* ScrollToSelection */
  3153.  
  3154.  
  3155.  
  3156. /* OVERRIDE: */
  3157. void    CPStyleText::ScrollToOffset (long charOffset)
  3158. {
  3159.     /*
  3160.         Change Chris' algorithm for computing the
  3161.         horizontal coord. of the new scroll position:
  3162.     */
  3163.     
  3164.     
  3165.         long        charLine;
  3166.         short        hSpan, vSpan;
  3167.         LongPt        selPos, selPt;
  3168.     
  3169.     
  3170.     /* Calculate the number of panorama units spanned by the frame: */
  3171.     
  3172.     GetFrameSpan( &hSpan, &vSpan );
  3173.     
  3174.     /* Calculate the new vertical scroll position: */
  3175.     
  3176.     charLine = FindLine( charOffset );
  3177.     if (charLine >= position.v + vSpan)
  3178.         selPos.v = charLine - vSpan + 1;
  3179.     else if (charLine < position.v)
  3180.         selPos.v = charLine;
  3181.     else
  3182.         selPos.v = position.v;
  3183.     
  3184.     /*
  3185.         Calculate the new horizontal scroll position.
  3186.         When horizontal scrolling is permitted, if
  3187.         we're already visible or we have selected
  3188.         wrap-to-window-width, do NOT scroll horizontally
  3189.         at all.  If wrapping to window width, force user
  3190.         to grow the window to get the offset in view:
  3191.     */
  3192.     
  3193.     if (!scrollHoriz)
  3194.     {
  3195.         /*
  3196.             Class instance variable set to
  3197.             turn OFF horizontal scrolling:
  3198.         */
  3199.         
  3200.         selPos.h = position.h;
  3201.     }
  3202.     else
  3203.     {
  3204.         /****   C H A N G E D   A L G O R I T H M   ****/
  3205.         
  3206.         if (lineWidth == -1)
  3207.         {
  3208.             /* wrap-to-window-width: */
  3209.             
  3210.             selPos.h = position.h;
  3211.         }
  3212.         else
  3213.         {
  3214.             GetCharPoint( charOffset, &selPt );
  3215.             selPos.h = selPt.h / hScale;
  3216.             if (selPos.h > position.h + hSpan)        /* NOT >= */
  3217.                 selPos.h -= (hSpan * 3) / 4;
  3218.             else if (selPos.h < position.h)
  3219.                 selPos.h -= hSpan / 4;
  3220.             else
  3221.                 selPos.h = position.h;                /* Already visible. */
  3222.                 
  3223.             selPos.h = Max(selPos.h, 0);
  3224.             
  3225.         }    /* NOT wrapping to window width */
  3226.         
  3227.     }    /* scrollHoriz is TRUE */
  3228.     
  3229.     /* Scroll the pane if necessary: */
  3230.     
  3231.     if ((selPos.v != position.v) || (selPos.h != position.h))
  3232.         ScrollTo( &selPos, kRedraw );
  3233.         
  3234. }    /* ScrollToOffset */
  3235.  
  3236.  
  3237.  
  3238. /**** P R I N T I N G   M E T H O D S ****/
  3239.  
  3240.  
  3241. /* OVERRIDE: */
  3242. void    CPStyleText::PrintPage (short pageNum, short pageWidth,
  3243.                                 short pageHeight, CPrinter *aPrinter)
  3244. {
  3245.  
  3246.     if (printClip == clipPAGE)
  3247.     {
  3248.         /*
  3249.             Expand viewRect to the size of a page.
  3250.             It will be restored by DonePrinting().
  3251.         */
  3252.         
  3253.         itsViewRect.right = itsViewRect.left + pageWidth;
  3254.         
  3255.         if (wholeLines)
  3256.             itsViewRect.bottom = itsViewRect.top + vScale * (pageHeight / vScale);
  3257.         else
  3258.             itsViewRect.bottom = itsViewRect.top + pageHeight;
  3259.         
  3260.     }    /* clipPAGE */
  3261.  
  3262.     CPEditText::PrintPage( pageNum, pageWidth, pageHeight, aPrinter );
  3263.  
  3264. }    /* PrintPage */
  3265.  
  3266.  
  3267.  
  3268. /* OVERRIDE: */
  3269. void    CPStyleText::DonePrinting (void)
  3270. {
  3271.  
  3272.     CPEditText::DonePrinting();
  3273.  
  3274.     /*
  3275.         TCL 1.1:
  3276.         
  3277.         Call CalcTERects (CalcPERects) rather
  3278.         than setting the viewRect directly.
  3279.     */
  3280.     
  3281.     CalcPERects();
  3282.     
  3283.     if (active)        HideSelection (FALSE /* show caret */, TRUE /* re-draw */ );
  3284.  
  3285. }    /* DonePrinting */
  3286.  
  3287.  
  3288.  
  3289. /**** I N T E R N A L   M E T H O D S ****/
  3290.  
  3291.  
  3292. /**** C O M M A N D   M E T H O D S ****/
  3293.  
  3294.  
  3295. /* OVERRIDE */
  3296. CTextStyleTask    *CPStyleText::MakeStyleTask (long styleCmd)
  3297. {
  3298.         CStylePStyleTask    *newTask = NULL;
  3299.         short                taskIndex;
  3300.     
  3301.     
  3302.     TRY
  3303.     {
  3304.         taskIndex = cFirstTaskIndex > 0 ? cFirstTaskIndex + undoFormatting : 0;
  3305.         newTask = new (CStylePStyleTask);
  3306.         
  3307.         newTask->IStylePStyleTask( this, styleCmd, taskIndex );
  3308.     }
  3309.     CATCH
  3310.     {
  3311.         ForgetObject( newTask );
  3312.     }
  3313.     ENDTRY;
  3314.     
  3315.     return    (newTask);
  3316.     
  3317. }    /* MakeStyleTask */
  3318.  
  3319.  
  3320.  
  3321. /**** D I S P L A Y   M E T H O D S ****/
  3322.  
  3323.  
  3324. /******************************************************************************
  3325.  CountRangeSPACEs
  3326.  
  3327.         Internal method to count space characters
  3328.         in the given range of text.
  3329.  ******************************************************************************/
  3330.  
  3331.     static long CountSPACEs (register Ptr textP, register long numChars)
  3332.     {
  3333.             register Byte    space      = kSpace;
  3334.             register long    numSPACEs = 0;
  3335.     
  3336.     
  3337.         while (numChars--)
  3338.         {
  3339.             if (*textP++ == space)
  3340.                 ++numSPACEs;
  3341.         }
  3342.     
  3343.         return (numSPACEs);
  3344.     
  3345.     }    /* CountSPACEs */
  3346.  
  3347.  
  3348.  
  3349. long    CPStyleText::CountRangeSPACEs (long start, long end)
  3350. {
  3351.     /*
  3352.         Counts the number of spaces in the given range of text.
  3353.     */
  3354.     
  3355.     
  3356.         Ptr        textP        = *itsTextHandle;
  3357.         long    length        = end - start;
  3358. #if qPEUseInsertionGap
  3359.         long    gapPosition    = itsGapPosition;
  3360.         long    gapLength    = itsGapLength;
  3361. #endif
  3362.         long    spaceCount;
  3363.     
  3364.     
  3365.     if (start >= end)
  3366.         spaceCount = 0;
  3367. #if qPEUseInsertionGap
  3368.     else if (end <= gapPosition)
  3369.         spaceCount = CountSPACEs( textP + start, length );
  3370.     else if (start >= gapPosition)
  3371.         spaceCount = CountSPACEs( textP + start + gapLength, length );
  3372.     else
  3373.     {
  3374.             long    lenBeforeGap = gapPosition - start;
  3375.             
  3376.         spaceCount = CountSPACEs( textP + start, lenBeforeGap );
  3377.         spaceCount += CountSPACEs( textP + start + gapLength + lenBeforeGap,
  3378.                                    length - lenBeforeGap );
  3379.     }
  3380. #else
  3381.     else
  3382.         spaceCount = CountSPACEs( textP + start, length );
  3383. #endif
  3384.     
  3385.     return    (spaceCount);
  3386.     
  3387. }    /* CountRangeSPACEs */
  3388.  
  3389.  
  3390.  
  3391. /* OVERRIDE: */
  3392. void    CPStyleText::DrawLineRange (long startLine, long endLine,
  3393.                                     long startLineOffset, Boolean erase)
  3394. {
  3395.         Boolean            showInvisibles = fShowInvisibles;
  3396.         char            textHState;
  3397.         register char    tab;
  3398.         Handle            invisH;
  3399.         long            line;
  3400.         long            lineStart;
  3401.         long            lineEnd;
  3402.         long            firstChar;
  3403.         long            boundsHExtent;
  3404.         long            horizInset       = HorizLeftInset();
  3405. #if qPEUseInsertionGap
  3406.         long            gapPosition       = itsGapPosition;
  3407.         long            gapLength       = itsGapLength;
  3408. #endif
  3409.         LongPt            textPt;
  3410.         LongRect        lr;
  3411.         Point            penPt;
  3412.         Point            startPt;
  3413. #if qPEUseInsertionGap
  3414.         Ptr                gapP;
  3415. #endif
  3416.         Rect            eraseRect;
  3417.         RgnHandle        clipRgn;
  3418.         register Ptr    textP;
  3419.         register short    numChars;
  3420.         register short    index;
  3421.         register short    tabWidth       = itsTabWidth;
  3422.         ShortHandle        widthsH           = NULL;
  3423.         short            width;
  3424.         short            *widthsP;
  3425.         short            fontAscent       = itsFontAscent;
  3426.         short            lineHeight;
  3427.     
  3428.     
  3429.     /* Lock down the text Handle: */
  3430.     
  3431.     textHState = HGetState( itsTextHandle );
  3432.     HLock( itsTextHandle );
  3433.  
  3434.     /* If we are erasing, save the current clipping region: */
  3435.     
  3436.     if (erase)
  3437.     {
  3438.         GetClip( cSaveClipRgn );
  3439.         clipRgn = NewRgn();
  3440.     }
  3441.     
  3442.     /* Compute the vertical coordinate for the first line of text: */
  3443.     
  3444.     textPt.v = GetHeight( 0, startLine - 1 ) + fontAscent + VertTopInset();
  3445.     
  3446.     /* Draw the specified lines of text: */
  3447.     
  3448.     for (line = startLine; line <= endLine; ++line)
  3449.     {
  3450.         lineHeight = GetHeight( line, line );        /* See comments within Draw. */
  3451.         
  3452.         /* Compute the number of characters to draw: */
  3453.         
  3454.         lineStart = GetLineStart( line );
  3455.         lineEnd = GetLineEnd( line );
  3456.         
  3457.         widthsH = MeasureLineWidths( line );
  3458.  
  3459.         if ((line == startLine) && (startLineOffset > 0))
  3460.             textPt.h = (*widthsH)[startLineOffset] + horizInset;
  3461.         else
  3462.             textPt.h = horizInset;
  3463.             
  3464.         /* See comments within GetCharPoint: */
  3465.         
  3466.         if (itsAlignCmd == cmdAlignLeft)
  3467.             textPt.h += itsLeftMargin;
  3468.         else if (itsAlignCmd == cmdAlignRight)
  3469.             textPt.h += HorizPixelExtent() - (*widthsH)[GetLineLength(line)];
  3470.         else if (itsAlignCmd == cmdAlignCenter)
  3471.             textPt.h += itsLeftMargin + (
  3472.                                            HorizPixelExtent() - itsLeftMargin -
  3473.                                            (*widthsH)[GetLineLength(line)]
  3474.                                         ) / 2;
  3475.         else
  3476.         {
  3477.             /*
  3478.                 For full justify alignment, the point where the
  3479.                 first text character is drawn is on the FAR left:
  3480.             */
  3481.             textPt.h += itsLeftMargin;
  3482.         }
  3483.         
  3484.         ForgetHandle( widthsH );        /* We're finished with this dude!!! */
  3485.  
  3486.         /* Erase the background behind the text, if desired: */
  3487.         
  3488.         if (erase)
  3489.         {
  3490.             SetLongRect( &lr,
  3491.                          (line == startLine && startLineOffset > 0 ?
  3492.                                                           textPt.h : frame.left),
  3493.                          textPt.v - fontAscent,
  3494.                          frame.right,
  3495.                          textPt.v - fontAscent + lineHeight );            
  3496.             FrameToQDR( &lr, &eraseRect );
  3497.             
  3498.             /* Adjust the clipping region so italic characters aren't clipped: */
  3499.  
  3500.             RectRgn( clipRgn, &eraseRect );
  3501.             SectRgn( clipRgn, cSaveClipRgn, clipRgn );
  3502.             SetClip( clipRgn );
  3503.         }
  3504.         
  3505.         firstChar = ( line == startLine && startLineOffset > 0 ?
  3506.                                      lineStart + startLineOffset :
  3507.                                      lineStart );
  3508.         numChars = lineEnd - firstChar;
  3509.         
  3510.         /*
  3511.             Convert invisible characters to their
  3512.             visible counterparts if desired:
  3513.         */
  3514.         if (showInvisibles)
  3515.         {
  3516.             invisH = CopyTextRange( firstChar, firstChar + numChars );
  3517.             numChars = ConvertInvisibles( invisH, numChars );
  3518.             HLock( invisH );
  3519.             textP = *invisH;
  3520. #if qPEUseInsertionGap
  3521.             gapP = NULL;
  3522.             gapPosition = gapLength = 0;
  3523. #endif
  3524.         }
  3525.         else
  3526.         {
  3527. #if qPEUseInsertionGap
  3528.             textP = *itsTextHandle;
  3529.             gapP = textP + gapPosition;
  3530.             textP += firstChar;
  3531.             if (firstChar > gapPosition)
  3532.                 textP += gapLength;
  3533. #else
  3534.             textP = *itsTextHandle + firstChar;
  3535. #endif
  3536.         }
  3537.         
  3538.         /* Now draw the line of text: */
  3539.         
  3540.         FrameToQD( &textPt, &startPt );
  3541.         penPt = startPt;
  3542.         MoveTo( penPt.h, penPt.v );
  3543.         if (erase)        EraseRect( &eraseRect );
  3544.         
  3545. #if THINK_C
  3546.         asm
  3547.         {
  3548.             clr.w    index                    ; reset index
  3549.             move.b    #kTab,tab                ; keep tab character in
  3550.                                             ;   register for speed
  3551.         @Loop:
  3552.             cmp.w    numChars,index            ; have we drawn all the text?
  3553.             bge.s    @Done                    ; branch if we have
  3554. #if qPEUseInsertionGap
  3555.             cmpa.l    gapP,textP                ; are we at the gap?
  3556.             bne.s    @NotAtGap                ; branch if not
  3557.             
  3558.         @AtGap:
  3559.             bsr.s    @DrawText                ; else draw the text to the
  3560.                                             ;   left of the gap
  3561.             adda.l    gapLength,textP            ; and skip over the gap
  3562.         
  3563.         @NotAtGap:
  3564. #endif
  3565.             addq.w    #1,index                ; else bump the index        
  3566.             cmp.b    (textP)+,tab            ; is character from text a tab?
  3567.             bne.s    @Loop                    ; loop if not
  3568.             
  3569.         @IsTab:
  3570.             subq.w    #1,textP                ; decrement ptr to text
  3571.             subq.w    #1,index                ; decrement # of characters to draw
  3572.             bsr.s    @DrawText                ; draw the text to the left of the tab
  3573.             
  3574.             movea.l    thePort,a0                ; get current GrafPtr
  3575.             move.l    OFFSET(GrafPort,pnLoc)(a0),penPt ; get pen location
  3576.             
  3577.             tst.b    showInvisibles            ; are we displaying invisible characters?
  3578.             beq.s    @NoInvis1                ; branch if not
  3579.             move.w    #kInvisTab,-(a7)        ; else push invisible tab character
  3580.             _DrawChar                        ; and draw it
  3581.             
  3582.         @NoInvis1:
  3583.             moveq    #0,d0                    ; clear out all of D0
  3584.             move.w    penPt.h,d0                ; get horiz pen location
  3585.             sub.w    startPt.h,d0            ; subtract starting location
  3586.             divu.w    tabWidth,d0                ; divide by tab width
  3587.             addq.w    #1,d0                    ; add one to tab width
  3588.             mulu.w    tabWidth,d0                ; get pixel offset
  3589.             add.w    startPt.h,d0            ; add starting location
  3590.             
  3591.             move.w    d0,-(a7)                ; push horiz coordinate
  3592.             move.w    penPt.v,-(a7)            ; push vert coordinate
  3593.             _MoveTo                            ; move pen location
  3594.             
  3595.             addq.w    #1,textP                ; bump ptr to text
  3596.             subq.w    #1,numChars                ; subtract 1 from length of text
  3597.             bra.s    @Loop                    ; and loop
  3598.     
  3599.         @DrawText:
  3600.             tst.w    index                    ; any text to draw?
  3601.             beq.s    @NoText                    ; skip if not
  3602.             
  3603.             movea.l    textP,a0                ; get ptr to next character
  3604.             suba.w    index,a0                ; subtract index
  3605.             move.l    a0,-(a7)                ; push ptr to text
  3606.             clr.w    -(a7)                    ; push offset into text
  3607.             move.w    index,-(a7)                ; push length of text to draw
  3608.             _DrawText                        ; call DrawText
  3609.             
  3610.             sub.w    index,numChars            ; subtract from length of text
  3611.             clr.w    index                    ; reset index to 0
  3612.             
  3613.         @NoText:
  3614.             rts
  3615.     
  3616.         @Done:
  3617.             bsr.s    @DrawText                ; draw rest of text
  3618.         }
  3619.         
  3620. #else    /* not THINK_C */
  3621.  
  3622.     #if qPEUseInsertionGap
  3623.         AsmDrawLineRange( textP, numChars, tabWidth, gapP,
  3624.                           gapLength, startPt, showInvisibles );
  3625.     #else
  3626.         AsmDrawLineRange( textP, numChars, tabWidth, NULL,
  3627.                           0, startPt, showInvisibles );
  3628.     #endif
  3629.  
  3630. #endif
  3631.                     
  3632.         /* Dispose the temporary handle for invisible characters: */
  3633.         
  3634.         if (showInvisibles)
  3635.             DisposHandle( invisH );
  3636.             
  3637.         /* Increment the text vertical coordinate: */
  3638.         
  3639.         textPt.v += lineHeight;
  3640.         
  3641.     }    /* for-loop */
  3642.     
  3643.     /*
  3644.         Restore the previous clipping region and
  3645.         the previous state of the text handle:
  3646.     */
  3647.     if (erase)
  3648.     {
  3649.         SetClip( cSaveClipRgn );    
  3650.         DisposeRgn( clipRgn );
  3651.     }
  3652.     
  3653.     HSetState( itsTextHandle, textHState );
  3654.  
  3655. }    /* DrawLineRange */
  3656.  
  3657.  
  3658.  
  3659. /* OVERRIDE: */
  3660. ShortHandle        CPStyleText::MeasureTextWidths (long startPos, long endPos,
  3661.                                                 short maxWidth)
  3662. {
  3663.         long    extraWidthPerSpace;
  3664.         
  3665.         
  3666.     if (itsAlignCmd == cmdJustify)
  3667.     {
  3668.             long            numSpaces, availableWidth;
  3669.             ShortHandle        compactWidthsH;
  3670.             
  3671.         numSpaces = CountRangeSPACEs( startPos, endPos );
  3672.         if (numSpaces > 0)
  3673.         {
  3674.             availableWidth = itsDestRect.right - itsDestRect.left - itsLeftMargin;
  3675.             if (availableWidth <= 0)
  3676.                 extraWidthPerSpace = 0;
  3677.             else
  3678.             {
  3679.                 /*
  3680.                     Set "spExtra" field of "thePort" to zero in
  3681.                     order to measure the UNexpanded widths.  Below,
  3682.                     we'll set "spExtra" to its expanded value so
  3683.                     we can return the expanded widths Handle:
  3684.                 */
  3685.                 SpaceExtra( 0 );
  3686.                 compactWidthsH = CPEditText::MeasureTextWidths( startPos, endPos,
  3687.                                                                 maxWidth );
  3688.                 extraWidthPerSpace = (
  3689.                                         availableWidth -
  3690.                                         (*compactWidthsH)[endPos - startPos]
  3691.                                     
  3692.                                      ) / numSpaces;
  3693.                 extraWidthPerSpace = Max( extraWidthPerSpace, 0 );
  3694.             }
  3695.             
  3696.         }    /* numSpaces > 0 */
  3697.         
  3698.         else
  3699.             extraWidthPerSpace = 0;
  3700.             
  3701.     }    /* cmdJustify */
  3702.     
  3703.     else
  3704.         extraWidthPerSpace = 0;
  3705.         
  3706.     SpaceExtra( (Fixed) (extraWidthPerSpace << 16) );
  3707.  
  3708.     return    ( CPEditText::MeasureTextWidths(startPos, endPos, maxWidth) );
  3709.     
  3710. }    /* MeasureTextWidths */
  3711.  
  3712.  
  3713.  
  3714. /* OVERRIDE: */
  3715. void    CPStyleText::HiliteTextRange (long startChar, long endChar)
  3716. {
  3717.         Boolean        isActive   = fReallyActive;
  3718.         long        startLine;
  3719.         long        endLine;
  3720.         LongPt        startPt;
  3721.         LongPt        endPt;
  3722.         LongRect    hiliteRect;
  3723.         PenState    penState;
  3724.         Rect        qdRect;
  3725.         RgnHandle    hiliteRgn;
  3726.         RgnHandle    rectRgn;
  3727.         short        hSpan;
  3728.         short        vSpan;
  3729.         short        fontAscent = itsFontAscent;
  3730.         short        lineHeight;
  3731.     
  3732.     
  3733.     if ( isActive || fOutlineHilite )
  3734.     {
  3735.             /* Outline the selection range if the pane is not active */
  3736.         
  3737.         if (!isActive)
  3738.         {
  3739.             hiliteRgn = NewRgn();
  3740.             rectRgn = NewRgn();
  3741.         }
  3742.         
  3743.             /* Get the starting and ending selection lines    */
  3744.             /* and the number of lines spanned by the frame    */
  3745.             
  3746.         startLine = FindLine( startChar );
  3747.         endLine = FindLine( endChar );
  3748.         GetFrameSpan( &hSpan, &vSpan );
  3749.         
  3750.             /* Take a quick exit if the selection is */
  3751.             /* not visible within the frame span     */
  3752.         
  3753.         if (startLine >= position.v + vSpan || endLine < position.v)
  3754.             return;
  3755.         
  3756.             /* Adjust the starting and ending selection */
  3757.             /* lines if they are outside the frame        */
  3758.         
  3759.         if (startLine < position.v)
  3760.         {
  3761.             startLine = position.v;
  3762.             startChar = GetLineStart(startLine);
  3763.         }
  3764.         
  3765.         if (endLine > position.v + vSpan)
  3766.         {
  3767.             endLine = position.v + vSpan;
  3768.             endChar = GetLineEnd( endLine );
  3769.         }
  3770.         
  3771.             /* Get the frame coordinates corresponding to the */
  3772.             /* start and end of the selection range              */
  3773.             
  3774.         GetCharPoint( startChar, &startPt );
  3775.         GetCharPoint( endChar, &endPt );
  3776.         
  3777.             /* Adjust the horizontal coordinates if the either of the starting */
  3778.             /* or ending characters lies at the beginning of a line               */
  3779.         
  3780.         if ( startChar == GetLineStart(startLine) )
  3781.             startPt.h = frame.left;
  3782.         if ( endChar == GetLineStart(endLine) )
  3783.             endPt.h = frame.left;
  3784.         
  3785.             /* Check for and handle a multiple-line selection range */
  3786.         
  3787.         if (startPt.v != endPt.v)
  3788.         {
  3789.                 /* Highlight the first line of the selection range */
  3790.             
  3791.             lineHeight = GetHeight( startLine, startLine );
  3792.             SetLongRect( &hiliteRect,
  3793.                          startPt.h,
  3794.                          startPt.v - fontAscent,
  3795.                          frame.right,
  3796.                          startPt.v - fontAscent + lineHeight );
  3797.             FrameToQDR( &hiliteRect, &qdRect) ;
  3798.             
  3799.             if (isActive)
  3800.                 HiliteHook( &qdRect );
  3801.             else
  3802.             {
  3803.                 qdRect.left -= 1;
  3804.                 RectRgn( rectRgn, &qdRect );
  3805.                 UnionRgn( rectRgn, hiliteRgn, hiliteRgn );
  3806.             }
  3807.             
  3808.                 /* Highlight the middle lines of the selection range */
  3809.             
  3810.             hiliteRect.left = frame.left;
  3811.             
  3812.             if (isActive)
  3813.             {
  3814.                 FrameToQDR( &hiliteRect, &qdRect );
  3815.                 
  3816.                 while (++startLine < endLine)
  3817.                 {
  3818.                     lineHeight = GetHeight( startLine, startLine );
  3819.                     qdRect.top += lineHeight;
  3820.                     qdRect.bottom += lineHeight;
  3821.                     HiliteHook( &qdRect );
  3822.                 }
  3823.             }
  3824.             else
  3825.             {
  3826.                 hiliteRect.top += lineHeight;
  3827.                 hiliteRect.left -= 1;
  3828.                 hiliteRect.bottom = endPt.v - fontAscent + 1;
  3829.                 FrameToQDR( &hiliteRect, &qdRect );
  3830.                 RectRgn( rectRgn, &qdRect );
  3831.                 UnionRgn( rectRgn, hiliteRgn, hiliteRgn );
  3832.             }
  3833.             
  3834.             SetLongPt( &startPt, hiliteRect.left, endPt.v );
  3835.             
  3836.         }    /* startPt.v ≠ endPt.v */
  3837.         
  3838.             /* Hilite the last part of the selection range */
  3839.         
  3840.         lineHeight = GetHeight( endLine, endLine );
  3841.         SetLongRect( &hiliteRect,
  3842.                      startPt.h,
  3843.                      endPt.v - fontAscent,
  3844.                      endPt.h,
  3845.                      endPt.v - fontAscent + lineHeight );
  3846.         FrameToQDR( &hiliteRect, &qdRect );
  3847.         
  3848.         if (isActive)
  3849.             HiliteHook( &qdRect );
  3850.         else
  3851.         {
  3852.             if (qdRect.right > qdRect.left + 1)
  3853.             {
  3854.                 qdRect.bottom += 1;
  3855.                 RectRgn( rectRgn, &qdRect );
  3856.                 UnionRgn( rectRgn, hiliteRgn, hiliteRgn );
  3857.             }
  3858.             
  3859.                 /* Intersect the highlight region with the pane frame */
  3860.             
  3861.             FrameToQDR( &frame, &qdRect );
  3862.             RectRgn( rectRgn, &qdRect );
  3863.             SectRgn( rectRgn, hiliteRgn, hiliteRgn );
  3864.             DisposeRgn( rectRgn ) ;
  3865.             
  3866.                 /* Outline the highlight region */
  3867.             
  3868.             GetPenState( &penState );
  3869.             PenNormal();
  3870.             PenMode( patXor );
  3871.             FrameRgn( hiliteRgn );
  3872.             DisposeRgn( hiliteRgn );
  3873.             SetPenState( &penState );
  3874.             
  3875.         }    /* NOT active */
  3876.         
  3877.     }    /* if ( isActive || fOutlineHilite ) */
  3878.  
  3879. }    /* HiliteTextRange */
  3880.  
  3881.  
  3882.  
  3883. /* OVERRIDE: */
  3884. void    CPStyleText::CalcLineHeight (void)
  3885. {
  3886.         FontInfo        macFontInfo;
  3887.     
  3888.     
  3889.         /* Update itsLineHeight and itsFontAscent */ 
  3890.     
  3891.     GetMacFontInfo( &macFontInfo );
  3892.     itsLineHeight = macFontInfo.ascent + macFontInfo.descent + macFontInfo.leading;
  3893.     itsFontAscent = macFontInfo.ascent;
  3894.     itsMaxCharWidth = macFontInfo.widMax;
  3895.     
  3896.     if (itsSpacingCmd == cmd1HalfSpace)
  3897.     {
  3898.         itsLineHeight *= 3;
  3899.         itsLineHeight /= 2;
  3900.     }
  3901.     else if (itsSpacingCmd == cmdDoubleSpace)
  3902.         itsLineHeight *= 2;
  3903.     
  3904.     CalcTabWidth();
  3905.     
  3906.         /* Refresh the contents of the pane */
  3907.     
  3908.     Refresh();
  3909.     SetScales( macFontInfo.widMax, itsLineHeight );
  3910.     /*
  3911.         See comments within SetTextPtr method:
  3912.     */
  3913.     SetWholeLines( wholeLines );
  3914.     AdjustBounds();
  3915.     Refresh();
  3916.     
  3917. }    /* CalcLineHeight */
  3918.  
  3919.  
  3920.  
  3921. /* OVERRIDE: */
  3922. void    CPStyleText::RefreshTextAfter (long afterPos, Boolean refreshOnlyLine)
  3923. {
  3924.         long        startLine, endLine;
  3925.         long        vertInset = VertTopInset();
  3926.         long        cumLineHt, endPointHt;
  3927.         LongRect    lr;
  3928.         Rect        r;
  3929.     
  3930.     
  3931.     Prepare();
  3932.     
  3933.         /* Redraw the text after the given offset on the line */
  3934.     
  3935.     startLine = FindLine( afterPos );
  3936.     if (refreshOnlyLine)
  3937.     {
  3938.         endLine = startLine;
  3939.     }
  3940.     else
  3941.     {
  3942.         /*
  3943.             Instituted for-loop because eventually
  3944.             I will effect styled text wherein each
  3945.             line can have a unique height:
  3946.         */
  3947.         
  3948.         cumLineHt = 0;
  3949.         endPointHt = frame.bottom - vertInset;
  3950.         for (endLine = 0; endLine < itsNumLines; endLine++)
  3951.         {
  3952.             cumLineHt += GetHeight( endLine, endLine );        /* See Draw method. */
  3953.             if (cumLineHt >= endPointHt)
  3954.                 break;
  3955.         }
  3956.         endLine = Min( endLine, itsNumLines - 1 );
  3957.     }
  3958.     
  3959.     DrawLineRange( startLine, endLine, afterPos - GetLineStart(startLine),
  3960.                    kEraseText );
  3961.         
  3962.         /* Erase the area below the last text line, if necessary */
  3963.     
  3964.     if (!refreshOnlyLine)
  3965.     {
  3966.         SetLongRect( &lr,
  3967.                      frame.left, (endLine + 1) * itsLineHeight + vertInset,
  3968.                      frame.right, frame.bottom );
  3969.         
  3970.         if (lr.bottom > lr.top)
  3971.         {
  3972.             FrameToQDR( &lr, &r );
  3973.             EraseRect( &r );
  3974.         }
  3975.     }
  3976.     
  3977. }    /* RefreshTextAfter */
  3978.  
  3979.  
  3980.  
  3981. /**** W O R D   W R A P   M E T H O D S ****/
  3982.  
  3983.  
  3984. /* OVERRIDE: */
  3985. void    CPStyleText::CalcLineStarts (void)
  3986. {
  3987.     /*
  3988.         First call inherited method to calculate the pristine
  3989.         line count and line starts array.  However, instead of
  3990.         just calling Chris' method in one shot, expand it here,
  3991.         but withOUT its final call to AdjustBounds().  The reason
  3992.         is that WrapLineStarts() below may change itsNumLines
  3993.         which directly affects the new CPanorama bounds.
  3994.         
  3995.         As long as we're re-presenting it here, modify it for
  3996.         speed enhancement.  See comments below.
  3997.     */
  3998.     
  3999.         register Byte        cr             = kReturn;
  4000.         register long        charPos;
  4001.         register long        textLength     = itsTextLength;
  4002.         register long        *lineStarts;
  4003.         register long        numLines;
  4004. #if qPEUseInsertionGap
  4005.         long                gapLength     = itsGapLength;
  4006.         long                gapPosition     = itsGapPosition;
  4007.         register Ptr        gapP;
  4008. #endif
  4009.         register Ptr        textP;
  4010.     
  4011.     
  4012.     /*
  4013.         For speed enhancement, allocate the line
  4014.         starts array to the maximum size possible.
  4015.         Re-sized later after the lines are counted.
  4016.     */
  4017.     
  4018.     ResizeHandleCanFail( (Handle)itsLineStarts, MAX_LINES * sizeof(long) );
  4019.     if ( MemError() )
  4020.         FailOSErr( FileTooBig );
  4021.     
  4022.     /* Count the lines and build the line starts array: */
  4023.     
  4024.     lineStarts = *itsLineStarts + 1;
  4025.     textP = *itsTextHandle;
  4026. #if qPEUseInsertionGap
  4027.     gapP = textP + gapPosition;
  4028. #endif
  4029.     charPos = 0;
  4030.     numLines = 1;
  4031.     
  4032.     while (++charPos <= textLength)
  4033.     {
  4034. #if qPEUseInsertionGap
  4035.         if (textP == gapP)
  4036.             textP += gapLength;
  4037. #endif
  4038.         if (*textP++ == cr)
  4039.         {
  4040.             *lineStarts++ = charPos;
  4041.             AddLine( numLines - 1 );
  4042.             ++numLines;
  4043.         }
  4044.     }
  4045.     
  4046.     /* Resize Handle as promised and then update line count: */
  4047.  
  4048.     SetHandleSize( (Handle)itsLineStarts, numLines * sizeof(long) );
  4049.     itsNumLines = numLines;
  4050.     
  4051.     /*****   F O L D   I N   W O R D   W R A P   B Y   *****/
  4052.     /*****   S C A N N I N G   N E W   L I N E S       *****/
  4053.     
  4054.     WrapLineStarts( 0L, numLines - 1, kWillBeRefreshed );
  4055.     
  4056.     /*****   T H E N   A D J U S T   T H E   B O U N D S   *****/
  4057.  
  4058.     /*
  4059.         See comments within SetTextPtr method:
  4060.     */
  4061.     SetWholeLines( wholeLines );
  4062.     AdjustBounds();
  4063.         
  4064. }    /* CalcLineStarts */
  4065.  
  4066.  
  4067.  
  4068. /* OVERRIDE: */
  4069. void    CPStyleText::RecalcLineStarts (void)
  4070. {
  4071.     
  4072.     /*
  4073.         Called by ResizeFrame and DoWordWrap,
  4074.         this method folds in word-wrap by
  4075.         scanning all the lines in the text
  4076.         AND then re-adjusts the bounds:
  4077.     */
  4078.     
  4079.     WrapLineStarts( 0L, itsNumLines - 1, kWillBeRefreshed );
  4080.     
  4081.     /*
  4082.         See comments within SetTextPtr method:
  4083.     */
  4084.     SetWholeLines( wholeLines );
  4085.     AdjustBounds();
  4086.     
  4087. }    /* RecalcLineStarts */
  4088.  
  4089.  
  4090.  
  4091. /* OVERRIDE: */
  4092. void    CPStyleText::WrapLineStarts (register long startLine, register long endLine,
  4093.                                      Boolean willBeRefreshed)
  4094. {
  4095.     /*
  4096.         Called by my CalcLineStarts and RecalcLineStarts
  4097.         to change line count and line starts array as
  4098.         needed to accomodate word-wrap.  Note that we
  4099.         may have to scan or break a line several times
  4100.         BEFORE we proceed to the next one.
  4101.         
  4102.         ( Used LOTS!!! of registers for speed )
  4103.     */
  4104.     
  4105.         LongHandle                saveLineStarts;
  4106.      // char                    savedLSHState;
  4107.         register long            *lineStarts;
  4108.         Boolean                    redrawWrappedLines = FALSE;
  4109.         register long            numLines           = itsNumLines;
  4110.         register long            lineNbr;
  4111.         tCharBuf                theChar;
  4112.         Byte                    cr                   = kReturn;
  4113.         long                    textLength           = itsTextLength;
  4114.         long                    startUnbrokenLineNbr, endUnbrokenLineNbr;
  4115.         long                    deltaParts;
  4116.         long                    startUnbrokenLinePos, endUnbrokenLinePos;
  4117.         long                    startUnbrokenWordLineNbr;
  4118.         register long            brokenLineNbr;
  4119.      // Boolean                    wordWrapThisLine, breakThisLineAgain;
  4120.         ShortHandle                widthsH;
  4121.         register short            *widthsP;
  4122.         short                    totalSpaceForText;
  4123.         register short            spaceAvailable;
  4124.         long                    wholeTextBreak;
  4125.         register short            lineBreak;
  4126.         register short            unbrokenLineLen, lineSegmentLength;
  4127.         short                    endTextPt, widthTextRemaining;
  4128.         long                    wordStart, wordEnd;
  4129.         long                    nextLine;
  4130.         register short            offset;
  4131.         register short            width /*, lastWidth */;
  4132.         register short            *breakWidthsP;
  4133.      // long                    numLinesDelta;
  4134.      // Boolean                    savedAlloc;
  4135.         Boolean                    justify,
  4136.                                 tooBig      = FALSE;
  4137.  
  4138.         #define DOTIMING        TRUE
  4139. #if DOTIMING
  4140.         long                    startTime = TickCount(),
  4141.                                 endTime, elapsedTime;
  4142. #endif
  4143.  
  4144.     
  4145.     Prepare();
  4146.         
  4147.     totalSpaceForText = itsDestRect.right - itsDestRect.left;
  4148.     
  4149.     /*
  4150.         Compute the available remaining space for text.
  4151.         
  4152.         This statement would have to appear after the
  4153.         "while (breakLineAgain)" statement if we had to
  4154.         adjust "spaceAvailable" for different text
  4155.         alignments.  See comments below about alignments.
  4156.         
  4157.         Note that this remaining space appears AFTER the
  4158.         left margin.  Therefore, since totalSpaceForText
  4159.         includes the left margin, this margin must be
  4160.         subtracted here:
  4161.     */
  4162.     spaceAvailable = totalSpaceForText - itsLeftMargin;
  4163.     
  4164.     if (spaceAvailable <= 0)    return;        /* CYA !!! */
  4165.         
  4166.     /*
  4167.         For speed enhancement, allocate the line starts
  4168.         array to the maximum size possible so we don't
  4169.         have to call SetHandleSize as we go.  Re-sized
  4170.         just once after ALL the lines are wrapped.
  4171.         
  4172.         In addition, I save a copy of the original
  4173.         line starts Handle just in case we need to go
  4174.         beyond MAX_LINES as we're wrapping.
  4175.         
  4176.         I also save one de-reference by doing one at
  4177.         the beginning rather than:
  4178.         
  4179.             (*itsLineStarts)[...]
  4180.         
  4181.         everytime.
  4182.     */
  4183.     
  4184.     /*
  4185.         The original itsLineStarts is NOT changed,
  4186.         but saveLineStarts is changed in place:
  4187.     */
  4188.     
  4189.     saveLineStarts = itsLineStarts;
  4190.     if ( HandToHand((Handle*) &saveLineStarts) )
  4191.         FailOSErr( FileTooBig );
  4192.     ResizeHandleCanFail( (Handle)saveLineStarts, MAX_LINES * sizeof(long) );
  4193.     if ( MemError() )
  4194.         FailOSErr( FileTooBig );
  4195.  /*
  4196.     Gonna eventually dispose of the Handle's copy ...
  4197.  
  4198.     savedLSHState = HGetState( (Handle)saveLineStarts );
  4199.  */
  4200.     HLock( (Handle)saveLineStarts );
  4201.     lineStarts = *saveLineStarts;
  4202.     
  4203.     for (lineNbr = startLine; lineNbr <= endLine; ++lineNbr)
  4204.     {
  4205.         if (tooBig)
  4206.         {
  4207.             /* Very large file required a wrapped line count > MAX_LINES: */
  4208.             
  4209.             break;        /* out of for-loop */
  4210.         }
  4211.         
  4212.         /*
  4213.             First, reconstitute lines to UNbroken lines ...
  4214.             
  4215.             OUTER while-loop gets the start of the UNbroken
  4216.             line and the INNER while-loop gets the end.
  4217.         */
  4218.         
  4219.         brokenLineNbr = lineNbr;
  4220.         
  4221.         while    (brokenLineNbr >= 0)
  4222.         {
  4223.             /* Not possible for line number being > last line: */
  4224.  
  4225.             startUnbrokenLinePos = ( /* brokenLineNbr >= numLines ? textLength : */
  4226.                                                  lineStarts[brokenLineNbr] );
  4227.             GetCharBefore( &startUnbrokenLinePos, theChar );
  4228.             if (
  4229.                   Length(theChar) == 0    /* 1st line                  */    ||
  4230.                   theChar[1]      == cr    /* start of unbroken line */
  4231.                )
  4232.             {
  4233.                 startUnbrokenLineNbr = brokenLineNbr;
  4234.                 brokenLineNbr = lineNbr;
  4235.                 while    (/* brokenLineNbr <= numLines - 1 */ TRUE)
  4236.                 {
  4237.                     endUnbrokenLinePos = ( brokenLineNbr == numLines - 1 ?
  4238.                                            textLength :
  4239.                                            lineStarts[brokenLineNbr + 1] - 1 );
  4240.                     GetCharAfter( &endUnbrokenLinePos, theChar );
  4241.                     if (
  4242.                           Length(theChar) == 0    /* last line            */    ||
  4243.                           theChar[1]      == cr    /* end of unbroken line */
  4244.                        )
  4245.                     {
  4246.                         endUnbrokenLineNbr = brokenLineNbr;
  4247.                         
  4248.                         break;    /* out of INNER while-loop */
  4249.                     }
  4250.                     
  4251.                     ++brokenLineNbr;
  4252.                     
  4253.                 }    /* inner while-loop */
  4254.  
  4255.                 break;    /* out of OUTER while-loop */
  4256.                 
  4257.             }    /* reached the start of the UNbroken line */
  4258.             
  4259.             --brokenLineNbr;
  4260.  
  4261.         }    /* outer while-loop */
  4262.         
  4263.         if ( (deltaParts = endUnbrokenLineNbr - startUnbrokenLineNbr + 1) > 1 )
  4264.         {
  4265.             /*
  4266.                 Line is split due to word-wrap, so first
  4267.                 UNwrap the line before we REwrap it:
  4268.             */
  4269.             
  4270.             BlockMove( lineStarts + startUnbrokenLineNbr + deltaParts,
  4271.                        lineStarts + startUnbrokenLineNbr + 1,
  4272.                        (numLines - endUnbrokenLineNbr - 1) * sizeof(long) );
  4273.             numLines -= deltaParts - 1;
  4274.          // SetHandleSize( (Handle)itsLineStarts, numLines * sizeof(long) );
  4275.  
  4276.             /* Receed for-loop variables: */
  4277.             
  4278.             if (lineNbr == startLine)    startLine = startUnbrokenLineNbr;
  4279.             lineNbr      = startUnbrokenLineNbr;
  4280.             if (endLine > endUnbrokenLineNbr)    endLine -= deltaParts - 1;
  4281.             else                                endLine = startUnbrokenLineNbr;
  4282.             
  4283.         }    /* line is split due to word-wrap */
  4284.             
  4285.         /****                             ****/
  4286.         /****   N O W,   R E - W R A P   ****/
  4287.         /****                             ****/
  4288.         
  4289.         /* For SUPER long words: */
  4290.         startUnbrokenWordLineNbr = startUnbrokenLineNbr;
  4291.     
  4292.         /* startUnbrokenLinePos = GetLineStart( lineNbr ); */
  4293.         startUnbrokenLinePos = (
  4294.                                   lineNbr >= numLines ? textLength :
  4295.                                                           lineStarts[lineNbr]
  4296.                                );
  4297.         /* endUnbrokenLinePos = GetLineEnd( lineNbr ); */
  4298.         endUnbrokenLinePos = (
  4299.                                 lineNbr >= numLines - 1 ? textLength :
  4300.                                                           lineStarts[lineNbr + 1]
  4301.                              );
  4302.         
  4303.         /*
  4304.             Before we retrieve the widths Handle, see if we have
  4305.             selected full text justification.  If we have, then
  4306.             set a flag and also change the instance variable =
  4307.             "itsAlignCmd" to anything OTHER THAN "cmdJustify".
  4308.             Because we are word-wrapping, we need to retrieve
  4309.             an UNexpanded widths Handle to determine if we
  4310.             NEED to word-wrap.  As soon as we retieve the
  4311.             UNexpanded widths Handle, we immediately reset
  4312.             "itsAlignCmd" for drawing after leaving WrapLineStarts.
  4313.             
  4314.             At the very end of this method, we check to see if
  4315.             we need to update the text.  If we have selected
  4316.             full text justification, we ALWAYS need to update.
  4317.         */
  4318.         
  4319.         if (itsAlignCmd == cmdJustify)
  4320.         {
  4321.             justify = TRUE;
  4322.             itsAlignCmd = cmdAlignLeft;
  4323.         }
  4324.         else
  4325.             justify = FALSE;
  4326.         widthsH = MeasureTextWidths( startUnbrokenLinePos, endUnbrokenLinePos,
  4327.                                      MAXINT );
  4328.         if (justify)    itsAlignCmd = cmdJustify;
  4329.         
  4330.         HLock( (Handle)widthsH );        /* Returned Handle is UNlocked: */
  4331.         widthsP = *widthsH;
  4332.         
  4333.         unbrokenLineLen = endUnbrokenLinePos - startUnbrokenLinePos;
  4334.         
  4335.         lineBreak = 0;                /* A fresh line has NO line breaks. */
  4336.         
  4337.      // breakThisLineAgain = TRUE;
  4338.      
  4339.         while (TRUE /* breakThisLineAgain */)
  4340.         {
  4341.             if ( numLines > MAX_LINES )        /* CYA !!! */
  4342.             {
  4343.                 tooBig = TRUE;
  4344.                 
  4345.                 break;            /* out of while-loop */
  4346.             }
  4347.  
  4348.             /* See GetCharPoint ... */
  4349.             
  4350.             widthTextRemaining = widthsP[unbrokenLineLen] - widthsP[lineBreak];
  4351.             
  4352.             /*
  4353.                 Below we compare endTextPt with totalSpaceForText.
  4354.                 Since totalSpaceForText includes the left margin
  4355.                 so must endTextPt:
  4356.             */
  4357.             endTextPt = itsLeftMargin + widthTextRemaining;
  4358.             
  4359.          /*
  4360.             DrawLineRange addresses text alignments,
  4361.             so pay attention here just to raw widths:
  4362.             
  4363.             if (itsAlignCmd == cmdAlignLeft)
  4364.                 ;
  4365.             else if (itsAlignCmd == cmdAlignRight)
  4366.                 endTextPt += HorizPixelExtent() - itsLeftMargin - widthTextRemaining;
  4367.             else if (itsAlignCmd == cmdAlignCenter)
  4368.                 endTextPt += (
  4369.                                 HorizPixelExtent() - itsLeftMargin -
  4370.                                   widthTextRemaining
  4371.                              ) / 2;
  4372.             else    // cmdJustify
  4373.             {
  4374.                 ;
  4375.             }
  4376.          */
  4377.              
  4378.             if (endTextPt > totalSpaceForText)
  4379.             {
  4380.                 /*****   W E   N E E D   T O   B R E A K   T H E   L I N E   *****/
  4381.                 
  4382.              /*
  4383.                 See above comment:
  4384.                     
  4385.                 if (itsAlignCmd == cmdAlignLeft)
  4386.                     ;
  4387.                 else if (itsAlignCmd == cmdAlignRight)
  4388.                     spaceAvailable -= HorizPixelExtent() - itsLeftMargin -
  4389.                                         widthTextRemaining;
  4390.                 else if (itsAlignCmd == cmdAlignCenter)
  4391.                     spaceAvailable -= (
  4392.                                         HorizPixelExtent() - itsLeftMargin -
  4393.                                           widthTextRemaining
  4394.                                       ) / 2;
  4395.                 else    // cmdJustify
  4396.                 {
  4397.                     ;
  4398.                 }
  4399.              */
  4400.                     
  4401.                 offset = 0;
  4402.              /* lastWidth = 0;  --  see below */
  4403.                 lineSegmentLength = (
  4404.                                         lineNbr < numLines - 1 ?
  4405.                                                 lineStarts[lineNbr + 1] :
  4406.                                                 textLength
  4407.                                     ) - lineStarts[lineNbr];
  4408.                 breakWidthsP = widthsP + lineBreak;
  4409.                     
  4410.                 while (
  4411.                         (offset < lineSegmentLength)    &&
  4412.                         (
  4413.                           (width = *++breakWidthsP - widthsP[lineBreak]) <
  4414.                           spaceAvailable
  4415.                         )
  4416.                       )
  4417.                 {
  4418.                  /* lastWidth = width;  --  see below */
  4419.                     ++offset;
  4420.                 }
  4421.                     
  4422.              /*
  4423.                 GetCharOffset bumps the offset to the character's right
  4424.                 if the available width intersects the char.  Do NOT do
  4425.                 this here, but rather keep the offset at the char's left:
  4426.  
  4427.                 if (width > lastWidth)
  4428.                     ++offset;
  4429.  
  4430.                 if (offset > lineSegmentLength)
  4431.                     offset = lineSegmentLength;
  4432.              */
  4433.                 
  4434.                 if (offset == 0)
  4435.                 {
  4436.                     /*
  4437.                         SUPER-DUPER large font size, e.g. 72 points,
  4438.                         resulting in a single letter larger than
  4439.                         the available width:
  4440.                     */
  4441.                     
  4442.                     offset = 1;
  4443.                 }
  4444.                 
  4445.                 wholeTextBreak = lineStarts[lineNbr] + offset;
  4446.                 
  4447.                 wordStart = WordBreakHook( wholeTextBreak, kBreakLeft );
  4448.                 if (wordStart == lineStarts[startUnbrokenWordLineNbr])
  4449.                 {
  4450.                     /*
  4451.                         Breaking a SUPER long word, that is, ONE
  4452.                         word spans the ENTIRE line.  Therefore,
  4453.                         break this super long word close to the
  4454.                         right border of the available width:
  4455.                     */
  4456.                     
  4457.                  /* wholeTextBreak = lineStarts[lineNbr] + offset; */
  4458.  
  4459.                 }
  4460.                 else if (wordStart == lineStarts[lineNbr])
  4461.                 {
  4462.                     /*
  4463.                         Still breaking a SUPER long word, but
  4464.                         this word is on a "sub-line" component
  4465.                         of a longer word-wrapped line:
  4466.                     */
  4467.                     
  4468.                     startUnbrokenWordLineNbr = lineNbr;
  4469.  
  4470.                 }
  4471.                 else
  4472.                 {
  4473.                     wholeTextBreak = wordStart;
  4474.                 }
  4475.                 
  4476.                 lineBreak = wholeTextBreak - startUnbrokenLinePos;
  4477.  
  4478.                 /*
  4479.                     Find the line corresponding to where we're breaking the
  4480.                     text and then start the adjustment with the NEXT one.
  4481.                     Also, increment the outer while-loop counter to
  4482.                     correspond to adding the one line.
  4483.                 
  4484.                     This entire algorithm looks VERY similar to that in
  4485.                     Chris' AdjustLineStarts method, but without the added
  4486.                     overhead in order to enhance speed:
  4487.                 */
  4488.     
  4489.                 nextLine = ++lineNbr;
  4490.                 ++endLine;
  4491.  
  4492.              // numLinesDelta = 1L;
  4493.              // lineNbr += numLinesDelta;
  4494.  
  4495.                 /*
  4496.                     Insert new entries in the line starts
  4497.                     array starting at startLine, but withOUT
  4498.                     resizing as explained above:
  4499.                 */
  4500.         
  4501.              // savedAlloc = SetAllocation( kAllocCantFail );
  4502.              // SetHandleSize( (Handle) itsLineStarts,
  4503.              //                (numLines + 1 /* numLinesDelta */) * sizeof(long) );
  4504.              // (void) SetAllocation( savedAlloc );
  4505.              // if ( MemError() )        FailOSErr( FileTooBig );
  4506.         
  4507.                 if (nextLine < numLines)
  4508.                     BlockMove( lineStarts + nextLine,
  4509.                                lineStarts + nextLine + 1 /* numLinesDelta */,
  4510.                                (numLines - nextLine) * sizeof(long) );
  4511.              // numLines += numLinesDelta;
  4512.                 ++numLines;
  4513.         
  4514.                 /* Insert value for the new entry */
  4515.         
  4516.                 lineStarts[nextLine] = wholeTextBreak;
  4517.                 
  4518.              // wordWrapThisLine = TRUE;
  4519.              
  4520.                 redrawWrappedLines = TRUE;
  4521.             
  4522.             }    /* endTextPt > totalSpaceForText */
  4523.             
  4524.             else
  4525.             {
  4526.              // wordWrapThisLine = FALSE;
  4527.              
  4528.                 break;                        /* Finished breaking the line */
  4529.             }
  4530.             
  4531.          // breakThisLineAgain = wordWrapThisLine;
  4532.                                         
  4533.         }    /* while (breakThisLineAgain) */
  4534.         
  4535.         DisposHandle( (Handle)widthsH );    /* We're finally finished with it !!! */
  4536.  
  4537.     }    /* for-loop */
  4538.     
  4539.     if (tooBig)
  4540.     {
  4541.         /*
  4542.             A very large file size required a wrapped line
  4543.             count > MAX_LINES.  Therefore, just dispose of
  4544.             the Handle copy and leave itsLineStarts and
  4545.             itsNumLines in their pristine state.
  4546.             
  4547.             Yes, the line starts and line count won't be
  4548.             altered.  However, if this excessive line count
  4549.             was caused by too closely separated margins,
  4550.             for example, then the UNwrapped lines will still
  4551.             be drawn between these close margins and perhaps
  4552.             not erasing where the text WAS.  Therefore, we
  4553.             need to ensure that the whole CPane gets drawn.
  4554.         */
  4555.         
  4556.      // HSetState( (Handle)saveLineStarts, savedLSHState );
  4557.         DisposeHandle( (Handle)saveLineStarts );
  4558.  
  4559.         Refresh();
  4560.         
  4561.         FailOSErr( FileTooBig );
  4562.     }
  4563.     
  4564.     else
  4565.     {
  4566.         /* Update instance variable and resize Handle as promised: */
  4567.     
  4568.         itsNumLines = numLines;
  4569.         SetHandleSize( (Handle)itsLineStarts, numLines * sizeof(long) );
  4570.         BlockMove( *(Handle)saveLineStarts, *(Handle)itsLineStarts,
  4571.                    numLines * sizeof(long) );
  4572.  
  4573.         /* Contents now moved to instance variable, so dump the copy: */
  4574.         
  4575.         DisposeHandle( (Handle)saveLineStarts );
  4576.     }
  4577.         
  4578. #if DOTIMING
  4579.     endTime = TickCount();
  4580.     elapsedTime = endTime - startTime;
  4581. #endif
  4582.  
  4583.     if ( !willBeRefreshed || redrawWrappedLines || justify )
  4584.     {
  4585.         /*
  4586.             Redraw immediately rather than calling
  4587.             Refresh, but ONLY the visible text lines.
  4588.             Note that we canNOT simply call the Draw
  4589.             method because it passes kDontEraseText
  4590.             to DrawLineRange.
  4591.             
  4592.             Calling RefreshTextAfter not only satisfies
  4593.             the necessity of passing kEraseText to
  4594.             DrawLineRange, but it also erases the area
  4595.             below the last line if required.  This
  4596.             erasure is necessary, for example, if
  4597.             deleting the last line.
  4598.         */
  4599.             
  4600.             Boolean        eraseAreaBelowLastLine = FALSE;
  4601.             long        startPos               = (*itsLineStarts)[startLine];
  4602.             
  4603.         RefreshTextAfter( startPos, eraseAreaBelowLastLine );
  4604.     }
  4605.     
  4606. }    /* WrapLineStarts */
  4607.  
  4608.  
  4609.  
  4610. /* OVERRIDE: */
  4611. void    CPStyleText::AdjustLineStarts (long startChar, register long numCharsDelta,
  4612.                                        long numLinesDelta )
  4613. {
  4614.     /*
  4615.         As with CalcLineStarts, instead of just
  4616.         calling the inherited method, duplicate
  4617.         it withOUT its final call to AdjustBounds().
  4618.         First, call WrapLineStarts() for each case
  4619.         of numLinesDelta.  THEN, call AdjustBounds().
  4620.         
  4621.         Note that within the various methods that
  4622.         call AdjustLineStarts, the selection is NOT
  4623.         adjusted until AFTER AdjustLineStarts.
  4624.         However, "itsTextLength" is adjusted BEFORE.
  4625.     */
  4626.     
  4627.         register Byte        cr = kReturn;
  4628.         register long        *lineStarts;
  4629.         register long        count;
  4630.         long                startLine, nextLine;
  4631.         long                startLineCount;
  4632.         register Ptr        textP;
  4633. #if qPEUseInsertionGap
  4634.         register Ptr        gapP;
  4635. #endif
  4636.     
  4637.     
  4638.         /* Starting line count */
  4639.         
  4640.     startLineCount = itsNumLines;
  4641.     
  4642.         /*
  4643.             Find the line that follows
  4644.             the one corresponding to the
  4645.             starting character offset
  4646.         */
  4647.     
  4648.     startLine = FindLine( startChar );
  4649.     nextLine = startLine + 1;
  4650.     
  4651.         /* Adjust the line starts array depending */
  4652.         /* on the change in the number of lines      */
  4653.     
  4654.     if (numLinesDelta == 0)
  4655.     {
  4656.         
  4657.             /*
  4658.                 Bump the values in the line starts array, starting at nextLine.
  4659.                 Note that neither Chris nor I test for numCharsDelta = 0.  This
  4660.                 number is either > 0 for inserting or < 0 for deleting:
  4661.             */
  4662.         
  4663.         lineStarts = *itsLineStarts + nextLine;
  4664.         count = itsNumLines - startLine;
  4665.         while (--count > 0)
  4666.             *lineStarts++ += numCharsDelta;
  4667.             
  4668.             /*
  4669.                 Even if deleting characters, re-wrapping of lines MAY be needed:
  4670.             */
  4671.  
  4672.         WrapLineStarts( startLine, startLine, kWontBeRefreshed );
  4673.         
  4674.     }    /* numLinesDelta = 0 */
  4675.     
  4676.     else if (numLinesDelta > 0)
  4677.     {
  4678.         /* "numCharsDelta" MUST be > 0 if we're adding lines */
  4679.         
  4680.         
  4681.             Boolean        savedAlloc;
  4682. #if qPEUseInsertionGap
  4683.             long        gapPosition    = itsGapPosition;
  4684.             long        gapLength    = itsGapLength;
  4685. #endif
  4686.         
  4687.         
  4688.             /* Insert new entries in the line starts array starting at nextLine  */
  4689.         
  4690.         savedAlloc = SetAllocation( kAllocCantFail );
  4691.         SetHandleSize( (Handle) itsLineStarts,
  4692.                        (itsNumLines + numLinesDelta) * sizeof(long) );
  4693.         (void) SetAllocation( savedAlloc );
  4694.         if ( MemError() )        FailOSErr( FileTooBig );
  4695.         
  4696.         if (nextLine < itsNumLines)
  4697.             BlockMove( *itsLineStarts + nextLine,
  4698.                        *itsLineStarts + nextLine + numLinesDelta,
  4699.                        (itsNumLines - nextLine) * sizeof(long) );
  4700.         itsNumLines += numLinesDelta;
  4701.         
  4702.             /* Calculate values for the new entries */
  4703.         
  4704.         lineStarts = *itsLineStarts + nextLine;
  4705.         
  4706.         textP = *itsTextHandle;
  4707. #if qPEUseInsertionGap
  4708.         gapP = textP + gapPosition;
  4709. #endif
  4710.         textP += startChar;
  4711. #if qPEUseInsertionGap
  4712.         if (startChar >= gapPosition)
  4713.             textP += gapLength;
  4714. #endif
  4715.                     
  4716.             /*
  4717.                 This "while-loop" does NOT execute if "numCharsDelta"
  4718.                 is zero or negative.  However, "numCharsDelta" MUST
  4719.                 be > 0 if we're adding lines.
  4720.             */
  4721.         
  4722.         count = numCharsDelta;
  4723.         while (--count >= 0)
  4724.         {
  4725. #if qPEUseInsertionGap
  4726.             if (textP == gapP)
  4727.                 textP += gapLength;
  4728. #endif
  4729.             if (*textP++ == cr)
  4730.                 *lineStarts++ = startChar + (numCharsDelta - count);
  4731.         }
  4732.         
  4733.             /* Then, adjust the values of the remaining entries */
  4734.         
  4735.         count = itsNumLines - (nextLine + numLinesDelta);
  4736.         while (--count >= 0)
  4737.             *lineStarts++ += numCharsDelta;
  4738.         
  4739.             /*
  4740.                 Finally, wrap the inserted lines.  Note that
  4741.                 we may be adding alot of characters to startLine
  4742.                 BEFORE the first <CR> is encountered, which
  4743.                 is why we begin with startLine.
  4744.             */
  4745.             
  4746.         WrapLineStarts( startLine, startLine + numLinesDelta, kWontBeRefreshed );
  4747.         
  4748.     }    /* numLinesDelta > 0 */
  4749.     
  4750.     else    /* numLinesDelta < 0 */
  4751.     {
  4752.             /*
  4753.                 Delete entries from the line starts array
  4754.                 starting at nextLine.  "numCharsDelta"
  4755.                 MUST be < 0 if we're deleting lines.
  4756.             */
  4757.         
  4758.         itsNumLines += numLinesDelta;
  4759.         if (nextLine < itsNumLines)
  4760.             BlockMove( *itsLineStarts + nextLine + (-numLinesDelta),
  4761.                        *itsLineStarts + nextLine,
  4762.                        (itsNumLines - nextLine) * sizeof(long) );
  4763.         SetHandleSize( (Handle) itsLineStarts, itsNumLines * sizeof(long) );
  4764.         
  4765.             /* Bump the values in the remaining entries */
  4766.         
  4767.         lineStarts = *itsLineStarts + nextLine;
  4768.         count = itsNumLines - startLine;
  4769.         while (--count > 0)
  4770.             *lineStarts++ += numCharsDelta;
  4771.  
  4772.             /*
  4773.                 Then, wrap the line which housed the start of the deletion:
  4774.             */
  4775.             
  4776.         WrapLineStarts( startLine, startLine, kWontBeRefreshed );
  4777.         
  4778.     }    /* numLinesDelta < 0 */
  4779.     
  4780.         /* Adjust the bounds rectangle if the number of lines changed */
  4781.     
  4782.     if (itsNumLines != startLineCount)
  4783.     {
  4784.         /*
  4785.             See comments within SetTextPtr method:
  4786.         */
  4787.         SetWholeLines( wholeLines );
  4788.         AdjustBounds();
  4789.     }
  4790.     
  4791. }    /* AdjustLineStarts */
  4792.  
  4793.  
  4794.  
  4795. /* OVERRIDE: */
  4796. void    CPStyleText::CalcPERects (void)
  4797. {
  4798.     /*
  4799.         Accomodates word wrap:
  4800.         
  4801.         Called by ResizeFrame, DoWordWrap and
  4802.         DonePrinting which call ForceNextPrepare().
  4803.     */
  4804.     
  4805.     itsViewRect = aperture;
  4806.     
  4807.     if (fWordWrap)
  4808.     {
  4809.         /*
  4810.             If lineWidth = -1, user has
  4811.             selected wrapping to the Window:
  4812.  
  4813.             ( see SetWordWrap method )
  4814.         */
  4815.     
  4816.         itsDestRect.right = itsDestRect.left +
  4817.                                            ( lineWidth == -1 ? width : lineWidth );
  4818.     }
  4819.     else
  4820.     {
  4821.         itsDestRect.right = itsDestRect.left +
  4822.                                     itsRightMargin /* = lineWidth from SetWordWrap */;
  4823.     }
  4824.  
  4825. }    /* CalcPERects */
  4826.  
  4827.  
  4828.  
  4829. /* OVERRIDE: */
  4830. void    CPStyleText::AdjustBounds (void)
  4831. {
  4832.     /* Overridden to accomodate word wrap: */
  4833.             
  4834.         long        widthDestRect;
  4835.         LongRect    newBounds;
  4836.     
  4837.             
  4838.     widthDestRect = itsDestRect.right - itsDestRect.left;
  4839.     newBounds.top = newBounds.left = 0;
  4840.     newBounds.bottom = GetNumLines();
  4841.     if ( (lineWidth != -1) && (width < widthDestRect) )
  4842.     {
  4843.         /*
  4844.             NOT wrapping to window width and window
  4845.             narrower than bounds.  This adaption is
  4846.             required to force the activation of the
  4847.             horizontal Scroll Bar as close as possible
  4848.             to the specified line width:
  4849.         */
  4850.         
  4851.         newBounds.right = (widthDestRect - 1) / hScale + 1;
  4852.     }
  4853.     else
  4854.         newBounds.right = widthDestRect / hScale;
  4855.         
  4856.     SetBounds( &newBounds );
  4857.  
  4858. }    /* AdjustBounds */
  4859.  
  4860.  
  4861.  
  4862. /* OVERRIDE: */
  4863. void    CPStyleText::DrawCaret (void)
  4864. {
  4865.     /*
  4866.         Modified Chris' DrawCaret method for drawing the caret
  4867.         at the end of a "sub-line" of a word-wrapped line.
  4868.         Chris' DrawCaret method is declared NON-virtual within
  4869.         his "CPEditText.h" interface file.  So the only simple!
  4870.         way I can override it for both THINK C and Symantec C++
  4871.         is to declare Chris' DrawCaret a virtual method within
  4872.         that interface file.
  4873.     */
  4874.  
  4875.             
  4876.     if (fEndWrapLine)
  4877.     {
  4878.         /*
  4879.             We WERE or ARE at the end of a
  4880.             "sub-line" of a word-wrapped line:
  4881.         */
  4882.     
  4883.             LongPt        caretPt;
  4884.             LongRect    caretRect;
  4885.             Rect        qdRect;
  4886.             tCharBuf    breakChar;
  4887.             long        beforeBreakPos;
  4888.             short        delta;
  4889.             
  4890.         
  4891.         if (itsSelStart == itsSelEnd)
  4892.         {            
  4893.             beforeBreakPos = itsSelStart - 1;
  4894.             GetCharPoint( beforeBreakPos, &caretPt );
  4895.             GetCharAfter( &beforeBreakPos, breakChar );
  4896.             delta = CharWidth( breakChar[1] );
  4897.             SetLongRect( &caretRect,
  4898.                          caretPt.h - 1 + delta,
  4899.                          caretPt.v - itsFontAscent,
  4900.                          caretPt.h + delta,
  4901.                          caretPt.v - itsFontAscent + itsLineHeight );
  4902.             FrameToQDR( &caretRect, &qdRect );
  4903.             CaretHook( &qdRect );
  4904.         
  4905.             /*
  4906.                 Once you've erased the old caret, you're done with
  4907.                 it until you click somewhere else or press a key:
  4908.             */
  4909.         
  4910.             fOldEndWrapLineCaret = FALSE;
  4911.             
  4912.         }    /* itsSelStart = itsSelEnd */
  4913.         
  4914.     }    /* special caret placement */
  4915.     
  4916.     else
  4917.         CPEditText::DrawCaret();
  4918.  
  4919. }    /* DrawCaret */
  4920.  
  4921.  
  4922.  
  4923. /**** C O N S T R U C T I O N / D E S T R U C T I O N   M E T H O D S ****/
  4924.  
  4925.  
  4926. void    CPStyleScrollPane::IPStyleScrollPane (CView *anEnclosure,
  4927.                                               CBureaucrat *aSupervisor,
  4928.                                               short aWidth, short aHeight,
  4929.                                               short aHEncl, short aVEncl,
  4930.                                               SizingOption aHSizing,
  4931.                                               SizingOption aVSizing,
  4932.                                               Boolean hasHoriz, Boolean hasVert,
  4933.                                               Boolean hasSizeBox)
  4934. {
  4935.  
  4936.     CPane::IPane( anEnclosure, aSupervisor, aWidth, aHeight,
  4937.                   aHEncl, aVEncl, aHSizing, aVSizing );
  4938.     
  4939.     hStep = vStep = 1;
  4940.     hOverlap = vOverlap = 1;
  4941.     hUnit = vUnit = 1;
  4942.     
  4943.     IPStyleScrollPaneX( hasHoriz, hasVert, hasSizeBox );
  4944.     
  4945.     hScale = vScale = 1;
  4946.     
  4947. }    /* IPStyleScrollPane */
  4948.  
  4949.  
  4950.  
  4951. void    CPStyleScrollPane::IPStyleScrollPaneX (Boolean hasHoriz, Boolean hasVert,
  4952.                                                Boolean hasSizeBox)
  4953. {
  4954.         short                shrinkage;
  4955.         CPStyleScrollBar    *theHorizSBar, *theVertSBar;
  4956.         
  4957.     
  4958.     wantsClicks = TRUE;
  4959.     
  4960.     shrinkage = (hasSizeBox || (hasHoriz && hasVert)) ? SBARSIZE1 : 0;
  4961.     
  4962.     if (hasHoriz)
  4963.     {
  4964.         theHorizSBar = new (CPStyleScrollBar);
  4965.         theHorizSBar->IPStyleScrollBar( this, this, HORIZONTAL, width - shrinkage,
  4966.                                         frame.left, frame.bottom - SBARSIZE );
  4967.         theHorizSBar->SetActionProc( SBarActionProc );
  4968.         theHorizSBar->SetThumbFunc( SBarThumbFunc );
  4969.         ;
  4970.         itsHorizSBar = theHorizSBar;
  4971.     }
  4972.     else
  4973.     {
  4974.         itsHorizSBar = NULL;
  4975.     }
  4976.     
  4977.     if (hasVert)
  4978.     {
  4979.         theVertSBar = new (CPStyleScrollBar);
  4980.         theVertSBar->IPStyleScrollBar( this, this, VERTICAL, height - shrinkage,
  4981.                                        frame.right - SBARSIZE, frame.top );
  4982.         theVertSBar->SetActionProc( SBarActionProc );
  4983.         theVertSBar->SetThumbFunc( SBarThumbFunc );
  4984.         ;
  4985.         itsVertSBar = theVertSBar;
  4986.     }
  4987.     else
  4988.     {
  4989.         itsVertSBar = NULL;
  4990.     }
  4991.     
  4992.     if (hasSizeBox)
  4993.     {
  4994.         itsSizeBox = new (CSizeBox);
  4995.         itsSizeBox->ISizeBox( this, this );
  4996.     }
  4997.     else
  4998.     {
  4999.         itsSizeBox = NULL;
  5000.     }
  5001.     
  5002.     itsPanorama = NULL;
  5003.  
  5004. }    /* IPStyleScrollPaneX */
  5005.  
  5006.  
  5007.  
  5008. void    CPStyleScrollBar::IPStyleScrollBar (CView *anEnclosure,
  5009.                                             CBureaucrat *aSupervisor,
  5010.                                             Orientation anOrientation, short aLength,
  5011.                                             short aHEncl, short aVEncl)
  5012. {
  5013.  
  5014.     CScrollBar::IScrollBar( anEnclosure, aSupervisor, anOrientation,
  5015.                             aLength, aHEncl, aVEncl );
  5016.     
  5017. }    /* IPStyleScrollBar */
  5018.  
  5019.  
  5020.  
  5021. /**** M O U S E   A N D   K E Y S T R O K E   M E T H O D S ****/
  5022.  
  5023.  
  5024. /* OVERRIDE */
  5025. void    CPStyleScrollBar::DoClick (Point hitPt, short modifierKeys, long when)
  5026. {
  5027.     /*
  5028.         Added another autoscroll capability effected when the user presses
  5029.         the <Option> key while initially dragging the Thumb in either scroll
  5030.         bar.  I say "initially" because once the user presses the <Option>
  5031.         key while clicking on the Thumb, s/he can release the key and continue
  5032.         the autoscrolling.  It DOES matter, however, if the mouse wanders far
  5033.         from the Scroll Bar while it's still pressed because then the
  5034.         autoscrolling ceases.
  5035.         
  5036.         The user would implement Scroll Bar <Option> scrolling if s/he wished
  5037.         to scroll VERY fast through the text, with the speed limited only by
  5038.         the speed of the mouse movement.  If s/he wished to scroll only a
  5039.         line at a time, then the user would <Option> click directly in the
  5040.         text pane itself and NOT in the Scroll Bar.
  5041.         
  5042.         < See Andy Hertzfeld's "sbarcdev.a" source on the Developer's CD. >
  5043.     */
  5044.     
  5045.         #define SLOPSIZE    SBARSIZE
  5046.         #define ARROWSIZE    SBARSIZE
  5047.  
  5048.         typedef long        (*ControlDefFuncPtr) (short varCode,
  5049.                                                   ControlHandle theControl,
  5050.                                                   short    message,
  5051.                                                   long param);
  5052.         ControlDefFuncPtr    ctlDefFunc;
  5053.         CPStyleScrollPane    *theScrollPane;
  5054.         Boolean                optionKeyDown = ( (modifierKeys & optionKey) != 0 );
  5055.         register short        whichPart;
  5056.         Point                origThumbPt, movedThumbPt;
  5057.         LongPt                origThumbPoint, movedThumbPoint;
  5058.         LongRect            slopRect;
  5059.         Rect                slopR, scrollBarRect, thumbBox;
  5060.         RgnHandle            thumbRgn;
  5061.         short                topOfThumbToArrow, inThumbOffset;
  5062.         long                ctlDefLong;
  5063.         short                varCode;
  5064.         long                longVH;
  5065.         short                origThumbValue, movedThumbValue;
  5066.         
  5067.     
  5068.     /*
  5069.         The passed hitPt is in frame coordinates for a CScrollBar since
  5070.         "usingLongCoord" = FALSE for this class.  Therefore, I need to
  5071.         call FrameToWind to convert the passed Point to one local to the
  5072.         window so TestControl works right.
  5073.         
  5074.         Note that calling QDToLongPt is the same as calling QDToFrame
  5075.         since "usingLongCoord" = FALSE.
  5076.     */
  5077.     QDToLongPt( hitPt, &origThumbPoint );
  5078.     FrameToWind( &origThumbPoint, &origThumbPt );
  5079.  
  5080.     whichPart = TestControl( macControl, origThumbPt );
  5081.     
  5082.     if ( (whichPart >= inThumb) && optionKeyDown )
  5083.     {
  5084.         /* <Option> drag ... */
  5085.     
  5086.         PenNormal();
  5087.  
  5088.         theScrollPane = (CPStyleScrollPane*) itsEnclosure;
  5089.  
  5090.         /*
  5091.             Compute the slop and the distance from the top/left
  5092.             of the Thumb to the Arrow:
  5093.             
  5094.             Note that the slop allowed in the length direction
  5095.             is 3-times that in the opposite direction in order
  5096.             to allow dragging the Thumb to the max.
  5097.             
  5098.             The distance from the top/left of the Thumb to the
  5099.             Arrow is required to prevent scrolling below the
  5100.             minimum value when the control CDEV function
  5101.             receives a positioning message and subsequently
  5102.             calls SetCtlValue.  Such sensitivity does NOT seem
  5103.             to exist when scrolling beyond the maximum value ???
  5104.             
  5105.             By the way, before the control CDEV function calls
  5106.             SetCtlValue, it rounds UP the passed position,
  5107.             so I need to maintain the "inThumbOffset" value
  5108.             AFTER this CDEV function is called.  Otherwise,
  5109.             the scrolling Thumb will get out-of-sync with
  5110.             the Mouse, that is, may "get ahead" of the Mouse.
  5111.         */
  5112.         
  5113.         slopRect = frame;
  5114.         thumbRgn = (RgnHandle) (**macControl).contrlData;
  5115.         thumbBox = (**thumbRgn).rgnBBox;
  5116.         scrollBarRect = (**macControl).contrlRect;
  5117.         
  5118.         if (theOrientation == HORIZONTAL)
  5119.         {
  5120.             InsetLongRect( &slopRect, - 3*SLOPSIZE, - SLOPSIZE );
  5121.             
  5122.             topOfThumbToArrow = thumbBox.left - scrollBarRect.left - ARROWSIZE;
  5123.             inThumbOffset = origThumbPt.h - thumbBox.left;
  5124.         }
  5125.         else
  5126.         {
  5127.             InsetLongRect( &slopRect, - SLOPSIZE, -3*SLOPSIZE );
  5128.             
  5129.             topOfThumbToArrow = thumbBox.top - scrollBarRect.top - ARROWSIZE;
  5130.             inThumbOffset = origThumbPt.v - thumbBox.top;
  5131.         }
  5132.         FrameToWindR( &slopRect, &slopR );
  5133.         QDToLongRect( &slopR, &slopRect );
  5134.         
  5135.         varCode = GetCVariant( macControl );
  5136.         ctlDefLong = (long) (**macControl).contrlDefProc;
  5137.         ctlDefLong &= 0x00FFFFFF;        /* Strip variation code. */
  5138.         ctlDefFunc = (ControlDefFuncPtr) *((Handle) ctlDefLong);
  5139.         
  5140.         origThumbValue = GetValue();
  5141.  
  5142.         while ( WaitMouseUp() )
  5143.         {
  5144.             Prepare();
  5145.             
  5146.             /*
  5147.                 The "macPort" of the CScrollBar is the same as that
  5148.                 of its enclosing CScrollPane which is the CWindow.
  5149.                 CScrollBar is a CControl whose Prepare() sets its
  5150.                 macPort's origin = {0, 0}.  Therefore, the Point
  5151.                 returned by GetMouse is already in window coords.
  5152.             */
  5153.             GetMouse( &movedThumbPt );
  5154.             
  5155.             if ( PtInRect(movedThumbPt, &slopR) )
  5156.             {
  5157.                 QDToLongPt( movedThumbPt, &movedThumbPoint );
  5158.                 
  5159.                 /*
  5160.                     Reposition the Thumb and update the Control's value by
  5161.                     sending a "posCntl" message to the Control Definition
  5162.                     Function.  Note that for a position control message,
  5163.                     the returned result = zero and is meaningless.
  5164.                 */
  5165.                                 
  5166.                 if (theOrientation == HORIZONTAL)
  5167.                 {
  5168.                     if (movedThumbPoint.h < origThumbPoint.h - topOfThumbToArrow)
  5169.                     {
  5170.                         /* Mouse below minimum value: */
  5171.                         
  5172.                         movedThumbPoint.h = origThumbPoint.h - topOfThumbToArrow;
  5173.                     }
  5174.                     longVH = /* high word = 0 + */
  5175.                                                 movedThumbPoint.h - origThumbPoint.h;
  5176.                     (void) (*ctlDefFunc) ( varCode, macControl, posCntl, longVH );
  5177.                     
  5178.                     movedThumbValue = GetValue();
  5179.                     theScrollPane->DoThumbDrag( movedThumbValue - origThumbValue, 0 );
  5180.                     
  5181.                     /*
  5182.                         Recalculate new position based
  5183.                         on a newly offset Thumb RgnHandle:
  5184.                     */
  5185.                     thumbBox = (**thumbRgn).rgnBBox;
  5186.                     topOfThumbToArrow = thumbBox.left - scrollBarRect.left -
  5187.                                                                             ARROWSIZE;
  5188.  
  5189.                     /*
  5190.                         Update the starting point, but maintain
  5191.                         the relative position within the Thumb
  5192.                         as I talked about above:
  5193.                     */
  5194.                     origThumbPoint.h = thumbBox.left + inThumbOffset;
  5195.                     
  5196.                 }    /* Horizontal Scroll Bar */
  5197.  
  5198.                 else
  5199.                 {
  5200.                     if (movedThumbPoint.v < origThumbPoint.v - topOfThumbToArrow)
  5201.                         movedThumbPoint.v = origThumbPoint.v - topOfThumbToArrow;
  5202.                     longVH = (movedThumbPoint.v - origThumbPoint.v) << 16
  5203.                                                                 /* + low word = 0 */;
  5204.                     (void) (*ctlDefFunc) ( varCode, macControl, posCntl, longVH );
  5205.                     
  5206.                     movedThumbValue = GetValue();
  5207.                     theScrollPane->DoThumbDrag( 0, movedThumbValue - origThumbValue );
  5208.                     
  5209.                     thumbBox = (**thumbRgn).rgnBBox;
  5210.                     topOfThumbToArrow = thumbBox.top - scrollBarRect.top - ARROWSIZE;
  5211.                     origThumbPoint.v = thumbBox.top + inThumbOffset;
  5212.                 
  5213.                 }    /* Vertical Scroll Bar */
  5214.                 
  5215.                     
  5216.              /*
  5217.                 CScrollPane's DoThumbDrag calls DoScroll and the
  5218.                 stuff that Calibrate does is addressed by sending a
  5219.                 "posCntl" message to the Control Definition Function:
  5220.                     
  5221.                 theScrollPane->DoScroll( hDeltaPanUnits, vDeltaPanUnits );
  5222.                 theScrollPane->Calibrate();
  5223.              */
  5224.                  
  5225.                 /* Update the control value: */
  5226.                     
  5227.                 origThumbValue = movedThumbValue;
  5228.                 
  5229.             }    /* PtInRect */
  5230.                                         
  5231.         }    /* while-loop */
  5232.         
  5233.         ForceNextPrepare();
  5234.  
  5235.     }    /* <Option> dragging */
  5236.     
  5237.     else
  5238.     {
  5239.         CScrollBar::DoClick( hitPt, modifierKeys, when );
  5240.     }
  5241.  
  5242. }    /* DoClick */
  5243.  
  5244.  
  5245.  
  5246. void    CStylePStyleTask::IStylePStyleTask (CPStyleText *aTextPane,
  5247.                                             long aStyleCmd, short firstTaskIndex)
  5248. {
  5249.     /*
  5250.         Called by CPStyleText's MakeStyleTask which was
  5251.         called by CAbstractText's DoCommand when we changed:
  5252.         
  5253.             a) font ID, size or style
  5254.             b) text alignment or spacing
  5255.     */
  5256.  
  5257.  
  5258.         long    selStart, selEnd;
  5259.  
  5260.     
  5261.     oldStyles = NULL;
  5262.     
  5263.     CTextStyleTask::ITextStyleTask( aTextPane, aStyleCmd, firstTaskIndex );
  5264.     
  5265.     itsTextPane->GetSelection( &selStart, &selEnd );
  5266.     this->selStart = selStart;
  5267.     this->selEnd = selEnd;
  5268.     
  5269. }    /* IStylePStyleTask */
  5270.  
  5271.  
  5272.  
  5273. /* OVERRIDE */
  5274. void    CStylePStyleTask::Dispose (void)
  5275. {
  5276.  
  5277.     ForgetHandle( oldStyles );
  5278.     
  5279.     CTextStyleTask::Dispose();
  5280.  
  5281. }    /* Dispose */
  5282.  
  5283.  
  5284.  
  5285. /* OVERRIDE */
  5286. void    CStylePStyleTask::Do (void)
  5287. {
  5288.     /*
  5289.         Called by CAbstractText's DoCommand following
  5290.         its call to MakeStyleTask which returns the
  5291.         appropriate CTask which is the grandparent
  5292.         of my CStylePStyleTask.
  5293.     */
  5294.         
  5295.         
  5296.         CPStyleText        *theStylePane = (CPStyleText*) itsTextPane;
  5297.         /*
  5298.             Changes in FONTs, font sizes and
  5299.             font styles can affect word-wrap:
  5300.         */
  5301.         Boolean            rewrap          = FALSE;
  5302.         Str255            itemName;
  5303.         long            fontSize;
  5304.      
  5305.      
  5306.      SaveStyle();
  5307.      
  5308.     oldAlignCmd = theStylePane->GetAlignCmd();
  5309.     oldSpacingCmd = theStylePane->GetSpacingCmd();
  5310.     
  5311.     /*
  5312.         CTextStyleTask's "styleCmd" is saved by CAbstractText's DoCommand
  5313.         when it calls CAbstractText::MakeStyleTask.  The latter calls
  5314.         ITextStyleTask which stuffs its passed Command into "styleCmd".
  5315.         So "styleCmd" is the NEW command to be reckoned with.
  5316.     */
  5317.     
  5318.     if (styleCmd < 0) 
  5319.     {
  5320.     
  5321.         switch ( HiShort(-styleCmd) ) 
  5322.         {
  5323.         
  5324.             case MENUfont:
  5325.                 gBartender->GetCmdText( styleCmd, itemName );
  5326.                 theStylePane->SetFontName( itemName );
  5327.                 styleAttribute = doFont;
  5328.                 rewrap = TRUE;
  5329.                 break;
  5330.                 
  5331.             case MENUsize:
  5332.                 gBartender->GetCmdText( styleCmd, itemName );
  5333.                 StringToNum( itemName, &fontSize );
  5334.                 theStylePane->SetFontSize( fontSize );
  5335.                 styleAttribute = doSize;
  5336.                 rewrap = TRUE;
  5337.                 break;            
  5338.         }
  5339.         
  5340.     }    /* styleCmd < 0 */
  5341.  
  5342.     else
  5343.     {
  5344.         switch    (styleCmd)
  5345.         {
  5346.             case cmdPlain:
  5347.                 theStylePane->SetFontStyle( NOTHING );
  5348.                 styleAttribute = doFace;
  5349.                 rewrap = TRUE;
  5350.                 break;
  5351.                 
  5352.             case cmdBold:                /* These toggle <--> ... */
  5353.             case cmdItalic:
  5354.             case cmdUnderline:
  5355.             case cmdOutline:
  5356.             case cmdShadow:
  5357.             case cmdCondense:
  5358.             case cmdExtend:
  5359.                 theStylePane->SetFontStyle( 1 << (styleCmd - cmdBold) );
  5360.                 styleAttribute = doFace;
  5361.                 rewrap = TRUE;
  5362.                 break;
  5363.                 
  5364.             case cmdJustify:
  5365.                 theStylePane->SetAlignCmd( styleCmd );
  5366.                 styleAttribute = doAlign;
  5367.                 break;
  5368.                 
  5369.             default:
  5370.                 /*
  5371.                     For remaining alignments and
  5372.                     all spacing commands:
  5373.                 */
  5374.                 
  5375.                 CTextStyleTask::Do();
  5376.         }
  5377.         
  5378.     }    /* styleCmd >= 0 */
  5379.  
  5380.     if (rewrap)
  5381.     {
  5382.         /*
  5383.             RecalcLineStarts calls WrapLineStarts,
  5384.             followed by SetWholeLines + AdjustBounds:
  5385.         */
  5386.         theStylePane->RecalcLineStarts();
  5387.     }
  5388.         
  5389.     theStylePane->ScrollToSelection();
  5390.  
  5391. }    /* Do */
  5392.  
  5393.  
  5394.  
  5395. /* OVERRIDE */
  5396. void    CStylePStyleTask::Undo (void)
  5397. {
  5398.     /*
  5399.         This method does both undo and redo and is
  5400.         overridden because changes in FONTs, font
  5401.         sizes and font styles can affect word-wrap.
  5402.         
  5403.         For now, duplicate CTextStyleTask's Undo().
  5404.     */
  5405.     
  5406.     
  5407.         Boolean            rewrap;
  5408.      // StScrpHandle    swapStyles, newStyles;
  5409.         TextStyle        swapStyle, newStyle;
  5410.         CPStyleText        *theStylePane = (CPStyleText*) itsTextPane;
  5411.         
  5412.         
  5413.     switch (styleAttribute)
  5414.     {
  5415.         case doFont:
  5416.         case doSize:
  5417.         case doFace:
  5418.             rewrap = TRUE;
  5419.             break;
  5420.         
  5421.         case doAlign:
  5422.         case doSpacing:
  5423.             rewrap = FALSE;
  5424.             break;
  5425.     }
  5426.  
  5427.  // swapStyles = newStyles = NULL;
  5428.  
  5429.     TRY
  5430.     {
  5431.         if (styleAttribute < doAlign)
  5432.         {
  5433.             
  5434.             theStylePane->Prepare();    
  5435.             theStylePane->SetSelection( selStart, selEnd, TRUE );
  5436.             
  5437.             swapStyle/*s*/ = oldStyle/*s*/;        /* Save the current styling.     */
  5438.          // oldStyles = NULL;
  5439.             
  5440.             SaveStyle();
  5441.         
  5442.             newStyle/*s*/ = oldStyle/*s*/;
  5443.             oldStyle/*s*/ = swapStyle/*s*/;        /* Restore the previous styling. */
  5444.          // swapStyles = NULL;
  5445.         
  5446.             RestoreStyle();
  5447.             
  5448.          // ForgetHandle( oldStyles );
  5449.             
  5450.             oldStyle/*s*/ = newStyle/*s*/;        /* Swap current and previous.     */
  5451.             
  5452.             theStylePane->SetSpacingCmd( theStylePane->GetSpacingCmd() );
  5453.          /*
  5454.             Postpone till later ...
  5455.             
  5456.             theStylePane->SetWholeLines( theStylePane->GetWholeLines() );
  5457.             theStylePane->AdjustBounds();
  5458.          */
  5459.          
  5460.             undone = !undone;
  5461.  
  5462.         }
  5463.         else
  5464.             CTextStyleTask::Undo();
  5465.     }
  5466.     CATCH
  5467.     {
  5468.      // ForgetHandle( oldStyles );
  5469.      // ForgetHandle( newStyles );
  5470.      // ForgetHandle( swapStyles );
  5471.     }
  5472.     ENDTRY;
  5473.     
  5474.     if (rewrap)
  5475.     {
  5476.         /* See comment in Do() method about refreshing: */
  5477.         
  5478.         theStylePane->RecalcLineStarts();
  5479.     }
  5480.     else
  5481.     {
  5482.         /* RecalcLineStarts calls these dudes: */
  5483.         
  5484.         theStylePane->SetWholeLines( theStylePane->GetWholeLines() );
  5485.         theStylePane->AdjustBounds();
  5486.     }
  5487.     
  5488.     theStylePane->ScrollToSelection();
  5489.     
  5490. }    /* Undo */
  5491.  
  5492.  
  5493.  
  5494. /* OVERRIDE */
  5495. void    CStylePStyleTask::SaveStyle (void)
  5496. {
  5497.  
  5498.     oldStyles = NULL;            /* GetStyleScrap may fail. */
  5499.     
  5500.     oldStyles = ((CPStyleText*) itsTextPane)->GetStyleScrap();
  5501.     
  5502. }    /* SaveStyle */
  5503.  
  5504.  
  5505.  
  5506. /* OVERRIDE */
  5507. void    CStylePStyleTask::RestoreStyle (void)
  5508. {
  5509.  
  5510.     if (styleAttribute < doAlign)
  5511.     {
  5512.             CPStyleText        *theStylePane = (CPStyleText*) itsTextPane;
  5513.         
  5514.         
  5515.         /* +++ work around problem with redoing cmdPlain on styled text? */
  5516.         if ( (styleCmd == cmdPlain) && (undone == TRUE) )
  5517.             theStylePane->SetFontStyle( NOTHING );
  5518.         else
  5519.             theStylePane->SetStyleScrap( selStart, selEnd, oldStyles, TRUE );        
  5520.     }
  5521.     else
  5522.         CTextStyleTask::RestoreStyle();
  5523.  
  5524. }    /* RestoreStyle */
  5525.  
  5526.  
  5527.  
  5528. /****   G E N E R A L   U T I L I T I E S   ****/
  5529.  
  5530.  
  5531. /*
  5532.     For now these utilities are NOT called by any class
  5533.     function provided in this file.  Rather, I call them
  5534.     from within several member functions of CStyleText's
  5535.     parent CPane and of other supervising CDocuments that
  5536.     are linked to CStyleText's supervisor.  This way, the
  5537.     choice of using these utilities is yours, NOT mine.
  5538.     
  5539.     For example, I could call DirtyDocument from within
  5540.     the DoCommand and DoKeyDown methods belonging to the
  5541.     parent of my CStyleText CPane.  However, be sure to
  5542.     call the inherited methods AFTER DirtyDocument because
  5543.     both inherited methods call CDocument's Notify and
  5544.     Notify sets the "dirty" instance variable = TRUE.  If
  5545.     DirtyDocument is called after the inherited method,
  5546.     "dirty" is already TRUE and DirtyWindow is not called.
  5547.     
  5548.     If I wanted to just call DirtyWindow within DoCommand,
  5549.     for example, I would have to include the following:
  5550.     
  5551.             CWindow        *theWindow = GetWindow();
  5552.             Boolean        prevDirty  = ((CDocument*)itsSupervisor)->dirty;
  5553.             
  5554.          CPStyleText::DoCommand( theCommand );
  5555.         if ( !prevDirty )    DirtyWindow( theWindow );
  5556. */
  5557.  
  5558.  
  5559. void    DirtyWindow (CWindow *theWindow)
  5560. {
  5561.         Str255            itsDirty    = "\p•",
  5562.                         oldTitle, dirtyTitle;
  5563.  
  5564.     
  5565.     if (theWindow)
  5566.     {
  5567.         theWindow->GetTitle( oldTitle );
  5568.         CopyPString( itsDirty, dirtyTitle );
  5569.         ConcatPStrings( dirtyTitle, oldTitle );
  5570.         theWindow->SetTitle( dirtyTitle );
  5571.     }
  5572.     
  5573. }    /* DirtyWindow */
  5574.  
  5575.  
  5576.  
  5577. void    DirtyDocument (CDocument *theDocument)
  5578. {
  5579.         
  5580.     if ( theDocument && !theDocument->dirty )
  5581.     {
  5582.         theDocument->dirty = TRUE;
  5583.         DirtyWindow( theDocument->itsWindow );
  5584.     }
  5585.     
  5586. }    /* DirtyDocument */
  5587.  
  5588.  
  5589.  
  5590. void    UndirtyWindow (CWindow *theWindow)
  5591. {
  5592.         Str255        dirtyTitle;
  5593.         short        dirtyLength;
  5594.  
  5595.     
  5596.     if (theWindow)
  5597.     {
  5598.         theWindow->GetTitle( dirtyTitle );
  5599.         dirtyLength = Length( dirtyTitle );
  5600.         dirtyTitle[0] = dirtyLength - 1;
  5601.         BlockMove( dirtyTitle + 2, dirtyTitle + 1, dirtyLength - 1L );
  5602.         theWindow->SetTitle( dirtyTitle );        /* Actually now a "clean" title. */
  5603.     }
  5604.     
  5605. }    /* UndirtyWindow */
  5606.  
  5607.  
  5608.  
  5609. void    UndirtyDocument (CDocument *theDocument)
  5610. {
  5611.         
  5612.     if ( theDocument && theDocument->dirty )
  5613.     {
  5614.         theDocument->dirty = FALSE;
  5615.         UndirtyWindow( theDocument->itsWindow );
  5616.     }
  5617.     
  5618. }    /* UndirtyDocument */
  5619.  
  5620.  
  5621.  
  5622.  
  5623. /*    { end file "CPStyleText.c" }  */
  5624.