7. 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.

7.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 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 independant graphical toolkit without the overhead of the Amulet object system.

Gem, which stands for the "Graphics and Events Manager", can be used independent 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.

7.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 the input events. For a complete description of Amulet include files and how to use them, see Section 1.6 in the Overview chapter.

7.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 X/11 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.

7.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 X/11, 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:

virtual Am_Drawonable* Create_Offscreen (
	int width = 0, int height = 0,
	Am_Style background_color = Am_No_Style) = 0;

7.3.2 Modifying and Querying Drawonables

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

7.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 X/11 to make the graphics appear. It is not strictly necessary on the PC or Macintosh, but to create machine independant 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.

7.4.1 General drawonable operations

7.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 ration, and so on. Therefore, the following are methods of drawonables rather than of images and font objects.

7.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 7.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.

7.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 7.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 (short left, short top, unsigned short width,
		            unsigned short height) = 0;
  virtual void Push (Am_Region* region) = 0;
  virtual void Push (short left, short top, unsigned short width,
		             unsigned short height) = 0;
  virtual void Pop () = 0;
  virtual void Union (short left, short top, unsigned short width,
	                    unsigned short height) = 0;
  virtual void Intersect (short left, short top, unsigned short width,
	                        unsigned short height) = 0;
  virtual bool In (short x, short y) = 0;
  virtual bool In (short left, short top, unsigned short width,
	                 unsigned short height, bool &total) = 0;
  virtual bool In (Am_Region *rgn, bool &total) = 0;
};

7.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. The supported values for Am_Draw_Function are Am_DRAW_COPY, Am_DRAW_OR and Am_DRAW_XOR.

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.

7.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.

7.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 retreived, 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)

7.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 occured, 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:

7.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 eventss. 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.

7.5.3 Main Loop

The normal 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 or Am_Do_Events (Section 4.3.4). These routines then call a Gem level level routine where the events are actually processed. This routine is Am_Drawonable::Process_Event(). An 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 or when the queue is empty

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.

// Should Am_Drawonable::Main_Loop and Am_Main_Event_Loop keep running?
extern bool Am_Main_Loop_Go;

class Am_Drawonable {
public:
    ...
    static void Main_Loop ();
    static void Process_Event ();
    static void Process_Immediate_Event ();
    ...
Am_Main_Event_Loop uses Process_Event, and Am_Do_Events uses Process_Immediate_Event.


Last Modified: 04:07pm EDT, April 11, 1996