| 001
#include
<stdio.h> 002 #include <exec/types.h> 003 #include <graphics/gfx.h> 004 #include <intuition/intuition.h> 005 #include <dos/dos.h> 006 007 /* Prototypes for system functions. */ 008 #include <proto/exec.h> 009 #include <proto/graphics.h> 010 #include <proto/intuition.h> 011 #include <proto/dos.h> 012 013 /* Prototypes for our functions. */ 014 int program_init(void); 015 void program(void); 016 void clean_up(void); 017 018 /* Global variables. */ 019 struct GfxBase *GfxBase = NULL; 020 struct IntuitionBase *IntuitionBase = NULL; 021 struct EasyStruct *MyRequester = NULL; 022 023 enum { RED = 1, BLUE, GREEN, WHITE, BLACK }; 024 025 int main(void) 026 { 027 int error; 028 029 error = program_init(); /* Open needed libraries and the main window. */ 030 031 if ( !error ) 032 { 033 program(); /* If everything was initialized, execute the main program code. */ 034 } 035 036 clean_up(); /* We must clean up before we exit. */ 037 038 return 0; 039 } 040 041 042 int program_init(void) 043 { 044 int result = 0; 045 046 /* First open the graphics.library. */ 047 GfxBase = (struct GfxBase *)OpenLibrary( "graphics.library", 0L ); 048 049 if ( GfxBase != NULL ) 050 { 051 /* Second open the intuition.library. */ 052 IntuitionBase = (struct IntuitionBase *)OpenLibrary( "intuition.library", 0L ); 053 054 /* Was the library opened? */ 055 if ( IntuitionBase != NULL ) 056 { 057 MyRequester = (struct EasyStruct *)AllocMem( sizeof(struct EasyStruct), MEMF_ANY ); 058 059 if ( MyRequester ) 060 { 061 /* The memory has been reserved. */ 062 MyRequester->es_StructSize = sizeof(struct EasyStruct); 063 MyRequester->es_Flags = 0; 064 MyRequester->es_Title = "My Requester"; 065 MyRequester->es_TextFormat = "What is your favorite color?"; 066 MyRequester->es_GadgetFormat = "Red|Blue|Green|White|Black|None"; 067 } 068 else 069 { 070 /* The memory allocation failed. */ 071 printf( "Failed to allocate memory for the requester!\n" ); 072 result = -1; 073 } 074 } 075 else 076 { 077 /* The intuition.library was not opened so display a message. */ 078 printf( "Unable to open the intuition.library!\n" ); 079 result = -2; 080 } 081 } 082 else 083 { 084 /* The graphics.library was not opened so display a message. */ 085 printf( "Unable to open the graphics.library!\n" ); 086 result = -3; 087 } 088 089 return result; 090 } 091 092 093 void clean_up(void) 094 { 095 /* If the window is open, close it. */ 096 if ( MyRequester != NULL ) 097 { 098 FreeMem( MyRequester, sizeof(struct EasyStruct) ); 099 } 100 101 /* If the intuition.library is open, close it. */ 102 if ( IntuitionBase != NULL ) 103 { 104 CloseLibrary( (struct Library *)IntuitionBase ); 105 } 106 107 /* If the graphics.library is open, close it. */ 108 if ( GfxBase != NULL ) 109 { 110 CloseLibrary( (struct Library *)GfxBase ); 111 } 112 113 return; 114 } 115 116 117 void program(void) 118 { 119 int color; 120 char *color_name = NULL; 121 LONG pen; 122 123 /* Display the requester. */ 124 color = EasyRequest( NULL, MyRequester, NULL, NULL ); 125 126 switch ( color ) 127 { 128 case RED: 129 pen = ObtainBestPenA( ViewAddress()->ViewPort->ColorMap, 0xFFFFFFFF, 0x0, 0x0, NULL ); 130 color_name = "Red"; 131 break; 132 case BLUE: 133 pen = ObtainBestPenA( ViewAddress()->ViewPort->ColorMap, 0x0, 0x0, 0xFFFFFFFF, NULL ); 134 color_name = "Blue"; 135 break; 136 case GREEN: 137 pen = ObtainBestPenA( ViewAddress()->ViewPort->ColorMap, 0x0, 0xFFFFFFFF, 0x0, NULL ); 138 color_name = "Green"; 139 break; 140 case WHITE: 141 pen = ObtainBestPenA( ViewAddress()->ViewPort->ColorMap, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, NULL ); 142 color_name = "White"; 143 break; 144 case BLACK: 145 pen = ObtainBestPenA( ViewAddress()->ViewPort->ColorMap, 0x0, 0x0, 0x0, NULL ); 146 color_name = "Black"; 147 break; 148 default: 149 pen = -1; 150 color_name = NULL; 151 } 152 153 if ( color_name && (pen > 0)) 154 { 155 BOOL end = FALSE; 156 ULONG signals; 157 ULONG window_signal; 158 struct IntuiMessage *message; 159 UWORD msg_code; 160 ULONG msg_class; 161 struct Window *ColorWindow = NULL; 162 163 /* Open the color window. */ 164 ColorWindow = OpenWindowTags( NULL, 165 WA_Left, 50, 166 WA_Top, 30, 167 WA_Width, 200, 168 WA_Height, 150, 169 WA_MaxWidth, 500, 170 WA_MaxHeight, 400, 171 WA_MinWidth, 50, 172 WA_MinHeight, 50, 173 WA_Title, (ULONG)color_name, 174 WA_DepthGadget, TRUE, 175 WA_CloseGadget, TRUE, 176 WA_SizeGadget, TRUE, 177 WA_DragBar, TRUE, 178 WA_SmartRefresh, TRUE, 179 WA_Activate, TRUE, 180 WA_GimmeZeroZero, TRUE, 181 WA_IDCMP, IDCMP_CLOSEWINDOW | IDCMP_NEWSIZE, 182 TAG_END ); 183 184 if ( ColorWindow ) 185 { 186 /* Define the window signal. */ 187 window_signal = 1L << ColorWindow->UserPort->mp_SigBit; 188 189 /* Clear the window area with the selected color. */ 190 SetRast( ColorWindow->RPort, pen ); 191 192 /* Change the titles of the window and screen. */ 193 SetWindowTitles( ColorWindow, color_name, color_name ); 194 195 /* The event processing loop. */ 196 while ( !end ) 197 { 198 /* Wait for a signal. */ 199 signals = Wait( window_signal ); 200 201 /* Test is the signal is for our window. */ 202 if (signals & window_signal ) 203 { 204 /* Process all the messages. */ 205 while ( message = (struct IntuiMessage *)GetMsg( ColorWindow->UserPort ) ) 206 { 207 /* Copy the necessary data from the message. */ 208 msg_class = message->Class; 209 msg_code = message->Code; 210 211 /* Reply as soon as possible. */ 212 ReplyMsg( (struct Message *)message ); 213 214 /* Take action based on the message. */ 215 switch ( msg_class ) 216 { 217 case IDCMP_CLOSEWINDOW: /* Close gadget pressed. */ 218 CloseWindow( ColorWindow ); 219 end = TRUE; 220 break; 221 case IDCMP_NEWSIZE: /* The window has been resized. */ 222 /* Clear the window area with the selected color. */ 223 SetRast( ColorWindow->RPort, pen ); 224 break; 225 default: 226 break; 227 } 228 } 229 } 230 } 231 } 232 else 233 { 234 printf( "Unable to open window.\n" ); 235 } 236 } 237 } |
Line 23 introduces a new C type called enum which is short for "enumerator" and is used to assign integer values to a given set of names. This works much like the #define preprocessor directive, but allows for type checking as I will explain later. enum takes the form:
enum [<enum_name>] { <name>[ = <value>][,<name>[ = <value>]] ... } [<identifiers>];
The enum keyword is followed by an optional name which can be used to refer to this enumeration. The name is then followed by a list of comma delimited names enclosed within curly braces. Each of the names may optionally be followed immediately by an equals sign and a value. Finally, the enum may end with a list of identifiers which are instances of this particular enum.
If no optional values are specified for the names, enum assigns zero to the first name, one to the second, two to the third, and so on until all names have been given values. Values are always incremented by 1. If a value is specified, then it is assigned to the corresponding name and incremented for each successive name. Thus, in our code we gave RED the value of 1, so BLUE is assigned 2, GREEN is 3, etc.
Our use of enum does not give a name to the enumeration, nor does it declare any variables of this enumerator type. We use it mainly to assign the values to the list of names because writing:
enum { RED = 1, BLUE, GREEN, WHITE, BLACK };
is quicker than writing:
#define
RED 1
#define BLUE 2
#define GREEN 3
#define WHITE 4
#define BLACK 5
But the effect is the same. However, we can declare enum variables which are essentially integers with a limited range and descriptive names:
enum beatles { JOHN, PAUL, GEORGE, RINGO };
creates an enumerated set of names called beatles. Using enumerations also allows for type checking:
enum beatles fab_4;
creates a variable called fab_4 which can only contain the values 0, 1, 2, and 3 or JOHN, PAUL, GEORGE, and RINGO, respectively. Assigning a value other than these to fab_4 is not valid, but the compiler may not warn about this. For instance, GCC 2.95.3, which I'm using for this tutorial, does not warn me if I assign an enum a value that is out of bounds. The same applies to type checking function arguments. Take the following prototype:
int is_beatles_member( enum beatles );
which tells the compiler that only JOHN, PAUL, GEORGE, and RINGO may by passed to the is_beatles_member() function. Again, the strictness of the type checking depends on your compiler and settings. But at the very least enumerations clue the programmer in to the fact that a variable has a limited range. As long as you use the names and don't assign an enum variable an integer value, things will work out fine and be more descriptive. After all, while they are essentially the same thing, it's much clearer to say:
enum beatles my_favorite = JOHN; /* John Lennon rocks! */
than to say:
int my_favorite = 0; /* John Lennon rocks! */
The next change is on line 57 where we allocate memory for the struct EasyStruct:
MyRequester = (struct EasyStruct *)AllocMem( sizeof(struct EasyStruct), MEMF_ANY );
Here we use the Amiga specific memory allocation function AllocMem(). Using this function instead of the ANSI C memory allocation function--malloc()--allows us to specify the type of memory we wish to allocate. AllocMem() takes two arguments. The first is the size (in bytes) of the memory block we wish to allocate and the second is a list of flags that specify the type of memory to allocate. AllocMem() returns a void * to the start of the reserved memory block or NULL if the allocation fails. Here we use the sizeof() macro to get the size of the struct EasyStruct.
Macros are preprocessor definitions that take arguments, much like functions. However, macros found in the source code are actually replaced by their definition during the preprocessor phase of compilation.
#define my_macro(x) my_really_long_function_name(x)
The above example shows how a macro is created. Just like defining constants, macros begin with a #define directive followed by the macro name. Following the name is a pair of parentheses which contain a comma delimited list of arguments. White space (tabs or spaces) then separates the macro from its definition. In this case the definition is a very long function name. Whenever the preprocessor encounters the macro in the source code it will substitute the definition in its place. It will also substitute any arguments passed to the macro in the definition. The arguments do not have to be enclosed in parentheses in the definition. A definition may extend to the next line by using the "\" character as the last character in the line. Here is another example:
#define divide(x, y) (x/y)
will appear in the source code like:
result = divide(3, 4);
the preprocessor will replace the macro with the following code prior to compilation:
result = (3/4);
Here's a more complex, multi-line example:
#define
cross_product(n, a, b)\
( (n)[X] = (a)[Y] * (b)[Z]
- (a)[Z] * (b)[Y],\
(n)[Y] = (a)[Z] * (b)[X] - (a)[X] * (b)[Z],\
(n)[Z] = (a)[X] * (b)[Y] - (a)[Y] * (b)[X] )
Macros are usually used to make complex or repetitive code easier to input and maintain.
The purpose of the sizeof() macro is to find the size (in bytes) of a variable type or structure. This is why we use it in the AllocMem() function. It's the easiest way to compute the size of the structure.
The second argument of AllocMem() sets the type of memory to allocate. The autodocs for AllocMem()call these "requirements". They are described in the table below:
MEMF_ANY |
Allows the system to allocate the best type of memory based on the current system resources. On classic Amigas, fast memory (if available) is used first. On OS4 this also implies private memory, although it is not currently enforced. |
MEMF_PUBLIC |
Allocates memory that is visible to all tasks and will not become unaddressable. In the past this flag has often been erroneously used as a sort of catch all--this is incorrect--use MEMF_ANY instead. Do not use this flag unless absolutely necessary. On classic systems this type of memory is is used for interrupts, messages, and message ports. On OS4 only interrupts need to use this flag. |
MEMF_CHIP |
Allocates only chip memory. On classic systems the custom chips can only access a certain memory address range. Therefore, all data that must be accessed by the custom chips (graphics, audio, copper lists, etc.) must be allocated with this flag. This flag cannot be used at the same time as the MEMF_FAST flag. |
MEMF_FAST |
Allocates only fast memory. If no fast memory is available (e.g. an unexpanded classic system), this allocation will fail. This flag cannot be used at the same time as the MEMF_CHIP flag. |
MEMF_LOCAL |
Allocates memory that remains accessible immediately after a reset (e.g. the motherboard memory of classic systems). This may be useful for applications that wish to recover data after a reboot. Some RAM expansion memory is not immediately accessible because it is configured later in the boot sequence. Chip memory is typically MEMF_LOCAL. |
MEMF_24BITDMA |
Allocates memory within the 24bit Direct Memory Access rage of Zorro-II expansion devices. If data is to be accessed by a Zorro-II device it must be allocated with this flag. |
MEMF_KICK |
Allocates memory that is accessible during system initialization. |
MEMF_SHARED |
OS4 ONLY: Allocates memory that can be shared between tasks. Use instead of MEMF_PUBLIC. |
MEMF_PRIVATE |
OS4 ONLY: Allocates memory that cannot be accessed by other tasks. Not currently implemented. MEMF_ANY implies this flag. |
MEMF_EXECUTABLE |
OS4 ONLY: Allocates memory that can contain executable code. If you wish to write dynamically created code it must be placed in a memory block allocated with this flag. Creating self-modifying code is not possible because the executable code area is read-only under OS4. |
There are also 3 additional flags called "options" which are used in special cases. They are OR'ed to the requirements flags when needed:
MEMF_CLEAR |
Initializes the allocated memory block to all zeros. Takes longer but is useful in many instances. |
MEMF_REVERSE |
Allocates memory from higher addresses first. Usually the allocation is made at the lowest address that fits the requirements. This option changes that priority. |
MEMF_NO_EXPUNGE |
In the event of a failed memory allocation this flag instructs the OS not to clean up the memory entry. |
Using these tags we can allocate 500 bytes of cleared chip memory on a classic Amiga like so:
void
*memory_chunk = NULL;
memory_chunk = AllocMem(
500, MEMF_CHIP | MEMF_CLEAR
);
in OS4 this there is no distinction between chip and fast memory so the allocation would look like:
void
*memory_chunk = NULL;
memory_chunk = IExec->AllocMem(
500, MEMF_ANY | MEMF_CLEAR
);
Line 59 tests the success of the memory allocation. If it was successful lines 62 though 66 initialize the struct EasyStruct named MyRequester. The es_StructSize member holds the total size of the structure. Typically this will be set to sizeof(struct EasyStruct). There is no current use for an EasyStruct of a different size, but future enhancements may require it. The es_Flags member is also reserved for future use and must be set to 0 for now, since the current implementation of EasyStruct doesn't use flags. The es_Title member is a pointer to a character string containing a title for the requester window. es_TextFormat is also a pointer to a string, with a twist--it points to a format string. This means that it can include special codes that are replaced with data passed to the EasyRequest() function while the program is running. It works like this:
Lets say we have a program that allows the user to delete files, but we want to prevent the user from unintentionally deleting files. So we want a confirmation dialog box to appear that says, "Are you sure you want to delete <the file name>?". The problem is that we don't know which file the user will select until the program is run. So in the string we pass to the es_TextFormat member of our EasyStruct we use a format code to take the place of the file name string (%s is the code that represents a string):
ConfirmDeleteRequester->es_TextFormat = "Are you sure you want to delete %s?";
Then, when we want to display the dialog box we pass the name of the selected file the EasyRequest() function call which inserts it into the string in the place of our format code.
comfirm = EasyRequest( NULL, ConfirmDeleteRequester, NULL, file_name );

Here is a list of common formatting codes:
| Symbol | Represented Type |
|---|---|
%d |
Signed decimal (integer) value |
%u |
Unsigned decimal (integer) value |
%x |
Hexadecimal value |
%s |
String pointer |
%c |
Single character (ASCII value) |
For more information on format strings, look in the autodocs under exec.library/RawDoFmt(). The es_GadgetFormat is also a format string. It is used to name each of the selection buttons on the requester. The name for each button is separated by a "|" character. Each of these buttons is assigned an integer value which is returned by EasyRequest() when that button is selected. The numbering of the buttons begins with 1 and increases from left to right. The only exception is the right most button which is always given the value of 0. This is usually used as a negative response button, such as "No", "Exit", or "Cancel".
The else clause on line 68 through 73 should be familiar; it reports an error if the the memory allocation in line 57 fails.
Additions to the clean_up() function are on lines 96 through 99. This if statement simply releases memory allocated for the struct EasyStruct if it was successfully allocated. The call to FreeMem() takes two arguments. The first argument is a pointer to the memory block to be freed. This pointer must have been previously obtained by a call to AllocMem(). If an invalid pointer is passed to FreeMem(), a visit from the guru may be in your future. The second argument is the size of the memory block. This also must match the size of the memory block returned by a previous call to AllocMem().
Note that if you do not want to burden yourself with remembering the sizes of all memory allocations you can use the AllocVec() function in the place of AllocMem(). AllocVec() means "Allocate Vector". A vector is an Amiga API term for a memory block that tracks its own size. AllocVec() is identical to AllocMem() except that it keeps a record of the memory block size for you. To free a vector allocated by AllocVec() use the FreeVec() function. FreeVec() only takes one argument--a pointer to a previously allocated vector.
Next is the program() function on line 117. This subroutine contains the main program loop and the code to open the dialog box and window. We've already seen variable declarations such as those found on lines 119 through 121. Here color will hold the result returned by the requester, color_name will hold a string containing the color's name, and pen will hold the value of the color from the system's index of colors. We will talk more about pens shortly.
Line 124 contains the call to EasyRequest(). This is the API function that displays the requester. It takes 4 or more arguments. The first argument is a pointer to a struct Window. If it's not NULL, the requester will appear on the same screen as the specified widow and also share its title, if no title is given in the struct EasyStruct. If this pointer is NULL then the requester is displayed on the Workbench screen. The second argument is a pointer to a struct EasyStruct that is used to build the requester--in our case this is the MyRequester structure that we initialized in program_init(). The third argument is a pointer to an a variable containing IDCMP flags that will terminate this requester. This may be useful if, for instance, you create a disk requester. If you include a pointer to a variable containing an IDCMP flag of IDCMP_DISKINSERTED, then the requester will close when the user inserts a disk into the disk drive, even if they do not click any of the requester options. The final argument should be set to NULL unless the strings initialized in the struct EasyStruct used formatting codes. If formatting codes were used, then the data in argument four and any additional arguments are used as data to complete the format strings.
Line 128 is a switch statement. It uses the return value from the call to EasyRequest() to set the fill color for the window. EasyRequest() puts its return value into the color variable. This return value indicates which button was pressed on the requester. As stated above, 1 is used for the left-most button, the next button to the right is 2, the next 3, and so on. The right-most button is given the value 0.
Notice that when we initialized MyRequester->es_GadgetFormat on line 66, we used the same order for the color names that we did in our enum on line 23. We did this on purpose. Since the enum value RED is equal to 1 and the requester button labeled "Red" also returns the value 1, we can use the enum value RED to make our code more readable. We switch based on color. If the color is RED then "case RED" is executed. This is true for the other enumerator values also. In reality the compiler is using only numbers. It has no understanding of the concept of colors, but by making clever use of enumerators we can make it more intuitive for the human reader of the code.
In "case RED", on line 129, we call a new function named ObtainBestPenA() which is used to find the closest match to a given color in the screen's color palette. To fully understand what this function does, we must talk about the concept of pens.
A computer screen can only display a limited number of colors. In the old days the Amiga had screen modes that could display 2, 4, 8, 16 or 32 colors and sometimes more by using hardware tricks. Later Amigas added a 256 color mode. These modes used a concept called bitmaps to store the image data and held the color data in a separate data structure called a "color map". A color map is a list or colors that could be used on a given screen--like a painter's palette. For example, a two color screen could have a color map with black and white, or blue and yellow, or any other combination of two colors. Likewise, a 256 color screen has a color map with 256 entries. Each of these entries can be a unique color.
By keeping the color information separate from the image data, it was possible to create some interesting effects without changing the image data. Changing the color map entries for an image or screen is called "re-mapping".
Each entry in a color map is called a color index or pen. The Amiga graphics functions support up to 256 pens, but the number of pens actually available for use depends on the screen mode being used. A two color screen has two available pens while a 256 color screen may use all 256 pens.
ObtainBestPenA() is used to find a pen that matched a given color. It takes 4 arguments. The first argument is a pointer to a struct ColorMap which is used to determine the available colors. In our case we want to use the current screen's color map, but this presents a bit of a problem--there is no direct way to access the current screen's color map since we did not open the current screen! So, we must get a bit creative.
We need to find the struct ColorMap associated with the Workbench screen. If we had opened the screen ourselves we could find the screen's ViewPort structure by accessing the ViewPort member get the screen structure:
The ViewPort structure holds information about each screen. Information about the screen's color map, dimensions, and screen mode as well as some more technical data can be found in this structure. But we did not open the Workbench screen, so we need to figure out a way to obtain either the Screen structure or the ViewPort structure of the Workbench screen.
There are several ways to do this. One way is to use the ViewAddress() function to get the address in memory of the current view structure. The View structure is different from the ViewPort structure. Think of a view as the image you can see on your monitor. Usually the monitor displays only one screen, but this is not necessarily so (i.e. if you use the screen dragging feature of the classic Amigas or OS4, there may be more than one screen in the current view). A view can contain one or more view ports. A view port is like porthole into a screen. A view port may or may not display the entire contents of a screen, as is evident when using the screen dragging feature.
It is important to realize that our code is assuming that we are not using the screen dragging feature and that the only ViewPort on the current View is the Workbench screen. If this is not the case, then our program may give unexpected results. Later we can look for a better solution, but for now we will continue.
ViewAddress() returns the address of the current view. Using this address we can use the ViewPort member of the View structure to find the ViewPort address of the Workbench screen. Using the ViewPort address we can find the address of the Workbench screen's ColorMap structure by accessing the ViewPort's ColorMap member. We can do all of this at once by linking the indirection operators ("->") together:
This entire expression is evaluated and passed as the first argument to ObtainBestPenA().
A better approach may be to use the GetScreenData() function to copy the data for the Workbench's Screen structure into a locally reserved struct Screen variable and use the ViewPort.ColorMap members. This should ensure that we are accessing the correct ViewPort and ColorMap even if more than one screen is in the view:
For practice, see if you can make this change on your own. If you get stuck, feel free to e-mail me.
The next three arguments of ObtainBestPenA() are related. They specify the intensity of the red, green, and blue components of the desired color, respectively. They are all 32 bit, ULONG values. We use hexadecimal notation to specify these values because it is usually more descriptive than using base 10--0xFFFFFFFF is a clearer indication of full intensity than 4,294,967,295--although they are both equal.
Our examples use full intensity or zero intensity for each component. This in not necessary. ObtainBestPenA() can take intermediate values for each component to find a much, much larger range of colors.
The final argument of ObtainBestPenA() is a tag list. We did not use it because the default values were good enough, but sometimes it is useful to have more control over how the color matching is made. There are only two tags used for ObtainBestPenA(): OBP_Precision and OBP_FailIfBad.
OBP_Precision is used to set the tolerance the rules on how close a color must be to be considered a match. There are four levels of precision. In order of strictness they are: PRECISION_GUI, PRECISION_ICON, PRECISION_IMAGE, and PRECISION_EXACT. The default is PRECISION_IMAGE. Be careful about using PRECISION_EXACT since the chances of finding an exact match of three 32-bit values is 1 in 7.92 x 10^28. You may get lucky on the extremes like full red, green, blue, black, and white, but the intermediate values will be difficult to match.
OBP_FailIfBad is used to return a failure (-1) if no color is found within the specified tolerance. If it is set to FALSE, then ObtainBestPenA() will attempt to allocate a new pen with the specified color if no match is made--it will only fail if no shared pens are available. If it is set to TRUE, then no attempt will be made to allocate a new pen if the color is out of tolerance.
Once a matching pen is obtained from ObtainBestPenA(), the color_name variable is initialized with the text name of the selected color. color_name will be used to set the window title.
Notice the default case on like 148 is to set the pen to an error value and set color_name to NULL. This case will probably never be executed, but just in case an unknown error occurs it is wise to always have a default case.
Line 153 tests for a valid color_name and pen value before opening the window.
The next new code is found on line 190. SetRast() is used to set an entire raster port to a single color--it erases all the current raster port contents. We talked about raster ports in Lesson 4. SetRast() takes a pointer to a raster port as its first argument and the number of a pen as its second argument. The entire raster port will be set to the color of the given pen. We use SetRast() again on line 223 in response to the IDCMP_NEWSIZE message. This ensures that the entire window is filled with the given color even if it is made larger than its original size.
The final change to this code is on line 193. SetWindowTitles() changes the text that appears in the window title bar and also the text that appears in the screen title bar when that window is active. It takes 3 arguments. The first is a pointer to the window to change. The second argument is a pointer to a character string containing the title for the window. The third argument is a pointer to a character string containing the title for the screen.
And that's how we do that.
Next we'll discuss menus and how to create and respond to menu inputs.
Last updated on
Saturday, February 11, 2006 22:12