< Lesson 1: Hello World  |  Index  |  Lesson 3: Close Window >


Lesson 2:  Simple Window


In this tutorial we are going to move away from the shell. "Cool, shells are lame anyway." you may be saying. Well, you're right, but leaving the shell comes at a large price in overhead and advanced topics. But this tutorial is about jumping into the deep end and that's exactly what we are going to do. I remember, when I was first learning how to program the Amiga API, how I would see simple example, but the moment I went in to change anything it would all fall apart. It was exceedingly frustrating, especially since I didn't know how to fix it. I will try to point out areas that I had difficulty with in hopes of making the transition a little less painful for you.

The first thing we need to do is create a new source file. So open up your favorite editor again and type in the following code (Note that I've added line numbers for reference purposes only, do not add these line numbers in you source file):

01   #include <stdio.h>
02   #include <exec/types.h>
03   #include <intuition/intuition.h>
04   #include <dos/dos.h>
05
06   /* Prototypes for system functions. */
07   #include <proto/exec.h>
08   #include <proto/intuition.h>
09   #include <proto/dos.h>
10
11   struct IntuitionBase *IntuitionBase = NULL;
12   struct Window *MyWindow = NULL;
13
14   int main(void)
15   {
16      /* First open the intuition.library. */
17      IntuitionBase = (struct IntuitionBase *)OpenLibrary( "intuition.library", 0L );
18
19      /* Was the library opened? */
20      if ( IntuitionBase != NULL )
21      {
22         /* Since the library was opened, we can open a window. */
23         MyWindow = (struct Window *)OpenWindowTags( NULL,
24                         WA_Left, 20,
25                         WA_Top, 20,
26                         WA_Width, 300,
27                         WA_Height, 100,
28                         WA_Title, (ULONG)"My Window", /* struct TagItem expects a ULONG. */
29                         WA_DepthGadget, TRUE,
30                         WA_CloseGadget, TRUE,
31                         WA_SizeGadget, TRUE,
32                         WA_DragBar, TRUE,
33                         TAG_END );
34
35         /* Was the window opened? */
36         if ( MyWindow == NULL )
37         {
38            /* The window was not opened so display a message. */
39            printf( "Unable to open the window!\n" );
40         }
41         else
42         {
43            struct IntuiText MyText;
44
45            MyText.LeftEdge = 10;
46            MyText.TopEdge = 20;
47            MyText.IText = "Hello Intuition!";
48            MyText.ITextFont = NULL;
49            MyText.DrawMode = JAM1;
50            MyText.FrontPen = 1;
51            MyText.NextText = NULL;
52
53            PrintIText( MyWindow->RPort, &MyText, 0, 0 );
54         }
55      }
56      else
57      {
58          /* The library was not opened so display a message. */
59          printf( "Unable to open the intuition.library!\n" );
60      }
61
62      /* Wait for about 10 seconds */
63      Delay( 500L );
64
65      /* If the window is open, close it. */
66      if ( MyWindow != NULL )
67      {
68         CloseWindow( MyWindow );
69      }
70
71      /* If the library is open, close it. */
72      if ( IntuitionBase != NULL )
73      {
74         CloseLibrary( (struct Library *)IntuitionBase );
75      }
76
77      return 0; /* main() returns an integer. */
78   }


Simple Window The first thing that we notice about the new source code is that it is considerably larger than our previous program. The added overhead is necessary to define and maintain all of the components of the window. In a shell program these things are handled by the operating system, but here we must take control.

The program begins with the familiar #include directive. Again, the stdio.h file is included, but three additional files are also included. The first of these additional files is exec/types.h. This file defines various things that make our life a little easier. For instance, the words NULL, TRUE, and FALSE are defined in exec/types.h, and without the inclusion of this file the compiler would not know what these words mean.

The power of the C language lies in its simplicity. C is a very small language with very few restrictions and simple, yet powerful tools. One of these tools is the #define preprocessor directive. Like #include, the #define directive is processed before the actual compiling of the program begins. #define is used to give aliases to particular strings of text in the rest of the program. Usually these aliases are used to make the code more readable and this is the case in our program. If we did not include exec/types.h (which contains various #define directives) our code would be much harder to understand. For example, all references to the word NULL would have to be replaced by a 0, and all occurrences of TRUE and FALSE would have to be changed to 1 and 0 respectively. In fact, the compiler actually performs this replacement process every time it encounters a word that has been previously defined. By convention, defines appear in all uppercase characters.

The next two includes are files that contain specific information about the Amiga user interface (known as Intuition) and Amiga DOS. These files setup defines, as well as complex variables called structures that are necessary for displaying windows and accessing DOS functions. Before we dive into structures, however, I'd better give a crash course in simple variables so that we are all on the same page.

All programs need a way to store and change data that they use during their execution. All temporary program data is stored in the computer's memory as a sequence of numbers. Imagine your computer memory as a very high stack of blocks. Each block in the stack is numbered. The bottom-most block is numbered 0 the next block is 1, the next 2, and so on. Each block is a small storage location in the computer's memory called a byte. Each byte contains eight "bits". A bit is essentially a switch that can be either on or off. A computer uses these bits to represent numbers in binary notation. The skinny of it is that on the Amiga, every byte can hold a number between 0 and 255. Two bytes that are next to each other in memory can be combined to hold a number between 0 and 65,535; these two-byte combinations are called words. Two words that are next to each other in memory can also be combined to create storage for a number between 0 and 4,294,967,295; these 4-byte combinations are called longwords. These same memory locations can also hold negative numbers, but it is done by using a trick that we will discover in future tutorials. For now it is sufficient to know that this trick allows for negative numbers by cutting the maximum value in a byte, word, or longword in half and using the other half of the numbers to denote negative values.

In a C program we can tell the compiler to reserve space in memory for bytes, words, and longwords. We can then use these memory locations to store our program's data. C doesn't use the words "byte", "word", or "longword". Instead it uses its own keywords called types. Types are generic concepts that can be understood to mean a "particular kind of storage space". Some of the basic types in C are char (character) and int (integer).

char is the smallest type in C. It is equivalent to a byte in memory and can hold a whole number value between -128 and +127. This is the amount of space required to hold a character from the ASCII character table.  An int is equivalent to a word and holds only whole number quantities between -32,768 and +32,767. A longword can be reserved in C by using the long modifier before int. A long int can hold integer values between -2,147,483,648 and +2,147,483,647. All of these integer types can be modified with the unsigned type modifier. unsigned creates a storage space for positive numbers only, but these positive numbers can have twice the value of their signed counterparts. (NOTE: signed and short are also type modifiers in C, but they are implied by omitting any modifiers.)  The table below shows all integer types and modifier combinations and their minimum and maximum values:

Type Size (in bytes) Minimum Value Maximum Value
char 1 -128 +127
unsigned char 1 0 +255
signed char 1 -128 +127
int 2 -32,768 +32,767
unsigned int
2
0
+65,535
signed int
2
-32,768
+32,767
short int
2
-32,768
+32,767
unsigned short int
2
0
+65,535
signed short int
2
-32,768
+32,767
long int
4
-2,147,483,648
+2,147,483,647
signed long int 4 -2,147,483,648 +2,147,483,647
unsigned long int
4
0
+4,294,967,295

In addition to these integer types, C uses two other types to hold decimal values. These are the float and double types. float stands for "floating point" and uses 4 bytes to store a decimal value in an encoded form that can hold a much larger range of numbers. However, the way that these numbers are encoded causes them to lose some accuracy when they are used. (For example, after a long series of calculations such as the expression 7+(5*8)/2-10+3, the result may come out as 4.999999 instead of 5.0.)  For most purposes a float is accurate enough, but if you are writing programs that depend on accurate and precise data (like a navigation and guidance computer, or high-end 3D graphics) you will probably want to use the more precise double type. double stands for "double precision floating point" and can hold still larger numbers with a great deal of accuracy, but it uses twice the space as a float. Below is a table showing the minimum and maximum values for the floating point types.

Type Size (in bytes) Minimum Value Maximum Value
float 4 3.4e-38 3.4e+38
double 8 1.7e-308 1.7e+308

After we have told the compiler what type of data we will be using we must give the data a name. Essentially we give a name to a location in memory that holds our data. Most of the time the values at these locations can be changed, and the names we give to these locations are commonly called variable names because their contents can vary during the execution of the program. The whole package (the memory location and name) is generically called a variable. Declaring a variable takes the form:

[ <type modifiers> ] <type> <name> [ = <value> ] ;

The items in square brackets are optional. Type can be any of the types that we have discussed above as well as others we will cover in time. The name is case sensitive meaning that NaMe refers to a different memory location than NAME, or name. Names cannot start with a number or contain special symbols other than an underscore. The optional value assignment at the end of the statement sets the initial value of the variable. If an initial value is not given, the variable may contain an arbitrary value--it is not set to zero automatically. Here are some examples:
int MyFirstVariable;                      /* 2 byte integer */
char _my_1st_little_char_;                /* 1 byte integer */
unsigned long int _2Million = 2000000;    /* 4 byte integer, initial value 2000000 */
float FloatingPoint;                      /* 4 byte decimal */
double BigNumber = 1.8e45;                /* 8 byte decimal, initial value 1.8 * e^45 */ 

Before we move too far along we must discuss memory addresses because C programmers use them constantly. Remember our vertical stack of numbered blocks that represent the different bytes in a computer's memory?  The number of each block is called its address; each byte has an address. When two or more adjacent bytes are joined to form a larger storage space, the address of the lowest byte (i.e. the lowest numbered block) is used as the address of the whole unit. We can easily find the address of a variable rather than its value by putting an ampersand ("&") in front of it. Here is a program that finds the address of a variable:
#include <stdio.h>

int main(void)
{
    /* Declare Var1 as a two byte integer with a value of 10. */
   int Var1 = 10;

   printf( "value   = %d\n",  Var1 );   /* Value of Var1, will be 10. */
   printf( "address = %d\n", &Var1 );   /* Address of Var1, will likely not be 10. */
}

It is often extremely useful to refer to a variable by its address rather than by its name. This is accomplished by using a pointer. A pointer is a special type in C that is denoted by an asterisk (or star symbol) before the variable name. It is essentially an unsigned long int that holds the address of a variable rather than the variable's value. This is a subtle but powerful distinction. Because pointers are stored in memory, they also have addresses and it is possible (and exceedingly confusing) to create a pointer to a pointer. Once a pointer has been initialized it is possible to find the value of the variable pointed to by using the indirection operator--the asterisk ("*"). Here is an example of using a pointer:
#include <stdio.h>

int main(void)
{
   int MyVariable = 10;   /* Initialize MyVariable to 10. */
   int Value = 0;         /* Initialize Value to 0. */
   int *MyPointer;        /* Reserve some space to hold an address of an integer. */

   MyPointer = &MyVariable;   /* Copy the address of MyVariable into MyPointer. */
   Value = *MyPointer;        /* Use indirection to obtain the value of the variable
                                 pointed to by MyPointer (the value of MyVariable). */

   printf( "Value = %d\n", Value );   /* Print the value of Value. It will be 10. */
}

On to structures. A structure (keyword struct) is another C variable type, but it is more complex than most other variables. Although it is called "complex", a structure is simply a grouping of other variable types into a single, larger unit for the sake of organization. A structure can contain any number of other variable types, even other structures. For example, lets say we wanted to keep track of a list of data relating to an automobile. Say we need to know the year the car was made as well as its position in latitude and longitude coordinates, the amount of fuel the car has remaining, the total weight of the vehicle, and the number of passengers. We could create a number of separate variables to keep track of each of these items, but that would become a bit cumbersome, especially if there were many cars that we needed to keep track of. Instead, we will create a structure containing all of these variables. Here is how we do it:
struct Auto
{
   unsigned int Year;  /* Year the car was made.        */
   float Latitude;     /* Car's current latitude.       */
   float Longitude;    /* Car's current longitude.      */
   float Fuel;         /* Remaining fuel in liters.     */
   float Weight;       /* Car's weight in kilograms.    */
   char Passengers;    /* Current number of passengers. */
};

We now have a single data type that holds all the information about our Auto. The keyword  struct is followed by a name by which we want to refer to this grouping of variables. A list of variable types and their names (each followed by a semi-colon) are placed within curly braces and finally followed by a final semi-colon.

Note that the above is called a structure definition. No space is actually reserved in memory for any of these items, we are just letting the compiler know what an Auto is. Usually structure definitions like this appear in header files and are included. The compiler will now know how to reserve enough memory to hold all of these separate variables next to each other whenever we declare an Auto variable. Before we create an actual instance of this variable type, I'd like to point out that an even better structure definition would first create a structure to hold the other related items like latitude and longitude:

struct Position
{
   float Latitude;      /* latitude.     */
   float Longitude;     /* longitude.    */
};

struct
Auto
{
   unsigned int Year;          /* Year the car was made.      */
   struct Position Location;   /* Lat & Long location of car. */
   float Fuel;                 /* Remaining fuel in liters.  */
   float Weight;               /* Car's weight in kilograms.  */
   char Passengers;            /* Current number of passengers. */
};

Notice that we first created a structure to hold generic latitude and longitude positions. Then we substituted the new Position  structure into our Auto structure. To the compiler both structures look exactly the same in memory, but we have another structure that we can use again if we want to keep track of the location of something else.

So we have now told the compiler what an Auto is (or more correctly, the data that we wish to organize under the heading Auto), we now have to actually reserve space in memory to hold this data. A structure declaration is similar to the declaration of any other type. First we tell the compiler the type of variable that we are declaring. Since it is a  struct we must also tell it the name of the structure to which we are referring. We must then give the newly reserved memory location a name followed by a semi-colon. Here is an example:

struct Auto Car1;

This line actually reserves 21 contiguous bytes of memory to hold our structure and gives this memory location the name "Car1". We can now access the specific data elements within our structure by using the . (dot) operator like so:

float RemainingFuel;
char Occupants;

Car1.Year = 1995;
RemainingFuel = Car1.Fuel;
Car1.Weight = 900.0;
Occupants = Car1.Passengers;
Car1.Location.Longitude = 0.5;
Car1.Location.Latitude = 51.32;

Structures can become very large. The structure above is 21 bytes in size and structures can often become much larger. Because of this it is often much more efficient to refer to a structure by its address, using a pointer. A pointer is always 4 bytes no matter what size the structure may be and is ideal when a structure must be passed as an argument to a function. Rather than copy all the elements of the structure (which would slow down our code and use a large amount of memory) we can pass a pointer telling our function where the data is in memory. To access elements in a structure using a pointer we must setup a pointer to the structure and then use the -> (arrow operator) instead of the dot operator to access the elements of the structure:

struct Auto *pCar1;

pCar1->Year = 1938;
pCar1->Occupants = 4;
pCar1->Location.Longitude = 0.5;
pCar1->Location.Latitude = 51.32;

(Note that the dot operator is used after Location because in the structure definition Location is not a pointer to a Position structure.)

Now, finally, we will return to our program.

Line 6 contains a C comment. All text between the /* and the following */ are ignored by the compiler. These notes are used purely for the programmer.

Lines 7 through 9 include prototypes for system functions. A prototype tells the compiler how many and what type of arguments a function takes. With this information the compiler can warn you if you attempt to use the wrong type of variable in a function call.

All of the above discussion of variables was necessary to introduce the next two lines. Lines 11 and 12 declare pointers to structures that are defined in the intuition.h header file. These pointers are set to NULL (zero) so that we don't accidentally use them before they are initialized with the address of a valid memory location.

Initially setting pointers to NULL is a highly recommended practice. Because C does not set the pointer to zero automatically, it is possible to use the pointer before it is properly initialized. An uninitialized pointer may be pointing to a random address in memory (an area that may hold vital information for other programs or the operating system) and changing the data in this area may corrupt the data already at that location. Doing this can lead to a computer crash. These crashes are notoriously difficult to diagnose and on a computer without memory protection (like the classic Amigas) such errors will usually take the whole computer down, making many angry users and giving programmers a headache. It is much easier to find an uninitialized NULL pointer than it is to find an uninitialized pointer that contains a random value. This problem is one of the reasons that pointers have gotten a bad reputation among proponents of other languages, but in spite of their quirks, when pointers are used properly they offer huge benefits.

Lines 14 and 15 we have seen before. These lines denote the start of the main function and the beginning of program execution.

Line 17 is the first statement of the program. It opens a dynamic library. There are two types of libraries in C. The first is called a static library or a link library. We talked about these libraries briefly in the last lesson. Static libraries contain code that is actually inserted into your program where necessary during the compilation process. This code is actually part of the program executable file. A dynamic library is the opposite of this; it is a library that is always separate from the executable program. It must be loaded from the disk by the program before its code can be used. (Actually some of these libraries, such as the exec.library are automatically loaded at the start of every program, but for the most part a dynamic library must be explicitly opened.)  The Amiga has many of these dynamic libraries. Here we are opening the intuition.library which contains functions necessary to create and manage windows and other elements of the Amiga's user interface.

The function used to open the library is the OpenLibrary() function. This function is part of the exec.library that is automatically opened at the start of every program. OpenLibrary() requires two arguments. The first argument is the name of the library to open surrounded by quotes, and the second is the version number of the library if needed. (Some functions are only available in newer versions of a library and this version number allows the programmer to determine if the user has access to these newer functions.)  In our code we are opening the intuition.library and any version will do so we specify zero for the version number. The "L" after the zero tells the compiler to make certain that this zero is a 4 byte value so that it is read correctly. The OpenLibrary() function opens the library and then returns the address of the beginning of the library's memory area as a result. This result is placed into the IntuitionBase pointer variable.

You may be wondering what the deal is with the items in parentheses between the equals sign and the OpenLibrary() function. This is called a cast. A cast is used to over-ride the compiler's type checking. As the compiler analyzes the program it remembers the type of each variable and attempts to ensure that only compatible types are used in assignments, such as this one, and also in function arguments. This is called type checking and is usually very helpful in eliminating problems caused by data being assigned to a variable that is not able to hold its value properly  (such as an int being assigned to a char).

In our case the OpenLibrary() function always returns a pointer to a Library structure (struct Library *), but the structure that we created to hold the address of the library is of type struct IntuitionBase *. Because these are two different types, the compiler will generate an error. To avoid the error we must use a cast which tells the compiler to take the struct Library * and try to convert it to a struct IntuitionBase *. Because they are both pointers to structures this can be accomplished and the compiler does not complain; it assumes that we know what we are doing. The trick here is that the definition of the IntuitionBase structure begins with a struct Library followed by additional variables necessary to maintain the intuition.library, the OpenLibrary() function can fill in the needed data elements properly because, at least for the first several bytes, it is essentially a Library structure.

Line 20 introduces a new keyword. If you have programmed at all on any platform and in any language (with the possible exception of assembly) you will recognize this form. The if keyword is called a conditional statement. It is followed by an expression in parentheses. This expression is evaluated, and if the result is true then the code between the next two curly braces is executed. (Generically, the term for code between a pair of open and closed curly braces is a "block".) If the expression is false the code is skipped and the program continues with the first statement after the end curly brace. Sometimes there is also and else statement following the first block. This allows code to be executed if the expression is false.

You may be asking yourself what true and false are. Earlier in the chapter I said that C did not know what TRUE and FALSE were until we explicitly defined them. So, what if we had not defined them?  Well, C's default notion of truth is that any value that is not equal to zero is true, all values that are equal to zero are false. Whenever an expression is evaluated the answer is always either non-zero or zero, true or false. This allows us to use a single value as a test. In a minute I will show you a way to tighten up the code using this rule.

In our first if statement we are testing the value of our IntuitionBase pointer. This test is done to verify that the intuition.library was opened successfully. If the library was opened this pointer will contain a value other than NULL. If it has not opened the value will remain NULL. The test != is read "not equal". The exclamation mark always means "not". So in english we are saying, "If the IntutionBase pointer is not equal to NULL, then execute the following block."  Here are a list of many of the possible tests and their verbal :equivalents

Value Test Example English
== A == B If A equals B then...
!= A != B If A is not equal to B then...
> A > B If A is greater than B then...
< A < B If A is less than B then...
>= A >= B If A is greater than or equal to B then...
<= A <= B If A is less than or equal to B then...
Boolean Test Example English
&& A && B If A and B are both true then...
|| A || B If either A or B are true then...
! !A  If A is not true then...


Because of the truth rule in C (i.e. all values other than zero are true) we do not even have to use the != test in our if statement. If we omit a test, the compiler simply uses the value in the variable as a true or false test. We could have written:
if ( IntuitionBase  )

The statement resolves to true if IntuitionBase has a value other than zero (NULL), and since we initialize the pointer to NULL it will only be true if the library opened. This is called an implicit condition, where the first example is called an explicit condition. If you are new to these concepts, I recommend using explicit conditions until you are comfortable with the flow of logic. I will use explicit conditions throughout this tutorial.

We will assume that the library opened. If it had not, for whatever reason (not enough memory, etc.) the if test would fail and program flow would jump to line 56 (the else statement) and execute the block that prints a message on the screen stating that the library was unable to be opened. Since the library did open, IntuitionBase has a value other than NULL and we enter this first block.

Line 23 is the beginning of a very long, multi-line statement. The function here is the OpenWindowTags() function from the intuition.library. (This is why we bothered to open the intuition.library.)  OpenWindowTags() uses an optional struct NewWindow * and a list of items called tags to obtain enough information to open a window on the screen. This function is the main method of opening windows in AmigaOS 2 and above. Generally NULL is supplied as the struct NewWindow *. The tags that follow the pointer are in pairs. We first give the name of the tag followed by the value that we want that tag to have. Here are the tags we have used:

WA_Left gives the offset of the of the left side of the window from the left side of the screen. This value is given in pixels.
WA_Top gives the offset of the top of the window from the top of the screen. It is also given in pixels.
WA_Width sets the horizontal size of the window in pixels. This is the total size from the far left to the far right including the borders.
WA_Height sets the vertical size of the window in pixels. This is the total size from the top to the bottom also including the borders.
WA_Title sets the text that is desplayed at the top of the window. The text between the quotes has a type of char *, but the TagItem structure expects the value after the tag to always be an integer (a ULONG specifically). Therefore, we must cast our char * to a ULONG to avoid a compiler warning.
WA_DepthGadget is either TRUE or FALSE and indicates if our window should have a depth gadget in the upper right of the window (so our window can be hidden behind or brought to the front of other windows.
WA_CloseGadget indicates if we want a close gadget in the upper left corner of our window.
WA_SizeGadget
indicates if the window should be a constant size or if the user can change the size by using a size gadget at the lower right of the window.
WA_DragBar indicates if the user should be able to move the window by moving the mouse while clicking on the top portion of the window.
TAG_END tells the function that we are finished with our list of tags. This tag does not have a value.

(A list of all tags and their meanings and values can be found in the official Amiga Autodocs under the OpenWindow() function.)

Using our tags and values the function will open a window that is 300 pixels wide and 100 pixels in height. It will place the upper-left corner of the window 20 pixels from top and 20 pixels from the left side of the screen. The window will have a title that reads "My Window" (without the quotes) and the user will be allowed to re-size and move the window. The user will be able to bring it to the front or move it behind other windows on the screen and it will have a close button. (In this example the close button will not function, we will add that ability in the next tutorial.)

If the OS was able to create the window it will put the address of a Window structure containing the necessary values into the MyWindow pointer variable, otherwise it will return NULL. This value must be checked because if the window was not opened, trying to use it in any way will lead to a system crash. A window may fail to open for various reasons, but usually because there is not enough memory.

Our program tests the MyWindow pointer on line 36. If the pointer is NULL then it prints a message to the screen explaining that the window could not be opened, otherwise it jumps to line 41 and begins the else block. This test uses the opposite logic as our test for the opening of the intuition.library (positive logic instead of negative). It does not matter which logic you choose to use as long as you understand what is being expressed.

Again we will assume that the window did open. Line 43 begins the else block by reserving an IntuiText structure. IntuiText is short for "Intuition Text" and holds information necessary to display text in a window or on a screen. In addition to the actual text that we wish to print, this structure holds such information as the exact position and color of the text.

This is also a good time to discuss scope. Scope is sometimes a difficult concept to grasp. It refers to the location where a variable is able to be used. In languages such as BASIC you can use a variable anywhere in a program. C has variables like this also--they are variables declared outside any functions and are said to be of global scope or global variables because they can be used anywhere in the program. Our IntuitionBase and MyWindow pointers declared at the beginning of our program are examples of variables in global scope. Sometimes, however, it is more confusing than helpful to have a variable in global scope, especially in larger programs. Because of this, C has local variables. Local variables are declared directly after an open curly brace "{" and are only valid until the corresponding close curly brace "}" is encountered. You may have noticed that all functions begin with an open curly brace and end with a close curly brace; this means that variables declared at the beginning of the function are not valid outside the function, they are only "in scope" inside the function.

The two curly braces and everything between them is called a block. Every block has its own scope. If one block is inside another block it inherits the scope of the first block, but also has it own scope. Variables may only be use wile they are in scope. Maybe an example will help clarify things:

01 void MyFunction(int A)
02 {   /* Begin a new block, call it block X. */
03    int B = 10;
04
05    if (A == B)
06    {   /* Begin a second block, call it block Y. */
07       int C;
08
09       C = A * B;
10    }   /* End the second block. C is out of scope, A and B are still in scope. */
11
12    A = C + 2;  /* This line will generate a compiler error because C is out of scope. */
13
14 }   /* End the first block. A and B are now out of scope. */

Here we have a function called MyFunction. This function takes one argument A. All function arguments are in scope throughout the entire function and therefore may be used anywhere in the function. The function block actually begins on line 2 with the open curly brace. B is declared inside the function block (block X for reference) and so B is also in scope throughout the whole function. Line 6 begins a second block (block Y). C is declared in block Y and can only be used in block Y. A and B, however, can still be used because they are still within the bounds of block X. Trying to use C in line 12 will cause an error because C is out of scope.

In our program, the IntuiText structure MyText is only in scope within its immediate block. Since we don't need to print text in any other part of the program this is sufficient. Lines 45 through 51 give values to the members of MyText. The text will begin 10 pixels to the right of the starting position and 20 pixels below the starting position. MyText.LeftEdge and MyText.TopEdge hold this location information.  Line 47 assigns the actual text that we want to display ("Hello Intuition!") to the MyText.IText pointer. The ITextFont member is a pointer to a struct TextAttr that holds information about which font we wish to use. NULL means use the default font which will do just fine for our example. The DrawMode and FrontPen members hold information about how to color the text. JAM1 is one of several drawing modes that the Amiga can use. JAM1 essentially tells the Amiga that you want to ignore and overwrite any graphics already in the window. FrontPen sets the foreground color of the text. In our case we want the text to be black so we choose color 1. (Note that color 1 is not necessarily black. The colors are set by the user preferences.)  Finally the NextText member is a pointer to another IntuiText structure. This can be used to print additional text on the screen with only one call to the PrintIText() function.

The PrintIText() function takes four arguments. The first is a pointer to a raster port. A raster port can be thought of as a canvas that can be used to draw on. Every window has its own unique raster port and its address is found in the RPort member of the Window structure. Passing the address of our window's raster port directs the drawing operation to our window. The next argument is the address of the IntuiText structure that we wish to draw. The final two arguments are vertical and horizontal offsets from the upper left corner of the raster port. We want our text to be drawn with the upper left corner of the window as the starting point so both arguments are zero. However, if we wanted the text to be drawn at a different location within the window we could provide different offset values to serve as the starting location.

We have already discussed the else clause so we will move on to line 63. The Delay() function is used to pause a program for a period of time. The argument is a long integer (hence the "L" following the number) indicating the time to pause in fractions of a second. The actual period of time that the program halts depends on the electrical power system that is used in your country. In North America the power grid alternates at 60 cycles per second so the argument is presented in 1/60ths of a second. In Europe this is usually 50 cycles per second and so the argument is measured in 1/50ths of a second. Our program will pause for 500 cycles or about 10 seconds in Europe, slightly less in North America.

The next two if statements test if the window and the intuition.library were successfully opened. If they were they must be closed before the program exits. The CloseWindow() function closes the window associated with the Window structure pointer that is passed as its argument. Similarly the CloseLibrary() function closes the dynamic library associated with the Library structure pointer that is passed to it.

The program ends at line 77 when the return statement for the main() function is encountered. The main function was declared as a returning an int value on line 14. Among other things, this value could be used to return error codes if the program failed to load properly, but for now we will just return 0 to keep the compiler happy.

On line 78 the main() function (and thus the whole program) goes out of scope and the program is unloaded from memory. And that is an in-depth look at using simple windows on the Amiga.

Next time we will look at message processing and how we can use it to allow the user to close the window and perform other operations.

< Lesson 1: Hello World  |  Index  |  Lesson 3: Close Window >

If you find any errors or have any questions or comments please e-mail me.

Last updated on Saturday, February 11, 2006 19:34