002 #include <exec/types.h>
003 #include <graphics/gfx.h>
004 #include <intuition/intuition.h>
005 #include <dos/dos.h>
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>
013 /* Prototypes for our functions. */
014 int program_init(void);
015 void program_loop(void);
016 void clean_up(void);
018 /* Global variables. */
019 struct GfxBase *GfxBase = NULL;
020 struct IntuitionBase *IntuitionBase = NULL;
021 struct Window *MyWindow = NULL;
023 int main(void)
025 int error;
027 error = program_init(); /* Open needed libraries and the main window. */
029 if ( !error )
031 program_loop(); /* If everything was initialized, execute the main program code. */
034 clean_up(); /* We must clean up before we exit. */
036 return 0;
040 int program_init(void)
042 int result = 0;
044 /* First open the graphics.library. */
045 GfxBase = (struct GfxBase *)OpenLibrary( "graphics.library", 0L );
047 if ( GfxBase != NULL)
049 /* Second open the intuition.library. */
050 IntuitionBase = (struct IntuitionBase *)OpenLibrary( "intuition.library", 0L );
052 /* Was the library opened? */
053 if ( IntuitionBase != NULL )
055 /* Since the library was opened, we can open a window. */
056 MyWindow = (struct Window *)OpenWindowTags( NULL,
057 WA_Left, 20,
058 WA_Top, 20,
059 WA_Width, 200,
060 WA_Height, 200,
061 WA_Title, (ULONG)"My Window",
062 WA_DepthGadget, TRUE,
063 WA_CloseGadget, TRUE,
064 WA_SizeGadget, TRUE,
065 WA_DragBar, TRUE,
066 WA_GimmeZeroZero, TRUE,
067 WA_ReportMouse, TRUE,
068 WA_IDCMP, IDCMP_CLOSEWINDOW | IDCMP_MOUSEBUTTONS | IDCMP_MOUSEMOVE,
069 TAG_END );
071 /* Was the window opened? */
072 if ( MyWindow == NULL )
074 /* The window was not opened so display a message. */
075 printf( "Unable to open the window!\n" );
076 result = -1;
081 /* The intuition.library was not opened so display a message. */
082 printf( "Unable to open the intuition.library!\n" );
083 result = -2;
088 /* The graphics.library was not opened so display a message. */
089 printf( "Unable to open the graphics.library!\n" );
090 result = -3;
093 return result;
097 void clean_up(void)
099 /* If the window is open, close it. */
100 if ( MyWindow != NULL )
102 CloseWindow( MyWindow );
105 /* If the intuition.library is open, close it. */
106 if ( IntuitionBase != NULL )
108 CloseLibrary( (struct Library *)IntuitionBase );
111 /* If the graphics.library is open, close it. */
112 if ( GfxBase != NULL )
114 CloseLibrary( (struct Library *)GfxBase );
121 void program_loop(void)
123 ULONG signals;
124 ULONG window_signal;
125 struct IntuiMessage *message;
126 UWORD msg_code;
127 ULONG msg_class;
128 BOOL end = FALSE;
129 BOOL left_mouse_button = FALSE;
131 /* Define the window signal. */
132 window_signal = 1L << MyWindow->UserPort->mp_SigBit;
134 /* Main program loop. */
136 /* Wait until the close button is pressed. */
137 while ( !end && MyWindow )
139 signals = Wait( window_signal );
141 /* Check the signal bit for our message port. Will be true if these is a message. */
142 if ( signals & window_signal )
144 WORD X_coord, Y_coord;
146 /* There may be more than one message, so keep processing messages until there are no more. */
147 while ( message = (struct IntuiMessage *)GetMsg(MyWindow->UserPort) )
149 /* Copy the necessary information from the message. */
150 msg_class = message->Class;
151 msg_code = message->Code;
152 X_coord = message->MouseX - MyWindow->BorderLeft;
153 Y_coord = message->MouseY - MyWindow->BorderTop;
155 /* Reply as soon as possible. */
156 ReplyMsg((struct Message *)message);
158 /* Take the proper action in response to the message. */
159 switch ( msg_class )
161 case IDCMP_CLOSEWINDOW: /* User pressed the close window gadget. */
162 end = TRUE;
164 case IDCMP_MOUSEBUTTONS: /* The status of the mouse buttons has changed. */
165 switch ( msg_code )
167 case SELECTDOWN: /* The left mouse button has been pressed. */
168 left_mouse_button = TRUE;
170 case SELECTUP: /* The left mouse button has been released. */
171 left_mouse_button = FALSE;
174 case IDCMP_MOUSEMOVE: /* The position of the mouse has changed. */
175 if ( left_mouse_button == TRUE )
177 WritePixel( MyWindow->RPort, X_coord, Y_coord );
Up until now our programs have been quite small and have consisted of only one function named main(). However, our programs are becoming ever larger and more complex. Being able to divide the code into smaller sections would make it more manageable. Functions allow us to do this, and also provide a great way to reuse code that may be needed in several places. This approach to programming is called "structured programming" and is the philosophy with which C was developed.
(On a side note: One of the
popular offshoots to C, namely C++, was designed with a different philosophy
called "object oriented programming". This philosophy builds upon the
benefits of structured programming to give the designers of extremely large
programs an easier way of visualizing their large projects. There is nothing
special about programs written in C++ other than the approach used to write
the code. Anything that is created in C++ can also be written in C, although
it may not look as pleasing to the eyes. In fact, many early C++ compilers
simply converted C++ programs into C prior to compiling.)
Lines 1 through 11 we have seen before. They include the necessary definitions and prototypes for us to use the system functions. Lines 14, 15, and 16 are new. These lines are called function prototypes. They notify the compiler that there will be three user-defined functions named program_init(), program_loop(), and clean_up(). Function prototypes are also used to tell the compiler what types of arguments can be passed to a function and what type of argument the function returns. But before we continue let discuss what a function is in general terms.
In its most basic form a function is a method of processing information. Some of you may recall learning about mathematical functions in school. C functions are similar to mathematical functions in that both take in arguments and return a single result. For instance, the function:
y = f(x)
is a simple mathematical function. f() is the name of this generic function and both x and y are real numbers. It doesn't really matter what the internals of the function are, all we need to know is that if we give the function an x it will return y. Some of the more well-known functions in mathematics are the trigonometric functions like sine, cosine, and tangent. These functions compute the ratio of different sides of a triangle. For instance:
B = sine(A)
takes an angle A as an argument and returns the ratio B as the result. Again, the actual workings of the sine function are unimportant to those who are only interested in the ratio.
In C, functions may have more than one argument, but only return one value as a result:
q = my_function(x, y, z);
This uses the numbers x, y, z and returns some value in q based on these. Unlike mathematical functions, C function arguments and return values need not be only numbers. The arguments can be any type defined by the user or one of C's internal types. This is where function prototypes come in. They let the compiler know that the program uses a particular function, what the types of arguments are expected, and what type of value (if any) the function returns. The prototype on line 14:
tells the compiler that this program uses a function called program_init(). The void between the parentheses means that this function doesn't take any arguments, and the int means that it will return an integer value when it is finished. The other two prototypes on lines 15 and 16 take no arguments and return no values because void is used both in the parentheses and before the function name. If prototypes are not used and a function is not defined prior to its use in the code, the compiler will usually assume that it returns an int and take the number and type of arguments that occur in the first use. This is called an implicit prototype and will generally result in compiler warnings.
Now, technically all of this prototyping is not necessary, we could just move all of our function definitions from lines 97 through 184 and place them prior to the main() function on line 23. If we do this the compiler will see that the functions are defined prior to their use in . This approach will work well for small programs like we are writing now, but when we start writing large programs it is often impractical to write out all the function definitions before the main() function. Using prototypes allows us to tell the compiler what a function looks like even if we have not written the internals of the function yet. This way the compiler is not surprised when we use the function. It's like saying to the compiler, "Hey, compiler, I'm going to be using some new functions in my program. Here is what they look like, so don't be surprised when you see them in my code, ok?" It's just a courtesy thing. Prototypes will become very handy in later tutorials.
Lines 19 through 21 define the global pointers to the the libraries and window we will use. Line 25 declares a variable used to hold the return value from program_init(). Line 27 actually calls program_init(). At this point program flow jumps to line 42--the first line of program_init(). If we look at the contents of this function we can see that it isn't really that different from close_window.c lines 22 through 66. There are some items that have been added or changed, like line 45 which opens the graphics.library and the missing struct IntuiText, but the majority of the code is verbatim. The theme here is that it is easy to split your programs into smaller sections. Line 93 returns the result of the function: 0 if everything happened like it should, or less than zero if there was an error. This value is then placed into the result variable when the program flow returns to line 27.
Line 29 is a test of the errors variable to determine if there have been any problems thus far. Again, "!errors" is read "not errors" and causes the test to be true if errors is zero and false if errors is non-zero. So if program_init() has no errors (i.e. returns zero), then program_loop() will be called. Otherwise clean_up() is called to close any libraries or windows that program_init() did manage to open.
Now, what about the program_loop() function? This is the main loop that checks for messages sent to our window and responds to each properly. Note that the variable declarations on lines 123 through 129 are local to the program_loop() function. This is an example of the proper use of local variables. These variables are not available in other functions, and this reduces the possibility that another function could unexpectedly change their values and also frees us, the programmers, from having keep track of the names of all the variables in our entire program, we only need to remember the variables used in a particular function. Line 124 is the only new local variable (window_signal) added to this example. It will be used later to hold the signal bit for our window.
Line 132 loads window_signal with the value of MyWindow's signal bit. The code "1L << MyWindow->UserPort->mp_SigBit" is used to compute the value of this signal bit. Here's how it works:
The "<<" is called the left shift operator. It is used to shift the actual binary digits in a number a specific number of places to the left. It puts zeros in vacated bits. (">>" is the right shift operator and works the same way, only it shifts bits to the right.) In our case, we are shifting the bits in the value 1L. Adding "L" is the way we ensure that a small value, such as 1, is extended to be 32 bits long (the size of a ULONG) instead of 16 or 8 bits. Our example creates the 32-bit value of 1, which is:
00000000 00000000 00000000 00000001
MyWindow->UserPort->mp_SigBit holds the number of the particular bit used to represent the message signal for MyWindow. So, if MyWindow->UserPort->mp_SigBit happens to be 26, then we will shift the 1 to the left 26 places:
00000010 00000000 00000000 00000000
|------------ 26 -----------|
thus, the decimal value of window_signal would be 33,554,432. That really doesn't matter here, because we're interested in the bit number much more than the value, but it may be useful to understand what's really going on. Just remember that the signal bit for your window may be any number.
So lets look at the new items added to the main loop. The first thing to note here is the call to Wait() on line 139. This is a change from the SetSignal() used in close_window.c. The reason for this change is that using SetSignal() in this context is actually extreamly inefficient. Wait() causes the program to pause and wait for a signal, but using SetSignal() allows the program to continue executing, even when there is not signal to process--this is called a busy loop because it is constantly running but not doing anything important. A busy loop is bad because it steals processor time from other programs that could really use it. Using Wait() stops our program until one or more of the specified signals are received. This frees up the processor for other tasks. Once a signal is received, the program continues on as normal. More than one signal can be passed to Wait() by separating them by the OR ("|")operator:
signals = Wait( signal1 | signal2 );
The OR operator compares the bits in one binary number with the bits in another binary number. If a bit is set in either number then it is set in the resulting number.
Here is an OR logic table (A | B = C):
|OR Logic Table|
|AND Logic Table|
When AND is applied to two binary numbers we get:
01000110 = A
11010111 = B
01000110 = C
01000110 & 11010111 = 01000110
So C is only true if both A and B are true, thus A "and" B.
So our test is true only if our window's signal bit is one of the signals that has been set, otherwise we go back to waiting for another signal.
Line 144 declares two local 16-bit WORD values that we will use to store the X and Y mouse coordinates. The mouse coordinates are recorded with every message that is sent to the window and we will use them later to plot pixels in the window.
Line 146 through 156 are our normal message handling routines with the exception of line 152 and 153. These two lines compute the window coordinates from the mouse coordinates in each message and assign these values to X_coord and Y_coord respectively. The mouse coordinates are measured in pixels from the extreme upper left corner of the window.
We've declared our window with the WA_GimmeZeroZero tag. This tag creates a separate drawing area inside the boundaries of the window's border that is offset by the size of the borders. This requires more memory, but we don't need to worry about drawing over the window borders. In the example window to the left the GimmeZeroZero drawing area is in gray. This means that when we calculate the pixel coordinates from the mouse coordinates we must subtract the size of the left and upper borders from the X and Y mouse coordinate. The border areas are in blue. This ensures that the pixel directly underneath the mouse pointer is set to the new color.
Lines 159 through 182 actually handle the messages. We've already seen how to handle the IDCMP_CLOSEWINDOW message, but there are two new messages that we handle in this program. The first is the IDCMP_MOUSEBUTTONS message. The second is the IDCMP_MOUSEMOVE message.
The IDCMP_MOUSEBUTTONS message is sent each time the mouse buttons change state. That is, each time a mouse button is pressed or released. This message contains an additional code found in msg_code. This additional code indicates which mouse button is being referred to and if it has been depressed or released. The Amiga OS supports mice with up to 3 buttons. The left button, which we are most interested in for this example, is referred to as the "select" button. Thus SELECTDOWN and SELECTUP codes refer to the left mouse button being either pressed (SELECTDOWN) or released (SELECTUP). Similar codes exist for the middle button and the right "menu" button. These codes can be found in the intuition/intuition.h header file.
Notice that when the select button is pressed left_mouse_button becomes TRUE and when it is released left_mouse_button becomes FALSE. This is necessary because the SELECTDOWN and SELECTUP codes are only sent with the IDCMP_MOUSEBUTTONS message when the status of the button changes. Remembering the sate of the button allows the user to hold the button down and draw, rather than trying to constantly click the button to plot a pixel. You could try to remove these two assignments and see what happens.
The IDCMP_MOUSEMOVE message is handled next. This message is sent every time the mouse pointer changes position on the screen. Each time the mouse moves we check if the left mouse key is pressed (left_mouse_button == TRUE). If it is pressed we call WritePixel() to plot the pixel at the current coordinates, otherwise we skip the plot and continue on. The last two arguments are the adjusted X and Y coordinates we computed on lines 152 and 153. The first argument of WritePixel() is a pointer to the struct RastPort of the window's drawing area. To the compiler, a raster port is really just a structure that contains information about a drawing surface. Every area on the screen that can display and image is part of a raster port, much like a painter's canvas. Every screen has its own raster port and every window also has its own. You can define your own raster ports using the graphics.library functions. Take a look at the graphics/rastport.h header file to view the actual RastPort structure. You can see that it contains pointers to bit maps (the actual memory areas where the image data is stored), it has information about fonts, line patters, drawing pens, etc. All very useful stuff for anyone interested in graphics or user interface programming.
Now back to the code...
Notice that there is no break statement after line 173 and before the case IDCMP_MOUSEMOVE on line 174. The break statement causes the program flow to jump out of the current block, but since there is no break, the program flow "falls through" to the next case and executes the code for that case also. This can be a handy feature of the switch statement. In our example it allows us to use only one call to WritePixel() where two would be necessary otherwise.
And that's all there is to it! Not too bad, right? We've covered a lot of ground in the last couple of lessons and we're well on our way to mastering Amiga programming in C. In the next lessons I'll explain some of the changes in the new Amiga OS 4 API and then we will move on to creating simple request dialog boxes and how to use them to get input from the user.
If you find any errors or have any questions or comments please e-mail me.
Last updated on
Saturday, February 11, 2006 21:08