TweetFollow Us on Twitter

About Scrolling
Volume Number:5
Issue Number:4
Column Tag:C Workshop
Related Info: Quickdraw

All About Scrolling Windows

By John A. Nairn, Salt Lake City, UT

A Scrolling Manager: Why and How to Scroll

The Macintosh user interface is based on windows. Windows provide a solution to the problem of keeping track of much information on a single computer screen. Each window can be structured to contain a single document. By document, I mean a logically connected set of text, graphics, or text and graphics that provides the working environment for a particular application. By the process of activating a window which brings that window to the foreground, the user can work on the document in that window. While working in one window, the contents of deactivated windows are within easy reach - a mouse click away.

Because the physical size of a window is limited to the physical size of the computer screen, a common occurrence will be that the document is larger than the window. In this situation, the window will only be able to display a portion of the document. The window based interface would fail if it were not possible to relocate the window and to display different portions of the document. The task of relocating windows or rather of repositioning documents behind windows is accomplished by scroll bars. Two scroll bars are required - a vertical scroll bar for scrolling the window contents up or down and a horizontal scroll bar and for scrolling the window contents left or right.

Figure 1. Scrollbars and Toolbars

While scroll bars are an important user interface feature, like many other Macintosh features, their implementation is less than trivial. My first attempt at using scroll bars was a time consuming process. Aided by an excellent book (C Programming Techniques for the Macintosh™ by Z. R. Mednieks and T. M. Schilke) and after much trial and error, I was able to work out the kinks of window scrolling. My second attempt was much easier and remarkable similar to the first attempt. After a few more attempts, I decided it was time to generalize the scrolling routines to elevate inclusion of scrolling in applications to the trivial matter that it ought to be.

My efforts aimed at generalizing window scrolling have been developed into a scrolling manager. Using the scrolling manager and relatively few lines of code any window can be converted into a scrolling window. The window can have both vertical and horizontal scroll bars. The contents of the window can be graphic (i.e. application defined) or can contain a Text Edit record. Using the scrolling interface described in the next section, the scrolling manager supports tool bars and rulers. Additional features include automatic scrolling while selecting, drag scrolling, joy-stick scrolling, and automatic tracking of the selection point. This article will describe the development and use of the scrolling manager and illustrate its capabilities with a demonstration application.

A Scrolling Interface

The document window in the figure illustrates the important objects for the scrolling interface supported by the scrolling manager. The objects include vertical and horizontal scroll bars, tool bars, and rulers. The functions and the placement of each of these objects will be discussed in this section.

In virtually all applications, the horizontal scroll bar is along the bottom of the window with the right edge touching the size box in the lower right-hand corner of the window. Typically, the horizontal scroll bar extends the full width of the window with the left edge of the scroll bar touching the left edge of the window. Some applications stop the horizontal scroll bar before the left edge and leave a scroll bar margin which is used for some application-defined purpose (See figure). The scrolling interface defined by the scrolling manager supports horizontal scroll bars with one end touching the size box and the other end stopping an application-defined margin from the left edge of the window.

Vertical scroll bars are universally along the right edge of the window. In analogy with horizontal scroll bars, the scrolling manager supports vertical scroll bars whose bottom touches the size box and whose top stops at an application-defined margin from the top of the window.

The function of the scroll bars should be second nature to all experienced Macintosh users and will not be discussed at length here. In brief, the arrows scroll one line at a time, the gray regions between the arrows and the scroll box scroll one “page” (i.e. one window full) at a time and by dragging the scroll box, you can scroll to any position in the document.

Some applications (e.g. MacDraw™) incorporate tool bars along the edge of their windows. The tool bar areas are static areas that do not scroll as the contents of the rest of the window scrolls. While the most common use of tool bars will be to provide tools for the user, they can be used to display any static information that is meaningful to a particular application.

The scrolling manager supports vertical and/or horizontal tool bars. The vertical tool bar is a static area of application-defined width along the left edge of the window extending from the top of the window to the top of the horizontal scroll bar. The horizontal tool bar of application-defined height is along the top of the window and extends from the left edge of the window to the left edge of the vertical scroll bar.

Another common feature of in windows is rulers. Rulers are typically used in graphics applications to mark and measure position within a drawing. When scrolling occurs, the rulers should sometimes scroll and sometimes remain stationary. Consider for example a vertical ruler. As the window is scrolled in the vertical direction, the ruler should scroll along with the window contents. But, when horizontal scrolling occurs, the ruler should remain stationary otherwise it might scroll out of view. Likewise, horizontal rulers should scroll in the horizontal direction but remain stationary during vertical scrolling.

The scrolling manager supports vertical and/or horizontal rulers. The vertical ruler is of application-defined width and is just to the right of the vertical tool bar. The horizontal ruler is of application-defined height and is just below the horizontal tool bar.

The contents of rulers are not restricted to ruler markings. The ruler area, for example, might also include row and column labels in spreadsheet software. In terms of scrolling, row and column labels are logically equivalent to vertical and horizontal rulers. During vertical or row scrolling, row labels, just as vertical rulers, should scroll along with the window contents. During horizontal or column scrolling, the row labels, again just as vertical rulers, should remain stationary.

The last scrolling object is the content region which is the area of the window left for the document after removing the areas occupied by the size box, the scroll bars, the tool bars, and the rulers. In any scrolling window, the content region will be rectangular.

The scrolling objects defined in this section comprise a workable user-interface for scrolling windows. We note that the windows in virtually all Macintosh applications can be described by using some subset of these scrolling objects. The scrolling in all these applications could therefore be handled using the scrolling manager.

Scrolling in some applications could not be handled with the scrolling manager because those applications select “unusual” locations for scrolling objects. In Adobe™ Illustrator, for example, the rulers appear adjacent to the scroll bars rather than adjacent to the tool bars. Rulers in the position I described, however, would be equally functional to rulers located elsewhere. I therefore see no compelling reason for choosing a variety of ruler locations in a variety of different applications. It can be argued instead, that the user should be provided with a consistent scrolling interface. By providing specific locations for scroll bars, tool bars, and rulers, the scrolling manager attempts to provide a consistent scrolling interface.

The scrolling manager was developed to be stable with respect to all subsets of scrolling objects. Although this stability includes subsets lacking one or even both scroll bars, I view the use of less then the full complement of scroll bars a violation (albeit a mild one) of the Macintosh user interface. When an application does not provide scroll bars, the user is limited in his options as to the size of the document window. If the window is made too small, the user will not be able to view the entire document without the inconvenience of resizing the window. In short the user’s desktop ceases to be efficiently customizable to fit all user’s needs. Before MultiFinder, limitations on window sizes were not very severe. But under MultiFinder, with the possibility of many open windows, the user may have valid reasons for resizing windows in ways not envisioned be developers of specific applications. I therefore suggest that all document windows, no matter what their contents, should include both vertical and horizontal scroll bars.

ScrollInfo Record

To operate window scrolling, the scrolling manager needs to know which scroll bars to include, the size and whether or not to include tool bars and rulers, and some other information related to scrolling. This information is contained in a data structure of type ScrollInfo which is defined as follows:

/* 1 */

 typedef struct {
 ControlHandle hScrollHdl;
 int horizLines;
 int horizLinesVis;
 Rect horizScrollRect;
 Point hRectTopLeft;
 int hScrollMarg;
 ControlHandle vScrollHdl;
 int vertLines;
 int vertLinesVis;
 Rect vertScrollRect;
 Point vRectTopLeft;
 int vScrollMarg;
 TEHandle hTE;
 long refCon;
 } ScrollInfo,*ScrollPtr;

The hScrollHdl and vScrollHdl fields are handles for the scroll bars. Although as discussed in the introduction, it is recommended that all windows use both scroll bars, the scrolling manager will support windows with one or even with no scroll bars. When a scroll bar is absent, its handle field is set to NIL.

The horizLines and vertLines fields specify the total number of lines in the document. This information is needed to set the range of the scroll bars.

The horizLinesVis and vertLinesVis fields give the number of lines currently visible in the window. When the window is resized, these fields are adjusted accordingly.

The scrolling manager will also need to know the vertical and horizontal line size in number of pixels per line. This line size is used to calculate the number of lines in the document and the number of lines currently visible. The line size is also used to decide the number of pixels that should be scrolled when the user clicks in the scroll bar arrows and therefore calls for scrolling one line at a time.

If the line size is too small, scrolling will be smooth but might be too slow. If the line size is too large, scrolling will be fast but might be jerky. A happy medium can usually be found with line sizes between 8 and 16 pixels. For vertical scrolling of text windows, it make sense to set the vertical line size to the number of pixels in one line of text.

The scrolling manager stores the vertical and horizontal line sizes in the refCon’s of the scroll bars. The horizontal line size is stored as minus the horizontal line size. With this scheme, by checking the sign of a scroll bar’s refCon, the scrolling manager can determine if a given control handle corresponds to a horizontal or to a vertical scroll bar.

When the user initiates scrolling, the scrolling manager eventually calls the QuickDraw routine ScrollRect() which scrolls a specified rectangle by a specified number of pixels in the vertical or horizontal directions. The horizScrollRect and vertScrollRect fields are the rectangles passed to ScrollRect() during horizontal or vertical scrolling, respectively. The horizScrollRect will include the content region and the horizontal ruler. The vertScrollRect will include the content region and the vertical ruler. For windows which contain rulers, these two rectangles will be different.

The hRectTopLeft and vRectTopLeft fields are used to define the scrolling rectangles and they give the top-left corner of the rectangles. The bottom-right corner of the scrolling rectangles will be at the top-left corner of the size box when both scroll bars are being used and will extend to the window edge when one of the scroll bars is missing. These fields also determine the presence or absence and the size of tool bars and rulers.

The vScrollMarg and hScrollMarg fields contain the scroll bar margins illustrated in the figure.

For text windows that use the Text Edit manager, the field hTE holds the handle to the Text Edit record.

Finally, the refCon field is provided for application-defined contents. A logical use of the refCon and the reason for its inclusion are described latter.

Using The Scrolling Manager

Typical event-driven applications have sections of code that handle common events such as activating, deactivating, and updating windows or responding to key down or mouse events. Within these or other common sections of code, simple calls to the scrolling manager will take care of all scrolling.

When a new scrolling window is created, call SetScrollWindow() which will return a pointer to the ScrollInfo record. This pointer will need to be passed to many of the scrolling manager routines.

The procedures ScrollActivate() and ScrollDeactivate() must be called whenever an activate or a deactivate event is reported for a scrolling window. These routines take care of hilighting or unhilighting the scroll bars and also set an internal ScrollInfo record pointer which points to the current active window’s ScrollInfo record. The internal pointer is used by routines which apply only to the currently active window.

Whenever an active window is resized call GrowScroll() which moves and resizes the scroll bars. Before and immediately after resizing the window using SizeWindow(), call InvalBars() which invalidates the scroll bar rectangles to insure that the old scroll bars are erased and that the new scroll bars are correctly drawn.

The section of code which updates window contents must be written to be accessible by a call using

/* 2 */

 UpdateWindow(upWind,scrollType)

where upWind is a pointer to the window requiring updating and scrollType will be equal to VertBar (1) or HorizBar (2) when called from the scrolling manager and should be FALSE (0) when called from your application. The way applications should use scrollType is discussed latter.

Whenever the document changes size in either the vertical or horizontal directions, call SetScroll(). This routine will reset the appropriate variable vertLines or horizLines and will reset the appropriate scroll bar’s maximum value.

When a mouse down event in the content of a window is reported call DoScroll(). If DoScroll() returns FALSE then handle the mouse down event as appropriate. If DoScroll() returns TRUE then the mouse down event was a scrolling event that was handled by the scrolling manager and no further action needs to be taken.

When the user is making a selection of text or of graphics and drags the mouse outside the window, it is desirable to implement autoscrolling to enable the user to make a selection that is larger than the window. Implementing autoscrolling in Text Edit windows is easy - merely replace the standard call to TEClick() with a call to ScrollTEClick(). ScrollTEClick() calls TEClick() from a section of code that will always be in the same code segment as the click loop routine provided by the scrolling manager.

Implementing autoscrolling in graphic windows is not much harder. Whenever the application believes that autoscrolling may be necessary, a call to FollowMouse() will scroll the window in the direction of the mouse if the mouse has been dragged out of the content region. An example of graphic autoscrolling while drawing a polygon is given in the routine TrackPoly() in the listing of Scroller.

A second need for autoscrolling arises when the current selection is not visible in the window and the user performs some operation on it. Inside Macintosh recommends that applications scroll the selection into view before performing the operation. The two routines FixInsertPt() and FixTEInsertPt() scroll the insertion point of the active window into view. The latter brings the insertion point in Text Edit windows into view. If no place else, FixTEInsertPt() should be called after every key down event in Text Edit windows. This call will insure that the window scrolls as the user types beyond the bottom of the window.

It might be desirable to provide the user with alternative means to scrolling. Two such means in the scrolling manager are drag scrolling and joy-stick scrolling. Drag scrolling is available in many applications as a hand tool. As the mouse is clicked and dragged using the hand tool, the window is dragged along with it. Using the scrolling manager, when a mouse down event is reported and drag scrolling should follow, call DragScroll().

In joy-stick scrolling, the mouse is clicked at some point and dragged in any direction. The window is scrolled in the direction that the mouse is dragged and is scrolled faster the farther the mouse is dragged from the original point. Using the scrolling manager, when a mouse down event is reported and joy-stick scrolling should follow, call JoyStickScroll(). The joy-stick sensitivity, or how far the mouse must be dragged from the initial point to increase scrolling speed, is set by calling SetJoySpeed().

When a scrolling window is closed and the ScrollInfo record was allocated in the heap, call DisposeScroll().

A number of routines in the scrolling manager are provided as useful utilities:

GetMargins(): returns distance of the top-left corner of the window to the top-left corner of the document in pixels or in scrolling lines. This routine should probably be called from your update routine to determine which part of the document should be drawn.

ScrollSectRect(): returns a pointer to a rectangle which is the intersection of the vertScrollRect and the horizScrollRect and will usually define the content region.

ScrollSectRgn(): same as ScrollSectRect() except returns a handle to a region. If the window has no scroll bars, the region will also exclude the size box.

NonScrollRect(): returns pointer to rectangle that excludes the window’s scroll bars.

ScrollDrawGrowIcon(): replaces Toolbox DrawGrowIcon() and only draws the parts of the grow icon needed to outline the window’s scroll bars.

GetSRefCon(): returns the refCon in the ScrollInfo record.

SetSRefCon(): sets the refCon in the ScrollInfo record.

ActiveScrollPtr(): returns the ScrollPtr to the current active scrolling window.

Any routines in the scrolling manager not discussed in this section are intended for internal use.

As a bottom line, the scrolling manager does not require much work to implement. The most optimal situation would be a document that never changes size. Scrolling in this window would only take 7 lines of code. These lines would be calls to SetScrollWindow(), ScrollActivate(), ScrollDeactivate(), GrowScroll(), DoScroll(), and two calls to InvalBars(). Adding additional scrolling features such as variable size documents, autoscrolling, drag scrolling, and joy-stick scrolling will only require a few more lines of code.

Sample Application: Scroller

The source code for the application Scroller follows this article. The first half of the listing is the header and the source code for the scrolling manager. The second half is the source code for Scroller which functions to demonstrate the features of the scrolling manager.

Scroller has only two menus - File and Edit menus. Under the File menu are the various settings for the features in the scrolling window (see figure). The window can be a Text Edit window or a graphics window. The window can have tool bars and/or rulers. The window can have both scroll bars, only one scroll bar, or no scroll bars. Selecting one of the File menu items will check that menu item and incorporate that scrolling feature into the next scrolling window.

Once the desired scrolling features are checked, choose “New Window” and a scrolling window incorporating those features will be created. Only one window will ever be present in Scroller. If “New Window” is selected and a window is currently open, that current window will first be closed. Note also, that selecting scrolling features in the File menu will only take effect on the next window and will not effect the current window if one is open.

Graphic windows will open with a blank drawing that is ten inches square. For demonstration purposes, the mouse will function as a polygon tool. On each click and drag, a line segment will be drawn from the previous polygon point to the current mouse location. If you click and drag outside the content region, the window will autoscroll until it reaches the edge of the drawing.

Text windows will open with a paragraph of text. You can type into this window and cut and paste text using standard Macintosh text handling procedures. The Edit menu , which has the standard editing commands, will be enabled for Text Edit windows.

One feature not accessible in the File menu are the scroll-bar margins. To illustrate use of scroll-bar margins, Scroller will set the margins to the width of the tool bars whenever tool bars are present. In actually use, it is of course possible to set the scroll-bar margins to any value independent of the width of the tool bars.

Finally, drag scrolling and joy-stick scrolling can also be tried using Scroller. To scroll using drag scrolling hold down the option key. To scroll using joy-stick scrolling hold down the shift and the option keys. For drag scrolling the cursor changes to the standard hand shape. For joy-stick scrolling, the cursor changes to four small arrows pointing up, down, left, and right.

Technical Comments

The most important sections of the scrolling manager are the routines that actually do the scrolling. All scrolling, whether scroll-bar scrolling, drag scrolling, joy-stick scrolling, or autoscrolling is eventually handled by the internal routine ScrollWindow(theWindow, units) which scrolls the window pointed to by theWindow by units lines. Before calling ScrollWindow() the variables scrlp (a pointer to the active window’s ScrollInfo record) and scrollType (equal to VertBar (1) or HorizBar (2)) will have already been set. The way these variable get set will depend on the type of scrolling occurring.

In brief, ScrollWindow() will call ScrollRect() for graphics windows and call TEScroll() for Text Edit windows. If a Text Edit window has rulers than a call to ScrollRect() will be required to scroll the contents of the rulers. For purely Text Edit windows, the call to TEScroll() completes the job. Apparently TEScroll() calls TEUpdate() and therefore automatically updates the text. Graphics windows or Text Edit windows with rulers will have some application-defined contents. To update the application-defined portions of these windows that may have been disturbed by scrolling, ScrollWindow() ends by calling UpdateWindow() which as discussed above must be supplied by the application. Note that the application supplied UpdateWindow() routine must check the current scroll bar margins to draw the correct part of the document. The routine GetMargins() is provided to simplify this task.

The scrolling speed is determined by a combination of number of pixels per line and the efficiency of the scrolling manager routines. For a given line size, it turns out that the limiting factor in scrolling speed is the speed at which the application supplied routine UpdateWindow() can update the window contents. The variable scrollType is supplied to UpdateWindow() to help in updating efficiency. When UpdateWindow() is called from ScrollWindow(), scrollType will be equal to VertBar (1) or HorizBar (2) at which time only the contents of the vertScrollRect or of the horizScrollRect respectively will need to be redrawn. When UpdateWindow() is called from the application scrollType should be FALSE (0) and the whole window may need to be redrawn.

Because the efficiency of UpdateWindow() is crucial, applications that use rulers should use clever means of quickly drawing them. The application Scroller does not use a clever ruler scheme and the slow down in scrolling between windows with and without rulers is apparent. The techniques used for updating the tool bars are not important because they need never be called while scrolling.

With Inside Macintosh Volume IV autoscrolling was implemented into the Text Edit routines. Because the Text Edit autoscrolling routines are not aware of the details of the window’s scrolling interface, they were not of use in developing the scrolling manager. For Text Edit windows which have rulers, the scrolling manager will have to handle autoscrolling of the rulers anyway, so it might as well do the text autoscrolling at the same time.

To handle autoscrolling while selecting, the scrolling manager provides a default click loop called SMClikLoop(). The click loop routine merely checks the mouse location and scrolls one line in the appropriate direction if it is outside the Text Edit view rectangle. One complicating aspect of developing the click loop is that TEClick() which calls the click loop apparently sets the clip region to the view rectangle. The click loop routine must reset the clip region to the entire window otherwise the scroll bar graphics and the rulers will not scroll appropriately during autoscrolling. The click loop routine also better restore the clip region before returning to TEClick() or else the inverting of the selected text will not occur correctly. I would have been saved several hours of work had Inside Macintosh included a warning regarding the clip region and TEClick().

The hardest part of Text Edit autoscrolling was the routine FixTEInsertPt() which scrolls the beginning of the selection region into view. This routine is typically called whenever TEKey(), TECut(), TEDelete(), or TEPaste() have been called. Calls to FixTEInsertPt() will become frequent while the user is typing in text and it therefore must be fast.

FixTEInsertPt() begins with a binary search through the lineStarts array in the Text Edit record to find the line which has the start of the current selection. The search begins at the last line. By starting on the last line FixTEInsertPt() will be fastest during the most common form of typing which is adding new text at the end of document. Even for large Text Edit documents (which are limited to 32K) a binary search to any location in the document is fast. Frequent calls to FixTEInsertPt() will therefore not result in any response problems while entering text.

I like to think part of the speed of FixTEInsertPt() was a result of careful attention to the code for the binary search. Experienced C programmers could certainly see ways of shortening the code and making it look more elegant. My goal, however, was not to create concise C code but to optimize the machine code generated by LightSpeed C™. To achieve this goal, I used MacBugs to examine the machine code generated for the binary search. The C code was then modified until I ended up with the result given in the attached source code. The final machine code had about half the number of lines per loop and therefore is probably about twice as fast as the machine code resulting from more concise C code. Some of the key changes were to set up pointers before entering the search loop, use register variables wherever possible, and use logical operations (e.g. shifts) instead of arithmetic expressions wherever possible.

I note that FixTEInsertPt() assumes constant line heights as was always the case for original Text Edit records. With the recent implementation of various text attributes within a single Text Edit record, FixTEInsertPt() will have to be modified. The changes will be relatively minor and I plan to get to it as soon as I need that feature (or maybe even sooner).

In multiwindow applications, the application usually will not be concerned with what window is scrolling. Many of the scrolling interface sections of code can therefore be generalized to handle any window. One thing the application will probably need to know will be the pointer to the ScrollInfo record for each window. This pointer can be stored in the windows refCon and therefore always be available. For this strategy not to cause the loss of other uses for the window’s refCon, a replacement refCon is provided in the ScrollInfo record.

Scrolling Manager Routines

The useful routines in the scrolling manger are described in the section “Using the Scrolling Manager.” The calling procedures; that is, the type and description of the required parameters for each routine, are contained in the comments in the source code.

Listing:  ScrollMgr.h

/* Header file for scrolling manager
 © 1988 John A. Nairn, All Rights Reserved   */

#ifndef _ControlMgr_
#include <ControlMgr.h>
#endif
#ifndef _TextEdit_
#include <TextEdit.h>
#endif
#ifndef _MacTypes_
#include <MacTypes.h>
#endif
 
#define SBarWidth 15 /*Standard Constants*/
#define HorizBar 1
#define VertBar 2
#define inPixels 0
#define inLines 1

typedef struct {
 ControlHandle hScrollHdl;
 int horizLines;
 int horizLinesVis;
 Rect horizScrollRect;
 Point hRectTopLeft;
 int hScrollMarg;
 ControlHandle vScrollHdl;
 int vertLines;
 int vertLinesVis;
 Rect vertScrollRect;
 Point vRectTopLeft;
 int vScrollMarg;
 TEHandle hTE;
 long refCon;
} ScrollInfo,*ScrollPtr;

ScrollPtr SetScrollWindow();
RgnHandle ScrollSectRgn();
Rect *NonScrollRect();
Rect *ScrollSectRect();
long GetSRefCon();
ScrollPtr ActiveScrollPtr();
Listing: Scroll.c

/* Scrolling Manager Library of Subroutines
 © 1988 John A. Nairn, All Rights Reserved

 This library of routines allow any application to
 easily implement scrolling in any window. The library
 can handle application-defined graphic windows as well
 as Text Edit windows.

 General variables appearing as common parameters
 1. theScrlp: the ScrollPtr for scrolling window
 2. theWindow: window pointer to scrolling window
 
 Routines marked “internal use” should not need to be
 called by the application
 
 WARNING: A number of these routines assume scrolling
 is occurring in the active window. This means
 that ScrollActivate() must have been called
 before calling these routines. Failure to do so
 may result in system crash.
*/

#include <MacTypes.h>/* Includes */
#include <QuickDraw.h>
#include <ControlMgr.h>
#include <TextEdit.h>
#include “ScrollMgr.h”
#define Active 0 /* defines */
#define Inactive 255
#define NIL 0L

ScrollPtr scrlp; /* Global Variables */
int scrollAmt,scrollCode,scrollType,linesVis,lineSize;
int vJoySpeed=8;hJoySpeed=8;
pascal Boolean SMClikLoop();

/* Initialize ScollInfo data recordfor new window and
 return ScrollPtr as the function return. Note
 this routine calls ScrollActivate() to activate
 the scroll bars.
 1. theScrlp: pointer to use for ScrollInfo or NIL to
 put ScrollInfo in heap
 2. textWindow=TRUE if Text Edit window else =FALSE
 3. v or hLineSize: pixels per line to scroll (0 to
 not include scroll bar)
 4. v or hTotalLines: number of lines in document
 5. v or hTools: width of tool bars in pixels (0 if
 no tool bar)
 6. v or hRuler: width of rulers in pixels (0 if no
 ruler)
 7. vScrollTop or hScrollLeft: scroll bar margins
 8. sRefCon: ScrollInfo refCon */

ScrollPtr SetScrollWindow(theWindow,theScrlp,textWindow,
 vLineSize,vTotalLines,vTools,vRuler,
 vScrollTop,hLineSize,hTotalLines,hTools,
 hRuler,hScrollLeft,sRefCon)
 WindowPtr theWindow;
 ScrollPtr theScrlp;
 Boolean textWindow;
 int vLineSize,vTotalLines,vTools,vRuler;
 int vScrollTop,hLineSize,hTotalLines,hTools;
 int hRuler,hScrollLeft;
 long sRefCon;
{
 ScrollPtr newscrlp;
 Rect viewRect,scrollRect;

 if(theScrlp==NIL) 
 newscrlp=(ScrollInfo *)
 NewPtr((Size)sizeof(ScrollInfo));
 else
 newscrlp=theScrlp;
 
 /*vertical scoll bar initialization */
 newscrlp->vScrollMarg=vScrollTop;
 newscrlp->vRectTopLeft.v=hTools+hRuler;
 newscrlp->vRectTopLeft.h=vTools;
 if(vLineSize!=0)
 { SetVertRect(newscrlp,theWindow,vLineSize,
 hLineSize);
 scrollRect=theWindow->portRect;
 scrollRect.left=scrollRect.right-SBarWidth;
 scrollRect.right+=1;
 scrollRect.bottom-=14;
 scrollRect.top-=1-newscrlp->vScrollMarg;
 newscrlp->vScrollHdl=NewControl(theWindow,
 &scrollRect,”\p”,1,0,0,0,scrollBarProc,
 (long)vLineSize);
 SetScroll(newscrlp,vTotalLines,VertBar);
 }
 else
 newscrlp->vScrollHdl=NIL;
 
 /*horizontal scoll bar initialization */
 newscrlp->hScrollMarg=hScrollLeft;
 newscrlp->hRectTopLeft.v=hTools;
 newscrlp->hRectTopLeft.h=vTools+vRuler;
 if(hLineSize!=0)
 { SetHorizRect(newscrlp,theWindow,hLineSize,
 vLineSize);
 scrollRect=theWindow->portRect;
 scrollRect.left-=1-newscrlp->hScrollMarg;
 scrollRect.right-=SBarWidth-1;
 scrollRect.top=scrollRect.bottom-SBarWidth;
 scrollRect.bottom+=1;
 newscrlp->hScrollHdl=NewControl(theWindow,
 &scrollRect,”\p”,1,0,0,0,scrollBarProc,
 (long)(-hLineSize));
 SetScroll(newscrlp,hTotalLines,HorizBar);
 }
 else
 newscrlp->hScrollHdl=NIL;

 /*Text edit initialization (if text window) */    
 if(textWindow)
 { ScrollSectRect(newscrlp,theWindow,&viewRect);
 newscrlp->hTE=TENew(&viewRect,&viewRect);
 SetClikLoop(&SMClikLoop,newscrlp->hTE);
 }
 else 
 newscrlp->hTE=NIL;

 newscrlp->refCon=sRefCon;
 ScrollActivate(newscrlp);
 return(newscrlp);
}

/* Set the scrolling rectangles - internal use */

SetVertRect(newscrlp,theWindow,theLineSize,hasHorizBar)
 ScrollPtr newscrlp;
 WindowPtr theWindow;
 int theLineSize,hasHorizBar;
{
 Rect r;
 
 r=theWindow->portRect;
 r.left+=newscrlp->vRectTopLeft.h;
 r.top+=newscrlp->vRectTopLeft.v;
 r.right-=SBarWidth;
 if(hasHorizBar)
 r.bottom-=SBarWidth;
 newscrlp->vertScrollRect=r;
 newscrlp->vertLinesVis=(r.bottom-r.top)/theLineSize;
}

SetHorizRect(newscrlp,theWindow,theLineSize,hasVertBar)
 ScrollPtr newscrlp;
 WindowPtr theWindow;
 int theLineSize,hasVertBar;
{
 Rect r;
 
 r=theWindow->portRect;
 r.left+=newscrlp->hRectTopLeft.h;
 r.top+=newscrlp->hRectTopLeft.v;
 r.bottom-=SBarWidth;
 if(hasVertBar)
 r.right-=SBarWidth;
 newscrlp->horizScrollRect=r;
 newscrlp->horizLinesVis=(r.right-r.left)/theLineSize;
}

/* Call when window activates: will activate scroll bars
 and set internal pointer scrlp to newscrlp which
 is ScrollPtr for window being activated     */

ScrollActivate(newscrlp)
 ScrollPtr newscrlp;
{
 scrlp=newscrlp;
 if(scrlp->vScrollHdl!=NIL)
 HiliteControl(scrlp->vScrollHdl,Active);
 if(scrlp->hScrollHdl!=NIL)
 HiliteControl(scrlp->hScrollHdl,Active);
}

/* Call when window deactivates: will deactivate scroll
 bars. oldscrlp is ScrollPtr for deactivating
 window */

ScrollDeactivate(oldscrlp)
 ScrollPtr oldscrlp;
{
 if(oldscrlp->vScrollHdl!=NIL)
 HiliteControl(oldscrlp->vScrollHdl,Inactive);
 if(oldscrlp->hScrollHdl!=NIL)
 HiliteControl(oldscrlp->hScrollHdl,Inactive);
}

/* Call when scrolling window changes size.
  Note: assumes growing window is active window */

GrowScroll(theWindow)
 WindowPtr theWindow;
{
 int theLineSize,hasOtherBar;
 int newTop,newRight,newBottom,newLeft;
 Rect tempRect;
 
 if(scrlp->vScrollHdl!=NIL)
 { /* Move vertical Bar */
 newTop=theWindow->portRect.top-1+
 scrlp->vScrollMarg;
 newRight=theWindow->portRect.right-SBarWidth;
 newBottom=theWindow->portRect.bottom-SBarWidth;
 MoveControl(scrlp->vScrollHdl,newRight,newTop);
 SizeControl(scrlp->vScrollHdl,SBarWidth+1,
 newBottom-newTop+1);
 
 /*Reset vertical scrolling rectangle */
 theLineSize=GetCRefCon(scrlp->vScrollHdl);
 hasOtherBar=(scrlp->hScrollHdl!=NIL)?TRUE:FALSE;
 SetVertRect(scrlp,theWindow,theLineSize,
 hasOtherBar);
 
 /*Reset vertical scroll bar range */
 SetScroll(scrlp,scrlp->vertLines,VertBar);
 }
 
 if(scrlp->hScrollHdl!=NIL)
 { /* Move horizontal Bar */
 newTop=theWindow->portRect.bottom-SBarWidth;
 newLeft=theWindow->portRect.left-1+
 scrlp->hScrollMarg;
 newRight=theWindow->portRect.right-SBarWidth;
 MoveControl(scrlp->hScrollHdl,newLeft,newTop);
 SizeControl(scrlp->hScrollHdl,newRight-newLeft+1,
 SBarWidth+1);
 
 /*Reset vertical scrolling rectangle */
 theLineSize=-GetCRefCon(scrlp->hScrollHdl);
 hasOtherBar=(scrlp->vScrollHdl!=NIL)?TRUE:FALSE;
 SetHorizRect(scrlp,theWindow,theLineSize,
 hasOtherBar);
 
 /*Reset vertical scroll bar range */
 SetScroll(scrlp,scrlp->horizLines,HorizBar);
 }
 
 /*Reset view rectangle (if text window) */  
 if(scrlp->hTE!=NIL)
 { ScrollSectRect(scrlp,theWindow,&tempRect);
 (**scrlp->hTE).viewRect=tempRect;
 }
}

/* Invalidate the scroll bar area before and after
 window resizing */

InvalBars(theWindow)
 WindowPtr theWindow;
{
 Rect r;
 
 r=theWindow->portRect;   /*Horizontal ScrollBar*/
 r.top=r.bottom-SBarWidth;
 InvalRect(&r);
 r.top=theWindow->portRect.top; /*Vertical Scroll Bar*/
 r.left=r.right-SBarWidth;
 InvalRect(&r);
}

/* Reset a scroll bar range when the document changes
   size. Updates window if necessary. 
 1. totalLines: total Lines in document
 2. barType: VertBar or HorizBar */

SetScroll(theScrlp,totalLines,barType)
 ScrollPtr theScrlp;
 int totalLines,barType;
{
 int n,max,currentValue,numLinesVis,tempLineSize,units;
 WindowPtr ctrlWindow;
 ControlHandle scrollHdl;
 
 if(barType==VertBar)
 { tempLineSize=GetCRefCon(theScrlp->vScrollHdl);
 numLinesVis=theScrlp->vertLinesVis;
 scrollHdl=theScrlp->vScrollHdl;
 theScrlp->vertLines=totalLines;
 }
 else
 { tempLineSize=-GetCRefCon(theScrlp->hScrollHdl);
 numLinesVis=theScrlp->horizLinesVis;
 scrollHdl=theScrlp->hScrollHdl;
 theScrlp->horizLines=totalLines;
 }
 
 n=totalLines-numLinesVis;
 currentValue=GetCtlValue(scrollHdl);
 max=(n>0 ? n : 0);
 SetCtlMax(scrollHdl,max);
 
 /* if necessary, scroll and redraw the window     */
 if(currentValue>max)
 { ctrlWindow=(*scrollHdl)->contrlOwner;
 InvalRect(&ctrlWindow->portRect);
 if(theScrlp->hTE!=NIL)
 { units=tempLineSize*(currentValue-max);    
 if(barType==VertBar)
 OffsetRect(&(**theScrlp->hTE).destRect,
 0,units);
 else
 OffsetRect(&(**theScrlp->hTE).destRect,
 units,0);
 }
 }
}

/* Return current margins in pixels or in scrolling
   lines for window.
 1. left or topMargin: margin returned in
 these variables
 2. lm or tmType: set to inPixels (0) or
 inLines (1) for result in pixels
 or lines */

GetMargins(theScrlp,leftMargin,lmType,topMargin,tmType)
 ScrollPtr theScrlp;
 int *leftMargin,*topMargin,lmType,tmType;
{
 int theLineSize;
 
 if(theScrlp->vScrollHdl!=NIL)
 { *topMargin=GetCtlValue(theScrlp->vScrollHdl);
 if(tmType==inPixels)
 { theLineSize=GetCRefCon(theScrlp->vScrollHdl);
 *topMargin*=theLineSize;
 }
 }
 else
 *topMargin=0;
 if(theScrlp->hScrollHdl!=NIL)
 { *leftMargin=GetCtlValue(theScrlp->hScrollHdl);
 if(lmType==inPixels)
 { theLineSize=-GetCRefCon(theScrlp->hScrollHdl);
 *leftMargin*=theLineSize;
 }
 }
 else
 *leftMargin=0;
}

/* Find intersection of two scrolling rectangles, if no
  scroll bars, this rect will include the grow box 
 1. drawRect: pointer to resulting rectangle - this
   pointer also returned as function value   */

Rect *ScrollSectRect(theScrlp,theWindow,drawRect)
 ScrollPtr theScrlp;
 WindowPtr theWindow;
 Rect *drawRect;
{
 NonScrollRect(theScrlp,theWindow,drawRect);
 drawRect->top=theScrlp->vRectTopLeft.v;
 if(theScrlp->hRectTopLeft.v>drawRect->top)
 drawRect->top=theScrlp->hRectTopLeft.v;
 drawRect->left=theScrlp->vRectTopLeft.h;
 if(theScrlp->hRectTopLeft.h>drawRect->left)
 drawRect->left=theScrlp->hRectTopLeft.h;
 return(drawRect);
}

/* Return handle for region that is intersection of two
 scrolling rectangles. If no scroll bars, region will
 exclude grow box*/

RgnHandle ScrollSectRgn(theScrlp,theWindow)
 ScrollPtr theScrlp;
 WindowPtr theWindow;
{
 Rect r;
 RgnHandle drawRgn,tempRgn;
 
 ScrollSectRect(theScrlp,theWindow,&r);
 drawRgn=NewRgn();
 RectRgn(drawRgn,&r);
 if((theScrlp->vScrollHdl==NIL)&&
 (theScrlp->hScrollHdl==NIL))
 { tempRgn=NewRgn();
 r.left=theWindow->portRect.right-SBarWidth;
 r.top=theWindow->portRect.bottom-SBarWidth;
 r.right=theWindow->portRect.right;
 r.bottom=theWindow->portRect.bottom;
 RectRgn(tempRgn,&r);
 DiffRgn(drawRgn,tempRgn,drawRgn);
 DisposeRgn(tempRgn);
 }
 return(drawRgn);
}

/* Calculate rectangle that excludes the scroll bars
 1. drawRect: pointer to resulting rectangle - this
  pointer also returned as function value    */

Rect *NonScrollRect(theScrlp,theWindow,drawRect)
 ScrollPtr theScrlp;
 WindowPtr theWindow;
 Rect *drawRect;
{
 *drawRect=theWindow->portRect;
 if(theScrlp->vScrollHdl!=NIL)
 drawRect->right-=SBarWidth;
 if(theScrlp->hScrollHdl!=NIL)
 drawRect->bottom-=SBarWidth;
 return(drawRect);
}

/* Draw enough grow icon to enclose active scroll bars */

ScrollDrawGrowIcon(theScrlp,theWindow)
 ScrollPtr theScrlp;
 WindowPtr theWindow;
{
 Rect r;
 RgnHandle tempRgn;
 
 GetClip(tempRgn=NewRgn());
 r=theWindow->portRect;
 if(theScrlp->vScrollHdl==NIL)
 r.top=r.bottom-SBarWidth;
 if(theScrlp->hScrollHdl==NIL)
 r.left=r.right-SBarWidth;
 ClipRect(&r);
 DrawGrowIcon(theWindow);
 SetClip(tempRgn);
 DisposeRgn(tempRgn);
}

/* Action procedure for scrolling  - internal use
 Scroll by scrollAmt (set by doScroll)
 ScrollCode is the part of the scroll bar being tracked
 (set by doScroll) ScrollType is type of scroll bar
 (set by doScroll)
 May also be called for tracking selections. In this
 case scrollType will be set by setSelectScroll,
 scrollAmt will be 1 or -1 (set by selectScroll)
 and scrollCode will be set to 0. */

pascal void ScrollProc(control,theCode)
 ControlHandle control;
 int theCode;
{
 int max,oldValue,newValue;
 WindowPtr ctrlWindow=(*control)->contrlOwner;
 
 if(theCode==scrollCode)
 { oldValue=GetCtlValue(control);
 newValue=oldValue+scrollAmt;
 if(newValue<0) newValue=0;
 max=GetCtlMax(control);
 if(newValue>max) newValue=max;
 SetCtlValue(control,newValue);
 ThumbMove(ctrlWindow,oldValue,newValue);
 }
}

/* Do scrolling, return TRUE if scrolled or FALSE if not 
 1. where: mouse location in Global coordinates
 Note: assumes scrolling in current active window */

DoScroll(theWindow,where)
 WindowPtr theWindow;
 long where;
{
 int partCode,oldValue,newValue;
 ControlHandle control;
 
 GlobalToLocal(&where);
 partCode=FindControl(where,theWindow,&control);
 if(partCode==0)      /*mouse down not in scroll bar*/
 return(FALSE);
 
 lineSize=GetCRefCon(control);
 if(lineSize<0)  /* horiz scrolling*/
 { linesVis=scrlp->horizLinesVis;
 scrollType=HorizBar;
 lineSize=-lineSize;
 }
 else   /* vert scrolling */
 { linesVis=scrlp->vertLinesVis;
 scrollType=VertBar;
 }
 
 switch(partCode)
 { case inUpButton:
 case inDownButton:
 case inPageUp:
 case inPageDown:
 switch(partCode)
 { case inUpButton:
 scrollAmt=-1;
 break;
 case inDownButton:
 scrollAmt=1;
 break;
 case inPageUp:
 scrollAmt=-linesVis;
 break;
 case inPageDown:
 scrollAmt=linesVis;
 break;
 }
 scrollCode=partCode;
 TrackControl(control,where,&ScrollProc);
 break;
 case inThumb:
 oldValue=GetCtlValue(control);
 if(TrackControl(control,where,-1L))
 { newValue=GetCtlValue(control);
 ThumbMove(theWindow,oldValue,newValue);
 }
 break;
 default:
 break;
 }
 return(TRUE);
}

/* Set to prepare for automatic scrolling of scroll bars 
 - internal use */

SetSelectScroll(control)
 ControlHandle control;
{
 lineSize=GetCRefCon(control);
 if(lineSize<0)  /* horiz scrolling*/
 { linesVis=scrlp->horizLinesVis;
 scrollType=HorizBar;
 lineSize=-lineSize;
 }
 else   /* vert scrolling */
 { linesVis=scrlp->vertLinesVis;
 scrollType=VertBar;
 }
 scrollCode=0;
}

/* Scroll dir lines using scroll bar set by
 setSelectScroll - internal use */

SelectScroll(dir)
 int dir;
{
 scrollAmt=dir;
 if(scrollType==HorizBar)
 ScrollProc(scrlp->hScrollHdl,0);
 else
 ScrollProc(scrlp->vScrollHdl,0);
}

/* Scroll along with mouse while mouse button is down 
 1. where: mouse location in Global coordinates
 Note: assumes scrolling in current active window */

DragScroll(where)
 Point where;
{
 WindowPtr theWindow;
 register int vLineSize=0,hLineSize=0,vAmt,hAmt;
 Rect dragRect;
 Point lastWhere;
 
 if(scrlp->vScrollHdl!=NIL) 
 { theWindow=(*(scrlp->vScrollHdl))->contrlOwner;
 vLineSize=GetCRefCon(scrlp->vScrollHdl);
 }
 if(scrlp->hScrollHdl!=NIL)
 { theWindow=(*(scrlp->hScrollHdl))->contrlOwner;
 hLineSize=-GetCRefCon(scrlp->hScrollHdl);
 }
 if((vLineSize==0)&&(hLineSize==0)) return;
 
 ScrollSectRect(scrlp,theWindow,&dragRect);
 GlobalToLocal(&where);
 if(!PtInRect(where,&dragRect)) return;
 lastWhere=where;
 while(StillDown())
 { GetMouse(&where);
 if(PtInRect(where,&dragRect))
 { if(vLineSize>0) /* vert scrolling */
 { if(vAmt=(lastWhere.v-where.v)/vLineSize)
 { SetSelectScroll(scrlp->vScrollHdl);
 SelectScroll(vAmt);
 lastWhere.v-=vAmt*vLineSize;
 }
 }
 if(hLineSize>0) /* horiz scrolling*/
 { if(hAmt=(lastWhere.h-where.h)/hLineSize)
 { SetSelectScroll(scrlp->hScrollHdl);
 SelectScroll(hAmt);
 lastWhere.h-=hAmt*hLineSize;
 }
 }
 }
 }
}

/* Do joystick scrolling as long as mouse is down
 1. where: mouse location in Global coordinates
 Note: assumes scrolling in current active window */

JoyStickScroll(where)
 Point where;
{
 int vAmt,hAmt;
 Point newWhere;

 if((scrlp->vScrollHdl==NIL)&&(scrlp->hScrollHdl==NIL))
 return;
 GlobalToLocal(&where);
 while(StillDown())
 { GetMouse(&newWhere);
 vAmt=(newWhere.v-where.v)/vJoySpeed;
 if((scrlp->vScrollHdl!=NIL)&&vAmt)
 { SetSelectScroll(scrlp->vScrollHdl);
 SelectScroll(vAmt);
 }
 hAmt=(newWhere.h-where.h)/hJoySpeed;
 if((scrlp->hScrollHdl!=NIL)&&hAmt)
 { SetSelectScroll(scrlp->hScrollHdl);
 SelectScroll(hAmt);
 }
 }
}

/* Set joy stick speed in pixels per speed increment 
 1. v or hSpeed: vertical or horizontal settings */

SetJoySpeed(vSpeed,hSpeed)
 int vSpeed,hSpeed;
{
 vJoySpeed=vSpeed;
 hJoySpeed=hSpeed;
}

/* If mouse is outside content region, scroll 1 line
    towards mouse location and return TRUE,
    otherwise just return FALSE
 Note: assumes scrolling in current active window  */

FollowMouse()
{
 Point where;
 RgnHandle tempRgn;
 int followed=FALSE;
 
 GetMouse(&where);
 GetClip(tempRgn=NewRgn());
 ClipRect(&thePort->portRect);
 
 if(scrlp->hScrollHdl!=NIL) /* horiz tracking */
 { if(where.h<scrlp->horizScrollRect.left)
 { SetSelectScroll(scrlp->hScrollHdl);
 SelectScroll(-1);
 followed=TRUE;
 }
 else if(where.h>scrlp->horizScrollRect.right)
 { SetSelectScroll(scrlp->hScrollHdl);
 SelectScroll(1);
 followed=TRUE;
 }
 }
 if(scrlp->vScrollHdl!=NIL) /* vert tracking */
 { if(where.v<scrlp->vertScrollRect.top)
 { SetSelectScroll(scrlp->vScrollHdl);
 SelectScroll(-1);
 followed=TRUE;
 }
 else if(where.v>scrlp->vertScrollRect.bottom)
 { SetSelectScroll(scrlp->vScrollHdl);
 SelectScroll(1);
 followed=TRUE;
 }
 }
 
 SetClip(tempRgn);
 DisposeRgn(tempRgn);
 return(followed);
}

/* Handle automatic scrolling of Text Edit Windows
 Note: assumes scrolling in current active window */

pascal Boolean SMClikLoop()
{
 Point where;
 RgnHandle tempRgn;
 
 GetMouse(&where);
 if(PtInRect(where,&(**scrlp->hTE).viewRect))
 return(TRUE);
 GetClip(tempRgn=NewRgn());
 ClipRect(&thePort->portRect);
 
 if(scrlp->hScrollHdl!=NIL) /* horiz tracking */
 { if(where.h<(**scrlp->hTE).viewRect.left)
 { SetSelectScroll(scrlp->hScrollHdl);
 SelectScroll(-1);
 }
 else if(where.h>(**scrlp->hTE).viewRect.right)
 { SetSelectScroll(scrlp->hScrollHdl);
 SelectScroll(1);
 }
 }
 if(scrlp->vScrollHdl!=NIL) /* vert tracking */
 { if(where.v<(**scrlp->hTE).viewRect.top)
 { SetSelectScroll(scrlp->vScrollHdl);
 SelectScroll(-1);
 }
 else if(where.v>(**scrlp->hTE).viewRect.bottom)
 { SetSelectScroll(scrlp->vScrollHdl);
 SelectScroll(1);
 }
 }
 
 SetClip(tempRgn); /* must restore clip region */
 DisposeRgn(tempRgn);
 return(TRUE);
}

/* Scroll window from oldValue to newValue
 - internal use */

ThumbMove(theWindow,oldValue,newValue)
 WindowPtr theWindow;
 int oldValue,newValue;
{
 int units=oldValue-newValue;
 
 if(units)
 ScrollWindow(theWindow,units);
}

/* Do the actual scrolling. When required this routine
 calls UpdateWindow() which must be supplied by the
 application - internal use */

ScrollWindow(theWindow,units)
 WindowPtr theWindow;
 int units;
{
 int absunits,rectLength;
 Boolean hasRuler=FALSE;
 RgnHandle tmpRgn;
 GrafPtr saveport;
 Rect rulerRect;
 
 GetPort(&saveport);
 SetPort(theWindow);
 absunits= units<0 ? -units : units;
 
 /* scroll text windows */
 if(scrlp->hTE!=NIL)
 { if(scrollType==HorizBar)
 { TEScroll(rectLength=lineSize*units,0,
 scrlp->hTE);
 if(scrlp->vRectTopLeft.v!=
 scrlp->hRectTopLeft.v)
 { hasRuler=TRUE;
 rulerRect.left=scrlp->hRectTopLeft.h;
 rulerRect.top=scrlp->hRectTopLeft.v;
 rulerRect.bottom=scrlp->vRectTopLeft.v;
 rulerRect.right=
 scrlp->horizScrollRect.right;
 }
 }
 else
 { TEScroll(0,rectLength=lineSize*units,
 scrlp->hTE);
 if(scrlp->vRectTopLeft.h!=
 scrlp->hRectTopLeft.h)
 { hasRuler=TRUE;
 rulerRect.left=scrlp->vRectTopLeft.h;
 rulerRect.top=scrlp->vRectTopLeft.v;
 rulerRect.right=scrlp->hRectTopLeft.h;
 rulerRect.bottom=
 scrlp->vertScrollRect.bottom;
 }
 }
 /*if rulers in text windows, scroll them    */
 if(hasRuler)
 { tmpRgn=NewRgn();
 if(scrollType==HorizBar)
 ScrollRect(&rulerRect,lineSize*units,0,
 tmpRgn);
 else
 ScrollRect(&rulerRect,0,lineSize*units,
 tmpRgn);
 InvalRgn(tmpRgn);
 DisposeRgn(tmpRgn);
 }
 }
 
 /*Scolling more than 1 page in graphic windows */ 
 else if(absunits>linesVis)
 { if(scrollType==HorizBar)
 InvalRect(&scrlp->horizScrollRect);
 else
 InvalRect(&scrlp->vertScrollRect);
 }
 
 /*Fractional page scolling in graphic windows     */
 else
 { tmpRgn=NewRgn();
 if(scrollType==HorizBar)
 ScrollRect(&scrlp->horizScrollRect,
 lineSize*units,0,tmpRgn);
 else
 ScrollRect(&scrlp->vertScrollRect,
 0,lineSize*units,tmpRgn);
 InvalRgn(tmpRgn);
 DisposeRgn(tmpRgn);
 }
 
 /*If required, call Update process*/
 if((scrlp->hTE==NIL)||hasRuler)
 UpdateWindow(theWindow,scrollType);
 
 SetPort(saveport);
}

/* Scroll the line and column numbers given by selLine
 and selColumn into view
 Note: assumes scrolling in current active window */

FixInsertPt(selLine,selColumn)
 int selLine,selColumn;
{
 if(scrlp->vScrollHdl!=NIL)
 MoveInsertPt(selLine,VertBar);
 if(scrlp->hScrollHdl!=NIL)
 MoveInsertPt(selColumn,HorizBar);
}

/* Scroll begining of selection of Text Edit window into
    view
 Note: assumes scrolling in current active window */

FixTEInsertPt()
{
 int selLine,selColumn=0,textLength,*line0;
 WindowPtr TEWindow;
 GrafPtr saveGraf;
 register int thePt,jumpSize,lineLook;

 if(scrlp->hTE==NIL) return;
 if(scrlp->vScrollHdl!=NIL) 
 TEWindow=(*(scrlp->vScrollHdl))->contrlOwner;
 else if(scrlp->hScrollHdl!=NIL)
 TEWindow=(*(scrlp->hScrollHdl))->contrlOwner;
 else
 return;

 /*Binary search for current line */
 thePt=(**scrlp->hTE).selStart;    /*Set up pointers*/
 lineLook=(**scrlp->hTE).nLines;
 line0=&(**scrlp->hTE).lineStarts[0];
 if(thePt>=*(line0+lineLook)) /*Check last line*/
 selLine=lineLook-1;
 else
 { jumpSize=lineLook>>1|1;/*div by 2, but not 0*/
 lineLook-=jumpSize;
 while(jumpSize=jumpSize>>1|1)/*reduce jumpsize*/
 { if(thePt<*(line0+lineLook))
 lineLook-=jumpSize; /*look back*/
 else if(thePt>=*(line0+lineLook+1))
 lineLook+=jumpSize; /*look forward*/
 else
 { selLine=lineLook; /*found line*/
 break;
 }
 }
 }

 /*Find column by getting width of text from start of
 line to insertion point. */
 if(scrlp->hScrollHdl!=NIL)
 { GetPort(&saveGraf);
 SetPort(TEWindow);
 HLock((**scrlp->hTE).hText);
 textLength=thePt-
 (**scrlp->hTE).lineStarts[selLine];
 selColumn=TextWidth(*((**scrlp->hTE).hText),
 (**scrlp->hTE).lineStarts[selLine],
 textLength);
 HUnlock((**scrlp->hTE).hText);
 selColumn/=(-GetCRefCon(scrlp->hScrollHdl));
 SetPort(saveGraf);
 }

 /* Correct selLine in case scrolling line is smaller
 than text lineHeight */
 if(GetCRefCon(scrlp->vScrollHdl)<
 (**scrlp->hTE).lineHeight)
 { selLine=selLine*(**scrlp->hTE).lineHeight/
 GetCRefCon(scrlp->vScrollHdl);
 if(selLine>GetCtlValue(scrlp->vScrollHdl))
 selLine+=1+((**scrlp->hTE).lineHeight/
 GetCRefCon(scrlp->vScrollHdl));
 }
 FixInsertPt(selLine,selColumn);
}

/* Scoll one direction to bring point into view
 - internal use */

MoveInsertPt(selStart,barType)
 int selStart,barType;
{
 int curValue,theLinesVis,units;
 ControlHandle scrollHdl;

 if(barType==VertBar)
 { theLinesVis=scrlp->vertLinesVis;
 scrollHdl=scrlp->vScrollHdl;
 }
 else
 { theLinesVis=scrlp->horizLinesVis;
 scrollHdl=scrlp->hScrollHdl;
 }
 
 curValue=GetCtlValue(scrollHdl);
 if(selStart<curValue)
 units=selStart-curValue;
 else if(selStart>(curValue+theLinesVis-1))
 units=selStart-curValue-theLinesVis+1;
 else
 return;
 
 SetSelectScroll(scrollHdl);
 SelectScroll(units);
}

/* Call TEClick - call from here insures SMClikLoop Code
 available. Meaning of parameters identical to
 Toolbox routine TEClick() */

ScrollTEClick(where,theShift,hTE)
 Point where;
 Boolean theShift;
 TEHandle hTE;
{
 TEClick(where,theShift,hTE);
}

/* If ScrollInfo in heap, dispose of pointer when done.
 Do not call if ScrollInfo not in heap*/

DisposeScroll(theScrlp)
 ScrollPtr theScrlp;
{
 DisposPtr(theScrlp);
}

/* Return the refCon in ScrollInfo record */

long GetSRefCon(theScrlp)
 ScrollPtr theScrlp;
{
 return(theScrlp->refCon);
}

/* Set the refCon in ScrollInfo  record */

SetSRefCon(theScrlp,sRefCon)
 ScrollPtr theScrlp;
 long sRefCon;
{
 theScrlp->refCon=sRefCon;
}

/* Return the current active ScrollPtr */

ScrollPtr ActiveScrollPtr()
{
 return(scrlp);
}

Listing:  ScrollerMain.c

/* Application Scroller, version 1.0 (August, 26, 1988)
 
 This application demonstrate features and use of the
    Scrolling manager
    
 © 1988 John A. Nairn, All Rights Reserved   */

/* Includes from Lightspeed C */
#include <QuickDraw.h>
#include <MacTypes.h>
#include <FontMgr.h>
#include <WindowMgr.h>
#include <MenuMgr.h>
#include <TextEdit.h>
#include <EventMgr.h>
#include <DeskMgr.h>
#include <DialogMgr.h>
#include <ToolboxUtil.h>
#include <ControlMgr.h>
#include “ScrollMgr.h”    /*Scrolling Manager header*/

enum { apple_menu=255,file_menu,edit_menu };
enum { drawCursor=300,handCursor,joyCursor };
#define sample_text 500
#define about_dlog 998
#define NIL 0L   /* Some constants */
#define RULERWIDTH 15
#define TOOLWIDTH 20
#define about_item 1 /* Menu items */
enum { new_window=1,graphic_window=3,text_edit_window,
 vert_bar=6,horiz_bar,vert_ruler=9,horiz_ruler,
 vert_tool_bar,horiz_tool_bar,quit=14 };

WindowPtr myWindow=NIL;   /* Global Variables */
TEHandle myhTE=NIL;
ScrollInfo scrollRecord;
ScrollPtr myScrlp;
Point polygon[200];
Rect dragrect;
int window_type=graphic_window,has_vBar=TRUE;
int has_vRuler=FALSE,has_vTools=FALSE,has_hRuler=FALSE;
int has_hTools=FALSE,has_hBar=TRUE;
int vRuler,vTools,hRuler,hTools,polySize=0;

/* Main Program  - Initialize and start event loop */

main()
{
 MenuHandle menu;
 Rect r;
 
 InitGraf(&thePort);
 InitFonts();
 InitWindows();
 InitCursor();
 FlushEvents(everyEvent,0);
 InitMenus();
 TEInit();
 InitDialogs(0L);
 r=screenBits.bounds;/* Window dragging Rect */
 SetRect(&dragrect,4,24,r.right-4,r.bottom-4);
 menu=GetMenu(apple_menu);               /*DA menu*/
 AddResMenu(menu,’DRVR’);
 InsertMenu(menu,0);
 menu=GetMenu(file_menu); /*File menu*/
 InsertMenu(menu,0);
 CheckItem(menu,window_type,TRUE);
 CheckItem(menu,horiz_bar,has_vBar);
 CheckItem(menu,vert_bar,has_hBar);
 InsertMenu(GetMenu(edit_menu),0); /*Edit Menu*/
 MaintainMenus();
 do_about();
 event_loop();
}

/* Enable or Disable Edit Menu */

MaintainMenus()
{
 if(FrontWindow()==myWindow)
 { if(myhTE==NIL)
 DisableItem(GetMHandle(edit_menu),0);
 else
 EnableItem(GetMHandle(edit_menu),0);
 }
 else
 EnableItem(GetMHandle(edit_menu),0);
 DrawMenuBar();
}

/* Maintain cursor according to window type, keys
 down, and mouse location */

MaintainCursor()
{
 KeyMap theKeys;
 RgnHandle drawRgn;
 Point where;

 if(FrontWindow()!=myWindow)
 return;
 else if(myWindow==NIL)
 InitCursor();
 else
 { drawRgn=ScrollSectRgn(myScrlp,myWindow);
 GetMouse(&where);
 if(!PtInRgn(where,drawRgn))
 InitCursor();
 else
 { GetKeys(&theKeys);
 if((theKeys.Key[1]&4)&&(theKeys.Key[1]&1))
 SetCursor(*GetCursor(joyCursor));
 else if(theKeys.Key[1]&4)
 SetCursor(*GetCursor(handCursor));
 else if(myhTE!=NIL)
 SetCursor(*GetCursor(iBeamCursor));
 else
 SetCursor(*GetCursor(drawCursor));
 }
 DisposeRgn(drawRgn);
 }
}

/* Program main event loop*/

event_loop()
{
 EventRecord event;
 Boolean valid;
 char theChar;
 
 while (1)
 { SystemTask();
 MaintainCursor();
 if(myhTE!=NIL) TEIdle(myhTE);
 valid=GetNextEvent(everyEvent,&event);
 if (!valid) continue;
 switch(event.what)
 { case mouseDown:
 do_mouse_down(&event);
 break;
 case keyDown:
 case autoKey:
 theChar=event.message&charCodeMask;
 if(event.modifiers&cmdKey)
 { if(event.what!=autoKey)
 do_menu(MenuKey(theChar));
 }
 else
 { if(myhTE!=NIL)
 { if((**myhTE).teLength<32760)
 { TEKey(theChar,myhTE);
 if(myScrlp->vScrollHdl!=NIL)
 SetScroll(myScrlp,
 (**myhTE).nLines,VertBar);
 FixTEInsertPt();
 }
 else
 SysBeep(10);  /* TE full */
 }
 else
 SysBeep(10);  /* bad key down*/
 }
 break;
 case updateEvt:
 do_update(&event);
 break;
 case activateEvt:
 do_activate((WindowPtr)event.message,
 event.modifiers&activeFlag);
 break;
 default:
 break;
 }
 }
}

/* Handle mouse down events */

do_mouse_down(eventp)
 EventRecord *eventp;
{
 WindowPtr windowp;
 int place_type=FindWindow(eventp->where,&windowp);
 Boolean theshift;
 KeyMap theKeys;
 
 switch(place_type)
 { case inMenuBar:
 do_menu(MenuSelect(eventp->where));
 break;
 case inSysWindow:
 SystemClick(eventp,windowp);
 MaintainMenus();
 break;
 case inContent:
 if(windowp!=FrontWindow())
 { SelectWindow(windowp);
 MaintainMenus();
 }
 else if(windowp==myWindow)
 { if(DoScroll(windowp,eventp->where)==FALSE)
 { GetKeys(&theKeys);
 if((theKeys.Key[1]&4)&&
 (theKeys.Key[1]&1))
 JoyStickScroll(eventp->where);
 else if(theKeys.Key[1]&4)
 DragScroll(eventp->where);
 else if(myhTE==NIL)
 TrackPoly(eventp->where);
 else
 { GlobalToLocal(&eventp->where);
 if(PtInRect(eventp->where,
 &(**myhTE).viewRect))
 { if(eventp->modifiers&shiftKey)
 theshift=TRUE;
 else
 theshift=FALSE;
 ScrollTEClick(eventp->where,
 theshift,myhTE);
 }
 else
 SysBeep(10);
 }
 }
 }
 break;
 case inDrag:
 DragWindow(windowp,eventp->where,&dragrect);
 break;
 case inGrow:
 grow_window(windowp,eventp->where);
 break;
 case inGoAway:
 if(TrackGoAway(windowp,eventp->where))
 { do_close(windowp);
 MaintainMenus();
 }
 break;
 default:
 break;
 }
}

/* Track mouse and draw a polygon - for Graphic windows*/

TrackPoly(where)
 Point where;
{
 int lm,tm;
 Point lastWhere;
 Rect drawRect;
 RgnHandle drawRgn;

 GlobalToLocal(&where);
 ScrollSectRect(myScrlp,myWindow,&drawRect);
 if(!PtInRect(where,&drawRect))
 { SysBeep(10);  /* illegal click location */
 return;
 }
 GetMargins(myScrlp,&lm,inPixels,&tm,inPixels);
 if(polySize==0)
 { polygon[polySize].h=where.h+lm;
 polygon[polySize].v=where.v+tm;
 }
 else if(polySize==200)
 { SysBeep(10);
 return;
 }
 drawRgn=ScrollSectRgn(myScrlp,myWindow);    
 SetClip(drawRgn); 
 PenMode(patXor);
 MoveTo(polygon[polySize].h-lm,polygon[polySize].v-tm);
 LineTo(where.h,where.v);
 lastWhere=where;
 while(StillDown())
 { GetMouse(&where);
 if(PtInRgn(where,drawRgn))
 { if((where.h!=lastWhere.h)||
 (where.v!=lastWhere.v))
 { MoveTo(polygon[polySize].h-lm,
 polygon[polySize].v-tm);
 LineTo(lastWhere.h,lastWhere.v);
 MoveTo(polygon[polySize].h-lm,
 polygon[polySize].v-tm);
 LineTo(where.h,where.v);
 lastWhere=where;
 }
 }
 else
 { MoveTo(polygon[polySize].h-lm,
 polygon[polySize].v-tm);
 LineTo(lastWhere.h,lastWhere.v);
 FollowMouse();
 GetMargins(myScrlp,&lm,inPixels,&tm,inPixels);
 MoveTo(polygon[polySize].h-lm,
 polygon[polySize].v-tm);
 LineTo(where.h,where.v);
 lastWhere=where;
 }
 }
 MoveTo(polygon[polySize].h-lm,polygon[polySize].v-tm);
 LineTo(lastWhere.h,lastWhere.v);
 PenMode(patCopy);
 if(lastWhere.h>drawRect.right)
 lastWhere.h=drawRect.right;
 if(lastWhere.h<drawRect.left)
 lastWhere.h=drawRect.left;
 if(lastWhere.v>drawRect.bottom)
 lastWhere.v=drawRect.bottom;
 if(lastWhere.v<drawRect.top)
 lastWhere.v=drawRect.top;
 MoveTo(polygon[polySize].h-lm,polygon[polySize].v-tm);
 LineTo(lastWhere.h,lastWhere.v);
 polygon[++polySize].h=lastWhere.h+lm;
 polygon[polySize].v=lastWhere.v+tm;
 ClipRect(&myWindow->portRect);
 DisposeRgn(drawRgn);
}

/* Close the scrolling demonstration window */

do_close(windowp)
 WindowPtr windowp;
{
 if (myWindow!=NIL)
 { if(myhTE!=NIL)
 { ZeroScrap();
 TEToScrap();
 TEDispose(myhTE);
 }
 CloseWindow(windowp);
 myWindow=NIL;
 myhTE=NIL;
 }
}

/* Handle menu command events */

do_menu(command)
 long command;
{
 int menu_id=HiWord(command);
 int item=LoWord(command);
 if(menu_id!=0)
 do_menu_item(menu_id,item);
}

/* Do a menu command */

do_menu_item(menu_id,item)
 int menu_id,item;
{
 char item_name[32];
 int newCheck;
 
 switch(menu_id)
 { case apple_menu:
 switch(item)
 { case about_item:
 do_about();
 break;
 default:
 GetItem(GetMHandle(menu_id),
 item,item_name);
 OpenDeskAcc(item_name);
 HiliteMenu(0);
 MaintainMenus();
 break;
 }
 break;
 case file_menu:
 switch (item)
 { case new_window:
 do_close(myWindow);
 myNewWindow();
 HiliteMenu(0);
 MaintainMenus();
 break;
 case graphic_window:
 case text_edit_window:
 CheckItem(GetMHandle(file_menu),
 window_type,FALSE);
 window_type=item;
 CheckItem(GetMHandle(file_menu),
 window_type,TRUE);
 break;
 case vert_bar:
 newCheck=has_vBar=1-has_vBar;
 break;
 case horiz_bar:
 newCheck=has_hBar=1-has_hBar;
 break;
 case vert_ruler:
 newCheck=has_vRuler=1-has_vRuler;
 break;
 case horiz_ruler:
 newCheck=has_hRuler=1-has_hRuler;
 break;
 case vert_tool_bar:
 newCheck=has_vTools=1-has_vTools;
 break;
 case horiz_tool_bar:
 newCheck=has_hTools=1-has_hTools;
 break;
 case quit:
 do_close(myWindow);
 finish();
 default:
 break;
 }
 if(item>=vert_bar)
 CheckItem(GetMHandle(file_menu),
 item,newCheck);
 break;
 case edit_menu:
 if(!SystemEdit(item-1))
 { switch(item-1)
 { case undoCmd:
 break;
 case cutCmd:
 TECut(myhTE);
 break;
 case copyCmd:
 TECopy(myhTE);
 break;
 case pasteCmd:
 if(((long)(**myhTE).teLength+
 (long)TEGetScrapLen())<32760)
 TEPaste(myhTE);
 else
 SysBeep(10);
 break;
 case clearCmd:
 TEDelete(myhTE);
 break;
 default:
 break;
 }
 /* reset scroll bar */
 if(myScrlp->vScrollHdl!=NIL)
 SetScroll(myScrlp,(**myhTE).nLines,
 VertBar);
 
 /* if needed, move insertion point */
 if(((item-1)==cutCmd)||
 ((item-1)==clearCmd)||
 ((item-1)==pasteCmd))
 FixTEInsertPt();
 }
 default:
 break;
 }
 HiliteMenu(0);
}

/* Handle Update events */

do_update(event)
 EventRecord *event;
{
 WindowPtr up_wind=(WindowPtr)event->message;
 
 UpdateWindow(up_wind,FALSE);
}

/* Update the window */

UpdateWindow(up_wind,scrollType)
 WindowPtr up_wind;
 int scrollType;
{
 GrafPtr save_graf;
 
 if(up_wind==myWindow)
 { GetPort(&save_graf);
 SetPort(up_wind);
 BeginUpdate(up_wind);
 ClipRect(&up_wind->portRect);
 EraseRect(&up_wind->portRect);
 if(!scrollType)
 { DrawControls(up_wind);
 ScrollDrawGrowIcon(myScrlp,up_wind);
 }
 draw_content(up_wind,scrollType);
 EndUpdate(up_wind);
 SetPort(save_graf);
 }
}

/* Activate the window */

do_activate(act_wind,isactivate)
 WindowPtr act_wind;
 int isactivate;
{
 Rect r;
 
 SetPort(act_wind);
 if(act_wind==myWindow)
 { ScrollDrawGrowIcon(myScrlp,act_wind);
 if(isactivate)
 { ScrollActivate(myScrlp);
 if(myhTE!=NIL)
 { TEActivate(myhTE);
 TEFromScrap();
 }
 }
 else
 { ScrollDeactivate(myScrlp);
 if(myhTE!=NIL)
 { TEDeactivate(myhTE);
 ZeroScrap();
 TEToScrap();
 }
 }
 }
}

/* handle window growing */

grow_window(windowp,mouse_point)
 WindowPtr windowp;
 Point mouse_point;
{
 long new_bounds;
 
 InvalBars(windowp); /*invalidate scroll bars*/
 if((new_bounds=GrowWindow(windowp,mouse_point,
 &dragrect))==0)
 return;
 SizeWindow(windowp,LoWord(new_bounds),
 HiWord(new_bounds),(Boolean)TRUE);
 InvalBars(windowp); /*invalidate new scroll bars*/
 GrowScroll(windowp);/*update scroll bar window*/
}

/* Open new scrolling example window */

myNewWindow()
{
 Rect r,wrect;
 int hLineSize=0,vLineSize=0,vTotalLines=0;
 int vToolSize=0,vRulerSize=0,hToolSize=0,hRulerSize=0;
 int vScrollTop=0,hScrollLeft=0,hTotalLines=0;
 Boolean includeText=FALSE;
 FontInfo theFont;
 Handle texthdl;
 
 r=screenBits.bounds;
 SetRect(&wrect,20,60,r.right-20,r.bottom-20);
 myWindow=NewWindow(NIL,&wrect,”\pScrolling Example”,
 1,documentProc,-1L,1,0L);

 polySize=0;
 SetPort(myWindow);
 TextFont(geneva);
 TextSize(12);
 /*Note: add 1 to ruler and tool widths for line*/
 if(has_vRuler) vRulerSize=RULERWIDTH+1;
 if(has_hRuler) hRulerSize=RULERWIDTH+1;
 if(has_vTools)
 { vToolSize=TOOLWIDTH+1;
 hScrollLeft=TOOLWIDTH+1;
 }
 if(has_hTools)
 { hToolSize=TOOLWIDTH+1;
 vScrollTop=TOOLWIDTH+1;
 }
 if(has_hBar) hLineSize=18;
 if(window_type==graphic_window) 
 { vTotalLines=hTotalLines=40;
 if(has_vBar) vLineSize=18;
 }
 else
 { if(has_vBar)
 { GetFontInfo(&theFont);
 vLineSize=theFont.ascent+theFont.descent+
 theFont.leading;
 }
 vTotalLines=0;
 if(has_hBar)
 hTotalLines=(wrect.right-wrect.left-vToolSize-
 vRulerSize-SBarWidth)/hLineSize;
 includeText=TRUE;
 }

 /* Seto up scoll bars */
 myScrlp=SetScrollWindow(myWindow,&scrollRecord,
 includeText,vLineSize,vTotalLines,vToolSize,
 vRulerSize,vScrollTop,hLineSize,hTotalLines,
 hToolSize,hRulerSize,hScrollLeft,NIL);
 
 /*If text window, add some sample text */
 myhTE=myScrlp->hTE;
 if(myhTE!=NIL)
 { texthdl=GetResource(‘GNRL’,sample_text);
 HLock(texthdl);
 TESetText(*texthdl,SizeResource(texthdl),myhTE);
 HUnlock(texthdl);
 ReleaseResource(texthdl);
 if(myScrlp->vScrollHdl!=NIL)
 SetScroll(myScrlp,(**myhTE).nLines,VertBar);
 }

 /* Set current window tool and ruler flags  */    
 vTools=has_vTools;
 hTools=has_hTools;
 vRuler=has_vRuler;
 hRuler=has_hRuler;
}

/* Draw the window contents */

draw_content(windowp,scrollType)
 WindowPtr windowp;
 int scrollType;
{
 register int i;
 int lineSize=20,lm,tm,tick,nexttick;
 int vtoolsize=0,htoolsize=0,xtick,ytick,vtotal,htotal;
 static char vertTools[11]={10,’V’,’E’,’R’,’T’,’ ‘,’T’,
 ‘O’,’O’,’L’,’S’};
 static char horizTools[12]={11,’H’,’O’,’R’,’I’,’Z’,
 ‘ ‘,’T’,’O’,’O’,’L’,’S’};
 static int length[8]={0,5,8,5,11,5,8,5};
 char nstr[20];
 Rect drawRect,tempRect;
 RgnHandle drawRgn;

 GetMargins(myScrlp,&lm,inPixels,&tm,inPixels);
 ClipRect(NonScrollRect(myScrlp,windowp,&drawRect));
 if(vTools) vtoolsize=TOOLWIDTH;
 if(hTools) htoolsize=TOOLWIDTH;
 
 if(vTools&&(!scrollType))/*vert tool bar*/
 { MoveTo(vtoolsize,drawRect.top);
 LineTo(vtoolsize,drawRect.bottom);
 TextFace(outline+bold+shadow);
 for(i=1;i<=vertTools[0];i++)
 { MoveTo(0,htoolsize+20*i);
 LineTo(vtoolsize,htoolsize+20*i);
 MoveTo(6,htoolsize+20*i-5);
 DrawChar(vertTools[i]);
 }
 TextFace(0);
 }
 
 if(hTools&&(!scrollType))/*horiz tool bar*/
 { MoveTo(drawRect.left,htoolsize);
 LineTo(drawRect.right,htoolsize);
 TextFace(outline+bold+shadow);
 for(i=1;i<=horizTools[0];i++)
 { MoveTo(vtoolsize+25*i,0);
 LineTo(vtoolsize+25*i,htoolsize);
 MoveTo(vtoolsize+25*i-17,15);
 DrawChar(horizTools[i]);
 }
 TextFace(0);
 }
 
 if(vTools&&hTools&&(!scrollType))
 markX(0,0,htoolsize,vtoolsize);
 
 if(vRuler&&hRuler&&(!scrollType))
 markX(vtoolsize,htoolsize,RULERWIDTH,RULERWIDTH);
 
 TextSize(9);
 if(vRuler&&(scrollType!=HorizBar))
 { vtotal=vtoolsize+RULERWIDTH;
 MoveTo(vtotal,drawRect.top+htoolsize);
 LineTo(vtotal,drawRect.bottom);
 drawRect.top=htoolsize+RULERWIDTH*hRuler;
 ClipRect(&drawRect);
 tick=tm/72;
 nexttick=drawRect.top+72*tick-tm-72;
 while((nexttick+=72)<=drawRect.bottom)
 { MoveTo(vtoolsize,nexttick);
 LineTo(vtotal,nexttick);
 if(tick)
 { NumToString((long)tick,nstr);
 MoveTo(vtotal-StringWidth(nstr),
 nexttick+8);
 DrawString(nstr);
 }
 for(i=1;i<=7;i++)
 { MoveTo(vtotal,ytick=nexttick+9*i);
 LineTo(vtotal-length[i],ytick);
 }
 tick++;
 }
 }
 
 if(hRuler&&(scrollType!=VertBar))
 { htotal=htoolsize+RULERWIDTH;
 drawRect.top=0;
 ClipRect(&drawRect);
 MoveTo(drawRect.left+vtoolsize,htotal);
 LineTo(drawRect.right,htotal);
 drawRect.left=vtoolsize+RULERWIDTH*vRuler;
 ClipRect(&drawRect);
 tick=lm/72;
 nexttick=drawRect.left+72*tick-lm-72;
 while((nexttick+=72)<=drawRect.right+8)
 { MoveTo(nexttick,htoolsize);
 LineTo(nexttick,htotal);
 if(tick)
 { NumToString((long)tick,nstr);
 MoveTo(nexttick-1-StringWidth(nstr),
 htoolsize+10);
 DrawString(nstr);
 }
 for(i=1;i<=7;i++)
 { MoveTo(xtick=nexttick+9*i,htotal);
 LineTo(xtick,htotal-length[i]);
 }
 tick++;
 }
 }
 TextSize(12);
 
 if(myhTE==NIL)  /*Graphic Window Update*/
 { drawRgn=ScrollSectRgn(myScrlp,windowp);
 SetClip(drawRgn);
 MoveTo(polygon[0].h-lm,polygon[0].v-tm);
 for(i=1;i<=polySize;i++)
 LineTo(polygon[i].h-lm,polygon[i].v-tm);
 ClipRect(&windowp->portRect);
 DisposeRgn(drawRgn);
 }
 else   /*Text Window Update*/
 { ClipRect(&windowp->portRect);
 tempRect=(**myhTE).viewRect;
 TEUpdate(&tempRect,myhTE);
 }
}

/* Draw X in window*/

markX(beginx,beginy,xwidth,ywidth)
int beginx,beginy,xwidth,ywidth;
{MoveTo(beginx,beginy);
 LineTo(beginx+xwidth,beginy+ywidth);
 MoveTo(beginx,beginy+ywidth);
 LineTo(beginx+xwidth,beginy);
}

/* Present about box and wait for mouse click */

do_about()
{
 DialogPtr aboutBox;
 EventRecord event;
 
 aboutBox=GetNewDialog(about_dlog,NIL,-1L);
 DrawDialog(aboutBox);
 
 /* wait for key or mouse click - 
 from MacTutor, V4,#3,pg10 */
 while(!EventAvail(keyDownMask|autoKeyMask|mDownMask,
 &event))
 SystemTask();
 
 if(event.what==mouseDown)    /* remove mouse down */
 { GlobalToLocal(&event.where);
 if(PtInRect(event.where,&aboutBox->portRect))
 GetNextEvent(mDownMask,&event);
 }
 
 DisposDialog(aboutBox);
}

/* Exit to shell after saving TE scrap */

finish()
{
 ZeroScrap();
 TEToScrap();
 LoadScrap();
 ExitToShell();
}
Listing: Scroller Project.R

***************************************
*  *
*RMaker resource file source for   *
*Application Scroller  *
*  *
*© 1988 John A. Nairn  *
*  *
***************************************

Scroller Project.rsrc
????????

Type SCRL = STR 
 ,0
Demo of the Scrolling Manager - ++
August 13, 1988

Type FREF
 ,128
APPL 0

Type BNDL
 ,128
SCRL 0
ICN#
0 128
FREF
0 128

Type MENU
 ,255 (0) ;;Apple Menu
\14
About Scroller 
(-

 ,256 (0)
File    ;;File Menu
New Window/N
(-
Graphics Window
Text Edit Window
(-
Vertical Scroll Bar
Horizontal Scroll Bar
(-
Vertical Ruler
Horizontal Ruler
Vertical Tool Bar
Horizontal Tool Bar
(-
Quit/Q

 ,257 (0)
Edit    ;;Edit Menu
Undo/Z
(-
Cut/X
Copy/C
Paste/V
Clear

Type DITL ;;Text for About Box
 ,998
3;;Number of items

StatText Disabled
11 58 29 320
Scroller (© 1988, All Rights Reserved)

StatText Disabled
35 10 88 372
This application demonstrates the ++
features of the Scrolling Manager ++
library of routines which can help ++
other applications easily accomplish ++
window scrolling.

StatText Disabled
93 87 175 288
For information contact:\0D ++
    John A. Nairn\0D ++
    7108 S. Pine Cone St.\0D ++
    Salt Lake City, UT 84121\0D ++
    (801)-942-7768

Type DLOG ;;About dialog
 ,998
About Box
62 62 250 445
Visible NoGoAway
3;;Process ID
0;;refCon
998;;Item list ID

Type GNRL ;;Text Edit window text
 ,500
.H
2020 2020 2054 6865 2074 6578 7420 6564
6974 696E 6720 696E 2074 6869 7320 7769
6E64 6F77 2069 7320 6861 6E64 6C65 6420
6279 2074 6865 2054 6578 7420 4564 6974
206D 616E 6167 6572 2E20 5468 6520 7363
726F 6C6C 696E 6720 6973 2068 616E 646C
6564 2062 7920 7468 6520 5363 726F 6C6C
696E 6720 4D61 6E61 6765 722E 0D0D 2020
2020 2059 6F75 2063 616E 2074 7970 6520
696E 2074 6578 742C 2061 6E64 2075 7365
2074 6865 2063 7574 2C20 636F 7079 2C20
7061 7374 652C 2061 6E64 2063 6C65 6172
2063 6F6D 6D61 6E64 732E 2057 6865 6E20
7365 6C65 6374 696E 6720 7465 7874 2066
6F72 2065 6469 7469 6E67 2061 6E64 2079
6F75 2064 7261 6720 6F75 7473 6964 6520
7468 6520 7769 6E64 6F77 2C20 7468 6520
5363 726F 6C6C 696E 6720 4D61 6E61 6765
7220 7769 6C6C 2061 7574 6F6D 6174 6963
616C 6C79 2073 6372 6F6C 6C20 7468 6520
7769 6E64 6F77 2074 6F20 666F 6C6C 6F77
2074 6865 206D 6F75 7365 2E20 5468 6520
5363 726F 6C6C 696E 6720 4D61 6E61 6765
7220 616C 736F 2069 6E73 7572 6573 2074
6861 7420 7468 6520 696E 7365 7274 696F
6E20 6261 7220 6973 2061 7574 6F6D 6174
6963 616C 6C79 2073 6372 6F6C 6C65 6420
696E 746F 2076 6965 7720 7768 656E 2061
7070 726F 7072 6961 7465 2E0D 0D20 2020
2020 486F 6C64 696E 6720 646F 776E 2074
6865 206F 7074 696F 6E20 6B65 7920 696D
706C 656D 656E 7473 2064 7261 6720 7363
726F 6C6C 696E 672E 2048 6F6C 6469 6E67
2064 6F77 6E20 7468 6520 7368 6966 7420
616E 6420 6F70 7469 6F6E 206B 6579 7320
696D 706C 656D 656E 7473 206A 6F79 2073
7469 636B 2073 6372 6F6C 6C69 6E67 2E

Type ICN# = GNRL ;;Application Icon
 ,128
.H
FFFF FFFF 8000 0001 BC18 00A1 A5D3 6EAD
A40A 4AA1 BDDB 4EAD 8000 0001 FFFF FFFF
8000 0111 8000 0139 8A00 017D AA00 0139
AA00 01FF AE00 01AB B100 0155 A71F 81FF
B11F C183 A26F E183 BEE0 71FF BEC8 9129
BE95 5145 A388 9929 A302 0911 FFFF FFFF
86BE A101 9563 6971 AEA2 B55D C563 6375
AEA2 B525 9563 693D 86BE A101 FFFF FFFF
FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF
FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF
FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF
FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF
FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF
FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF
FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF
FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF

TYPE CURS = GNRL
 ,300   ;;Pencil Cursor
.H
00F0 0088 0108 0190 0270 0220 0420 0440
0840 0880 1080 1100 1E00 1C00 1800 1000
00F0 00F8 01F8 01F0 03F0 03E0 07E0 07C0
0FC0 0F80 1F80 1F00 1E00 0000 0000 0000
0010 0003

 ,301   ;;Hand Cursor
.H
0180 1A70 2648 264A 124D 1249 6809 9801
8802 4002 2002 2004 1004 0808 0408 0408
0180 1BF0 3FF8 3FFA 1FFF 1FFF EFFF FFFF
FFFE 7FFE 3FFE 3FFC 1FFC 0FF8 07F8 07F8
0009 0008

 ,302   ;;JoyStick Cursor
.H
0080 01C0 03E0 0080 0080 1084 3086 7FFF
3086 1084 0080 0080 03E0 01C0 0080 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0007 0008

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Whitethorn Games combines two completely...
If you have ever gone fishing then you know that it is a lesson in patience, sitting around waiting for a bite that may never come. Well, that's because you have been doing it wrong, since as Whitehorn Games now demonstrates in new release Skate... | Read more »
Call of Duty Warzone is a Waiting Simula...
It's always fun when a splashy multiplayer game comes to mobile because they are few and far between, so I was excited to see the notification about Call of Duty: Warzone Mobile (finally) launching last week and wanted to try it out. As someone who... | Read more »
Albion Online introduces some massive ne...
Sandbox Interactive has announced an upcoming update to its flagship MMORPG Albion Online, containing massive updates to its existing guild Vs guild systems. Someone clearly rewatched the Helms Deep battle in Lord of the Rings and spent the next... | Read more »
Chucklefish announces launch date of the...
Chucklefish, the indie London-based team we probably all know from developing Terraria or their stint publishing Stardew Valley, has revealed the mobile release date for roguelike deck-builder Wildfrost. Developed by Gaziter and Deadpan Games, the... | Read more »
Netmarble opens pre-registration for act...
It has been close to three years since Netmarble announced they would be adapting the smash series Solo Leveling into a video game, and at last, they have announced the opening of pre-orders for Solo Leveling: Arise. [Read more] | Read more »
PUBG Mobile celebrates sixth anniversary...
For the past six years, PUBG Mobile has been one of the most popular shooters you can play in the palm of your hand, and Krafton is celebrating this milestone and many years of ups by teaming up with hit music man JVKE to create a special song for... | Read more »
ASTRA: Knights of Veda refuse to pump th...
In perhaps the most recent example of being incredibly eager, ASTRA: Knights of Veda has dropped its second collaboration with South Korean boyband Seventeen, named so as it consists of exactly thirteen members and a video collaboration with Lee... | Read more »
Collect all your cats and caterpillars a...
If you are growing tired of trying to build a town with your phone by using it as a tiny, ineffectual shover then fear no longer, as Independent Arts Software has announced the upcoming release of Construction Simulator 4, from the critically... | Read more »
Backbone complete its lineup of 2nd Gene...
With all the ports of big AAA games that have been coming to mobile, it is becoming more convenient than ever to own a good controller, and to help with this Backbone has announced the completion of their 2nd generation product lineup with their... | Read more »
Zenless Zone Zero opens entries for its...
miHoYo, aka HoYoverse, has become such a big name in mobile gaming that it's hard to believe that arguably their flagship title, Genshin Impact, is only three and a half years old. Now, they continue the road to the next title in their world, with... | Read more »

Price Scanner via MacPrices.net

B&H has Apple’s 13-inch M2 MacBook Airs o...
B&H Photo has 13″ MacBook Airs with M2 CPUs and 256GB of storage in stock and on sale for up to $150 off Apple’s new MSRP, starting at only $849. Free 1-2 day delivery is available to most US... Read more
M2 Mac minis on sale for $100-$200 off MSRP,...
B&H Photo has Apple’s M2-powered Mac minis back in stock and on sale today for $100-$200 off MSRP. Free 1-2 day shipping is available for most US addresses: – Mac mini M2/256GB SSD: $499, save $... Read more
Mac Studios with M2 Max and M2 Ultra CPUs on...
B&H Photo has standard-configuration Mac Studios with Apple’s M2 Max & Ultra CPUs in stock today and on Easter sale for $200 off MSRP. Their prices are the lowest available for these models... Read more
Deal Alert! B&H Photo has Apple’s 14-inch...
B&H Photo has new Gray and Black 14″ M3, M3 Pro, and M3 Max MacBook Pros on sale for $200-$300 off MSRP, starting at only $1399. B&H offers free 1-2 day delivery to most US addresses: – 14″ 8... Read more
Department Of Justice Sets Sights On Apple In...
NEWS – The ball has finally dropped on the big Apple. The ball (metaphorically speaking) — an antitrust lawsuit filed in the U.S. on March 21 by the Department of Justice (DOJ) — came down following... Read more
New 13-inch M3 MacBook Air on sale for $999,...
Amazon has Apple’s new 13″ M3 MacBook Air on sale for $100 off MSRP for the first time, now just $999 shipped. Shipping is free: – 13″ MacBook Air (8GB RAM/256GB SSD/Space Gray): $999 $100 off MSRP... Read more
Amazon has Apple’s 9th-generation WiFi iPads...
Amazon has Apple’s 9th generation 10.2″ WiFi iPads on sale for $80-$100 off MSRP, starting only $249. Their prices are the lowest available for new iPads anywhere: – 10″ 64GB WiFi iPad (Space Gray or... Read more
Discounted 14-inch M3 MacBook Pros with 16GB...
Apple retailer Expercom has 14″ MacBook Pros with M3 CPUs and 16GB of standard memory discounted by up to $120 off Apple’s MSRP: – 14″ M3 MacBook Pro (16GB RAM/256GB SSD): $1691.06 $108 off MSRP – 14... Read more
Clearance 15-inch M2 MacBook Airs on sale for...
B&H Photo has Apple’s 15″ MacBook Airs with M2 CPUs (8GB RAM/256GB SSD) in stock today and on clearance sale for $999 in all four colors. Free 1-2 delivery is available to most US addresses.... Read more
Clearance 13-inch M1 MacBook Airs drop to onl...
B&H has Apple’s base 13″ M1 MacBook Air (Space Gray, Silver, & Gold) in stock and on clearance sale today for $300 off MSRP, only $699. Free 1-2 day shipping is available to most addresses in... Read more

Jobs Board

Medical Assistant - Surgical Oncology- *Apple...
Medical Assistant - Surgical Oncology- Apple Hill Location: WellSpan Medical Group, York, PA Schedule: Full Time Sign-On Bonus Eligible Remote/Hybrid Regular Apply Read more
Omnichannel Associate - *Apple* Blossom Mal...
Omnichannel Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Cashier - *Apple* Blossom Mall - JCPenney (...
Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Mall Read more
Operations Associate - *Apple* Blossom Mall...
Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Business Analyst | *Apple* Pay - Banco Popu...
Business Analyst | Apple PayApply now " Apply now + Apply Now + Start applying with LinkedIn Start + Please wait Date:Mar 19, 2024 Location: San Juan-Cupey, PR Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.