A Simpler Way to Program Intuition : EzLib Dominic Giampaolo INTRODUCTION : Programming the Amiga is a bear, no questions asked. To write even a simple Intuition program requires pulling out over 1,700 pages of documentation (the Libraries & Devices and the Includes & Autodocs RKM's), delving into a myriad of include files and #defines, and writing a lot of redundant code. This doesn't mention the steep learning curve and the countless times a novice crashes the machine just trying to get a simple window opened. I got tired of that pretty quick and decided there had to be a better way to get the more common Intuition features without a lot of effort. One better way is a User Interface Builder which graphically lets you design an interface and then writes the code for you. So far on the Amiga they all suffer the same general problems of being unreliable and producing questionable code. What resulted from my frustration is EzLib, a link time library of twenty-nine functions that make Intuition programming easier. With EzLib, I can whip up an Amiga program replete with screens, windows, and all kinds of gadgets in about 10 minutes and a third as much source code. THE PROBLEM : A great feature of Intuition is its amazing flexibility. Unfortunately this is also its downfall when trying to accomplish simple things. How many times have you said to yourself, Gee I'd like a window with a few buttons in it, but it's such a pain to do? Using traditional methods, making a window with three buttons, requires you to fill out eighteen fields in the NewWindow structure and forty-five fields for the three boolean gadgets. Then you have to open Intuition.library and Graphics.library, error check all your results and finally open the window, add the gadgets to the display and then get to your main event loop. Of course a good number of the 63 fields you fill out will be zeros or maintained by Intuition, but it is still a tedious chore to set them up. What if you'd like a nice 3D look for the gadgets? Tack on a bunch more code to create the necessary Border structures. Shouldn't you be able to say you'd like a simple Boolean gadget at this X,Y position with this text in it? Consider using custom screens. Only 10 fields in a NewScreen structure, but if you want a screen deeper than 2 bitplanes get ready to write a considerable amount of code to allocate BitMaps and BitPlanes, again with full error checking. Couldn't you just say that you wanted a five bitplane LoRes screen? The rest is straightforward and should be handled for you, right? What about DiskFonts? Using custom fonts can greatly add to the impact and look of a program. But you must open DiskFont.library, fill out a TextAttr structure, open the font, and then close the DiskFont.library. When you just want a 12 point Helvetica font, it seems like an awful lot of work. How about the standard yes/no requestors every program has? Sure you can use an AutoRequest(), but don't forget about correctly filling the the three Intuitext structures, and making sure to check widths and height properly. You just wanted a yes or no requestor, not a hassle. Suppose you need to get a text string from the user? Go ahead and design your own requestor or window with a string, okay, and cancel gadgets? Add about 200 lines of code and data structure definitions. Wouldn't it be nice if there were a way to say "get a yes no answer" or "get a string from the user"? Then there are Proportional and String gadgets? Reknowned for their amazing complexity, coding Proportional gadgets can drive programmers up a wall. Why not be able to say you want a vertical or horizontal Proportional gadget with these dimensions and this ID? String gadgets generally require at least 50-75 lines of data definitions and code to set up. Again, if you want the nice 3D look to your string gadget, tack on some more code and headaches. Why isn't there a way to say that you want a string gadget at this position and this length? All of these problems stem from one source, flexibility. Intuition offers enormous flexibility at the expense of ease of use. For the most the last time you wanted to worry about the CheckMark field of a NewWindow structure or the BackPen field of a IntuiText structure? Sure you can set these to custom values, but when you would like simple things it can get annoying and tedious dealing with unwanted flexibility. THE SOLUTION : Clearly there is a need for higher level routines that deal with specific needs of Intuition programmers. I took the approach of writing a group of functions that accept a few arguments, and take care of each of the above problems. For example, consider opening a custom screen. Let's say that you know you want a hi-res, interlaced, 4 bitplane screen. You don't care about opening necessary libraries, allocating bitmaps and bitplanes, and error checking all results. Now consider the Ezlib approach, all you do is this : screen = CreateScreen(HIRES|INTERLACED, 4); if (screen == NULL) error_code_goes_here(); Ezlib handles everything from making sure that Intuition and Graphics libraries are opened to allocating bitmaps etc. It is much simpler both conceptually and codewise. You don't get tangled in a mess of code that's irrelevant to your problem, and you can more easily see what is happening in your program. Some people might complain that these functions must add considerable bloat to your code and slow it down. This is not true. EzLib is less than 14K of rock solid, completely error checked code. And since it is a link time library, you only link in the code you use. Efficiency is less of a concern as the routines contain mostly initializations and almost no loops. On top of that we should remember the immortal words of Kernighan and Plauger, (paraphrased) "if you want to improve efficiency, don't diddle the code, change the algorithim". EzLib will not be a bottleneck in your programs. Drawbacks : Obviously there is a trade off. The designers of Intuition didn't make things unnecessarily complicated, only enough to support the flexibility Intuition offers. The problem is that a majority of that flexibility goes unused most of the time (recall the CheckMark field of a NewWindow structure). In writing EzLib, I traded off the unused flexibility for ease of use. EzLib contains simple functions with only a few arguments (of integers, pointers, or character strings) that make it easy to code, but take a loss in flexibility. Of course I could have compensated by adding more arguments, but that just creates a different version of the same problem I tried to avoid. The most arguments an EzLib function accepts is eight, which you may balk at, but should consider that half of those arguments specify dimensions (left edge, top edge, width, and height). The arguments to EzLib functions are the bare essentials needed to specify the more common user interface objects. EXAMPLES : Now that you're introduced to EzLib, you probably want to know the specifics on what it can offer you. Let me first offer some background on Ezlib. The EzLib code is solid. None of the EzLib code generates any Enforcer or Mungwall hits, and every function error checks all arguments. You can pass NULLs where there should be window pointers, and EzLib will gracefully return a failure to you instead of crashing. All of the String and Boolean gadgets in EzLib have the proper 2.0 style 3D look to them. String gadgets look sunk in to the screen, and Boolean gadgets have the raised button look. EzLib properly frees all allocated memory and doesn't reliability of the EzLib code. EzLib functions come in three varieties (along with some miscellaneous other routines). There are "Make" functions which create an interface object and display it, the "Create" routines which are a level lower and generally just "create" the desired object, and finally the "Kill" routines which remove the object and free its resources. The simplest calls with the least flexibility are the "Make" level routines. The "Create" calls offer more flexibility at the expense of an added argument or two and the responsibility of ensuring that the object gets displayed. You can freely intermix calls to the "Make" and "Create" levels. EzLib functions only create the standard Intuition structures, so there are no new structures to learn. All objects once created can be manipulated and changed by your code except for the ExtData field of a custom screen (EzLib uses this privately). Now, on to the details, but remember that this is simply an overview, the actual Man page style documentation for each function is on disk. Libraries : Let's start with the first thing an Amiga program does, opening libraries. We are all familiar with the common first few lines of an Intuition program : GfxBase = (struct GfxBase *)OpenLibrary("graphics.library", 33); if (GfxBase == NULL) error_code_goes_here(); Of course you must write almost exactly the same code for intuition.library and whatever other libraries your program requires. Consider EzLib's OpenLibs() call : if (OpenLibs(GFX | INTUITION | REXX) == NULL) error_code_goes_here(); One call does it all. Graphics.library, intuition.library, and rexxsyslib.library, all opened with a single function call. Should one of the three fail to open, the others are closed down and NULL returned. Closing down libraries at the end of your program is equally simple with CloseLibs(). To close all three of the above libraries, just call CloseLibs() with the same arguments as you called OpenLibs(), or use the #define ALL_LIBS to close any libraries opened with OpenLibs(). Windows : The next thing a programmer usually does is to open a window. I won't repeat the tedious NewWindow structure, but suppose you would like a simple window with the usual arrangement of TitleBar, Depth Gadgets, Close Gadget, and Sizing gadget. How does this grab you : struct Window *window; window = MakeWindow(NULL, 0,0, 300, 100); if (window == NULL) error_code_goes_here(); The first NULL argument indicates we want the window on the WorkBench, and the next four arguments provide the window dimensions. Of course we don't all want fully dressed Intuition windows, and EzLib provides CreateWindow(). This time, assume we would like a TitleBar along with VANILLAKEY messages, but no Depth, Sizing or Close gadgets. Here it is : struct Window *window; window = CreateWindow(NULL, WINDOWDRAG, VANILLAKEY, 0,0, 300,100); if (window == NULL) error_code_goes_here(); Again, simple and to the point. With CreateWindow(), you can completely specify all of the IDCMP and other Flags you wish. Should you use MakeWindow() and decide that you need other IDCMP messages, you can always use Intuition's ModifyIDCMP(). Boolean Gadgets : Want to add some gadgets to your window? Simply call MakeBoolGadget(). Let's add two Boolean gadgets to our above window, one that says "Tastes Great" and the other "Less Filling". struct Window *win; /* opened earlier */ struct Gadget *gadg1, *gadg2; gadg1 = MakeBoolGadget(win, 50,50, "Tastes Great", 1); gadg2 = MakeBoolGadget(win, 50,90, "Less Filling", 2); if (gadg1 == NULL || gadg2 == NULL) { KillGadget(win, gadg1); /* ok if gadg1 is null */ KillGadget(win, gadg2); } That's all there is to it. Instantly we have two attractive 3D buttons above each other in our window, one with a GadgetID of 1, the other with a GadgetID of 2. The text we provided is centered tastefully in the box, and it all looks nice. Should we need to know the specific dimensions of the gadget EzLib created, say for algorithmic layout of a display, we can look in the Gadget structure itself. If MakeBoolGadget() fails for any reason it returns NULL. The KillGadget() routine is intelligent enough to recognize a NULL argument, and simply returns if we happen to pass it one. This makes cleanup considerably simpler. KillGadget() also works on all three types of Gadgets with the same exact call. Now as any Intuition programmer knows, less than 10 lines of code for two 3D boolean gadgets is considerably less work than what it normally takes. String Gadgets : String gadgets require equally little effort. Suppose we would like a string entry field of 200 pixels in length, positioned at 75,50, with a default string of "Tanstaafl", and a GadgetID of 3. Here it is : struct Window *win; /* opened earlier */ struct Gadget *str_gadg; str_gadg = MakeStringGadget(win, 75,50, 200, "Tanstaafl", 3); if (str_gadg == NULL) error_code_goes_here(); The string gadget has a text buffer of 255 characters which you can examine whenever you receive a GADGETUP message for the gadget. If you wish to use the text contained in the StringInfo buffer, you must copy it to a private area, but other than that, there are no restrictions. Proportional Gadgets : Proportional gadgets: perhaps the most useful interface objects and the most difficult to program correctly. If the rest of Intuition is difficult, doing Proportional gadgets correctly is near impossible. At best they are murderously difficult without EzLib. First, applications should deal with their own numbers, not Intuition's. Prop gadgets require you to convert all your numbers into Intuition's and back, an annoyance. Second, real time feedback from Prop gadgets is a feature of Intuition that no good programmer should disregard. Doing real time feedback for a Prop gadget can make you tear your hair out if you've never done it, and is always a place for bugs to creep in. EzLib solves all of these problems simply and gracefully. The only kind of Prop gadget EzLib doesn't let you create are joystick style Prop gadgets which move on both the X and Y axes. Consider a standard fare vertical Prop gadget at 50,75, size 25,100, and GadgetID 4. struct Window *win; /* opened earlier */ struct Gadget *prop_gadg; prop_gadg = MakePropGadget(win, 50,75, 25,100, FREEVERT, 4); if (prop_gadg == NULL) error_code_goes_here(); Assume we would like our Prop gadget to have a range of 1 to 45, with the knob positioned at the top, and the knob itself representing 10 (this is the knob's size relative to the entire prop gadget). Again, a simple call takes care of it : SetPropGadg(win, prop_gadg, 0, 10, 45); Now when you need to find out the value of the Prop gadget after the user modifies it, simply call : val = GetPropValue(prop_gadg) and you will receive numbers in the range of 0 to (in this case) 44. As mentioned, EzLib provides a method for doing interactive scroll bars. This method uses callbacks, something borrowed from the X window system. Assuming you received a GADGETDOWN message from Intuition and determined that it is for this Prop gadget, you then call RealtimeProp() as follows : RealtimeProp(win, prop_gadg, some_function, data_ptr); Where win is your window pointer, prop_gadg is a pointer to the gadget the user is playing with, some_function is the name of a function you would like called when the value of the Prop gadget changes, and data_ptr is a pointer to any arbitrary data you would like passed to some_function. When the user releases the Prop gadget, control is returned to your program. This method works out very nicely. Let's assume you have a text editor with a scroll bar on the right. When the user clicks on the scroll bar, you call RealtimeProp() and along with the window and gadget, give it the name of your scrolling function and a pointer to the text buffer (as the data pointer). Each time the Prop gadget changes, your scroll function is called. Trivial realtime scrolling. All your scrolling function needs to do is accept three arguments: the window pointer, the data pointer, and the new value of the Prop gadget (which is of course within your pre-defined limits). The only drawback to this method is that the function RealtimeProp() takes over receiving IntuiMessages for your window. For most types of applications this is not a problem, but DISKINSERTED, NEWPREFS, and INTUITICKS messages will still arrive and be ignored until the user lets go of the Prop gadget. If you need more control in specifying your Prop gadgets, for example when designing one that takes advantage of the REL positioning and size modes of Gadgets, you can use MakeVertProp() and MakeHorizProp(). Both functions accept Flags and Activation arguments that allow you to create Prop gadgets that always position or size themselves relative to the enclosing window. Of course for total control, you can use CreatePropGadget(), which allows you to specify all the relevant features of a Prop gadget. Miscellaneous : How often do you need to get a simple yes or no response from a user. "Erase this disk?", "Delete Selected Items?", or the ubiquitous "Really Quit?" are all simple yes/no questions. Intuition provides AutoRequest(), but that still requires you to fill out three IntuiText structures and calculate dimensions for the requestor. EzLib provides GetYN(). Assuming you would like to ask the user, "Really Quit Program?", with EzLib, all it takes is this : answer = GetYN(win, "Really Quit Program?"); if (answer == TRUE) quit_program(); Trivial to use and quite useful in many instances. GetYN() is one of those handy little items you wonder how you got along without before. Equally useful is GetString(). Many times you need to just get a single text string from a user, such as a person's name or any other single line of text. This can pose a problem in the midst of writing a much larger program, it distracts you from solving your real problem and increases the chances for bugs to show up. GetString() works simply and efficiently. Here's all you need to do : UBYTE *string; string = GetString(NULL, "A Title", "Default string"); if (string == NULL) got_no_string(); GetString() puts up a small window with a String gadget, a Cancel gadget, and an OK gadget. The first argument is the screen you would like the window to appear on, NULL means on the Workbench. The title string is the window title, and the default string (if not NULL) is placed in the string gadget to start. When the user clicks OK or presses return in the String gadget, you get a copy of whatever text is in the String gadget. If the user clicks Cancel or hits the Close gadget on the window, you get a NULL back. As the string returned to you is dynamically allocated, remember to FreeMem() any non-NULL value you get back. And because you must account for the NULL character, the length to call FreeMem() with is : strlen(string) + 1, not just strlen(string) because you have to account for the NULL byte at the end which strlen does not count. Disk based fonts can greatly enhance the appearance of any application. Accessing them normally is somewhat of a chore. EzLib provides GetFont(), which accepts two arguments, the font name and its size. To use a 24 point Times font, the following code is sufficient : struct Window *win; /* assume opened earlier */ struct TextFont *txfont; txfont = GetFont("times", 24); if (txfont == NULL) error_no_font(); SetFont(win->RPort, txfont); /* intervening code */ CloseFont(txfont); If for whatever reason the font cannot be opened, you get a NULL in return. Otherwise you are returned a TextFont structure you can immediately SetFont() into your window. Of course you can open and use multiple fonts simultaneously, switching among them using SetFont(). All you must do is CloseFont() each font you opened with GetFont(). Finally, a few convience functions. First is LacedWB(), which tells you if the WorkBench is running in interlaced mode or not. Next is MSG(string), which prints string on the console. Print(RastPort, string) will print text into a RastPort using the current font and location (set previously with Move()). Circle(RastPort, x,y, rad) will draw a circle at x,y of radius rad. Line(RastPort, x1,y1, x2,y2) draws a line from x1,y1 to x2,y2. Last but not least of the convience functions is SetColor(screen, col_reg, col). SetColor() sets the color register, col_reg, to the color col. Col is a number between 0 and 4095 (any of the Amiga's 4096 colors). Several predefined colors such as BLACK, WHITE, and more exotic ones such as INDIGO make the call even clearer to read. CONCLUSIONS : Without a doubt, EzLib makes short work of common Intuition programming problems. EzLib makes it easier for you to write code that solves problems, instead of getting caught up in the complexity of GUI programming. Now I can say to myself, I'd like to write a program to test this idea out, and be able to have a working model within an hour instead of poring through countless pages of documentation trying to recall obscure facts. EzLib doesn't give all the flexibility of raw Intuition calls, but the lost flexibility isn't used often and is offset by ease of use. And since the source if available, you can change or add the things you need. As an aside, you probably noticed that EzLib does not provide support for Intuition menus. This is not an oversight, but because code already exists to manage the task of creating menus (see the Premier issue of the Amiga World Tech Journal for more information), I prefer not to reinvent the wheel. EzLib is really just a toolbox of routines that every accomplished programmer accumulates over time. But EzLib is not buried on some wizard programmer's hard drive, it's available with full source code and documentation to make everyone's life a little easier. Even if you're an experienced programmer, take a look at the examples and documentation on disk and compare them to what it takes normally. Newcomers to Intuition programming will likewise benefit from looking at the examples. Try to write some programs that utilize EzLib. Almost guaranteed that once you write a program in 50 lines that used to take 200 or more, you'll be reluctant to go back.