|
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_loop(void); 016 void clean_up(void); 017 018 /* Global variables. */ 019 struct GfxBase *GfxBase = NULL; 020 struct IntuitionBase *IntuitionBase = NULL; 021 struct Window *MyWindow = NULL; 022 023 int main(void) 024 { 025 int error; 026 027 error = program_init(); /* Open needed libraries and the main window. */ 028 029 if ( !error ) 030 { 031 program_loop(); /* If everything was initialized, execute the main program code. */ 032 } 033 034 clean_up(); /* We must clean up before we exit. */ 035 036 return 0; 037 } 038 039 040 int program_init(void) 041 { 042 int result = 0; 043 044 /* First open the graphics.library. */ 045 GfxBase = (struct GfxBase *)OpenLibrary( "graphics.library", 0L ); 046 047 if ( GfxBase != NULL) 048 { 049 /* Second open the intuition.library. */ 050 IntuitionBase = (struct IntuitionBase *)OpenLibrary( "intuition.library", 0L ); 051 052 /* Was the library opened? */ 053 if ( IntuitionBase != NULL ) 054 { 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 ); 070 071 /* Was the window opened? */ 072 if ( MyWindow == NULL ) 073 { 074 /* The window was not opened so display a message. */ 075 printf( "Unable to open the window!\n" ); 076 result = -1; 077 } 078 } 079 else 080 { 081 /* The intuition.library was not opened so display a message. */ 082 printf( "Unable to open the intuition.library!\n" ); 083 result = -2; 084 } 085 } 086 else 087 { 088 /* The graphics.library was not opened so display a message. */ 089 printf( "Unable to open the graphics.library!\n" ); 090 result = -3; 091 } 092 093 return result; 094 } 095 096 097 void clean_up(void) 098 { 099 /* If the window is open, close it. */ 100 if ( MyWindow != NULL ) 101 { 102 CloseWindow( MyWindow ); 103 } 104 105 /* If the intuition.library is open, close it. */ 106 if ( IntuitionBase != NULL ) 107 { 108 CloseLibrary( (struct Library *)IntuitionBase ); 109 } 110 111 /* If the graphics.library is open, close it. */ 112 if ( GfxBase != NULL ) 113 { 114 CloseLibrary( (struct Library *)GfxBase ); 115 } 116 117 return; 118 } 119 120 121 void program_loop(void) 122 { 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; 130 131 /* Define the window signal. */ 132 window_signal = 1L << MyWindow->UserPort->mp_SigBit; 133 134 /* Main program loop. */ 135 136 /* Wait until the close button is pressed. */ 137 while ( !end && MyWindow ) 138 { 139 signals = Wait( window_signal ); 140 141 /* Check the signal bit for our message port. Will be true if these is a message. */ 142 if ( signals & window_signal ) 143 { 144 WORD X_coord, Y_coord; 145 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) ) 148 { 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; 154 155 /* Reply as soon as possible. */ 156 ReplyMsg((struct Message *)message); 157 158 /* Take the proper action in response to the message. */ 159 switch ( msg_class ) 160 { 161 case IDCMP_CLOSEWINDOW: /* User pressed the close window gadget. */ 162 end = TRUE; 163 break; 164 case IDCMP_MOUSEBUTTONS: /* The status of the mouse buttons has changed. */ 165 switch ( msg_code ) 166 { 167 case SELECTDOWN: /* The left mouse button has been pressed. */ 168 left_mouse_button = TRUE; 169 break; 170 case SELECTUP: /* The left mouse button has been released. */ 171 left_mouse_button = FALSE; 172 break; 173 } 174 case IDCMP_MOUSEMOVE: /* The position of the mouse has changed. */ 175 if ( left_mouse_button == TRUE ) 176 { 177 WritePixel( MyWindow->RPort, X_coord, Y_coord ); 178 } 179 break; 180 default: 181 break; 182 } 183 } 184 } 185 } 186 187 return; 188 } |
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:
int
program_init(void);
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 | ||
| A | B | C |
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 1 |
| AND Logic Table | ||
| A | B | C |
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
When AND is applied to two binary numbers we get:
01000110 = A
11010111 = B
--------
01000110 = C
or
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
-->