CHAPTER 4: OBJECT TREES AND DIALOG BOXES ---------------------------------------- [NOTE: Words enclosed in asterisks (i.e. *word*) should be read as italicized text. This text file is copyright 1993 by Clayton Walnum. All rights reserved.] We now know how to handle the two simplest of GEM's forms, the alert box and the file selector. So, it's time to move on to the granddaddy form of them all: the dialog box. Because the dialog box is so versatile, we could discuss its uses endlessly and still not exhaust its possibilities. For that reason, this chapter's discussion should not be considered as a complete guide to dialog boxes, but only as an introduction. Once you understand the way dialogs work, the only limit will be your imagination. --The Program-- In the CHAP4 folder of your *ST Assembly Language Workshop* disk are the files PROG4.S, PROG4.PRG, SAMPLE.RSC, SAMPLE.H and SAMPLE.DEF. The first two are the source code and the executable file for this chapter's sample program. The PROG4.PRG file is ready to run, but if you'd like to assemble the program yourself, follow the instructions that came with your assembler or see this book's Appendix A. The file SAMPLE.RSC is the resource file for this chapter's program. It must be in the same directory as PROG4.PRG when you run the program. Finally, SAMPLE.H and SAMPLE.DEF are the header file and definition file for the sample dialog. When you run this chapter's program (make sure the SAMPLE.RSC file is on the disk!), you'll see the dialog box shown in Figure 4.1. Clicking on the OK or CANCEL buttons will exit the dialog. Clicking on the up or down arrows will change the value displayed in the NUMBERS object. Clicking any of the other buttons brings up an alert box containing the name of the object selected. Notice that, with the radio buttons, only one may be selected at a time, while the OPTION 1 and OPTION 2 buttons can be on or off in any combination. [INSERT FIGURE 4.1.] You can enter your name and age (lie if you want to) in the text fields. Use the arrow keys on your keyboard to move between the two fields (or click on them with the mouse), since, due to the OK button being set up as a default, pressing Return will exit the dialog box. Try to enter something other than upper- or lowercase letters in the name field, or something other than a number in the age field. No dice, right? --The Definitions-- Before we get into a detailed discussion of dialog boxes, we must first define a couple of terms: objects and trees. Objects are used to visually represent each item that makes up a dialog box. You've seen them hundreds of times: boxes and buttons and text strings. Each object has its own set of attributes that tailor it to the programmer's (and eventually, the user's) needs. From a programming point of view, an object is a data structure, the members of which describe the object, storing all the necessary information to bring that object up on the screen. The objects of a dialog box are connected in an object tree. A tree is a way to link items in a hierarchical manner. That is, there's one main item (the tree's root), which has connected to it other items, which, relative to the tree's root are called children, and relative to each other are called siblings. The children may also have children of their own (and thus become parents), and so on down the line, each new group of siblings subordinate to the ones that have gone before. An object tree is an array of objects, the attributes of which are stored in the previously mentioned data structure. Three elements of an object's data structure determine the way the object fits in with the rest of the tree. Specifically, each object contains, among other things, a pointer to the next sibling, a pointer to the first child (the head) and a pointer to the last child (the tail). Because of this complexity, few programmers bother to try and design dialog boxes from scratch. They instead use a resource construction program. --An RCP Mini Tutorial-- When we created a menu bar in chapter 3, we used Megamax's Resource Construction Program (RCP) to build our resource. Now, we'll use it again to create this chapter's sample dialog box. If you're using the Atari Developer's Kit, don't fret; the Resource Construction Set (RCS) that came with your kit will work equally well for our purposes. The only difference is the operation of the programs. (Of course, the complete resource file is on your *ST Assembly Language Workshop* disk. You don't have to create it on your own, but this is good practice if you're not familiar with resource construction programs.) So, everybody load up their resource construction programs, and let's get busy. Figure 4.1 is the dialog box we'll be building. You should refer to this illustration as you construct your version. Once you have your resource construction program loaded, go to the File option of the menu bar and select New. A window titled NONAME will appear. To the left of this window are the types of resources we can build, represented in icon form. Place the mouse pointer over the dialog icon, press and hold down the left button, and drag the icon into the window. Release the button, and a dialog box will appear, asking you for the name of the new tree. Clear the NAME field by pressing Escape. Then type SAMPLE and press Return. Now, double-click the new dialog icon (the one you dragged to the window). This will open the dialog, presenting you with a "blank slate." The icons to the left will change to a dialog box parts kit. The parts shown are icon representations of the types of objects you can use to build your dialog box. The types are as follows: Button................A box containing centered text String................................A line of text FText.................................Formatted text FBoxText.............A box containing formatted text IBox........................An invisible graphic box Box....................................A graphic box Text....................................Graphic text BoxChar...A graphic box enclosing a single character BoxText.................A graphic box enclosing text Icon..........................Description of an icon Now let's start filling our dialog box with objects. Step 1: Drag the STRING object onto your dialog box, and then double-click it. A dialog box containing a number of attributes will appear. At the bottom will be a line labeled TEXT. Clear the line by pressing the escape key. Then enter (without the quotes) "THIS IS A SAMPLE DIALOG BOX," and press Return. Drag your new string to the top of the dialog box and center it, as shown in Figure 4.1. Step 2: Drag an ICON object onto your dialog box, and double-click it. Click the EDIT ICON button from the dialog box that appears, and draw the ANALOG icon (or any icon you like) with your mouse. When complete, click the OK button. Drag the icon to the left of the text created in Step 1 and position it as shown in Figure 4.1. Step 3: If the icon is not shown in inverse (selected), give it a single mouse click. When the icon is selected, type Control-C (copy), point the mouse to the right-hand side of your dialog box, and type Control-V (paste) twice (the first keystroke deselected the original icon). You should now have a duplicate of the first icon. Drag it into position, to the right of the string, as shown in Figure 4.1. Step 4: Drag the BOX object (the empty rectangle) onto your dialog box. Place the mouse cursor on the lower-right corner of the box and, holding down the left button, stretch the box until it's about the same size as the box labeled RADIO BUTTONS in Figure 4.1. Position the box, and double- click it. An attribute dialog box will appear. Use the mouse to select the SHADOWED attribute. Then click the OK button. Step 5: Using the same method as in Step 1, create a string that reads "RADIO BUTTONS," and position it at the top of the box created in Step 4. Step 6: Drag a BUTTON object into the box created in Step 4, and double-click it. When the attribute dialog appears, select the following options: SELECTABLE, RADIO BUTN, and TOUCHEXIT. (Note that sometimes an attribute--in this case, SELECTABLE--has already been activated for you.) Modify the text field to read "#1." Then click the OK button. While the radio button is still highlighted (shown in inverse), type Control-N, and name the object "RADIO1." Position this button in the upper left of the "Radio Button" box, beneath the string, as shown in Figure 4.1. Step 7: Use the copy and paste functions (as in Step 2) to place another radio button to the right of the one created in Step 6. Change the button's text to read "#2" and change the object's name to "RADIO2." Step 8: Using the method in Step 7, create four buttons labeled "#3," "#4," "#5," and "#6," and name them "RADIO3," "RADIO4," "RADIO5," and "RADIO6," respectively. See Figure 4.1 for placement. Step 9: Drag the EDIT:______ (not the one surrounded by a box) object into your dialog box, and double click it. If it isn't already selected, turn on the EDITABLE option. Clear the PTMPLT field with the escape key, and then type "NAME:" followed by one space and 10 underline characters. Use the down arrow on your keyboard to move the text cursor to the PVALID field. Then press Escape to clear the field. Type "aaaaaaaaaa." Use the down arrow key to move the text cursor to the PTEXT field. Then backspace till you reach a tilde (~) character. Now type "@" followed by nine spaces. Click the OK button. Then name the object "NAME." Position the object as shown in Figure 4.1. Step 10: Drag a second EDIT:______ object into your dialog box, and double-click it. Make sure the EDITABLE option is set. Change the PTMPLT field to "AGE:" followed by one space and two underlines. Change the PVALID field to "99." Move to the PTEXT field and backspace until you reach a tilde character. Then type "@" followed by one space. Click the OK button, name the object "AGE," and then position it as shown in Figure 4.1. Step 11: Drag another BUTTON object into your dialog box, and double-click it. Select the attributes SELECTABLE, SHADOWED and TOUCHEXIT. Change the button's text to "OPTION 1." Click the OK button, name the object "OPTION1," and position it as shown in Figure 4.1. Step 12: Use the copy and paste functions to create a duplicate of the button created in Step 11. Change the button's text to "OPTION 2," name the object "OPTION2," and position it as shown in Figure 4.1. Step 13: Drag the BOXTEXT object into your dialog box, and double-click it. Select the SHADOWED attribute. Clear the PTMPLT and PVALID fields (if necessary). Then change the PTEXT field to four spaces followed by four 0s and four more spaces. Using the method shown in Step 4, stretch the box one segment higher (as you pull down on the mouse, the box will automatically "snap" to the next size). Name the object "NUMBERS," and position it as shown in Figure 4.1. Step 14: Drag another BOXTEXT icon onto your dialog box, and double-click it. Set the TOUCHEXIT option. Make sure the PTMPLT, PVALID and PTEXT fields are clear. Then, when positioned on the PTEXT field, hit space, Control-A, space (the keys, not the words). Resize the object as in Step 13, and name it "UPARROW." Position it on top of the box created in Step 13 as shown in Figure 4.1. Step 15: Use the copy and paste functions to create a duplicate of the UPARROW object. Then clear the PTEXT field and press space, Control-B, space. Name the object "DWNARROW." Then position it on top of the NUMBERS object as shown in Figure 4.1. Step 16: Drag another BUTTON object into your dialog box, and double-click it. Set the SELECTABLE, DEFAULT and TOUCHEXIT options. Change the text field to "OK." Resize and position the object as shown in Figure 4.1. Then name it "OK." Step 17: Drag yet another button into your dialog box, and double-click it. Set the SELECTABLE and TOUCHEXIT options. Then change the TEXT field to "CANCEL." Resize and position the object as shown in Figure 4.1. Then name it "CANCEL." And that's it. You've just created your first dialog box. Now, to save all your hard work, close the dialog box by clicking on the upper-left corner of the window. Then select the SAVE AS option from the FILE menu. Name the file "SAMPLE," and you're on your way. To leave the RCP, select the Quit option from the File menu. You should now have three files on your disk: SAMPLE.H, SAMPLE.DEF and SAMPLE.RSC. These are the files that the RCP created. SAMPLE.H contains all your object and tree names as a series of C #defines. If you want to refer to the objects by name in your program, you must *include* this file in your source code. Unfortunately, because the file is in C format, you must first change it to assembly language form, by changing each #define line to an *equ* line. The SAMPLE.DEF file contains information the RCP uses for its own purposes, and the SAMPLE.RSC file is the tree data for our dialog box. We'll load this data into memory when we run our program. --Object Attributes-- That was a fast course in the use of a resource construction program. You probably have many unanswered questions. For example, what do all those attributes do? SELECTABLE simply means that the user can select the object. When the object is selected, it will be displayed in inverse video. If you set the DEFAULT option when editing an object, the object will be selectable with the Return key, as well as with a mouse click. Obviously, only one object at a time can be set as a default. The EXIT and TOUCHEXIT attributes are similar: they both cause the dialog box to be exited when selected. The difference is that, with TOUCHEXIT, the mouse button need not be released to exit the dialog box. What did you think about the RADIO BUTN option? Radio buttons are handy devices, allowing the programmer to set up a series of related buttons, only one of which may be selected at a time. As soon as a button is selected, the previously selected button is turned off. They get their name from those old car radio tuners with the push buttons to select the channel. An important note: In order for radio buttons to operate properly, they must have the same parent object; that is, they all must be "on top of" the same object. The CHECKED, SHADOWED, OUTLINED, CROSSED and DISABLED options affect the way the objects will be graphically represented on the screen. You can easily see their effect by using your RCP to set them for various objects. The options' names describe their effect. An EDITABLE object may be modified in some manner by the user. --Editable Text-- Now, what's the story behind those strange text fields PTMPLT, PVALID, and PTEXT? These three strings combine in such a way as to tell GEM which part of the text is editable and what characters the user is allowed to input. PTMPLT is used as an input mask. Any text entered here will be displayed on the screen and will be unchangeable (except underline characters) by the user. PTMPLT also tells GEM where the user can edit the text. We indicate this with underline characters. In Step 9 above, the unalterable text is "NAME:" and the editable area, where the user will enter his or her name, is represented by the 10 underlines. The PVALID field tells GEM what type of characters we want the input restricted to. Each underline character in the PTMPLT field must have an entry in the PVALID field as follows: Code Characters Allowed ---- ------------------ 9 0 to 9 A A to Z, space a A to Z, a to z, space N 0 to 9, A to Z, space n 0 to 9, A to Z, a to z, space F DOS filename characters, plus ? * : P DOS filename characters, plus \ ? * : p DOS filename characters, plus \ : X Any character In Step 9 we entered 10 lowercase A's in the PVALID field, limiting the user's input to upper- and lowercase letters. A logical choice for a person's name. Finally, the PTEXT string will be combined with PTMPLT when the latter is printed. Unlike the text in PTMPLT, the string stored in PTEXT is editable. This is handy when you want an editable text field displayed with a default setting. For example, in Step 9, if we had made the PTEXT string "FRED," when the dialog box appeared on the screen, the text cursor would appear to the right of the string "FRED." The user could then just leave the string as it is, and thus select FRED as his name, or he could backspace over it (or use the escape key to clear it) and type in something new. When the user exits the dialog box, the new information will be found in PTEXT, replacing what we had stored there previously. When we set up our editable text objects in Steps 9 and 10, however, we wanted to end up with the text cursor to the left of a blank field, ready for the user's input. To do this, we must either enter an "@" or a null as the first character of the PTEXT string. To reserve space for any text the user may enter, we must fill the rest of the PTEXT string with blanks (actually, any character will work; once GEM sees the "@: or null, it'll ignore the rest of the string and go on its merry way). --A Look at an Object's Structure-- Now that we've created our dialog box and played with it a little, it's time to dig into the actual structure of objects. We said before that an object is a data structure, the members of which describe the object, storing all the necessary information to bring that object up on the screen. When we load a resource file into memory, each object is allocated memory as follows: word ob_next word ob_head word ob_tail word ob_type word ob_flags word ob_state longword ob_spec word ob_x word ob_y word ob_w word ob_h Here, *ob_next* is the index of the object's next sibling, *ob_head* is the index of the object's first child, and *ob_tail* is the index of the object's last child. (Remember that the objects are stored in an array of structures. The indices mentioned above are the location of the object within the array.) The *ob_type* field is the object type and will contain one of the following values: Object Type Value ----------- ----- Box....................20 Text...................21 BoxText................22 Image..................23 ProgDef................24 IBox...................25 Button.................26 BoxChar................27 String.................28 FText..................29 FBoxText...............30 Icon...................31 Title..................32 The *ob_flags* field contains the object flags and will be one (or a combination of more than one) of the values shown below: NONE...............0x0000 SELECTABLE.........0x0001 DEFAULT............0x0002 EXIT...............0x0004 EDITABLE...........0x0008 RBUTTON............0x0010 LASTOB.............0x0020 TOUCHEXIT..........0x0040 HIDETREE...........0x0080 INDIRECT...........0x0100 You should recognize most of these from your work with the RCP. The *ob_state* field holds the current state of the object as follows: NORMAL.............0x0000 SELECTED...........0x0001 CROSSED............0x0002 CHECKED............0x0004 DISABLED...........0x0008 OUTLINED...........0x0010 SHADOWED...........0x0020 You've seen most of these before, right? The *ob_spec* field contains object-specific information and changes depending on the type of object that's being described. The possible values of this field are as below: Object Type Contents of *ob_spec* ----------- ---------------------- Box.............Object's color and thickness Text............Pointer to TEDINFO structure BoxText.........Pointer to TEDINFO structure Image...........Pointer to BITBLK structure ProgDef.........Pointer to APPLBLK structure IBox............Border's color and thickness Button..........Pointer to text string BoxChar.........Object's color and thickness, and the character to display String..........Pointer to text string FText...........Pointer to TEDINFO structure FBoxText........Pointer to TEDINFO structure Icon............Pointer to ICONBLK structure Title...........Pointer to text string Notice that the value stored in *ob_spec* can be a pointer to another structure containing additional information about the object. We'll take a look at one of the structures, TEDINFO, shortly. Finally, *ob_x*, *ob_y*, *ob_w*, and *ob_h* contain the object's coordinates, width and height, respectively. --The Mysterious TEDINFO-- The second structure type we need to look is called TEDINFO. Whenever our object has an editable text field, we need to store information about it in a TEDINFO structure (actually, when using an RCP, we don't have to worry about storing information in the structure; it's done for us). Here's what a TEDINFO structure looks like in memory: longword te_ptext longword te_ptmplt longword te_pvalid word te_font word te_junk1 word te_just word te_color word te_junk2 word te_thickness word te_txtlen word te_tmplen Here, *te_ptext* is a pointer to the PTEXT string, *te_ptmplt* is a pointer to the PTMPLT string, *te_pvalid* is a pointer to the PVALID string, *te_font* is the text font (3=system font; 5=small font), *te_just* is the justification (0=left; 1=right; 2=centered), *te_color* is the color and pattern type, *te_thickness* is the thickness in pixels of the border (0=no border; 1 to 128=thickness inward from the edge; -1 to -127=thickness outward from the edge), *te_txtlen* is the length of the string pointed to by *te_ptext*, and *te_tmplen* is the length of the string pointed to by *te_ptmplt*. You can ignore *te_junk2*; it's reserved for future use. Don't let all this technical stuff get you down. In most cases, when using the RCP to put together your dialog box, you won't have to worry about the contents of the above structures. But, in case you want to do something more sophisticated, you do need to understand where to find information about your dialog box. An example of this is the NUMBERS object in the sample dialog. In order to get the up and down arrows to change the value shown, we have to be able to get at the displayed strings. This is just one example of the creative ways you can use a dialog box. --The Workings-- Now, just to make absolutely sure your head is spinning, let's look at the source code for this chapter's program, to see how everything we've discussed ties in with the dialog box. At the top of the listing, we include the file SAMPLE.H. This file contains the name of the object tree that represents our dialog box, as well as the names of all the objects within the tree. Each object is given a number. This number is the index used to find the object within the array. (A tree is an array of objects, remember?) Listing 2 at the end of this chapter shows what the SAMPLE.H file contains. If you created your dialog box from scratch, you may find that your objects are numbered differently than those in Listing 2. Don't worry about it. That just means we constructed our dialog boxes a little differently. For instance, the example's object numbers don't run in perfect order. Some numbers are missing because, in the course of constructing the dialog, I removed a couple of objects from the screen without actually deleting them from the tree. Those objects were assigned numbers, but since they remain unnamed (and unused), they don't appear in the .H file. (They are, however, still taking up space in the resource file.) The equates after the *include* in Listing 1 assign to logical names the values of various parameters we'll be using when handling the dialog. Now, look at the data section of the program. The variable *num* will hold the current value of the dialog's number control. Following that is our AES parameter block. Notice something strange? The first element of this array, which should be the address of the control array, is 0. This is because, starting with this program, we're going to be using a shortcut for initializing the control array for each function call. If you look below the *apb*, you'll see how we're going to do this. Each AES function now has its own control array, already initialized with the proper values. All we have to do to set up our *apb* for an AES call is place the address of the function's control array into the first element of the *apb* array. You'll see how this is done a little later, when we discuss the main program. Following the control arrays is the filename for our resource file. Then, the byte array *number_str* is the string we'll use to change the displayed value of the NUMBERS object, in response to the user's clicking one of the arrows. Finally, in the *bss* section, we have several variables and buffers, as well as our GEM arrays and our stack. --The Main Program-- The main program starts off by performing all the usual initializations: calculating the size of the program area, releasing unused memory, setting the global array, and calling *appl_init*, *rsrc_load*, and *rsrc_gaddr*. The only thing different here is the way we're initializing the control array for each AES function call. Rather than five lines that initialize each control value individually, we're just moving the address of the already initialized arrays into the first element of the *apb*, like this: move.l #appl_init,apb This method saves us a lot of extra work and shortens our source code considerably. The process of loading a resource file and finding its address is the same for any resource, whether it be a dialog box, a menu, or something else. If you need a refresher on the *rsrc_load* and *rsrc_gaddr* functions, please refer to chapter 3. In this chapter, we'll start our discussion with how to display a dialog box. --Displaying a Dialog Box-- In order to display a dialog box, allow the user to use it, and close the dialog, we perform the following seven steps: 1) Center the dialog's coordinates. 2) Reserve screen space for the dialog. 3) Draw an expanding rectangle. 4) Draw the dialog. 5) Activate the dialog. 6) Draw a shrinking rectangle. 7) Erase the dialog. A few of these steps are optional. For example, although a dialog box usually looks best in the center of the screen, there's no reason you have to center it. Also, you can skip the expanding and shrinking rectangles, if you like. This will slightly speed up the process of displaying and removing the dialog, at the expense of losing some of the visual effects. Although some steps are optional, we will, of course, look at them all. --Dialog Display Step 1-- The first thing we do in the program is modify the coordinates of our dialog box so that it'll appear in the center of the screen. This is done with a call to AES function #54, *form_center*, like this: move.l #form_center,apb move.l dialog_addr,addr_in bsr aes move int_out+2,dial_x move int_out+4,dial_y move int_out+6,dial_w move int_out+8,dial_h First, we place the address of the *form_center* control array into the first element of the *apb*. Then, we place the address of our dialog box into the first element of the addr_in array, after which we call the AES. After the call, *int_out* will contain the dialog's new coordinates. We save these coordinates for later use, by moving them into *dial_x*, *dial_y*, *dial_w*, and *dial_h*. These are the dialog's x coordinate, y coordinate, width, and height, respectively. --Finding an Object's Coordinates-- Because we'll be doing some work by hand, as it were, on the NUMBERS object in order to update the number it displays, we must find its eventual position on the screen. We do this with a call to AES function #44, *obj_offset*, like this: move.l #objc_offset,apb move #NUMBERS,int_in move.l dialog_addr,addr_in bsr aes move int_out+2,num_x move int_out+4,num_y In the first line, we move the address of the *objc_offset* control array into the first element of our *apb*. Then, we place the number of the object for which we want the coordinates into the first element of *int_in*. Finally, we place the dialog's address in *addr_in* and call the AES. After the call, *int_out0* will contain an error code: 0 if an error occurred or a value greater than 0 if there was no error. Also, *int_out1* and *int_out2* will contain the X and Y coordinates of the object. In our program, we copy these values into the variables *num_x* and *num_y*. --Dealing with TEDINFO-- After we have these values, we need to tell the dialog to use our *number_str* string, instead of the one it's currently using. To do this, we must access the NUMBERS object's TEDINFO structure. Here are the lines of code that do this, the first of which is a macro call: tedinfo_str dialog_addr,NUMBERS move.l #number_str,(a5) The main body of the macro looks like this: move.l \1,a5 add.l #(\2*24)+12,a5 move.l (a5),a5 To trick the object into using our string, we have to find the pointer that points to the string contained in the NUMBERS object. Remember that our tree, which is now pointed to by *tree_addr*, is an array of structures, each structure describing one of the objects within the tree. Just like any other array, we can access a particular element by using an index. The object whose structure we wish to locate is NUMBERS, and, thanks to our handy RCP, NUMBERS has been defined in the SAMPLE.H header file to the value of the index we need. The *ob_spec* member of the structure that describes NUMBERS, contains a pointer to the TEDINFO structure that holds the pointer to our string. So, in the first line of the macro, we load the address of our resource into A5. Now, we need to locate the *ob_spec* field of the numbers object. If you look at an object's structure as shown previously, you'll see that each object is 24 bytes long. So, because NUMBERS is the index of the object in the tree, NUMBERS*24 gives us the first byte of the NUMBERS object. Another look at an object's structure will tell you that the *ob_spec* field is 12 bytes from the start of the object. So, by adding 12 to NUMBERS*24, we get the location of NUMBERS's *ob_spec* field from the beginning of the entire resource tree. The entire formula, then, is (NUMBERS*24)+12. By adding the result of this formula to the address we stored in A5, we'll have the address of NUMBER's *ob_spec* field in A5. That's what we do in the second line of the macro, as shown above. The third macro line above uses address register indirect addressing to load the contents of the address pointed to by A5 into A5. This address is the address of the tedinfo's string, because that address happens to be stored in the first four bytes of the tedinfo. --Dialog Display Step 2-- Now, on to step 2, reserving space on-screen for the dialog. We have to do this so that, when we remove the dialog from the screen, GEM will be able to restore the display. A call to AES function #51, *form_dial*, will take care of this bit of prestidigitation. The call looks like this: move.l #form_dial,apb move #FMD_START,int_in move #0,int_in+2 move #0,int_in+4 move #10,int_in+6 move #10,int_in+8 move dial_x,int_in+10 move dial_y,int_in+12 move dial_w,int_in+14 move dial_h,int_in+16 bsr aes After placing the address of the *form_dial* control array into our *apb*, we place the *form_dial* operations code into *int_in0*. The code must be one of those listed below: 0 FMD_START......................reserves screen space 1 FMD_GROW.........................draws expanding box 2 FMD_SHRINK.......................draws shrinking box 3 FMD_FINISH...releases screen space and does a redraw The *form_dial* function does a lot of work for us, as you can see. This one function can handle four of our dialog steps all by itself. For the first call to *form_dial*, we use FMD_START code, since we want to reserve space for our dialog. Next, we place the X,Y coordinates, width, and height of the smallest rectangle (we'll talk about this in a moment); and the X,Y coordinates, width, and height of the largest rectangle (the actual size of the dialog) into *int_in1* through *int_in8*, respectively. Finally, we call the AES to perform the function. --Dialog Display Step 3-- For step 3, drawing an expanding rectangle, we again call *form_dial*, but this time with the FMD_GROW code. The call looks exactly the same as that for the first call, except for the operation code. When the function is called, GEM draws the expanding box, starting with the coordinates and size of the smallest rectangle, and ending with the coordinates and size of the largest rectangle. Again, the drawing of both the expanding and shrinking boxes is optional. If you wish, you can skip over this step and go directly to the call below, which actually draws the dialog. --Dialog Display Step 4-- Finally, we're ready to draw our dialog, with a call to AES function #42, *objc_draw*: move.l #objc_draw,apb move #0,int_in move #2,int_in+2 move dial_x,int_in+4 move dial_y,int_in+6 move dial_w,int_in+8 move dial_h,int_in+10 move.l dialog_addr,addr_in bsr aes For this call, we place into succeeding elements of *int_in* the number of the first object to draw, how many objects deep to draw, and the coordinates of the dialog (which were returned by *form_center*), respectively. The dialog coordinates in this call are called the "clipping rectangle." The clipping rectangle is the portion of the display to which all our screen output is limited. For instance, if we print text that'll extend beyond the rectangle's border, the text will be "clipped" to fit; anything that would be drawn outside the clipping rectangle will be ignored. Thus we can protect the integrity of the rest of the display. When we set *int_in0* to 0 in the above call, we're asking that GEM start drawing the dialog with the first object in the tree. The first object in a tree is the root-- in our case, the box containing the rest of the objects that make up our dialog. To be sure all the objects contained within the dialog are drawn, we must set the depth to the proper value. If we had set it to 0, only the main box would have been drawn. If we had set it to 1, only the main box and its children would have been drawn, meaning that our radio button box would be missing its buttons and our number box would be missing its arrows. By setting depth to 2 in the sample program, the main box plus its children and grandchildren (the children of the children) are drawn, thus completing our dialog. If you want to be sure you get everything, just set depth to its maximum value of 8. --Dialog Display Step 5-- Now that our dialog is on the screen, how do we give control to the user? Simple! We call AES function #50, *form_do*: move.l #form_do,apb move #NAME,int_in move.l dialog_addr,addr_in bsr aes Here, we load the index of an editable text field (0 if there are no editable text fields) into *int_in0* and the address of our dialog into *addr_in0*. The value in *int_in0* tells *form_do* the number of the editable text field we want to be active when the dialog appears. Now that we've made our call to *form_do*, GEM will handle the dialog for us, highlighting any selectable fields clicked on and letting us enter text into any editable text field. When an exit button is clicked, GEM terminates the dialog and returns the number of the button clicked, in *int_out0*. The button number is the only piece of information *form_do* returns directly. If we want to see what was entered into the strings, we have to hunt. Obviously, only EXIT buttons will ever have their values returned from the *form_do* call, so if you want to know when a button is clicked, make sure, when you design your dialog, that one of the button's attributes is EXIT or TOUCHEXIT. (Actually, that's not entirely true. There is another way to get this information. Each time we click a button or fiddle with the dialog in some other way, the object's status is changed and recorded in the *ob_state* field of the OBJECT structure.) Because the only time we want to close our sample dialog box is when the OK or CANCEL button is clicked, we place the *form_do* call within a large loop. In the loop, we check to see which button was clicked (by looking at *int_out0*), and perform the necessary action. The loop repeats, continually activating and deactivating the dialog, until the OK or CANCEL button is clicked. Note that the call to *form_do* doesn't redraw the dialog; it only notifies GEM to accept more input from the form. --Modifying a TEDINFO string-- In our sample dialog, when a button is clicked, all the program does is print the object's name in an alert box. But when the user clicks on one of the arrows, we have to find a way to change the value shown in the NUMBERS object. Let's say the user clicks on the up arrow. At that point, program execution finds its way to the label *chk_but8*, where *num* is incremented. The *cmpi* instruction makes sure the displayed value doesn't exceed 9999. If *num* gets too big, we jump to the label *num_at_max*, which calls the *change_number* subroutine, without changing the value of *num*. Look at the *change_number* subroutine, where we change NUMBERS's string. The first thing we do is copy all zeroes into *number_str*. Then, we convert the value in *num* into ASCII form and copy it into the correct position of *number_str*. Finally, we call *objc_draw* to redraw only the NUMBERS object. To do this, we store the object's number in *int_in0*, and a drawing depth of 1 into *int_in1*. For the clipping rectangle, we use the X,Y coordinates for NUMBERS returned to us previously. On a high resolution screen, the width and height of this object are 96 and 32, respectively. To run the program in another resolution, these two numbers must be changed. Use 96 and 16 for medium resolution. (Of course, the best way to handle these resolution-dependent values is to use variables that you initialize according to the current resolution, which you check at the beginning of the program.) --Dialog Display Step 6 & 7-- When the user has finished with the dialog, we must remove it from the screen. We do this by performing two more *form_dial* calls: one to display the shrinking box (FMD_SHRINK) and one to restore the screen (FMD_FINISH). These two calls look like this: move.l #form_dial,apb move #FMD_SHRINK,int_in move #0,int_in+2 move #0,int_in+4 move #10,int_in+6 move #10,int_in+8 move dial_x,int_in+10 move dial_y,int_in+12 move dial_w,int_in+14 move dial_h,int_in+16 bsr aes move #FMD_FINISH,int_in bsr aes Why is the second call (the one using FMD_FINISH) so much shorter? Because the only parameter that changes between the calls is the operation code, FMD_FINISH. It'd be a waste of time to load the arrays with values that are already there. --Conclusion-- This finishes up our introduction to dialog boxes. By now, you should have a good idea of how versatile they can be. With a little creativity, you could write an entire program that used nothing but dialog boxes for all its input and output. Don't be afraid to experiment. Get as outrageous as you like! --Summary-- * An object is a data structure, the members of which describe the object, storing all the necessary information to bring that object up on the screen. * The objects of a dialog box (or other type of resource) are connected to form an object tree. * An object can be one of several types, including Button, String, FText, FBoxText, IBox, Box, Text, BoxChar, BoxText, and Icon. * An object can have several flags that control how the object is used, including NONE, SELECTABLE, DEFAULT, EXIT, TOUCHEXIT, RBUTTON, LASTOB, EDITABLE, HIDETREE, and INDIRECT. * An object can have various states, including NORMAL, SELECTED, CHECKED, SHADOWED, OUTLINED, CROSSED, and DISABLED. * Editable text in a dialog box uses three fields--PTEXT, PTMPLT, and PVALID--to tell GEM what text to display, what part of the text is editable, and what characters the user is allowed to input. * Editable text fields in an object are described by a TEDINFO structure. * There are seven steps to displaying and handling a dialog box. They are 1) center the dialog's coordinates, 2) reserve screen space for the dialog, 3) draw an expanding rectangle, 4) draw the dialog, 5) activate the dialog, 6) draw a shrinking rectangle, and 7) erase the dialog. * The AES function #54, *form_center*, centers a dialog's coordinates with respect to the screen. * AES function #44, *objc_offset*, finds an object's coordinates. * AES function #51, *form_dial*, has four different functions, depending on the operation code. A code of 0 reserves space on the screen for the dialog, a code of 1 displays an expanding box, a code of 2 displays a shrinking box, and a code of 3 removes a dialog from the screen. * AES function #42, *objc_draw*, draws an object or object tree on the screen. * AES function #50, *form_do*, gives control of a dialog box to the user.