home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 7 / Apprentice-Release7.iso / Source Code / C / Frameworks / Hsoi's App Shell 1.0a4 / Hsoi's App Shell Source / HASHelp.c < prev    next >
Encoding:
Text File  |  1997-01-28  |  37.4 KB  |  1,434 lines  |  [TEXT/CWIE]

  1. /*
  2.     HASHelp.c from Hsoi's App Shell. © 1995-1997 John C. Daub.  All rights reserved.
  3.         
  4.     This file handles all the help related stuff.
  5.     
  6.     Some of it is my original code (100% mine) and some is or is based upon
  7.     James Walker's Show_help 2.0 (and 3.2.8) code.  I originally used to use
  8.     Show_help "as is" in HAS, but then I decided that it'd be nice if Show_help
  9.     used WASTE instead of TextEdit.  Due to this fact, what I thought would be
  10.     a simple "replace TE calls with WE calls and a few other modifications for
  11.     compatability" turned into a big rewrite of Show_help.  So, my code does
  12.     have some similarities (as you can tell) to Show_help, but is quite different.
  13.     
  14.     Anyways, here's some usage and copyright info from Show_help 2.0
  15.     
  16.         Show_help by James W. Walker, June 1991
  17.     
  18.         version 2.0, updated July 1992
  19.         
  20.         This code is freely usable.  If you want to show your gratitude,
  21.         you could send me a free copy of whatever program you develop
  22.         with it.
  23.         
  24.         e-mail:
  25.             Internet            76367.2271@compuserve.com
  26.             CIS                    76367,2271
  27.             America Online        JWWalker
  28.         
  29.         This code displays scrolling text in a dialog box.  The text comes
  30.         from TEXT/styl resources, which can be created with ResEdit 2.1.
  31.         The text cannot be edited, but one can select text and copy it to
  32.         the clipboard using command-C, or save it as a TeachText file.
  33.         
  34.         Pictures can be included in the text using the same scheme as
  35.         TeachText: Each option-space character indicates where the top
  36.         edge of a picture should go, and pictures are centered horizontally.
  37.         Pictures come from consecutively-numbered PICT resources.  
  38.         
  39.         A popup menu can be used to jump to "bookmarks", which are indicated
  40.         by tab characters at ends of lines.
  41.         
  42.         Prototype:
  43.         
  44.         pascal void Show_help( short help_info_id,
  45.                     pascal void (*Handle_update)( WindowPtr ) );
  46.             
  47.         TO DO: error recovery, support for modeless use.
  48.     
  49.     -----
  50.     
  51.     that's James's old info...with some updating to Show_help 3.2.8 and
  52.     also my changes, there are somethings to note:
  53.     
  54.     to create the text, you'll have a 'TEXT', a 'styl', and a 'SOUP' resource.
  55.     you can create these in a number of ways.
  56.     
  57.     First, if you're not using any SOUP (no embedded objects like pictures or
  58.     sounds), make sure to #define USE_SOUP as zero (0).  And if this is
  59.     the case, you can just create you TEXT and styl in ResEdit (or any old
  60.     resource editor).  Also, make sure that whatever you set USE_SOUP to,
  61.     that it is conformant with the macro, WASTE_OBJECTS (from HAS HsoiHeaders.pch
  62.     and WASTEIntf.h).  If WASTE_OBJECTS == 0, and you want to use SOUP
  63.     (USE_SOUP == 1), then you could be in for trouble....i've tried to put
  64.     some #ifdef's in to deal with this, but be careful what you do! and
  65.     be sure that things are consistant.
  66.     
  67.     If you're using SOUP, here are a couple options:
  68.     
  69.     Open up any WASTE-based text editor (for example, the demo app for HAS, or
  70.     the WASTE Demo App that comes with the WASTE distribution).  Author your
  71.     help files as you'd like them to be:  the text, any style stuff (color,
  72.     different fonts, sizes, etc), and insert any objects you'd like (PICTs,
  73.     snd's, etc).
  74.     
  75.     Now, if your Mac supports drag-and-drop, you can just select all of
  76.     the text you wrote and then drag that onto the desktop to create
  77.     a clipping file.  By creating the clipping file, the 'TEXT', 'styl',
  78.     and 'SOUP' resources will be created.  You then open up the resource
  79.     file for your project in ResEdit and also open up the clipping file
  80.     in ResEdit.  Copy the 'TEXT', 'styl', and 'SOUP' from the clipping
  81.     and paste them into your app/project res fork.  it's not totally necessary,
  82.     but to ease things, try to make sure all the resources have the same ID
  83.     numbers. (if nothing else, make sure you put the proper ID numbers in
  84.     the 'Hso?' resource!)
  85.     
  86.     If you don't support drag-and-drop (or just want to do it this way),
  87.     save your help file as a regular HAS file (or whatever WASTE-editor you use).
  88.     Select all and copy and paste the text into a new 'TEXT' resource.  That
  89.     should give you your TEXT and styl.  Then, open up your saved file in
  90.     ResEdit and copy and paste the SOUP into the res file.  Again, make sure
  91.     the res ID's are the same number.
  92.     
  93.     then, input these ID numbers into the 'Hso?' resource for the ShowHelpInfo
  94.     struct to get.
  95.     
  96.     Incidentally, if you used any SOUP, in the TEXT resource you'll probably see
  97.     little boxes in place of where your objects are supposed to be.  don't delete
  98.     these!  sure they don't show up in thee TEXT resource, but those are the
  99.     placeholders that WASTE will use to insert the SOUP.
  100.     
  101.     now, for other changes:
  102.     
  103.     text can be selected and copied to the clipboard...i've added a "Select All"
  104.     (cmd-A) call in the hsoiHelpFilter() to help this...and the file is
  105.     NOT saved as a TeachText file...it is saved as a read-only file with a 
  106.     creator of 'Hsoi', so it's a Hsoi's App Shell file (this is to ensure
  107.     that SOUP is handled cause TeachText/SimpleText can't do that).  But in
  108.     the future, it might be nice to allow saving as a SimpleText file and
  109.     parse the output so all the "SOUP" stuff shows up in a format
  110.     SimpleText can understand.
  111.     
  112.     Pictures are no longer supported like James did it...opt-space characters
  113.     in the TEXT are just ignored by this rewrite.  If you want to have
  114.     a picture inline (or any sort of object, like a sound, or an HFS or
  115.     any other sort of object that you want to write the WASTE handler for)
  116.     just insert it into your text as you write the help file (remember, in
  117.     a WASTE-based editor).  As for how they're displayed, all the text
  118.     stuff in the help dialog is WASTE, so it's dealt with that way.
  119.     
  120.     someday, not only here in the help stuff but also all throughout Hsoi's App
  121.     Shell, i would like to have the file routines work to be able to read in
  122.     TeachText/SimpleText files and WASTE files and be able to work them either
  123.     and any way.  For example, read in a SimpleText file (that has PICTs and
  124.     snds in it as well as styl and text) and then be able to save as a WASTE
  125.     file (i.e. convert all the PICT and snd stuff to SOUP).  and of course,
  126.     vice versa and all that.  but those sorts of parsing/conversion in the
  127.     read/write routines will again be some future work (or a fun exercise
  128.     for you!).
  129.     
  130.     the popup menu still works the same...a tab character is looked for..then
  131.     anything between that tab and the preceeding carriage return turns into
  132.     a bookmark for the popup menu.  eg:
  133.     
  134.     <return>
  135.     This is a title<tab>
  136.     
  137.     that'll be a title....but do be careful using tabs in the body of your
  138.     text...like i said, that'll turn into a bookmark.
  139.     
  140.     and the function prototype has changed:
  141.     
  142.     
  143.     pascal void HsoiShow_help( short infoID, HsoiHelpUpdateUPP handleUpdate, 
  144.                                             HsoiBeginHelpUPP initCallback );
  145.                                             
  146.     I've attemped to write Universal ProcPtr's (UPP's) for the callback
  147.     routines...but being as how this is my first attempt at writing a
  148.     UPP (and considering I have little idea how to do it...no instructions,
  149.     no books, no references, just looking at WASTE.h and "copying" what
  150.     Marco did), i can only hope they work right :-\  So far from my testing
  151.     on my IIvx (68k) at home at some PowerMacs I have access to, all things
  152.     work ok...
  153.         
  154.     Otherwise, that's that....as for legal rights info, what's James's is
  155.     his and what's mine is mine.
  156.  
  157. */
  158.  
  159. #pragma mark ••• #includes •••
  160.  
  161. // include our other header files
  162.  
  163. #include "HASHelp.h"
  164. #include "HASGlobals.h"
  165. #ifndef __HSOIS_APP_SHELL__
  166. #include "HASMain.h"
  167. #endif
  168. #include "HASUtilDialogs.h"
  169. #include "HASUtilCursors.h"
  170. #include "HASMenus.h"
  171. #include "HASFiles.h"
  172. #include "HASMiscEvents.h"
  173. #include "HASLongControls.h"
  174. #include "HASUtilities.h"
  175. #include "HASMiscEvents.h"
  176.  
  177. #include "WASTE_Objects.h"
  178.  
  179. #pragma mark -
  180. #pragma mark ••• Static Function Prototypes •••
  181.  
  182. // these are static functions that will only be called by our help routines.
  183. // since they're static, we'll declare the function prototyps here instead
  184. // of in HASHelp.h
  185.  
  186. #ifdef __cplusplus
  187. extern "C" {
  188. #endif
  189.  
  190. static pascal Boolean    HsoiHelpHandleUpdate( WindowRef );
  191. static pascal void         HsoiHelpTextUserItemProc( WindowRef, short );
  192. static pascal void        HsoiHelpMenuUserItemProc( WindowRef, short );
  193. static MenuRef            HsoiBuildHelpPopup( WEReference, StringPtr );
  194. static void                HsoiHelpTopicMenu( DialogRef, MenuRef );
  195. static void                HsoiHelpSaveText( WEReference, StringPtr );
  196. static pascal Boolean    hsoiHelpFilter( DialogRef, EventRecord *, short * );
  197. static pascal void        hsoiHelpTextScrolled( WEReference );
  198. static void                HsoiHelpAdjustBar( DialogRef );
  199. static void                HsoiHelpScrollBarChanged( DialogRef );
  200. static void                HsoiDoHelpScrollBar( Point, EventModifiers, DialogRef );
  201. static pascal void        hsoiHelpScrollProc( ControlRef, ControlPartCode );
  202. static void                HsoiDoHelpScrollKey( SignedByte, DialogRef );
  203.  
  204. // a couple of local-global variables (global, yet static!)
  205.  
  206. #pragma mark -
  207. #pragma mark ••• Globals •••
  208.  
  209. static long                    sHelpScrollStep = 0;
  210. static ControlActionUPP        sHelpScrollProc = nil;
  211.  
  212.  
  213. #ifdef __cplusplus
  214. }
  215. #endif
  216.  
  217. #pragma mark -
  218. #pragma mark ••• Do Help •••
  219.  
  220. /*
  221.  *    When the user either hits the "Help" button on the extended keyboard, or calls up the
  222.  *    help via the Help menu, this is what gets called to actually do the help routines.
  223.  */
  224.  
  225. void    HsoiDoHelpStuff( void )
  226. {
  227.     SndListHandle        sndHandle;
  228.     OSErr                err;
  229.     HsoiHelpUpdateUPP    updateUPP = nil;
  230.     
  231.     // if there is a sound playing, stop it
  232.     
  233.     if ( SoundIsPlaying() )
  234.         StopCurrentSound();
  235.     
  236.     // just to be cute, play a "Help me" sound
  237.     
  238.     // if the user (via the prefs settings) wants to hear the sound..
  239.     
  240.     if ( gMyPrefs.playHelpSound )
  241.     {
  242.         //... get the sound resource
  243.         
  244.         sndHandle = (SndListHandle)GetResource( TYPE_SOUND , rSoundHelp );    
  245.     
  246.         // no handle, let's complain about it
  247.         
  248.         if ( sndHandle == nil )
  249.             HsoiDoError( rErrorStrings, errCantLoadSound, ResError(), kErrNote );
  250.     
  251.         // if we got the thing, let's play the sound.
  252.         
  253.         err = SndPlay( nil, sndHandle, true );                
  254.         
  255.         // if there's a problem, let's handle it
  256.         
  257.         if ( err )
  258.         {
  259.             HsoiDoError( rErrorStrings, errCantLoadSound, err, kErrNote );
  260.         }
  261.     
  262.         // we're done, so let's free up the memory that the sound takes up
  263.         
  264.         HsoiForgetResource( (Handle *)&sndHandle );
  265.  
  266.     }
  267.     
  268.     // since we're handling a lot of the dialog activities, one thing we must do is make
  269.     // sure that any other windows are deactivated
  270.     
  271.     HsoiDoActivate( false, FrontWindow() );
  272.     
  273.     // set this as such (cause we will be in a modal state) for proper menu adjustment
  274.     // and handling of menu commands and other events
  275.     
  276.     gInModalState = true;
  277.     
  278.     // adjust our menus properly
  279.     
  280.     HsoiAdjustMenus();
  281.     
  282.     // here's the actual call to our help routine/dialog
  283.     //Pass nil as the 3rd argument cause we don't have a callback routine...
  284.  
  285.     updateUPP = NewHsoiHelpUpdateProc(HsoiHelpHandleUpdate);
  286.     
  287.     HsoiShowHelp( rShowHelpInfo, updateUPP, nil );
  288.  
  289.     DisposeRoutineDescriptor( updateUPP );
  290.     
  291.     // we're done...no longer in a modal state, so set globals as necessary
  292.     
  293.     gInModalState = false;
  294.     
  295.     // adjust our menus to fit our situation
  296.     
  297.     HsoiAdjustMenus();
  298.     
  299.     // reactivate the front window
  300.     
  301.     HsoiDoActivate( false, FrontWindow() );
  302.     
  303.     return;
  304. }
  305.  
  306.  
  307. #pragma mark -
  308. #pragma mark ••• Help Dialog •••
  309.  
  310. // now, let's put in all the stuff that does our help movable modal dialog
  311.  
  312. #define        MODAL_MASK    0x017F    // don't take disk, OS, or high-level events
  313.  
  314. /*    ---------------------------------------------------------------------
  315.     Help_ModalDialog        ModalDialog cannot be used to handle movable
  316.                             modal dialogs (without skanky hacks) because
  317.                             it prevents one from switching to another
  318.     application, even if the window is not of the dBoxProc type.  Therefore
  319.     I use this routine, which simulates much of what ModalDialog does.
  320.     ---------------------------------------------------------------------
  321. */
  322.  
  323.  
  324. pascal void HsoiHelpModalDialog( ModalFilterUPP filterProc,short *itemHit )
  325. {
  326.     EventRecord        event;
  327.     Boolean            handled = false;
  328.     DialogRef        front, whichDialog;
  329.     short            whatHit;
  330.     GrafPtr            savePort;
  331.     
  332.     *itemHit = 0;
  333.     front = (DialogRef)FrontWindow();
  334.     
  335.     if (!front) // no window
  336.         return;
  337.     
  338.     GetPort( &savePort );
  339.     SetGrafPortOfDialog( front );
  340.     
  341.     do {
  342.         WaitNextEvent( MODAL_MASK, &event, 0, nil );
  343.         
  344.         if ( filterProc )
  345.             handled = CallModalFilterProc( filterProc, front, &event, itemHit );
  346.             
  347.         if (!handled)
  348.         {
  349.             if (IsDialogEvent(&event))
  350.             {
  351.                 if (DialogSelect( &event, &whichDialog, &whatHit ))
  352.                 {
  353.                     if (whichDialog == front)
  354.                     {
  355.                         *itemHit = whatHit;
  356.                     }
  357.                 }
  358.                 if (whichDialog == front)
  359.                     handled = true;
  360.             }
  361.             /*
  362.                 Beep if there is a mouse click on another window
  363.                 owned by this application.
  364.             */
  365.             if ( !handled && (event.what == mouseDown) )
  366.             {
  367.                 SysBeep(1);
  368.             }
  369.         }
  370.     
  371.  
  372.     } while (*itemHit == 0);
  373.  
  374.     SetPort( savePort );
  375.     
  376.     return;
  377. }
  378.  
  379.  
  380. // This is the updateFilterProc called by Help_ModalDialog() to make sure
  381. // that your applications windows and various other stuff get redrawn
  382. // if the user moves the help dialog box around the screen.
  383. // 
  384. // You'll have to add in your own code (or use the code from Hsoi's App Shell
  385. // if you're using this with Hsoi's App Shell) to do the actual updating
  386. // (redrawing, etc) of things.  In Hsoi's App Shell, all that DoUpdate() is
  387. // is the "regular" update handler for the shell (same thing called in the
  388. // MainEventLoop() when an updateEvt is received).  Makes life easier than
  389. // writing duplicate code...plus, having one update handler can make sure
  390. // that you handle all things properly.
  391. // 
  392. // I probably ought to use WaitNextEvent() instead of GetNextEvent() and
  393. // SystemTask(), but due to the nature of the filter/handler, I think
  394. // GetNextEvent() is more appropriate (correct me if I'm wrong). :)
  395.  
  396. static pascal Boolean HsoiHelpHandleUpdate( WindowRef whichWindow )
  397. {
  398. #pragma unused ( whichWindow )
  399.  
  400.     EventRecord        theEvent;
  401.     
  402.     GetNextEvent( everyEvent, &theEvent );
  403.     
  404.     if ( theEvent.what == updateEvt )
  405.         HsoiDoUpdate( (WindowRef)(theEvent.message) );
  406.     
  407.     SystemTask();
  408.     
  409.     return true;
  410. }
  411.  
  412. static pascal Boolean hsoiHelpFilter( DialogRef dialog, EventRecord *event, short *itemHit )
  413. {
  414.     Point            localPoint;
  415.     LongRect        longViewRect;
  416.     Rect            viewRect;
  417.     short            thePart;
  418.     ControlRef        theControl, theBar;
  419.     short            charcode,  keycode;
  420.     WEReference        we;
  421.     long            selStart, selEnd;
  422.     WindowRef        window;
  423.     Boolean            retval = false;
  424.     ModalFilterUPP    stdFilter;
  425.     Rect            grayRect;
  426.     HelpHandle        help;
  427.     MenuRef            editMenu;
  428.     short            i;
  429.     
  430.     
  431.     GetStdFilterProc( &stdFilter );
  432.  
  433.     help = (HelpHandle)GetWRefCon( GetDialogWindow(dialog));
  434.     we = (*help)->we;
  435.     
  436.     GetMouse( &localPoint );
  437.     WEGetViewRect( &longViewRect, we );
  438.     WELongRectToRect( &longViewRect, &viewRect );
  439.     if ( PtInRect( localPoint, &viewRect ) )
  440. //        SetCursor( *(GetCursor(iBeamCursor)));
  441.         SetCursor( *gEditCursor );
  442.     else
  443.         InitCursor();
  444.     
  445.     WEIdle( &gSleepTime, we );
  446.     
  447.     editMenu = GetMenuHandle( mEdit );
  448.     
  449.     for ( i = 0; i <= CountMenuItems( editMenu ); i++ )
  450.         DisableItem( editMenu, i );
  451.     
  452.     EnableItem( editMenu, 0 );
  453.     EnableItem( editMenu, iSelectAll );
  454.     
  455.     WEGetSelection( &selStart, &selEnd, we );
  456.     if ( selStart != selEnd )
  457.         EnableItem( editMenu, iCopy );
  458.     
  459.     
  460.     switch( event->what )
  461.     {
  462.         case nullEvent:
  463.         break;
  464.         
  465.         case updateEvt:
  466.             window = (WindowRef)event->message;
  467.             
  468.             if ( (window != GetDialogWindow(dialog)) && ((*help)->handleUpdate != nil ) )
  469.             {
  470.                 CallHsoiHelpUpdateProc( (*help)->handleUpdate, window );
  471.                 retval = true;
  472.             }
  473.         break;
  474.         
  475.         case mouseDown:
  476.             CKPT( "HelpFilter mousedown" );
  477.             
  478.             thePart = FindWindow( event->where, &window );
  479.             
  480.             if ( thePart == inMenuBar )
  481.             {
  482.                 MenuSelect( event->where );
  483.                 retval = true;
  484.             }
  485.             else if ( (window == GetDialogWindow(dialog)) && (thePart == inContent) )
  486.             {
  487.                 localPoint = event->where;
  488.                 GlobalToLocal( &localPoint );
  489.                 
  490.                 thePart = FindControl( localPoint, dialog, &theControl );
  491.                 if ( thePart && (GetControlMaximum(theControl) > 1 ) )
  492.                 {
  493.                     HsoiDoHelpScrollBar( localPoint, event->modifiers, dialog );
  494.                     
  495.                     *itemHit = 2;
  496.                     retval = true;
  497.                     break;
  498.                 }
  499.                 else if ( PtInRect( localPoint, &viewRect ) )
  500.                 {
  501.                     WEClick( localPoint, event->modifiers, event->when, we );
  502.                     retval = true;
  503.                 }
  504.             }
  505.             else if ( (window == GetDialogWindow(dialog)) && (thePart == inDrag ) )
  506.             {
  507.                 if ( gHasColorQD )
  508.                     grayRect = ( *GetGrayRgn())->rgnBBox;
  509.                 else
  510.                     grayRect = qd.screenBits.bounds;
  511.                 
  512.                 DragWindow( dialog, event->where, &grayRect );
  513.                 retval = true;
  514.             }
  515.         break;
  516.         
  517.         case keyDown:
  518.         
  519.             charcode = event->message & charCodeMask;
  520.             keycode = (event->message & keyCodeMask ) >> 8;
  521.             theBar = (*help)->scrollbar;
  522.             
  523.             if ( (charcode == kReturnKey) || (charcode == kEnterKey) ||
  524.                     (charcode == kEscKey) || ((charcode == '.') && (event->modifiers & cmdKey)) )
  525.             {
  526.                 *itemHit = cOkButton;
  527.                 HsoiFlashButton(dialog, cOkButton);
  528.                 retval = true;
  529.             }
  530.             else if ( ((charcode == 'c') && (event->modifiers & cmdKey)) ||
  531.                         (keycode == keyF3) ) // the Copy FKey
  532.             {
  533.                 ZeroScrap();
  534.                 HsoiStartVBLSpinning();
  535.                 WECopy( we );
  536.                 HsoiStopVBLSpinning();
  537. //                SystemEdit( 3 );
  538.                 WEGetSelection( &selStart, &selEnd, we );
  539.                 WESetSelection( selStart, selStart, we );
  540.                 event->what = nullEvent;
  541.                 retval = true;
  542.             }
  543.             else if ( (charcode == 'a') && (event->modifiers & cmdKey) )
  544.             {
  545.                 // select all!
  546.                 WESetSelection( 0, MAXLONG, we );
  547.             }
  548.             else if ( keycode == keyPgUp )
  549.             {
  550.                 HsoiDoHelpScrollKey( keyPgUp, dialog );
  551.             }
  552.             else if ( keycode == keyPgDn )
  553.             {
  554.                 HsoiDoHelpScrollKey( keyPgDn, dialog );
  555.             }
  556.             else if ( keycode == keyHome )
  557.             {
  558.                 HsoiDoHelpScrollKey( keyHome, dialog );
  559.             }
  560.             else if ( keycode == keyEnd )
  561.             {
  562.                 HsoiDoHelpScrollKey( keyEnd, dialog );
  563.             }
  564.         break;
  565.     
  566.     }// end switch (event->what)
  567.     
  568.     if ( !retval )
  569.         retval = CallModalFilterProc( stdFilter, dialog, event, itemHit );
  570.     
  571.     return retval;
  572. }
  573.  
  574. #pragma mark -
  575. #pragma mark ••• Show Help •••
  576.  
  577.  
  578. // and now all the fun stuff that actually does the help!
  579.  
  580. pascal void HsoiShowHelp( short infoID, HsoiHelpUpdateUPP handleUpdate, HsoiBeginHelpUPP initCallback )
  581. {
  582. //    DialogRef                dialog;
  583.     HelpHandle                hHelp;
  584.     WEReference                we;
  585.     short                    iHit;
  586.     Handle                    iHandle;
  587.     Rect                    helpItemBox;
  588.     Handle                    helpTEXT = nil;
  589.     StScrpHandle            helpStyl = nil;
  590.     WESoupHandle            helpSOUP = nil;
  591.     GrafPtr                    savePort;
  592.     Rect                    dest, view;
  593.     LongRect                longDest, longView;
  594.     ControlRef                theBar;
  595.     MenuRef                    helpPopup;
  596.     ShowHelpInfo            **theInfo;
  597.     StringPtr                defaultMenuname, defaultFilename;
  598.     UserItemUPP                textUPP, menuUPP;
  599.     ModalFilterUPP            filterUPP;
  600.     WEScrollUPP                weScroller = nil;
  601.     Boolean                    stoppedSpinning = false;
  602.     
  603.     // let's make sure there's a reasonable amount of memory before starting
  604.     
  605.     iHandle = NewHandle( PREFLIGHT_MEMORY );
  606.     if ( iHandle == nil )
  607.     {
  608.         SysBeep(1);
  609.         return;
  610.     }
  611.     else
  612.         HsoiForgetHandle( &iHandle );
  613.     
  614.     // set the cursor to the watch cursor cause this might take a bit...
  615.     
  616. //    SetCursor( *gWaitCursor );
  617.     HsoiStartVBLSpinning();
  618.     
  619.     // make sure we're using our application's resource fork
  620.     
  621.     UseResFile( gAppResourceFork );
  622.     
  623.     // get the necessary info (what DLOG/TEXT/styl/SOUP/etc resource ID #'s)
  624.     // from the app's resource fork (from the 'Hso?' resource)
  625.     
  626.     theInfo = (ShowHelpInfo **)Get1Resource( TYPE_HELP, infoID );
  627.     ASSERT( theInfo != nil, "Hso? resource missing" );
  628.     HLock( (Handle)theInfo );
  629.  
  630.     // separate out the default filename and the default menu name
  631.     // from the ShowHelpInfo struct
  632.     
  633.     defaultFilename = (**theInfo).strings;
  634.     defaultMenuname = defaultFilename + defaultFilename[0] + 1;
  635.     
  636.     // acclocate a relocateable block to hold a HelpRecord
  637.     
  638.     hHelp = (HelpHandle)NewHandleClear( sizeof( HelpRecord ) );
  639.     ASSERT( MemError() == noErr, "Failed NewHandleClear" );
  640.     
  641.     // get the dialog
  642.     
  643.     gHelpDialog = GetNewDialog( (**theInfo).dialogID, nil, MOVE_TO_FRONT );
  644.     ASSERT ( gHelpDialog != nil, "Failed GetNewDialog" );
  645.     
  646.     // link the HelpRecord to the dialog, and the dialog to the HelpRecord
  647.     
  648.     SetWRefCon( GetDialogWindow(gHelpDialog), (long)hHelp );
  649.     (*hHelp)->dialog = gHelpDialog;
  650.     
  651.     // set the dialog's windowKind for menu adjustment.
  652.     
  653.     // actually do NOT do this...i did this as an experiment some time ago, and unless
  654.     // the windowKind of a dialog is dialogKind, the Dialog Manager won't work with it.
  655.     
  656.     // so, don't mess with dialog's windowKind field...this is just left in as a
  657.     // learning example :)
  658.     
  659.     
  660. //    SetWindowKind( GetDialogWindow( gHelpDialog ), kHelpDialogKind );
  661.     
  662.     // set the port
  663.     
  664.     GetPort( &savePort );
  665.     SetGrafPortOfDialog( gHelpDialog );
  666.     
  667.     // put a handle to our update handler in the HelpRecord
  668.     
  669.     (*hHelp)->handleUpdate = handleUpdate;
  670.     
  671.     // store a handle to the I-Beam cursor in our HelpRecord
  672.     
  673.     (*hHelp)->ibeamCursor = GetCursor( iBeamCursor );
  674.     HLock( (Handle)(*hHelp)->ibeamCursor );
  675.     
  676.     // get our TEXT resource
  677.     
  678.     helpTEXT = Get1Resource( TYPE_TEXT, (**theInfo).textID );
  679.     if ( helpTEXT == nil )
  680.     {
  681.         stoppedSpinning = true;
  682.         HsoiStopVBLSpinning();
  683.         ASSERT( false, "Failed to find help TEXT resource" );
  684.         goto getout;
  685.     }
  686.     
  687.     // get our styl resource
  688.     
  689.     helpStyl = (StScrpHandle)Get1Resource( TYPE_STYLE, (**theInfo).stylID );
  690.     if ( helpStyl == nil )
  691.     {
  692.         HsoiStopVBLSpinning();
  693.         stoppedSpinning = true;
  694.         HsoiForgetHandle( &helpTEXT );
  695.         ASSERT_SET_NIL( helpTEXT );
  696.         ASSERT( false, "Failed to find styl resource" );
  697.         goto getout;
  698.     }
  699.  
  700. #if USE_SOUP    
  701.     // get our SOUP resource
  702.     
  703.     helpSOUP = (WESoupHandle)Get1Resource( TYPE_SOUP, (**theInfo).soupID );
  704.     if ( helpSOUP == nil )
  705.     {
  706.         HsoiStopVBLSpinning();
  707.         stoppedSpinning = true;
  708.         HsoiForgetHandle( &helpTEXT );
  709.         ASSERT_SET_NIL( helpTEXT );
  710.         HsoiForgetHandle( (Handle *)&helpStyl );
  711.         ASSERT_SET_NIL( helpStyl );
  712.         ASSERT( false, "Failed to find SOUP resource" );
  713.         goto getout;
  714.     }
  715. #endif
  716.     
  717.     // set up our text updating procedure for the DITL's user item
  718.     
  719.     textUPP = NewUserItemProc( HsoiHelpTextUserItemProc );
  720.     HsoiSetDialogItemProc( gHelpDialog, cHelpRect, textUPP );
  721.     
  722.     // set up things with the user rect to have the text area and the scroll bar area
  723.     
  724.     HsoiGetDialogItemRect( gHelpDialog, cHelpRect, &view );
  725.     InsetRect( &view, 1, 1 );
  726.     view.right -= kScrollBarWidth;
  727.     dest = view;
  728.     InsetRect( &dest, TEXT_INSET, 0 );
  729.     
  730.     // do a little conversion of things and get our WASTE instance
  731.     
  732.     WERectToLongRect( &dest, &longDest );
  733.     WERectToLongRect( &view, &longView );
  734.     WENew( &longDest, &longView, weDoAutoScroll +
  735.                                  weDoOutlineHilite +
  736.                                  weDoUseTempMem +
  737.                                  weDoDrawOffscreen, &we );
  738.     ASSERT( we != nil, "Failed WENew" );
  739.     
  740.     // set the alignment to weFlushLeft to "slop recalc" is disabled
  741.     
  742.     WESetAlignment( weFlushLeft, we );
  743.     
  744.     // save a reference to the dialog in the WASTE instance
  745.     // and also save the WE handle in the help record
  746.     // and stick a reference to our help record in the dialog
  747.     
  748.     WESetInfo( weRefCon, (WindowRef)&gHelpDialog, we );
  749.     (*hHelp)->we = we;
  750.     SetWRefCon( (WindowRef)gHelpDialog, (long)hHelp );
  751.     
  752.     // lock the handle to the TEXT
  753.     
  754.     HLock( helpTEXT );
  755.     
  756.     // insert the TEXT, styl, and SOUP into the WASTE instance
  757.     
  758.     WEInsert( *helpTEXT, GetHandleSize(helpTEXT), helpStyl, helpSOUP, we );
  759.  
  760.     // unlock the TEXT
  761.     
  762.     HUnlock( helpTEXT );
  763.     
  764.     // just for kicks, stick the insertion point at the beginning of the text
  765.     
  766.     WESetSelection( 0, 0, we );
  767.     
  768.     // make sure the text is activated
  769.     
  770.     WEActivate( we );
  771.     
  772.  
  773.     // recalc the line breaks
  774.     
  775.     WECalText( we );
  776.     
  777.     // get and set up our scrollbar
  778.     
  779.     HsoiGetDialogItemRect( gHelpDialog, cHelpRect, &helpItemBox );
  780.     helpItemBox.left = helpItemBox.right - kScrollBarWidth;
  781.     theBar = NewControl( gHelpDialog, &helpItemBox, NIL_STRING, true, 0, 0, 0, scrollBarProc, nil );
  782.     ASSERT( theBar != nil, "Failed NewControl for scroll bar" );
  783.     
  784.     
  785.     // attach a LongControl record the the scroll bar:  this allows us to use long
  786.     // settings and thus scroll text larger than 32,767 pixels
  787.     
  788.     HsoiLCAttach( theBar );
  789.     
  790.     // store a reference to our scrollbar in the help record
  791.     
  792.     (*hHelp)->scrollbar = theBar;
  793.  
  794.     // set up and install our scroll callback procedure
  795.     
  796.     if ( weScroller == nil )
  797.     {
  798.         weScroller = NewWEScrollProc( hsoiHelpTextScrolled );
  799.     }
  800.     
  801.     WESetInfo( weScrollProc, &weScroller, we );
  802.         
  803.     // make our popup menu
  804.     
  805.     helpPopup = HsoiBuildHelpPopup( we, defaultMenuname );
  806.     InsertMenu( helpPopup, hierMenu );
  807.     
  808.     // set the menu procedure for our popup menu
  809.     
  810.     menuUPP = NewUserItemProc( HsoiHelpMenuUserItemProc );
  811.     HsoiSetDialogItemProc( gHelpDialog, cPopupMenu, menuUPP );
  812.         
  813.     // call our BeginHelpProc, if it exists
  814.     
  815.     if ( initCallback != nil )
  816.         CallHsoiBeginHelpProc( initCallback, GetDialogWindow(gHelpDialog) );
  817.     
  818.     // set up the dialog's default and cancel items
  819.     
  820.     SetDialogDefaultItem( gHelpDialog, cOkButton );
  821.     SetDialogCancelItem( gHelpDialog, cOkButton );
  822.     
  823.     // make sure the WASTE instance is read-only
  824.     // NOTE: we *could* have passed the "weFReadOnly" flag to WENew, but then
  825.     // the call to WEInsert() would fail.  therefore, we do it here
  826.     
  827.     WEFeatureFlag( weFReadOnly, weBitSet, we );
  828.     
  829.     // adjust the scroll bar settings based on the total text height
  830.     
  831.     HsoiHelpAdjustBar( gHelpDialog );
  832.     
  833.     // and finally show the dialog!!
  834.     
  835.     ShowWindow( GetDialogWindow(gHelpDialog) );
  836.     
  837.     // make the cursor appear and be an arrow
  838.     
  839. //    InitCursor();
  840.     if ( !stoppedSpinning )
  841.         HsoiStopVBLSpinning();
  842.     
  843.     // get our dialog filter
  844.     
  845.     filterUPP = NewModalFilterProc( hsoiHelpFilter );
  846.     
  847.     // and do the dialog (our own little movable modal dialog thing)
  848.     
  849.     do {
  850.         HsoiHelpModalDialog( filterUPP, &iHit );
  851.         
  852.         if ( iHit == cSaveButton )
  853.             HsoiHelpSaveText( we, defaultFilename );
  854.         else if ( iHit == cPopupMenu )
  855.             HsoiHelpTopicMenu( gHelpDialog, helpPopup );
  856.     } while ( iHit != cOkButton );
  857.  
  858.     // dump the dialog filter
  859.     
  860.     DisposeRoutineDescriptor( filterUPP );
  861.         
  862.     // dump our ShowHelpInfo stuff
  863.     
  864.     HsoiForgetResource( (Handle *)&theInfo );
  865.     
  866.     // dump  all the popup menu stuff
  867.     
  868.     DeleteMenu( (**helpPopup).menuID );
  869.     DisposeMenu( helpPopup );
  870.     DisposeRoutineDescriptor( menuUPP );
  871. Scrollbar:
  872.     
  873.     // dump our handles to the TEXT, styl, and SOUP
  874.  
  875. #if USE_SOUP    
  876.     HsoiForgetResource( (Handle *)&helpSOUP );
  877.     ASSERT_SET_NIL( helpSOUP );
  878. #endif
  879.     HsoiForgetResource( (Handle *)&helpStyl );
  880.     ASSERT_SET_NIL( helpStyl );
  881.     HsoiForgetResource( &helpTEXT );
  882.     ASSERT_SET_NIL( helpTEXT );
  883.     
  884.     DisposeRoutineDescriptor( sHelpScrollProc );
  885.     
  886.     // dump our WASTE instance
  887.     
  888.     WEDispose( we );
  889.     DisposeRoutineDescriptor( textUPP );
  890.     DisposeRoutineDescriptor( weScroller );
  891.     ASSERT_SET_NIL( we );
  892. getout:
  893.  
  894.     // dump the dialog
  895.     DisposeDialog( gHelpDialog );
  896.     gHelpDialog = nil;
  897.     
  898.     // restore the original port
  899.     
  900.     SetPort( savePort );
  901.     
  902.     // bye!
  903.     return;
  904. }
  905.  
  906.  
  907. #pragma mark -
  908. #pragma mark ••• Procs and Callbacks •••
  909.  
  910. static pascal void HsoiHelpTextUserItemProc( WindowRef window, short item )
  911. {
  912.     WEReference        we;
  913.     HelpHandle        help;
  914.     RgnHandle        iRgn = nil;
  915.     Rect            userRect;
  916.     LongRect        viewRect;
  917.     
  918.     CKPT( "HsoiHelpTextUserItemProc" );
  919.     
  920.     help = (HelpHandle)GetWRefCon(window);
  921.     we = (*help)->we;
  922.  
  923.     // in TextEdit, TEUpdate() takes a Rect (for the viewRect) whereas WEUpdate() takes
  924.     // a RgnHandle.  So, we need to get the viewRect and turn that into a RgnHandle.
  925.     // here's how we do it!
  926.     
  927.     // get the viewRect
  928.     
  929.     WEGetViewRect( &viewRect, we );
  930.     
  931.     // convert the viewRect from a LongRect to a Rect
  932.     
  933.     WELongRectToRect( &viewRect, &userRect );
  934.     
  935.     // initialize a new Region (you MUST do this before you use a region)
  936.     // using an uninitialized Rgn can cause you problems!
  937.     
  938.     iRgn = NewRgn();
  939.     
  940.     // a nice little toolbox routine that converts a Rect to a Rgn (look at just
  941.     // what makes up a Region (check Inside Macintosh or THINK Reference) and
  942.     // you'll see this is pretty simple...also, it'll show you how a Rect is
  943.     // important and how a Region is important...cause not everything is a simple
  944.     // rectangle)
  945.     
  946.     RectRgn( iRgn, &userRect );
  947.     
  948.     // and now call WEUpdate
  949.     
  950.     WEUpdate( iRgn, we );
  951.     
  952.     // and make sure to dispose of this region we allocated since we don't need
  953.     // it anymore, and if it was left to hang around, we'd have all sorts of
  954.     // memory usage problems (like leaks)
  955.     
  956.     if ( iRgn != nil )
  957.         DisposeRgn( iRgn );
  958.     
  959.     // and throw a little frame around our viewRect
  960.     
  961.     HsoiGetDialogItemRect( (DialogRef)window, item, &userRect );
  962.     FrameRect( &userRect );
  963.     
  964.     return;
  965. }
  966.  
  967.  
  968. static pascal void HsoiHelpMenuUserItemProc( WindowRef window, short item )
  969. {
  970.     Rect        itemRect;
  971.     
  972.     CKPT( "HsoiHelpMenuUserItemProc" );
  973.     
  974.     // get the item's rectangle and frame it
  975.     
  976.     HsoiGetDialogItemRect( (DialogRef)window, item, &itemRect );
  977.     InsetRect( &itemRect, -1, -1 );
  978.     FrameRect( &itemRect );
  979.     
  980.     // draw the drop-shadow
  981.     
  982.     MoveTo( itemRect.left + 2, itemRect.bottom );
  983.     LineTo( itemRect.right, itemRect.bottom );
  984.     LineTo( itemRect.right, itemRect.top + 2 );
  985.     
  986.     return;
  987. }
  988.  
  989.  
  990. static pascal void hsoiHelpScrollProc( ControlRef bar, ControlPartCode partCode )
  991. {
  992.     long        value, step;
  993.     
  994.     if ( partCode == kControlNoPart )
  995.         return;
  996.         
  997.     value = HsoiLCGetValue( bar );
  998.     step = sHelpScrollStep;
  999.     
  1000.     if ( (( value < HsoiLCGetMax( bar )) && (step > 0 )) || (( value > 0 ) && ( step < 0 )) )
  1001.     {
  1002.         HsoiLCSetValue( bar, value + step );
  1003.         HsoiHelpScrollBarChanged( (DialogRef)FrontWindow() );
  1004.     }
  1005.     
  1006.     return;
  1007. }
  1008.  
  1009. // this is a callback rountine called whenever the text is scrolled automatically.
  1010. // since auto-scrolling is enabled, WEScroll may be invoked internally by WASTE
  1011. // in many different circumstances, and we want to be notified when this happens
  1012. // so we can adjust the scroll bar
  1013.  
  1014. static pascal void hsoiHelpTextScrolled( WEReference we )
  1015. {
  1016.     DialogRef        dialog = nil;
  1017.     
  1018.     // retrieve the window/dialog pointer stored in the WE instance as a refCon
  1019.     
  1020.     if ( WEGetInfo( weRefCon, (WindowRef)&dialog, we ) != noErr )
  1021.         return;
  1022.     
  1023.     // make sure the scroll bar is in synch with the destination rectangle
  1024.     
  1025.     HsoiHelpAdjustBar( dialog );
  1026.     
  1027.     return;
  1028. }
  1029.  
  1030.  
  1031. #pragma mark -
  1032. #pragma mark ••• Popup Menu •••
  1033.  
  1034. /*
  1035.     Build a popup menu of the sections of the help text.  We scan for tab
  1036.     characters.  The text between the tab character and the preceeding line
  1037.     break will be a menu item, unless it is the null string; then we use the
  1038.     default menu name that was passed to Show_help
  1039. */
  1040.  
  1041. static MenuRef HsoiBuildHelpPopup( WEReference we, StringPtr defaultMenuname )
  1042. {
  1043.     MenuRef            popup;
  1044.     short            menuID;
  1045.     SignedByte        textState;
  1046.     Handle            hText;    // handle to just the text
  1047.     Str255            menuData;
  1048.     char            *pText;    // pointer to the help text
  1049.     long            scan, lineStart;
  1050.     short            titleLength;
  1051.     long            textSize;
  1052.     
  1053.     
  1054.     // find an unused menu ID
  1055.     menuID = 1300; // just an arbitrary number
  1056.     while ( GetMenuHandle( menuID ) )
  1057.         ++menuID;
  1058.     
  1059.     popup = NewMenu( menuID, NIL_STRING );
  1060.     ASSERT( popup != nil, "NewMenu failed" );
  1061.     
  1062.     hText = WEGetText( we );
  1063.     textState = HGetState( hText );
  1064.     HLock( hText );
  1065.     pText = *hText;
  1066.     
  1067.     textSize = GetHandleSize( hText );
  1068.     lineStart = 0;
  1069.     
  1070.     for ( scan = 0; scan < textSize; scan++ )
  1071.     {
  1072.         if ( pText[scan] == '\r' )
  1073.         {
  1074.             lineStart = scan + 1;
  1075.         }
  1076.         else if ( pText[scan] == '\t' )
  1077.         {
  1078.             titleLength = scan - lineStart;
  1079.             if ( titleLength == 0 )
  1080.                 BlockMoveData( defaultMenuname, menuData, 256 );
  1081.             else
  1082.             {
  1083.                 menuData[0] = titleLength; // note: <= 255
  1084.                 BlockMoveData( &pText[lineStart], &menuData[1], menuData[0] );
  1085.             }
  1086.             
  1087.             AppendMenu( popup, "\p " );
  1088.             SetMenuItemText( popup, CountMenuItems(popup), menuData );
  1089.         }
  1090.     }
  1091.     
  1092.     HSetState( hText, textState );
  1093.     
  1094.     return popup;
  1095. }
  1096.  
  1097.  
  1098. /*
  1099.     This routine is called when the menu title is clicked.
  1100.     It pops up the menu and scrolls to the indicated tab character
  1101. */
  1102.  
  1103. static void HsoiHelpTopicMenu( DialogRef dialog, MenuRef menu )
  1104. {
  1105.     Rect            box;
  1106.     LongRect        longRect;
  1107.     Point            where;
  1108.     LongPt            longPoint;
  1109.     long            menuReturn;
  1110.     short            menuChoice;
  1111.     ControlRef        bar;
  1112.     short            i;
  1113.     WEReference        we;
  1114.     Handle            hText;
  1115.     short            offset;
  1116.     short            lineHeight;
  1117.     HelpHandle        help;
  1118.     
  1119.     if ( menu == nil )
  1120.         return;
  1121.     
  1122.     HsoiGetDialogItemRect( dialog, cPopupMenu, &box );
  1123.     where.h = box.left;
  1124.     where.v = box.bottom;
  1125.     LocalToGlobal( &where );
  1126.     
  1127.     InvertRect( &box );
  1128.     menuReturn = PopUpMenuSelect( menu, where.v, where.h, 0 );
  1129.     InvertRect( &box );
  1130.     
  1131.     if ( HiWrd( menuReturn ) ) // something was selected
  1132.     {
  1133.         menuChoice = LoWrd(menuReturn);
  1134.         help = (HelpHandle)GetWRefCon( GetDialogWindow(dialog) );
  1135.         bar = (*help)->scrollbar;
  1136.         we = (*help)->we;
  1137.         hText = WEGetText( we );
  1138.         
  1139.         // find tab character number menuChoice
  1140.         
  1141.         offset = -1L;
  1142.         for ( i = 1; i <= menuChoice; ++i )
  1143.         {
  1144.             ++offset; // so we don't find the same thing twice
  1145.             offset = HsoiFindChar( hText, offset, '\t' );
  1146.         }
  1147.         
  1148.         WEGetPoint( offset, hilite, &longPoint, &lineHeight, we );
  1149.         WELongPointToPoint( &longPoint, &where );
  1150.         
  1151.         where.v -= lineHeight; // align to TOP of tab
  1152.         
  1153.         // now where.v is in local coordinates
  1154.         
  1155.         WEGetDestRect( &longRect, we );
  1156.         WELongRectToRect( &longRect, &box );
  1157.         where.v -= box.top;
  1158.         HsoiLCSetValue( bar, where.v );
  1159.         
  1160.         HsoiHelpScrollBarChanged( dialog );
  1161.     }
  1162.     
  1163.     return;
  1164. }
  1165.  
  1166.  
  1167. #pragma mark -
  1168. #pragma mark ••• Scroll Bar •••
  1169.  
  1170.  
  1171. static void    HsoiHelpAdjustBar( DialogRef dialog )
  1172. {
  1173.     HelpHandle            help;
  1174.     WEReference            we;
  1175.     GrafPtr                savePort;
  1176.     LongRect            viewRect, destRect;
  1177.     long                value, max;
  1178.     ControlRef            bar;
  1179.     
  1180.     GetPort( &savePort );
  1181.     SetGrafPortOfDialog( dialog );
  1182.     
  1183.     // get the view and dest rects
  1184.     
  1185.     help = (HelpHandle)GetWRefCon( GetDialogWindow( dialog ) );
  1186.     we = (*help)->we;
  1187.     
  1188.     WEGetViewRect( &viewRect, we );
  1189.     WEGetDestRect( &destRect, we );
  1190.     
  1191.     // get the scroll bar
  1192.     
  1193.     bar = (*help)->scrollbar;
  1194.     
  1195.     // calc the new scroll bar settings
  1196.     
  1197.     value = viewRect.top - destRect.top;
  1198.     
  1199.     max = value + (destRect.bottom - viewRect.bottom );
  1200.     
  1201.     if ( max <= 0 )
  1202.         max = 0;
  1203.     
  1204.     // reset the scroll bar
  1205.     
  1206.     HsoiLCSetMax( bar, max );
  1207.     HsoiLCSetValue( bar, value );
  1208.     
  1209.     // if value exceeds max then the bottom of the destRect is above the bottom of the
  1210.     // view rectangle:  we need to scrol the text downward
  1211.     
  1212.     if ( value > max )
  1213.         HsoiHelpScrollBarChanged( dialog );
  1214.     
  1215.     SetPort( savePort );
  1216.     
  1217.     return;
  1218. }
  1219.  
  1220.  
  1221. static void HsoiHelpScrollBarChanged( DialogRef dialog )
  1222. {
  1223.     WEReference        we;
  1224.     HelpHandle        help;
  1225.     LongRect        viewRect, destRect;
  1226.     
  1227.     help = (HelpHandle)GetWRefCon( GetDialogWindow( dialog ) );
  1228.     we = (*help)->we;
  1229.     WEGetViewRect( &viewRect, we );
  1230.     WEGetDestRect( &destRect, we );
  1231.     
  1232.     WEScroll( 0, viewRect.top - destRect.top - HsoiLCGetValue( (*help)->scrollbar ), we );
  1233.     
  1234.     return;
  1235. }
  1236.  
  1237.  
  1238. static void HsoiDoHelpScrollBar( Point hitPt, EventModifiers modifiers, DialogRef dialog )
  1239. {
  1240.     HelpHandle            help;
  1241.     ControlRef            bar;
  1242.     LongRect            viewRect;
  1243.     ControlPartCode        partCode;
  1244.     short                step;
  1245.     
  1246.     help = (HelpHandle)GetWRefCon( GetDialogWindow( dialog ) );
  1247.     WEGetViewRect( &viewRect, (*help)->we );
  1248.     
  1249.     // find out if the scroll bar was hit and if so, in which part
  1250.     
  1251.     partCode = FindControl( hitPt, (WindowRef)dialog, &bar );
  1252.     
  1253.     if ( bar != nil )
  1254.     {
  1255.         // dispatch on partCode
  1256.         
  1257.         if ( partCode == kControlIndicatorPart )
  1258.         {
  1259.             // click in thumb: call TrackControl with no actionProc and adjust text
  1260.             
  1261.             partCode = TrackControl( bar, hitPt, nil );
  1262.             HsoiLCSynch( bar );
  1263.             HsoiHelpScrollBarChanged( dialog );
  1264.         }
  1265.         else
  1266.         {
  1267.             // dispatch on our partCode
  1268.             
  1269.             switch ( partCode )
  1270.             {
  1271.                 case kControlUpButtonPart:
  1272.                     if ( (modifiers & optionKey) == 0 )
  1273.                         step = -kScrollDelta;
  1274.                     else
  1275.                         step = -1;
  1276.                 break;
  1277.                 
  1278.                 case kControlDownButtonPart:
  1279.                     if ( (modifiers & optionKey) == 0 )
  1280.                         step = +kScrollDelta;
  1281.                     else
  1282.                         step = 1;
  1283.                 break;
  1284.                 
  1285.                 case kControlPageUpPart:
  1286.                     step = -(viewRect.bottom - viewRect.top) + kScrollDelta;
  1287.                 break;
  1288.                 
  1289.                 case kControlPageDownPart:
  1290.                     step = (viewRect.bottom - viewRect.top) - kScrollDelta;
  1291.                 break;
  1292.                 
  1293.                 default:
  1294.                     step = 0;
  1295.             }
  1296.         
  1297.             sHelpScrollStep = step;
  1298.         
  1299.             if ( sHelpScrollProc == nil )
  1300.                 sHelpScrollProc = NewControlActionProc( hsoiHelpScrollProc );
  1301.             partCode = TrackControl( bar, hitPt, sHelpScrollProc );
  1302.         }
  1303.     }    
  1304.     return;
  1305. }
  1306.  
  1307.  
  1308. static void HsoiDoHelpScrollKey( SignedByte keyCode, DialogRef dialog )
  1309. {
  1310.     HelpHandle            help;
  1311.     ControlRef            bar;
  1312.     long                v;
  1313.     LongRect            viewRect;
  1314.     
  1315.     help = (HelpHandle)GetWRefCon( GetDialogWindow( dialog ) );
  1316.     bar = (*help)->scrollbar;
  1317.     
  1318.     v = HsoiLCGetValue( bar );
  1319.     
  1320.     WEGetViewRect( &viewRect, (*help)->we );
  1321.     
  1322.     switch ( keyCode )
  1323.     {
  1324.         case keyPgUp:
  1325.             v = v - (viewRect.bottom - viewRect.top ) + kScrollDelta;
  1326.         break;
  1327.         
  1328.         case keyPgDn:
  1329.             v = v + (viewRect.bottom - viewRect.top ) - kScrollDelta;
  1330.         break;
  1331.         
  1332.         case keyHome:
  1333.             v = 0;
  1334.         break;
  1335.         
  1336.         case keyEnd:
  1337.             v = MAXLONG;
  1338.         break;
  1339.         
  1340.         default:
  1341.         break;
  1342.     }
  1343.     
  1344.     HsoiLCSetValue( bar, v );
  1345.     HsoiHelpScrollBarChanged( dialog );
  1346.     
  1347.     return;
  1348. }
  1349.  
  1350. #pragma mark -
  1351. #pragma mark ••• Misc Utils •••
  1352.  
  1353.  
  1354. pascal void HsoiFlashButton( DialogRef dialog, short item )
  1355. {
  1356.     Handle    hItem;
  1357.     long    time;
  1358.  
  1359.     hItem = HsoiGetDialogItemHandle( dialog, item );
  1360.     HiliteControl( (ControlRef)hItem, kControlButtonPart );
  1361.     Delay( 8L, &time );
  1362.     HiliteControl( (ControlRef)hItem, 0 );
  1363.     
  1364.     return;
  1365. }
  1366.  
  1367.  
  1368. static void HsoiHelpSaveText( WEReference we, StringPtr defaultFilename )
  1369. {
  1370.     StandardFileReply    reply;
  1371.     OSErr                err = noErr;
  1372.     GrafPtr                savePort;
  1373.     
  1374.     // tho it might seem unnecessary to stop a sound here, we just might have to
  1375.     // if there is a sound embedded in the help dialog and it's playing.
  1376.     
  1377.     // we'll use these preprocessor macros. if we're saving the dialog's text,
  1378.     // we know the only sounds that could be playing would be sounds within
  1379.     // the dialog (no way while this dialog is up that any sounds in other documents
  1380.     // could get played).  And if we're not using SOUP, we might run into problems
  1381.     // trying to stop a sound that can't exist.
  1382.     
  1383. #if USE_SOUP
  1384.  
  1385.     if ( SoundIsPlaying() )
  1386.         StopCurrentSound();
  1387. #endif
  1388.     
  1389.     WEDeactivate( we );
  1390.     GetPort( &savePort );
  1391.     
  1392.     StandardPutFile( "\pName of Hsoi's App Shell file:", defaultFilename, &reply );
  1393.     SetPort( savePort );
  1394.     
  1395.     if ( reply.sfGood )
  1396.     {
  1397.         err = HsoiWriteTextFile( &reply.sfFile, true, we );
  1398.     }    
  1399.     
  1400.     if ( err )
  1401.         SysBeep( 1 ); // lame error handling
  1402.     
  1403.     WEActivate( we );
  1404.     
  1405.     return;
  1406. }
  1407.  
  1408.  
  1409.  
  1410. /*
  1411.     This function finds a given character within a Handle.
  1412.     
  1413.     returns:  the offset to the character, or -1 if not found
  1414. */
  1415.  
  1416. static short HsoiFindChar( Handle hData, // handle to a block of characters
  1417.                        short offset, // initial offset within block
  1418.                        char what )     // the character we're looking for
  1419. {
  1420.     Ptr        text;
  1421.     long    textSize, scan;
  1422.     
  1423.     textSize = GetHandleSize( hData );
  1424.     text = *hData;
  1425.     
  1426.     for ( scan = offset; (text[scan] != what) && (scan < textSize); ++scan )
  1427.         ;
  1428.     
  1429.     if ( scan == textSize ) // not found
  1430.         scan = -1;
  1431.     
  1432.     return scan;
  1433. }
  1434.