Permission to reprint or excerpt is granted only if the following line appears at the top of the article: ANTIC PUBLISHING INC., COPYRIGHT 1985. REPRINTED BY PERMISSION. ANTIC is proud to present the first of Tim Oren's bi-monthly columns exploring the GEM programming environment. These columns are aimed at professional ST developers, but we encourage everyone to join in and collect the columns for future reference. Professional GEM by Tim Oren Column #1 - Windows, part 1 HELLO, WORLD! For those whom I have not met in person or electronically, an introduction is in order. I am a former member of the GEM programming team at Digital Research, Inc., where I designed and implemented the GEM Resource Construction Set and other parts of the GEM Programmer's Toolkit. I have since left DRI to become the user interface designer for Activenture, a startup company which is developing CD-ROM technology for use with the Atari ST and other systems. The purpose of Professional GEM is to pass along some of the information and tricks I have accumulated about GEM, and explore some of the user interface techniques which a powerful graphics processor such as the ST makes possible. GROUND RULES I am going to assume that you have both a working knowledge of the C programming language and a copy of the ST Programmer's Toolkit with documentation (available from Atari). If you lack either, don't panic. You can read the columns to get the flavor of programming the ST, and come back for a more serious visit later on. For now, I will be using code samples that will run with the Atari-supplied C compiler, also known as DR C-68K, or Alcyon C. I will be using the portability macros supplied with the Toolkit, so that the code will also be transferable to other GEM systems. Both of these items are subject to change, depending on reader feedback and the availability of better products. If you do not have a copy of the source to the DOODLE.C GEM example program, you should consider downloading a copy from SIG*ATARI. Although it is poorly documented, it shows real-life examples of many of the techniques I will discuss. Getting started with a windowed graphics system seems to be like getting into an ice-cold swimming pool: it's best done all at once. Anyone who has looked at "Inside Macintosh" has probably noticed that you have to have read most of it to understand any of it. GEM isn't really much different. You have all the reference guides in your hand, but nothing to show how it all works together. I am hoping to help this situation by leading a series of short tours through the GEM jungle. Each time we'll go out with a particular goal in mind and follow the path that leads there. We'll look at the pitfalls and strange bugs that lurk for the unwary, and show off a few tricks to amaze the natives. The first trip leaves immediately; our mission is to get a window onto the ST screen, with all of its parts properly initialized. WE DO WINDOWS One of the most important services which a graphics interface system provides for the user and programmer is window management. Windows allow the user to perform more than one activity on the same screen, to freely reallocate areas of the screen for each task, and even to pile the information up like pages of paper to make more room. The price for this increased freedom is (as usual) paid by you, the programmer, who must master a more complex method of interacting with the "outside world". The windowing routines provided by ST GEM are the most comprehensive yet available in a low-cost microcomputer. This article is a guide to using these services in an effective manner. IN THE BEGINNING In GEM, creating a window and displaying it are two different functions. The creation function is called wind_create, and its calling sequence is: handle = wind_create(parts, xfull, yfull, wfull, hfull); This function asks GEM to reserve space in its memory for a new window description, and to return a code or "handle" which you can use to refer to the window in the future. Valid window handles are positive integers; they are not memory pointers. GEM can run out of window handles. If it does so, the value returned is negative. Your code should always check for this situation and ask the program's user to close some windows and retry if possible. Handle zero is special. It refers to the "desktop", which is predefined as light green (or gray) on the ST. Window zero is always present and may be used, but never deleted, by the programmer. The xfull, yfull, wfull, and hfull parameters are integers which determine the maximum size of the window. Xfull and yfull define the upper left corner of the window, and wfull and hfull specify its width and height. (Note that all of the window coordinates which we use are in pixel units.) GEM saves these values so that the program can get them later when processing FULL requests. Usually the best maximum size for a window is the entire desktop area, excepting the menu bar. You can find this by asking wind_get for the working area of the desktop (handle zero, remember): wind_get(0, WF_WXYWH, &xfull, &yfull, &wfull, &hfull); Note that WF_WXYWH, and all of the other mnemonics used in this article, are defined in the GEMDEFS.H file in the ST Toolkit. The parts parameter of wind_create defines what features will be included in the window when it is drawn. It is a word of single bit flags which indicate the presence/absence of each feature. To request multiple features, the flags are "or-ed" together. The flags' mnemonics and meanings are: NAME- A one character high title bar at the top of the window. INFO- A second character line below the NAME. MOVER- This lets the user move the window around by "dragging" in the NAME area. NAME also needs to be defined. CLOSER- A square box at the upper left. Clicking this control point asks that the window be removed from the screen. FULLER- A diamond at upper right. Clicking this control point requests that the window grow to its maximum size, or shrink back down if it is already big. SIZER- An arrow at bottom right. Dragging the SIZER lets the user choose a new size for the window. VSLIDE- defines a right-hand scroll box and bar for the window. By dragging the scroll bar, the user requests that the window's "viewport" into the information be moved. Clicking on the gray box above the bar requests that the window be moved up one "page". Clicking below the bar requests a down page movement. You have to define what constitutes a page or line in the context of your application. UPARROW- An arrow above the right scroll bar. Clicking here requests that the window be moved up one "line". Sliders and arrows almost always appear together. DNARROW- An arrow below the right scroll bar. Requests that window be moved down a line. HSLIDE- These features are the horizontal equivalent of the RTARROW above. They appear at the bottom of the window. Arrows LFARROW usually indicate "character" sized movement left and right. "Page" sized movement has to be defined by each application. It is important to understand the correspondence between window features and event messages which are sent to the application by the GEM window manager. If a feature is not included in a window's creation, the user cannot perform the corresponding action, and your application will never receive the matching message type. For example, a window without a MOVER may not be dragged by the user, and your app will never get a WM_MOVED message for that window. Another important principle is that the application itself is responsible for implementing the user's window action request when a message is received. This gives the application a chance to accept, modify, or reject the user's request. As an example, if a WM_MOVED message is received, it indicates that the user has dragged the window. You might want to byte or word align the requested position before proceeding to move the window. The wind_set calls used to perform the actual movements will be described in the next article. OPEN, SESAME! The wind_open call is used to actually make the window appear on the screen. It animates a "zoom box" on the screen and then draws in the window's frame. The calling sequence is: wind_open(handle, x, y, w, h); The handle is the one returned by wind_create. Parameters x, y, w, and h define the initial location and size of the window. Note that these measurements INCLUDE all of the window frame parts which you have requested. To find out the size of the area inside the frame, you can use: wind_get(handle, WF_WXYWH, &inner_x, &inner_y, &inner_w, &inner_h); Whatever size you choose for the window display, it cannot be any larger than the full size declared in wind_create. Here is a good place to take note of a useful utility for calculating window sizes. If you know the "parts list" for a window, and its inner or outer size, you can find the other size with the wind_calc call: wind_calc(parts, kind, input_x, input_y, input_w, input_h, &output_x, &output_y, &output_w, &output_h); Kind is set to zero if the input coordinates are the inner area, and you are calculating the outer size. Kind is one if the inputs are the outer size and you want the equivalent inner size. Parts are just the same as in wind_create. There is one common bug in using wind_open. If the NAME feature is specified, then the window title must be initialized BEFORE opening the window: wind_set(handle, WF_NAME, ADDR(title), 0, 0); If you don't do this, you may get gibberish in the NAME area or the system may crash. Likewise, if you have specified the INFO feature, you must make a wind_set call for WF_INFO before opening the window. Note that ADDR() specifies the 32-bit address of title. This expression is portable to other (Intel-based) GEM systems. If you don't care about portability, then &title[0], or just title alone will work fine on the ST. CLEANING UP When you are done with a window, it should be closed and deleted. The call: wind_close(handle); takes the window off the screen, redraws the desktop underneath it, and animates a "zoom down" box. It doesn't delete the window's definition, so you can reopen it later. Deleting the window removes its definition from the system, and makes that handle available for reuse. Always close windows before deleting, or you may leave a "dead" picture on the screen. Also be sure to delete all of your windows before ending the program, or your app may "eat" window handles. The syntax for deleting a window is: wind_delete(handle); THOSE FAT SLIDERS One of ST GEM's unique features is the proportional slider bar. Unlike other windowing systems, this type of bar gives visual feedback on the fraction of a document which is being viewed, as well as the position within the document. The catch, of course, is that you have two variables to maintain for each scroll bar: size and position. Both bar size and position range from 1 to 1000. A bar size of 1000 fills the slide box, and a value of one gets the minimum bar size. To compute the proper size, you can use the formula: size = min(1000, 1000 * seen_doc / total_doc) Seen_doc and total_doc are the visible and total size of the document respectively, in whatever units are appropriate. As an example, if your window could show 20 lines of a 100 line text file, you should set a slider size of 200. Since the window might be bigger than the total document at some points, you need the maximum function. If the document size is zero, force the slider size to 1000. (Note: You will probably need to do the computation above with 32-bit arithmetic to avoid overflow problems.) Once you have computed the size, use the wind_set function to configure the scroll bar: wind_set(handle, WF_VSLSIZE, size, 0, 0, 0); This call sets the vertical (right hand) scroll bar. Use WF_HSLSIZE for the horizontal scroller. All of these examples are done for the vertical dimension, but the principles are identical in the other direction. Bar positioning is a little tougher. The most confusing aspect is that the 1-1000 range does not set an absolute position of the bar within the scroll box. Instead, it positions the TOP of the bar within its possible range of variation. Let's look at our text file example again to make this clearer. If there are always 20 lines of a 100 line file visible, then the top of the window must be always be somewhere between line 1 and line 81. This 80 line range is the actual freedom of movement of the window. So, if the window were actually positioned with its top at line 61, it would be at the three-quarter position within the range, and we should set a scroll bar position of 750. The actual formula for computing the position is: pos = 1000 * (top_wind - top_doc) / (total_doc - seen_doc) Top_wind and top_doc are the top line in the current window and the whole document, respectively. Obviously, if seen_doc is greater or equal to total_doc, you need to force a zero value for pos. This calculation may seem rather convoluted the first time through, but is easy once you have done it. When you have computed the position, wind_set configures the scroll bar: wind_set(handle, WF_VSLIDE, pos, 0, 0, 0); WF_HSLIDE is the equivalent for horizontal scrolling. It is a good practice to avoid setting the slider size or position if they are already at the value which you need. This avoids an annoying redraw flash on the screen when it is not necessary. You can check on the current value of a slider parameter with wind_get: wind_get(handle, WF_VSLIDE, &curr_value, &foo, &foo, &foo); Foo is a dummy variable which needs to be there, but is not used. Substitute WF_VSLIDE with whatever parameter you are checking. One philosophical note on the use of sliders: it is probably best to avoid the use of both sliders at once unless it is clearly appropriate to the type of data which is being viewed. Since Write and Paint programs make use of the sheet-of-paper metaphor, moving the window around in both dimensions is reasonable. However, if the data is more randomly organized, such as a tableau of icons, then it is probably better to only scroll in the vertical dimension and "reshuffle" if the window's width is changed. Then the user only needs to manipulate one control to find information which is off-screen. Anyone who has had trouble finding a file or folder within a Desktop window will recognize this problem. COMING UP NEXT In my next column in Antic Online, we'll conclude the tour of the ST's windowing system. I'll discuss the correct way to redraw a window's contents, and how to handle the various messages which an application receives from the window manager. Finally, we'll look at a way to redesign the desktop background to your own specifications. FEEDBACK One of the beauties of an on-line column is that you can make your comments known immediately. To register your opinions, select ST FEEDBACK, enter your message, leave your name, and enter a blank line to exit. I am interested in hearing proposals for topics, feedback on the technical level of the column, and reports on bugs and other "features" in both the column and the ST itself. Your comments will be read by the ANTIC staff and myself and, though we might not answer individual questions, they will be used to steer the course of future columns.