Classic Computer Magazine Archive ST-Log ISSUE 19 / MAY 1988 / PAGE 59



Using sliders and arrows to change a window's contents.

by Clayton Walnum

We've stated previously that a window is just a box that allows us to display data in a convenient manner, and the programmer is completely responsible for what is done with the window's work area. One of the things that GEM's windows provide, to help the user manipulate the displayed data, is the slider/arrow system. By moving the sliders or clicking the arrows, the user can "move" the data within the window to any position he likes.

This convenience is paid for by the programmer, however, because, when a slider or arrow is used, GEM doesn't do anything except send a message to the program. It's up to the programmer to decide what to do with the message and how to update the window's display.

The listing.

Type in Listing 1 and compile it. (You can also find the source code and compiled program on this month's disk version, or on the ANALOG Atari user group on Delphi.) Please note that the program was developed using Megamax C. If you have a different compiler you may have to make some small changes to the listing.

When the program is run, a window will be opened, the directory of the default drive (the one you ran the program from) will be read, and the filenames found there displayed in the window's work area. You may then use the sliders and arrows in their conventional way to move the data within the window. You can also enlarge or shrink the window by dragging (with the mouse, of course) the lower right corner of the window.

Note that the example program only provides the vertical arrows and sliders. I didn't include the horizontal ones because they're handled almost exactly the same way as their vertical counterparts.

Getting a directory.

The first thing of interest in the sample program is the method with which we can read a disk's directory. The code to accomplish this can be found in the get__fnames() function in Listing 1. Let's take a look at that now.

The first thing we must do is initialize a couple of variables. We'll be using the integer p as an array index, and the integer files.count (this is a member of the structure files, which is declared near the top of the listing) will contain the number of filenames read from the directory.

Next, we set an important address with the call:

Fsetdta (dta);

Here, dta is a pointer to character data (in our case, the address of the character array dta[]). The function Fsetdta() is GEMDOS function 0x1a and is declared in OSBIND.H. It sets the address of the DTA (Disk Transfer Address), a 44-byte buffer in which the directory data is stored. We supplied the buffer by defining the array dta[].

When we get around to actually reading a filename, the DTA will contain all sorts of useful information, as shown here:

Byte Contains
0 - 20 For OS use only
21 File attribute
22 - 23 Integer, file time stamp
24 - 25 Integer, file date stamp
26 - 29 Long integer, file size
30 - 43 Filename

So let's fill that DTA, shall we? We get the first filename with the call:

end = Fsfirst ( p, a );

Here, p is the pathname you want to use for the file search and a is an integer whose bit settings determine the search's attributes. The function call returns a negative integer in the case of an error (for instance, when there are no more filenames to be read). In the sample listing, we placed the pathname string, "*.*", directly into the call. This pathname takes advantage of "wild cards," so the search will match any file. The attributes set by a are determined as follows:

Bit Result
0 Search limited to normal files
1 Read only files included
2 Hidden files included
3 System files included
4 Folder names included
5 Subdirectory files included

By setting bits 0 and 4 in the search attributes argument, we'll read all the file and folder names from the root directory (and, because of the pathname, these will be read from the default drive), just as if you had just opened the drive from the desktop.

Now that we've got the first filename, we set up a while loop to get the rest, as well as to process the filenames into the form we need, later storing them into the two-dimensional array files.fnames[ ][ ]. All we're doing in the processing is making sure each entry in the filename array is exactly 15 characters long: the filename padded with spaces and ending with a null.

The rest of the filenames are retrieved, one by one, with the call:

end = Fsnext  ( )

This function (GEMDOS 0×4f), defined in OSBIND.H, also returns a negative value for an error condition.

We continue executing the while loop until end becomes negative, or files.count becomes greater than MAX.

Slipping and sliding.

Now that we've got all those filenames stored, we're ready to open our window. We've done all this stuff before; there's nothing new here, except the use of the integer top to keep track of where in the filename list the window's data display is to start (remember, the topmost filename in the display won't necessarily be the first one in our list), and setting up the sliders.

Once we open the window, we need to set the size and position of the slider. Our function calc__slid( ) takes care of this, requiring three integers as arguments: the handle of the window; the total number of text lines (in this case, the number of filenames in the list); and the total width of the text in columns.

The function first calls wind__get( ) to get the size of the window's work area, then calculates the number of lines and columns that'll fit that area. The size of the slider is then calculated like this:

size = 1000*lines_avail/line_count;

The size of the slider can range anywhere from 1 to 1000, and represents the relative portion of the document displayed. By dividing the number of lines available in the window by the number of lines in the "document," we end up with a value representing the portion of the document that'll fit the window. Multiplying this value by 1000 will give us the equivalent size of the slider.

For instance, let's say we have 10 lines to display, but the window can hold only 6. Dividing 6 by 10 gives us .6—the portion of the document that the window can display. When we multiply this value by 1000, we get 600—the slider's relative size. Keep in mind that, for greater accuracy, you might want to use floating-point math, rather than integer math.

Once the size of the slider is calculated, it's set with the call:

wind__set(w_h, WF_VSLSIZE, size, 0, 0, 0) ;

Here, w__h is the window's handle, WF__VSLSIZE is the function's operation flag (WF__HSLSIZE for horizontal sliders) as defined in GEMDEFS.H, size is an integer value between 1 and 1000, and the three zeroes are unused arguments.

Next, we need to calculate the slider's position within its track, which is also a value between 1 and 1000. The following calculation, done with floating-point math, the result of which is cast to an integer, does this:

pos = (int) (float) top / (float)
(line_cnt-lines_avail)  * 1000;

The integer top is the number of the uppermost displayed line in the window, line__cnt is the total number of lines in the document, and lines__avail is the number of lines that'll fit the window.

The slider's position is then set with the call:

wind_set (w_h, UF-VSLIDE, pos, 0, 0, 0)

Here, w__h is the window's handle, WF__VSLIDE is the function's operation flag (WF__HSLIDE for horizontal sliders) as defined in GEMDEFS.H, pos is an integer between 1 and 1000, and the three zeroes are unused arguments.

Me and my arrow.

Whenever the user clicks on one of the arrows, or in the slider's tracks, the program will receive a WM_ARROWED message. This message is a little different from the others we've worked with. It actually contains a sub-message, which GEM stores in msg__buf[4]. This forces us to do a little more work before we can take care of the user's request. This extra work is tackled in the function do__arrow( ), where we use the value stored in msg__buf[4] to determine exactly what the user wants to do.

If the user has clicked on the down arrow, msg__buf[4]will contain a WA__DNLINE message, and program execution will continue with the function do__dnline( ).

Before we look at that function, let's stop and think about what we're doing. What exactly is the user requesting when he clicks on the down arrow?

Generally, it means that we must move the window's display one unit upward and the slider one unit downward. Exactly what that "unit" is depends a great deal on your application. If our window contained some sort of graphic information, such as a map, a unit could be anything from a single scan line to dozens of scan lines. Luckily, our decision is a little easier. We're working with text, so the obvious unit of measurement is a text line.

We know now what we want to do, but how are we going to go about it? The easy way of updating the display would be just to increment top, then call draw__interior( ) to redraw the window's work area. The problem with that is that it's too slow, too sloppy. We're going to need something much more elegant. When you're working with the desktop's file windows, the text displays move neatly upward one line each time you click on the down arrow; you can't see the redrawing.

Almost all the information we need is on the screen, right? Only the bottom of the new list isn't shown. You know what that means? We can update most of the window using raster operations. All we have to do is "blit" the area of the window from the second filename down to a position one text line higher, then fill in the bottom with the new filename. And that's exactly what we do in do__dnline.

Let's run through that function now. First, we use wind__get() to get the size of the window's work area, then we calculate the number of text lines that'll fit. Armed with that information, we use an if statement to make sure we don't bother updating the window if there are no filenames left to display. In other words, if the last file in our list is already shown in the window, we want to ignore the request to scroll downward.

If our calculations show that the arrow message is okay to process, we increment top, calculate where in our list of filenames we'll find the new data that needs to be displayed, set clipping to on, turn off the mouse and do our raster stuff.

Because—depending on the size of the window—we may have a partial filename displayed at the bottom, we actually have to print two filenames, and if we're at the end of the list, the second filename should be a line of spaces. Here's what happens in our function:

If the window is a size in which an even number of filenames will fit, our code will blit the display up one line, then print at the bottom the next two filenames in the list. The first will go at the very bottom line, and the second won't appear at all, because we're trying to print it outside of the clipping area.

Now, let's take a case where the size of the window allows a partial filename to show at the bottom. When we do our calculations for the number of lines that'll fit the window, the result is an integer, which means the decimal portion has been truncated (i.e., rounded down to the nearest integer). Because of this, only the number of complete lines that'll fit the window are counted. That's why we always want to print two filenames, since the second may represent one that is only partially visible.

In the case of a partially displayed filename, we'll actually have to print one and a half filenames. Sound tricky? Naw. The clipping rectangle makes this little complication easy to handle. We just print two filenames, and anything that lies outside of the area gets clipped off, leaving us with a partial filename (the second one we printed) at the bottom.

Once we've gotten our display written, we have to recalculate the position of the slider. This is done the same way we did it when we first opened our window, with a call to our function calc__slid().

That completes the processing of the down arrow request. The function do__upline does the same thing for the WA__UPLINE message as do_dnline did for the WA__DNLINE message, except the process is reversed—and a little simpler. In do__upline we're rastering the window's work area down one line, then printing the next filename (thinking backwards) at the top. Because we'll never have a partial line here, we don't have the extra complications we had with do__dnline.

Paging all sliders.

Two other messages we may receive from the original WM__ARROWED message (not including messages for horizontal sliders and arrows, which are WA__LEPAGE, WA__RTPAGE, WA__LFLINE and WA__RTLINE) are WA__DNPAGE and WA__UPPAGE. These are sent to us by GEM whenever the user clicks in the slider's track, indicating that he wants the next "page" of information, the next windowful of lines following that already shown in the window.

Because the information we want displayed in the window is not available anywhere on the screen, we can't use raster operations. We have to do it the sloppy way: figure out the new line for the top of the window, then call our function draw__interior to do the work. But there are a couple of things we have to watch out for when calculating the new top. We have to make sure that top doesn't end up less than zero and that it doesn't become a value that will cause our display to go beyond the bottom of our text.

Let's look at the function do__dnpage. First, we use a wind__get() call to find the size of the window's work area, then we calculate the number of lines that'll fit. Since we want to move down that number of lines in the document, all we have to do to calculate the new value for top is to add lines__avail to its existing value. The only thing to check for is the bottom of the document. If our new top, plus the number of lines in the display equal a value greater than the total number of lines in the document, we have to set back the value of top in such a way that the last line of our document will also be the bottom line of the window. Not too tricky, really.

The function do__uppage() (which executes when we receive a WA__UPPAGE message) works in the same manner, except we subtract lines__avail from top, then check to make sure top isn't less than zero.

Anywhere you like.

Another way the user can change the window's display is to grab the slider with the mouse pointer and move it to a new position. When this occurs we get a WM__VSLID message from GEM (or WM__HSLID, if it's a horizontal slider).

This message is almost as easy to handle as the WA__UPPAGE and WA_DNPAGE messages. The key thing to know here is that the slider's new position is returned in msg__buf[4]. To find the corresponding position in our document, all we have to do is get the size of the window's work area, calculate the number of lines that'll fit the window, then perform the following calculation:

top = msg_buf[4] *
(line_cnt - lines_avail) / 1000;

We then set the slider to its new position with the call:

wind_set(w_h, WF_VSLIDE,
msg_buf [4], 0, 0, 0);

A call to our function draw__interior (which will use the new value calculated for top) completes the task.

An important note.

You should be aware that, many times, the calculations for finding the size and position of the sliders may have to be done with 32-bit math (using long integers) to avoid overflow problems, or with floating-point math when you need greater accuracy. Ignoring these possibilities could give you some perplexing results. If ever you find your sliders behaving mysteriously—and you're sure your logic is correct—check vour math.