< Lesson 2: Simple Window  |  Index  |  Lesson 4: Draw >


Lesson 3:  Close Window


In the last tutorial we created a window that pops up on the screen for about 10 seconds and then disappears. This is obviously not going to be very useful in the real world. Most applications need to open a window and allow the user to choose when it is appropriate to close the window by clicking on the close window gadget in the upper left corner of the window. In our last example the window had this gadget but it did not function, so this tutorial will explain how to create a functional close gadget and the concepts behind window messages.

Below is the code for this tutorial. It is very similar to the simple_window.c source file, so if you don't want to type in the whole program simply modify simple_window.c. The changes have been set in italics and their line numbers are in bold.

001   #include <stdio.h>
002   #include <exec/types.h>
003   #include <intuition/intuition.h>
004   #include <dos/dos.h>
005
006   /* Prototypes for system functions. */
007   #include <proto/exec.h>
008   #include <proto/intuition.h>
009   #include <proto/dos.h>
010
011   struct IntuitionBase *IntuitionBase = NULL;
012   struct Window *MyWindow = NULL;
013
014   int main(void)
015   {
016      ULONG signals;
017      struct IntuiMessage *message;
018      UWORD msg_code;
019      ULONG msg_class;
020      BOOL end = FALSE;
021
022      /* First open the intuition.library. */
023      IntuitionBase = (struct IntuitionBase *)OpenLibrary( "intuition.library", 0L );
024
025      /* Was the library opened? */
026      if ( IntuitionBase != NULL )
027     {
028         /* Since the library was opened, we can open a window. */
029         MyWindow = (struct Window *)OpenWindowTags( NULL,
030                          WA_Left, 20,
031                          WA_Top, 20,
032                          WA_Width, 300,
033                          WA_Height, 100,
034                          WA_Title, (ULONG)"My Window", /* struct TagItem expects a ULONG. */
035                          WA_DepthGadget, TRUE,
036                          WA_CloseGadget, TRUE,
037                          WA_SizeGadget, TRUE,
038                          WA_DragBar, TRUE,
039                          WA_IDCMP, IDCMP_CLOSEWINDOW,
040                          TAG_END );
041
042         /* Was the window opened? */
043         if ( MyWindow == NULL )
044         {
045            /* The window was not opened so display a message. */
046            printf( "Unable to open the window!\n" );
047         }
048         else
049         {
050            struct IntuiText MyText;
051
052            MyText.LeftEdge = 10;
053            MyText.TopEdge = 20;
054            MyText.IText = "Click the close button to exit!";
055            MyText.ITextFont = NULL;
056            MyText.DrawMode = JAM1;
057            MyText.FrontPen = 1;
058            MyText.NextText = NULL;
059
060            PrintIText( MyWindow->RPort, &MyText, 0, 0 );
061         }
062      }
063      else
064      {
065         /* The library was not opened so display a message. */
066         printf( "Unable to open the intuition.library!\n" );
067      }
068
069      /* Wait until the close button is pressed. */
070      while (!end && MyWindow)
071      {
072         signals = SetSignal(0L, 0L);
073
074         /* Check the signal bit for our message port. Will be true if these is a message. */
075         if (signals & (1L << MyWindow->UserPort->mp_SigBit))
076         {
077
078            /* There may be more than one message, so keep processing messages until there are no more. */
079            while (message = (struct IntuiMessage *)GetMsg(MyWindow->UserPort))
080            {
081               /* Copy the necessary information from the message. */
082               msg_class = message->Class;
083               msg_code = message->Code;
084

085               /* Reply as soon as possible. */
086               ReplyMsg((struct Message *)message);
087

088               /* Take the proper action in response to the message. */
089               switch (msg_class)
090               {
091                  case IDCMP_CLOSEWINDOW: /* User pressed the close window gadget. */
092                     end = TRUE;
093                     break;
094                  default:
095                     break;
096               }
097            }
098         }
099      }
100
101     /* If the window is open, close it. */
102     if ( MyWindow != NULL )
103     {
104        CloseWindow( MyWindow );
105     }
106
107     /* If the library is open, close it. */
108     if ( IntuitionBase != NULL )
109     {
110        CloseLibrary( (struct Library *)IntuitionBase );
111     }
112
113     return 0;
114  }


Simple Window First some theory. Like most modern computers, the Amiga uses a multi-tasking operating system. This means that more than one program can be running on the Amiga at any given time. These programs all share the same resources (i.e. memory, processor, keyboard, mouse, etc.). It is the job of the operating system to sort out which program gets access to the computer's resources and for how long it is given access. The OS also determines which program any given input (keyboard input, mouse input, etc.) is meant for.

The Amiga uses a system of messages and ports to communicate these kinds of events to the various running  programs. This is essentially setup like a postal system. When a program opens a window, that window creates a message port which is kind of like a mail box. Whenever anything of interest happens to that window the OS sends a message to the associated message port with details about the event. The OS also sets a flag (called a signal) to notify the program that it has a message waiting. We can specify which messages we would like to be notified of by using the WA_IDCMP tag when defining the window. This tag is followed by a list of messages we will be notified of, all other messages will be ignored. On to the program...

Take a look at lines 16 through 20. These five lines set up the variables needed to monitor window messages on the Amiga. The first varible is called signals and is an unsigned long (i.e. 4 byte, 32 bit) value. The definition ULONG is an Amiga specific definition that is found in the exec/types.h include file. There are many other useful defines in exec/types.h, feel free to examine this file to find out what other defines are made. We will cover many of them in our tutorials.

The variable signals will be used to hold the list of signals that the program will respond to. Signals are used by Amiga OS to inform programs of certain events. For instance, many programs respond to the control-C key combination. This key combination sets a particular bit in the program's "signal mask". Once that bit is set, the program can read the signals and take the proper action required to close. Sometimes a program will not be able to check the state of its signal mask because it is caught in an endless loop and will therefore not receive notice that the control-C combination has been pressed; the program is then considered "frozen" or "hanging". It is up to the programmer to ensure that the signal mask is checked periodically.

Line 17 declares a pointer to an IntuiMessage structure. The Amiga has several different types of messages that it sends back and forth to accomplish particular tasks. Messages that are sent because of an event that occurred on the Workbench screen are called Intuition Messages or IntuiMessages. Our variable message points to the location of the next message we have received.

Note:  For reasons of efficiency, the Amiga system of messages does not really send messages to and fro. The reason for this is that a message can be arbitrarily large and copying a message to another location in memory could take quite a long time. So instead of sending the actual message, only the location (or address in memory) of the message is sent. Sending the address of the message only requires copying 4 bytes regardless of the size of the actual message. The recipient program can then go to that location and read the message directly.

Lines 18 and 19 are used as temporary storage spaces for information in the message. IntuiMessages come in different "classes". Each of these classes have different "codes" that signify particular messages. These two lines reserve space to hold a message's class and code for later use. Line 20 declares a boolean (BOOL) variable (a variable that can be either TRUE or FALSE) for use in testing if the close button has been pressed. It is set to FALSE at the beginning of the program.

Line 39 adds a new tag to the window definition. WA_IDCMP is the tag that tells workbench to notify our program if the window receives any messages. The tag is followed by a list of the messages that we want to be notified about. In our case, we only want to know about one message:  IDCMP_CLOSEWINDOW. If there were more than one message that we wanted to know about we would separate the messages with the "or" ("|") operator.

Line 70 is the beginning of our loop. It consists of a while statement followed by a test. When the program encounters a while statement it first evaluates the test to its right. If the test is true then it performs the code in the block below the while statement, otherwise it skips the block completely. Every time the end of the block is reached the test is evaluated again and the loop continues only if it remains true. In our case the test reads "while not end and MyWindow". The loop will only begin if both the variable end is FALSE and the MyWindow structure pointer is not NULL. Testing the MyWindow pointer ensures that the window has been opened. It would be pointless (or worse) to wait for a message from a window that was not even opened.

At line 72, SetSignal() places a snapshot of the current signal bits into the variable signals. and line 70 tests to see if the message signal bit is set. If this bit is set then we have messages waiting and should check them as soon as possible. Note that we are not working directly with the signal bits, only a copy of them. This ensures that we don't accidentally change the bits or try to access them while they are being changed by the OS.

If the message bit is set, the program jumps to line 79 where we use GetMsg() to copy the address of the first message found in our window's message port, namely MyWindow->UserPort. We must cast the resulting pointer as an IntuiMessage * because by default the GetMsg() function assumes that it is dealing with normal Messages. IntuiMessages tack on some additional information to the end of the message and we want to have access to that information.

Lines 82 and 83 copy the information that we require into some temporary variables. Line 86 uses the ReplyMsg() function to inform the OS that we have looked at the message and it is now ok to remove it from the message port. Note that we had to cast message back to a Message * to ensure that it is removed correctly. It is important that messages are replied to promptly. Letting messages pile up in the message port is not advised.

Also notice how we again deal with copies of the message data rather than the actual data. This allows us to reply to the message more quickly and frees up the message port for other messages. Once we are signaled that a message is waiting the goal is the get the message, copy the needed information from it, and reply to it as quickly as possible. Not replying quickly can lead to degraded performance of the application or OS.

After we have replied to the message the program continues on the line 89. This switch statement takes the appropriate action based on the message's class. In our case the only message class that we are concerned about is the IDCMP_CLOSEWINDOW class which is the first case. This particular class doesn't have any additional codes, but many other classes of messages do have codes that give us additional information about the message. Once the message class has been identified we can take the appropriate action. In our case, the appropriate action is the set end equal to TRUE so that the main loop exits the next time around and then the program closes.

Before the program closes, however, we need to make sure that all the messages have been answered. This is why the GetMsg() function is part of a while loop condition. As long as there are more messages in the message port we must process them and reply to each one. Once we have responded to all the messages, the GetMsg() function returns NULL and the program can continue on. It should be clear that the fewer messages that we have to process, the faster our program can continue on with other, possibly more interesting, tasks. This, again, is why it is important to not allow messages to pile up in the message port.

If an IDCMP_CLOSEWINDOW message has been received, then end is TRUE and the while loop condition at line 70 fails, otherwise the program continues to process messages as they arrive at the message port. Once the loop has been exited the program cleans itself up just as it did in simple_window.c by first closing MyWindow and then closing the intuition.library.

And that's it really. Next time we will talk more about these messages and use them to create a simple drawing program.

< Lesson 2: Simple Window  |  Index  |  Lesson 4: Draw >

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

Last updated on Sunday, November 27, 2005 23:22