C-MANSHIP
DESK ACCESSORIES AND BUILT-IN RESOURCE TREES
BY CLAYTON WALNUM
Last time we looked at a short utility that will set the ST's date and time. Though that program works fine when run from the Desktop, it would be more convenient to have it as a desk accessory, so that it would be available to us from within other GEM programs. Programming a desk accessory isn't much more complicated than programming any other GEM application, but, in the case of our Date/Time utility, there arises one complication.
How often have you seen a desk accessory that requires a .RSC file? Not too often. Oh, there's a couple of them floating around, but it is not a good programming practice to force a desk accessory to rely on a .RSC file—and for a very good reason.
A desk accessory, once it has been loaded, is constantly active. Even if you haven't selected it from the Desk dropdown menu, it is still running, waiting for the cue that will set it in motion. In fact, the only way to terminate a desk accessory is to shut off your machine.
Now think about what we know about .RSC files. They have to be loaded into the ST's memory with a call to rsrc__load(), but more importantly, the memory the resource file takes up has to be returned to the system at the termination of a program by a call to rsrc_free(). Since a desk accessory is always "running," when do we return the memory used by our resource trees?
I know what you're thinking. "What difference does it make whether or not we ever call rsrc_free when the only way to terminate a desk accessory is to turn the machine off? I mean, last time I heard turning off the machine was a great way to release all the memory!" Right you are. But there is one situation where a desk accessory gets reinitialized: when you switch resolutions. If your desk accessory has to load a resource file, then each time you switch resolutions, more memory will be taken up, because the resource is being reloaded, even though the old one hasn't been released.
So before we can convert our Date/Time utility to a desk accessory, we have to build our resource tree right into the program. How complicated this process will be depends on what tools you have available. If you have a resource construction program that'll save your object trees out in source code form, then you're three-quarters of the way there. All you have to do is plug in a few addresses and you're on your way. If you don't have access to a RCP that will do this for you, you'll have to write your resource by hand, a tedious project indeed.
In either case, though, we're going to have to have a clear understanding of resource trees in order to get our desk accessory's dialog box working. This means we'll be doing a review of some material and applying what we learn directly to our Date/Time utility.
Our resource tree
Take a look at Listing 1. Near the top you'll see some data labeled "Resource tree." This is all the data for our dialog box as it was saved from Atari's RCS2 in source code form. (Actually, RCS2 isn't too bright and saves data for everything, even data structures not used in our tree. I've already deleted the unneeded data for the sake of clarity.)
At the top of the data, you'll see an array of pointers called rs__strings[]. (Remember, even though the strings themselves are shown in this array, we still have an array of pointers here, each pointer holding the address of its associated string.) These pointers point to all the strings we need for our dialog box, with some of the objects being allotted three strings. (The first nine strings shown are three groups of three.) Why three? Because editable text fields require not only a text string but a format template and validation string as well. Remember?
The first string shown in rs__strings[] is our dialog-box's title line. We don't want an editable text field here so the format and validation strings (the second and third strings shown in rs__strings[]) are empty. The next group of three strings is for the time field of our dialog box. First is the te__ptext string (the text that will be displayed when the dialog box is first drawn and the area where the text the user enters will be found after he exits the dialog box), followed by the te__ptmplt string (the uneditable text that is displayed in the text field) and the te__pvalid string (the string that determines what type of data is allowable in each position of the string). If all of this is confusing to you, please review the "C-manship" articles on dialog-boxes that appeared in Issues 13 and 14 of ST-Log.
The next three strings are the te__ptext,te__ptmplt and te__pvalid strings for our dialog-box's date field. And finally there is the text for our OK and CANCEL buttons.
Following rs__strings[] is another array called rs__tedinfo[]. As you can tell by the name of the array, the data here makes up the tedinfo structures for the editable text strings in our dialog box. If you think back, you'll remember that a tedinfo structure contains all the information GEM needs to draw and handle editable text fields. There are three tedinfos contained in rs__tedinfo[], one each for our dialog's title, time and date fields.
The first three long words of a tedinfo structure are pointers to the object's te__ptext, te__ptmplt and te_pvalid strings, respectively. If you look at the first three long words in the first tedinfo structure shown in rs__tedinfo[], you'll see that we've got the values 0L, 1L and 2L. These don't look very much like pointers, do they? That's because they're not! They are actually offsets into the rs__strings[] array, telling us which pointers we need to place in the tedinfo structure.
This tedinfo is for our dialog's title field. We are told here that the te__ptext string for this field is pointed to by element 0 of the rs__strings[] array, and the te__ptmplt and te__pvalid strings for this field are pointed to by elements 1 and 2, respectively. When we initialize our desk accessory, it is up to us to see that the right pointers get placed into the tedinfo.
The next six members of the tedinfo structure contain (in order) the font size, a reserved word, the horizontal justification of the text, color information, another reserved word and the border thickness for the object. These fields were all filled in by the resource construction program, so unless we are building our dialog box "by hand," we don't need to pay much attention to this data.
The last two members of the tedinfo are the length of the text string (te__ptext) and the length of the template string (te__ptmplt), and were also filled in by the resource construction program. You can see I here that the length of our text is shown as 14. If we count the characters in our title, "DATE AND TIME," we'll see that we have 13 characters. Add one for the required null, and we've got 14. The template string contains nothing but a null, so it has a length of 1.
The next two tedinfo structures in the rs__tedinfo[] array contain the information for our time and date fields. The first three elements of each of the tedinfos are the only ones we are really concerned with. It is here that we have to place the pointers to the te__ptext, te__ptmplt and te__pvalid strings.
Moving down to the next array in Listing 1, we get to rs__object[] which is our actual object tree, made up of six different objects. The first object is the box that will hold all of the parts of our dialog, the second object is our dialog's title field, the third and fourth objects are our time and date fields, and the fifth and sixth objects are the OK and CANCEL buttons.
The first three members of an object structure are the numbers of the next sibling, the first child and the last child. The next member is the object type. In the first object shown in our listing, the object type is G__BOX, which is defined as 20 in your obdefs.h file. Next are the ob__flags and ob__state fields which tell GEM how to draw the object and what its current state is. We don't have to be too concerned with the information in these six members, as the necessary values were filled in by the resource construction program.
The seventh member of the object structure, however, is very important to us as we set up our "code resident" dialog box. It is the ob__spec field which, in the case of editable text objects, is where we must put a pointer to the appropriate tedinfo structure. If you look at this field in the first object, you'll see the value 0×21121L. This value contains information on the box's color and border thickness. It doesn't hold a pointer to a tedinfo because a G__BOX contains no editable text.
But look at the second object in the tree. This is our dialog's title field, and if we look up the G_BOXTEXT object type in our reference materials, we'll find that the ob__spec field of this type of object does hold a pointer to a tedinfo structure. If you look at the ob__spec field for this object in our listing, you'll see the value 0×0L which is obviously not a pointer. Again, this is an offset, telling us that the tedinfo for our title field is element 0 of the rs__tedinfo[] array.
The next two objects in our tree, the time and date fields, also contain offsets in the ob_spec member, telling us that the tedinfos for these objects are Element 2 and 3 of the rs__tedinfo[] array.
The last two objects in our tree are the OK and CANCEL buttons. They are objects of the type G__BUTTON, and if we look up these objects in our reference materials, we'll find that their ob__spec fields should contain, not a pointer to a tedinfo, but a pointer to the string that will be displayed in the button. Looking at the ob__spec fields in the button objects, we see that we once again have some offsets, 0x9L and 0xAL. These values tell us that the pointers to the button strings are found in element 9 and 10 of the rs__strings[] array.
The last four members of our object structures contain the X coordinate, Y coordinate, width and height of the objects. It's important to note that these values are given as character coordinates rather than pixel coordinates. This is because the resource construction program has no idea what resolution we're going to be running the program in. We are going to have to adjust these values so that the dialog box is drawn properly in whatever resolution we happen to be in.
Our code-resident resource tree is concluded with the array rs__trindex[]. This array will contain the addresses of each of the separate trees that make up our resource tree. In our case, we have only one tree, a dialog box, so this array contains space for only one entry. Had we had several trees—for instance, three dialog boxes—this array would have had places for the addresses of each.
If you look at the value stored in the rs__trindex array, you will see that, once again, we are dealing with an offset, 0L. This tells us that the address of object 0 of rs__object[] should be placed in this element of the array.
Writing a desk accessory
As I said before, writing a desk accessory is a fairly simple process. As we've done with our Time/Date utility, we can actually write the program as a normal GEM application, and then, once we've got it running, convert it to a desk accessory.
Listing 1 is just such a conversion. Most of the program is identical to last month's, and so, if you typed in last month's listing, you can use most of the code directly in this month's program. The following functions have not changed: chk__date(), set__date(), get__time(), get__date(), get__tedinfo__string() and open___vwork().
Let's take a look at the function do__acc(), since it is here that we set up our desk accessory, as well as initialize our dialog box. The first thing we have to do to get our desk accessory up and running is to get its name placed on the menu bar so that the user can select it. This is done with the call:
menu_id = menu_register (gl_apid, " Name " );
where the integer menu__id is the ID number of our desk accessory returned from the call to menu_register() and the integer gl__apid is the application ID assigned by appl__init(). You don't have to worry about retrieving the value of gl_apid yourself; just define it as an external variable and everything else will be taken care of for you, much like the GEM global arrays.
Once we've taken care of getting our accessory registered on the menu bar, we have to initialize the dialog box. This requires replacing the offsets in the various structures with the proper pointers and changing the object coordinates and sizes from character form to pixel form.
The first thing to do is to store the address of our resource tree into the rs__trindex[] array, which is done like this:
rs_trindex[0] = (long) rs_object;
Had we two object trees in our resource, we would have had to fill in an address for rs__trindex[l] too. Because we have six objects in our dialog box, the first object of a second tree (if it existed) would be the seventh object in the rs__object[] array, and we would have stored its address like this:
rs_trindex[1] = (long) &rs_object[6];
The addresses of additional trees are stored in the same way, each address being placed in the next element of rs__trindex[] and each address being derived from the element of rs__objects[] that contains the first object in the object tree.
Now we move down one step in the hierarchy from the tree to the objects in the tree. Five of our objects require that the ob__spec fields be filled in with pointers. The first three—the title, time and date fields—require pointers to tedinfos. Using the offsets that the resource construction program left for us, we initialize these fields using this method:
rs_object[1].ob_spec = (char *) &rs_tedinfo[0];
In English the above code says, "The ob__spec field of the second object in the array rs__object[] gets the value of a pointer to character, and that pointer points to the first tedinfo structure in the array rs__tedinfo[]." Yikes! I think I liked the C version better! Anyway, we initialize the other two tedinfo pointers the same way, as you can see in Listing 1.
Next we have two objects—the OK and CANCEL buttons—that must have pointers to strings placed in their ob__spec fields. The code that accomplishes that job (for one of the buttons) looks like this:
rs_object[4].ob_spec = rs_strings[9];
Once again in English, the above says, "The ob__spec field of the fifth object in the array rs__object[] gets the string pointer stored in Element 9 of the array rs__strings[]."
Now we have to take another step down in our hierarchy and initialize our tedinfo structures. We have three of them that need to have pointers to their te__ptext, te__ptmplt and te__pvalid strings filled in. We do that using a code similar to the one we used for the ob__spec pointers above, like this:
rs_tedinfo[0].te_ptext = rs_strings[0]; rs_tedinfo[1].te_ptmplt = rs_strings[1]; rs_tedinfo[2].te_pvalid = rs_strings[2]
The other tedinfos are taken care of in the same way.
Now all we have left to do is convert the dialog's character coordinates to pixel coordinates. Luckily, there's a function that does that dirty work for us. The call
rsrc_obfix (tree_addr, object);
where tree___addr is the address of the object tree and the integer object is the index of the object within the tree, does all the conversions for us. In Listing 1, we've used a for loop to adjust all six objects with a single statement.
Waiting forever
As I said before, a desk accessory, once installed on the menu bar, waits to be called by the user. When the user clicks on the desk accessory's entry on the menu bar, the desk accessory is notified by a message from GEM. What we need to do is construct a while loop that'll loop "forever." In that loop we'll check to see if we've received a message to activate the desk accessory.
Once we get a message (in this case, from a call to evnt__mesage()), we'll check to see if the message type (stored in msg__buf[0]) is an AC__OPEN message. An AC__OPEN message is sent by GEM whenever a desk accessory menu entry is clicked on by the user. When we receive this message, we then have to compare the menu ID sent to us in msg_buf[4] with the menu ID we obtained with our call to menu__register(). If they match, we go ahead and bring up our dialog box so that the user can edit the date and time.
And that's about all there is to it. I should mention that there is also a AC__CLOSE message that is sent to your desk accessory when the desk accessory is "closed." This message is important if you're using windows in the desk accessory because GEM automatically closes these windows when it returns to the desktop. Without the AC_CLOSE message, you'd have no way of knowing when to reset any window flags or other related data items you may need to update. Both the AC_OPEN and AC_CLOSE messages are defined in the gemdefs.h file.
The desk accessory link
One last note: Whenever you link a program with Megamax C, some start-up code (contained in the init.o file) is automatically linked to your object file. Desk accessories, however, have to be initialized differently when they are run, so they require different start-up codes. The desk accessory start-up code is contained in the ACC.L file supplied with the Megamax compiler. (Other compilers will have their own versions of the start-up module.) The source code for your desk accessory is compiled in the same manner as any other program, but when you get to the link step, you must be sure to select ACC.L as the first file in your link list. If you don't, your desk accessory will not run.