9. Gem: Amulet Low-Level Graphics Routines

This manual describes GEM, the low-level graphics system in Amulet. Gem provides a machine-independent layer so the rest of Amulet can work on different window managers without changing the code. Most programmers will not use the Gem layer, but it is available for advanced programmers who need especially efficient code.

9.1 Introduction

Gem is the low-level graphics and input interface in Amulet that provides a machine independent API to all of the supported platforms' graphics and event routines. Most Amulet programmers will not use the Gem layer, since Opal and Interactors provide all the functionality of the Gem layer, with the added convenience of automatic redrawing and functionality of the ORE object system. Opal, Interactors and the Amulet Widgets are written using Gem. We provide the Gem interface for advanced programmers who are concerned about performance, who want to extend Opal or create new widgets, and those who need the convenience of a machine independent graphical toolkit without the overhead of the Amulet object system.

Gem, which stands for the ``Graphics and Events Manager'', can be used independently of most of the rest of the Amulet. Gem uses the wrapper mechanism (for styles and fonts), but it does not use the ORE object system.

9.2 Include Files

The primary include file for Gem is gem.h. Gem also uses types.h for wrappers, gdefs.h for styles, images, point lists, and fonts, and idefs.h for input events. For a complete description of Amulet include files and how to use them, see Section 1.8 in the Overview chapter.

9.3 Drawonables

The primary data structure in Gem is the Am_Drawonable, which is a C++ object that corresponds to a window-manager window or off-screen buffer (for example, in X11 it corresponds to a ``drawable''). We called it a ``drawonable'' because it is something that you can draw on. We also wanted to reserve the word ``window'' for the Opal level object that corresponds to the drawonable. In this manual, sometimes ``window'' is used for ``drawonable'' since drawonables are implemented as window-manager windows.

9.3.1 Creating Drawonables

Programmers create a root drawonable at initialization, and then create other drawonables as children of the root (or as children of another drawonable). The typical initialization is:

Am_Drawonable *root = Am_Drawonable::Get_Root_Drawonable();
At the Opal level, this is called automatically by Am_Initialize to set up the exported Am_Screen object. Under X11, Get_Root_Drawonable takes an optional string parameter which is the name of the screen. You can call therefore call Get_Root_Drawonable multiple times to support multiple screens.

Creating subsequent drawonables uses the Create method. If you use root.Create you get a top-level window, and if you use another drawonable, then it creates a sub-window. All of the parameters of the create call are optional, and are:

To create an off-screen drawonable, you can use the shorter form:

Am_Drawonable* Create_Offscreen (
	int width = 0, int height = 0,
	Am_Style background_color = Am_No_Style)
To destroy a drawonable call the Destroy method, which destroys the drawonable and all its children, including removing them from the screen:

void Destroy ()

9.3.2 Modifying and Querying Drawonables

There are a number of methods on drawonables that query and set the various properties:

9.4 Drawing

Drawing in a drawonable is a three step process. First set the drawonable's clip region to the region you want to draw in. Then draw using the various drawing methods provided. Finally, you should ``flush'' the drawonable to process all pending drawing requests. The flush is necessary on X11 to make the graphics appear. It is not strictly necessary on the PC or Macintosh, but to create machine independent code you should always explicitly call Flush_Output() on your drawonable if you want the output to appear.

The actual pixels drawn in a drawonable by the various drawing routines are described in detail in the Opal chapter of the Amulet manual. The Opal objects' slots are passed as parameters to the Gem level drawing routines. The Am_Style, Am_Image_Array, and Am_Font structures are also described there. That information will not be repeated here.

9.4.1 General drawonable operations

9.4.2 Size Calculation for Images and Text

In general, you cannot know the size of an image or a piece of text until you know where that image or text is going to be displayed. The same font may look different on different screens, depending on screen resolution, aspect ratio, and so on. Therefore, the following are methods of drawonables rather than of images and font objects.

9.4.3 Clipping Operations

Gem supports clipping of all graphic operations to a specified clip region. Once a clip region is specified, all subsequent drawing operations are clipped so only parts inside the current clip region will show. To change the clip region of a drawonable, invoke one of the member functions listed below.

There is only one clip mask per root drawonable. All drawonables which have the same root share the same clip region. It is necessary to set the clip region for each window before drawing the contents of that window. For efficiency, it's a good idea to complete drawing in one window before resetting the clip region and drawing in other windows.

The clip region can be set with a rectangular region, or with an arbitrarily shaped Am_Region. Regions are described in Section 9.4.4. The root drawonable stores a stack of clipping regions. Use Push_Clip() and Pop_Clip() to push regions onto this stack, or to pop them off. The current clip region is the intersection of all of the regions currently on the stack.

The In_Clip() routines provide a way to ask a drawonable if a point is inside its clip region. When asking if a given region is inside the drawonable's clip region, you can use the total parameter to determine whether the given region is completely inside the clip region, or whether it just intersects it.

9.4.4 Regions

Instances of the Am_Region class describe arbitrarily shaped regions. Am_Region is a generalization of a drawonable's clip region, discussed in Section 9.4.3. By using the member functions listed below, you can build a region of arbitrary shape to install as the clip region of a drawonable.

class Am_Region {
public:

  static Am_Region* Create ();
  virtual void Destroy () = 0;
  virtual void Clear () = 0;
  virtual void Set (int left, int top, unsigned int width,
		            unsigned int height) = 0;
  virtual void Push (Am_Region* region) = 0;
  virtual void Push (int left, int top, unsigned int width,
		             unsigned int height) = 0;
  virtual void Pop () = 0;
  virtual void Union (int left, int top, unsigned int width,
	                    unsigned int height) = 0;
  virtual void Intersect (int left, int top, unsigned int width,
	                        unsigned int height) = 0;
  virtual bool In (int x, int y) = 0;
  virtual bool In (int left, int top, unsigned int width,
	                 unsigned int height, bool &total) = 0;
  virtual bool In (Am_Region *rgn, bool &total) = 0;
};

9.4.5 Drawing Functions

All of the drawing functions take a Am_Draw_Function parameter which controls how the pixels of the drawn shape affect the screen. Since most programmers will use color screens, draw functions are not usually useful, although they are used for special effects such as fade groups in Opal. The supported values for Am_Draw_Function are:

The regular drawing operations (Am_DRAW_OR, etc.) compensate for black and white monitors which chose to make black 0 and white 1. The GRAPHIC versions provide unadulterated bitwise operations. The operation Am_DRAW_MASK_COPY is a short cut operator for drawing masks. Be advised that not all of the drawing functions support all of the values of Am_Draw_Function. In particular, Draw_Image only supports Am_DRAW_COPY and Am_DRAW_MASK_COPY.

The Am_Style objects Am_On_Bits and Am_Off_Bits are special colors for making masks. Am_On_Bits has a color of all ones so that:

Am_On_Bits AND Some_Color == Some_Color
Am_On_Bits OR Some_Color == Am_On_Bits
And Am_Off_Bits has a color of all zeros so that:

 Am_OFF_Bits AND Some_Color == Some_Color
Am_Off_Bits OR Some_Color == Am_Off_Bits
All of the following drawing routines draw in either an onscreen or offscreen drawonable. The parameters ls and fs specify the line style and fill style for the drawing operations.

9.5 Event Handling

Gem is usually used in event driven programs. In this style of program, there is a loop constantly running, checking for input events from the user, or other events the window manager might generate. These events are sent to Gem. When your program is ready to handle them, you tell Gem, and it dispatches one or more events to event handlers you have specified.

9.5.1 Am_Input_Event_Handlers

Gem dispatches events to the rest of your application by calling event handler routines you provide. The event handlers are stored as virtual methods of the C++ class Am_Input_Event_Handlers. You can specify one instance of this class for each of the drawonables in your application. The event handler methods take the drawonable and event information as parameters.

For example, each time the user hits a keyboard key over a certain window, that window's event handlers are retrieved, and, the Input_Event_Notify member function is called, with information about what key was pressed, which drawonable received the keypress, and where the mouse was positioned within the window when the key was pressed.

Opal defines standard event handlers, so anyone programming at the Opal layer or above will not have to provide these event handlers. Instead, you should use an interactor for event handling, or create a new interactor if none of the available interactors is appropriate. To add or change the functionality of the standard opal event handlers, derive a new class from the C++ class Am_Standard_Opal_Handlers (exported in opal_advanced.h), and replace some of the virtual functions with your own event handlers.

Am_Input_Event_Handlers is defined as:

class Am_Input_Event_Handlers {
public:
	virtual void Iconify_Notify (Am_Drawonable* draw, bool iconified) = 0;
	virtual void Frame_Resize_Notify (Am_Drawonable* draw, int left,
						    int top, int right, int bottom) = 0;
	virtual void Destroy_Notify (Am_Drawonable *draw) = 0;
	virtual void Configure_Notify (Am_Drawonable *draw, int left, int top,
						 int width, int height) = 0;
	virtual void Exposure_Notify (Am_Drawonable *draw,
						int left, int top,
						int width, int height) = 0;
	virtual void Input_Event_Notify (Am_Drawonable *draw,
						   Am_Input_Event *ev)=0;
};
The member functions are:

You can set and get the handlers in a particular drawonable using the following functions. If the event handlers are not set for a drawonable, they are inherited from the drawonable it was created from.

void Set_Input_Dispatch_Functions (Am_Input_Event_Handlers* evh)
void Get_Input_Dispatch_Functions (Am_Input_Event_Handlers*& evh)

9.5.2 Input Events

The input event passed to the Input_Event_Notify method is a C++ class containing the position of the mouse when the event occurred, the drawonable of the event, a timestamp, and an Am_Input_Char describing the event. Am_Input_Chars are described in Chapter 5, Interactors and Command Objects for Handling Input.

class Am_Input_Event {
public:
    Am_Input_Char input_char; //the char and modifier bits; see idefs.h
    int x;
    int y;
    Am_Drawonable *draw; // Drawonable this event happened in
    unsigned long time_stamp;
};
You can control which input events are generated for a drawonable using the following member functions of drawonables:

The following routine returns the child-most drawonable at the current cursor position.

Am_Drawonable * Get_Drawonable_At_Cursor()

9.5.2.1 Multiple Click Events

On Unix and the Macintosh, we support multi-click events up to seven clicks. On the PC, we only support single and double clicks. You can control the time threshold interval between mouse downs for multiple click events. The exported global variable Am_Double_Click_Time is the inter-click wait time in milliseconds. The default value is 250. If it is 0, then no double-click processing is done. On the Mac, Am_Double_Click_Time is ignored. The global system click time is used instead. It is usually set from the Mouse Control Panel.

9.5.3 Main Loop

A typical Amulet program calls Am_Initialize (which among other things, calls Get_Root_Drawonable), then sets up a number of objects, and then calls Am_Main_Event_Loop (Section 4.3.4) or Am_Do_Events (Section 4.3.5). These routines then call the Gem level routines where the events are actually processed.

extern bool Am_Main_Loop_Go;

class Am_Drawonable {
public:
    ...
    static void Main_Loop ();
    static void Process_Event (const Am_Time& timeout);
    static void Process_Immediate_Event ();
    static void Wait_For_Event();
    ...
Am_Main_Event_Loop calls Process_Event; Am_Do_Events calls Process_Immadiate_Event. A Gem-level programmer who wants to process events, but not use any of the higher-level Amulet operations like demons and Opal might use Am_Drawonable::Main_Loop(). This just repeatedly calls Am_Drawonable::Process_Event. To stop any of the main loops, you can set the exported bool called Am_Main_Loop_Go to false.

The difference between Process_Event and Process_Immediate_Event is that Process_Event waits for the next event, and processes exactly one input event and all non-input events (like refresh and configure_notify events) before and after that input event before returning. For example:

                  before             after
                xxxIyyyIzzz   --->   Izzz
Process_Event returns when it encounters a second input event, when the queue is empty or, if its timeout parameter is non-zero, when the time specified has expired. The class Am_Time is defined in amulet/include/gdefs.h, and includes a constructor that converts an unsigned long literal from milliseconds to an Am_Time object (Section 6.3).

Process_Immediate_Event does not wait for an event, but processes the first event in the queue and all non-input events after it until an input event is seen. Process_Immedi-ate_Event returns when it encounters an input event (excluding the case where the first event is an input event) or when the queue is empty.

Wait_for_Event waits until the event queue is non-empty. It does no processing.

9.6 Hints for Using Gem Directly

If you are using Gem to make a custom kind of graphical object that you will use with the higher level parts of Amulet, then it may be prudent to create an instance of Am_Rectangle and just override its draw method with whatever you need. Then the draw method will always be called at the appropriate times.

If you using X and want to use Gem directly without the higher level parts of Amulet, note that a window does not appear until it is mapped by the window manager when a Configure_Notify or Exposure_Notify event is processed. Any drawing you do before the window is mapped is lost. In the code that follows, the call to Process_Event forces the Configure_Notify event to be processed, which in turn causes the window to get mapped, and allows the following Draw_Rectangle to succeed.

#include <amulet.h>

Am_Style red    (1.0f, 0.0f, 0.0f);
Am_Style purple (1.0f, 0.0f, 1.0f);

int main (void)
{
  Am_Drawonable *root = Am_Drawonable::Get_Root_Drawonable();
  Am_Drawonable *win = root->Create(30, 50, 400, 400);
  win->Process_Event (0UL); // force the window to be mapped

  win->Draw_Rectangle(red, purple, 30, 40, 50, 50);
  win->Flush_Output();

  // to prevent the program from exiting too soon
  printf("Hit RETURN to exit:");
  getchar();
  win->Destroy();
  return 0;
}


Last Modified: 01:51pm EDT, August 13, 1997