4. Opal Graphics System

This chapter describes simple graphical objects, styles, and fonts in Amulet. ``Opal'' stands for the Object Programming Aggregate Layer. Opal makes it easy to create and manipulate graphical objects. In particular, Opal automatically handles object redrawing when properties of objects are changed.

4.1 Overview

This chapter describes the Opal graphical object system. The text assumes that the reader is familiar with Amulet Objects, Slots, and Constraints, as presented in the Amulet Tutorial and the ORE chapter.

4.1.1 Include Files

There are several data types documented in this chapter, declared in several different header files. You only need to include amulet.h at the top of your files, but some programmers like to look at header files. Here is a list of most of the objects and other data types discussed in this chapter, along with the header file in which they are declared.

For more information on Amulet header files and how to use them, see Section 1.6 in the Overview chapter.

4.2 The Opal Layer of Amulet

Opal, which stands for the Object Programming Aggregate Layer, provides simple graphical objects for use in the Amulet environment. The goal of Opal is to make it easy to create and edit graphical objects. To this end, Opal provides default values for all of the properties of objects, so simple objects can be drawn by setting only a few parameters. If an object is changed, Opal automatically handles refreshing the screen and redrawing that object and any other objects that may overlap it. Objects in Opal can be connected together using constraints, which are relations among objects that are declared once and automatically maintained by the system. An example of a constraint is that a line must stay attached to a rectangle. Constraints are discussed in the Tutorial and the ORE chapter.

Opal is built on top of the Gem module, which is the Graphics and Events Module that refers to machine-specific functions. Gem provides an interface to both X windows and to Windows NT, so applications implemented with Opal objects and functions will run on either platform without modification. Gem is described in chapter 7.

To use Opal, the programmer should be familiar with Amulet objects and constraints as presented in the Tutorial. Opal is part of the Amulet library, so all objects discussed in this chapter are accessible by linking the Amulet library to your program (see the Overview for instructions). Opal will work with any window manager on top of X/11, such as mwm, uwm, twm, etc. Opal provides support for color and gray-scale displays.

Within the Amulet environment, Opal forms an intermediary layer. It uses facilities provided by the ORE object and constraint system, and provides graphical objects that can be combined to build more complicated gadgets. Opal does not handle any input from the keyboard or mouse -- that is handled by the separate Interactors module, which is described in chapter 5. The Amulet Widgets, such as scroll-bars and menus, are partially built out of Opal objects and partly by calling Gem directly (for efficiency). Widgets are generally more complicated than the Opal objects, usually consisting of interactors attached to graphics, and are discussed in chapter 6.

4.3 Basic Concepts

4.3.1 Windows, Objects, and Groups

X/11, Windows NT, and Mac Quickdraw all allow you to create windows on the screen. In X they are called ``drawables'', and in Windows NT and on the Mac they are just called ``windows''. An Opal window is an ORE data structure that contains pointers to these machine-specific structures. Opal windows can be nested inside other windows to form subwindows. Windows clip all graphics so they do not extend outside the window's borders.

Each window defines a new coordinate system with (0,0) in the upper-left corner. The coordinate system uses the pixel as its unit of measurement. Amulet does not support other coordinate spaces, such as inches or centimeters, and it does not support transformations that take the display's aspect ratio into account. Amulet windows are discussed fully in Section 4.10.

The basics of Object Oriented Programming are beyond the scope of this chapter. The objects in Opal use the ORE object system, and therefore operate as a prototype-instance model. This means that each object can serve as a prototype (like a class) for any further instances; there is no distinction between classes and instances. Each graphic primitive in Opal is implemented as an object. When the programmer wants to cause something to be displayed in Opal, it is necessary to create instances of these graphical objects. Each instance remembers its properties so it can be redrawn automatically if the window needs to be refreshed or if objects change.

A group is a special kind of object that holds a collection of other objects. Groups can hold any kind of graphic object including other groups, but an object can only be in one group at a time. Groups form a pure hierarchy. The objects that are in a group are called parts of that group, and the group is called the owner of each of the parts. Groups, like windows, clip their contents to the bounding box defined by their left, top, width, and height. Groups also define their own coordinate system, so that the positions of their parts are relative to the left and top of the group.

The graphical hierarchy in Amulet has Am_Screen, the display device, at its root. Am_Screen's graphical parts are windows. Those windows can contain other windows, groups, or graphical objects as parts. No object is drawn unless Am_Screen is one of its ancestors in the graphical hierarchy. It is important to remember to add all windows to Am_Screen, or to another window on the screen, and to add all groups and objects to a window. If you do not, nothing will be displayed.

Non-graphical objects, like Interactors and Command objects (and application specific objects) can be added as parts to any kind of object, but graphical objects will only be displayed if they are added as parts to an Am_Window or a Am_Group (or an instance of thse objects).

4.3.2 The ``Hello World'' Example

An important goal of Opal is to make it simple to create pictures, hiding most of the complexity of the machine dependant graphics toolkits. We provide default values for all of the properties of graphical objects in Amulet, which makes it easy to use these objects in the most common way, while still providing the option of complete customization for more specialized applications.

A traditional introductory program is called Hello World. This program displays the string "Hello world" in a window on the screen, and automatically refreshes the window if it is obscured. In Amulet, Hello World is a very short program:

#include <amulet.h>

main (void)
{
  Am_Initialize ();

  Am_Screen
    .Add_Part (Am_Window.Create ("window")
      .Set (Am_WIDTH, 200)
      .Set (Am_HEIGHT, 50)
      .Add_Part (Am_Text.Create ("string")
        .Set (Am_TEXT, "Hello World!")));
  
  Am_Main_Event_Loop ();
  Am_Cleanup ();
}
This code is provided in the Amulet source code in the directory samples/hello/. The same source code is used on all three platforms: Unix/X, Windows NT/'95, and Mac.

Note that the Amulet code has a declarative programming feel to it, instead of standard imperative programming. Instead of giving Amulet step by step instructions on how to draw things, such as, ``first draw a window, then draw a string,'' and so on, you tell Amulet, ``Create a window. Create some text, and add it to the window. Go!'' The programmer never calls ``draw'' or ``erase'' methods on objects. Once you set up the graphical world how you want it, and ``Go'' (start the main event loop), Opal automatically draws and erases the the right things at the right time.

Section 4.5 presents all the kinds of objects available in Opal.

4.3.3 Initialization and Cleanup

Amulet requires a call to Am_Initialize() before referencing any Opal objects or classes. This function creates the Opal prototypes and sets up bookkeeping information in Amulet objects and classes. Similarly, a call to Am_Cleanup() at the end of your program allows Amulet to destroy the prototypes and classes, explicitly releasing memory and graphical resources that might otherwise remain occupied after the program exits.

4.3.4 The Main Event Loop

In order for interactors to perceive input from the mouse and keyboard, the main-event-loop must be running. This loop constantly checks to see if there is an event, and processes it if there is one. The automatic redrawing of graphics also relies on the main-event-loop. Exposure events, which occur when one window is uncovered or exposed, cause Amulet to refresh the window by redrawing the objects in the exposed area.

A call to Am_Main_Event_Loop() should be the second-to-last instruction in your program, just before Am_Cleanup(). Your program will continue to run until you tell it otherwise. All Amulet programs running Am_Main_Event_Loop() can be aborted by pressing the escape sequence, which by default is META_SHIFT_F1. Most programs will have a Quit option, in the form of a button or menu item, that calls the Am_Exit_Main_Event_Loop() routine, which will cause the main event loop to terminate.

4.3.5 Am_Do_Events

Amulet does not yet have support for animation, or other mechanisms for connecting to outside applications and databases. To implement these using Amulet, you have to write your own event loop (instead of using Am_Main_Event_Loop) and perform the external or time-based actions yourself. This section explains how to write your own loop.

Normally, in response to an input event, applications will make some changes to graphics or their internal state and then return. Sometimes, however, an application might want to make a graphical change, have it seen by the user while waiting a little, and then make another change. This might be necessary to make something blink a few times, or for a short animation. To do this, an application must call Am_Do_Events() to cause Amulet to update the screen based on all the changes that have happened so far. Eventually, Amulet will contain an Animation Interactor, but it is not implemented yet.

You might also use Am_Do_Events() to create your own event loop when Amulet's default event loop is not adequate. This might happen if you use Amulet with some other toolkit which also has its own main event loo. You might need to monitor non-Amulet queues, processes or inter-process-communication sockets. Calling Am_Do_Events() repeatedly in a loop will cause all the Interactors and Amulet activities to operate correctly, because the standard Am_Main_Event_Loop essentially does the same thing as calling Am_Do_Events() in a loop. Am_Do_Events() never blocks waiting for an event, but returns immediately whether there is anything to do or not. The return boolean from Am_Do_Events tells whether the whole application should quit or not. A main-loop might look like:

main (void) {
Am_Initialize ();
... // do any necessary set up and object creation

// use the following instead of Am_Main_Event_Loop ();
bool continue_looping = true;
while (continue_looping) {
continue_looping = Am_Do_Events();
... // check other queues or whatever
}
Am_Cleanup ();
}

4.4 Slots Common to All Graphical Objects

4.4.1 Left, Top, Width, and Height

All graphical objects have Am_LEFT, Am_TOP, Am_WIDTH, and Am_HEIGHT slots that determine their position and dimensions. Some objects have simple numerical values in these slots, and some have formulas that compute these values based on other properties of the object. Check the section below for a specific object to find its default values for these slots. All values must be ints.

For all graphical objects, Opal never draws outside the bounding box specified by the Am_LEFT, Am_TOP, Am_WIDTH, and Am_HEIGHT of the object.

4.4.2 Am_VISIBLE

The Am_VISIBLE slot of all windows and graphical objects contains a bool value which determines whether the object is visible or not. Objects that are not visible are not drawn on the screen. In a window, Am_VISIBLE controls whether the window is drawn on the screen. To make a group and all of its parts invisible, it is sufficient to set the Am_VISIBLE slot of the group to false.

Invisible objects are typically ignored by interactors and graphics routines. For example, you cannot use interactors to select an invisible object with the mouse, even if you click on the area where the invisible object would appear. Also, invisible parts of a group are generally not taken into account when the size of the group is computed.

4.4.3 Line Style and Filling Style

The Am_LINE_STYLE and Am_FILL_STYLE slots hold instances of the Am_Style class. If an object has a style in its Am_LINE_STYLE slot, it will have a border of that color. If it has a style in the Am_FILL_STYLE slot, it will be filled with that color. Other properties such as line thickness and stipple patterns are determined by the styles in these slots.

Often you do not have to create customized instances of Am_Style to change the color of an object. You can use a predefined style such as Am_Red instead. Styles are fully documented in Section 4.6.

Using special value Am_No_Style or NULL in the Am_LINE_STYLE or Am_FILL_STYLE slot will cause the object to have no border or no fill.

4.4.4 Am_HIT_THRESHOLD and Am_PRETEND_TO_BE_LEAF

The Am_HIT_THRESHOLD, Am_PRETEND_TO_BE_LEAF, and Am_VISIBLE slots are used by functions which search for objects given a rectangular region or an (x,y) coordinate. For example, suppose a mouse click in a window should select an object from a group of objects. When the mouse is clicked, Amulet compares the location of the mouse click with the size and position of all the objects in the window to see which one was selected.

First of all, only visible objects can be selected this way. If an object's Am_VISIBLE slot contains false, it will not respond to events such as mouse clicks with conventional Interactors programming techniques.

The Am_HIT_THRESHOLD slot controls the sensitivity of functions that decide whether an event (like a mouse click) occurred ``inside'' an object. If the Am_HIT_THRESHOLD of an object is 3, then an event 3 pixels away from the object will still be interpreted as being ``inside'' the object. The default value of Am_HIT_THRESHOLD for all Opal objects is 0. Note: it is often necessary to set the Am_HIT_THRESHOLD slot of all groups that are owners of the target object; if an event occurs ``outside'' of a group, then the selection functions will not check the parts of the group.

When the value of a group's Am_PRETEND_TO_BE_LEAF slot is true, then the selection functions will treat that group as a leaf object (even though the group has parts). See Section 4.9.2 regarding the function Am_Point_In_Leaf. Also, consult the Interactors chapter regarding the function Am_Inter_In_Leaf.

4.5 Specific Graphical Objects

The descriptions in this section highlight aspects of each object that differentiate it from other objects. Some properties of Opal objects are similar for all objects, and are documented in Section 4.4. All of the exported objects in Amulet are summarized in chapter 10.

4.5.1 Am_Rectangle

Am_Rectangle is a rectangular shaped object with a border of Am_LINE_STYLE and filled with Am_FILL_STYLE. The default rectangle is a 10 by 10 pixel square located at (0, 0), drawn with black line and fill styles.

4.5.2 Am_Line

Am_Line is a single line segment with endpoints (Am_X1, Am_Y1) and (Am_X2, Am_Y2) drawn in Am_LINE_STYLE (Am_FILL_STYLE is ignored). Am_X1, Am_Y1, Am_X2, Am_Y2, Am_LEFT, Am_TOP, Am_WIDTH, and Am_HEIGHT are constrained in such a way that if any one of them changes, the rest will automatically be updated so the endpoints of the line and its bounding box are always consistent.

4.5.3 Am_Arc

Am_Arc is used to draw circles, ellipses, and arc and pie shaped segments of circles and ellipses. Am_ANGLE1 determines the origin of the segment, measured in degrees counterclockwise from 3 o'clock. Am_ANGLE2 specifies the end of the arc segment, measured in degrees counterclockwise from Am_ANGLE1. Some examples: To draw a circle, Am_ANGLE1 can have any value, but Am_ANGLE2 must be 360 degrees or greater. To draw a semicircle from 12 o'clock counterclockwise to 6 o'clock, Am_ANGLE1 would be 90, and Am_ANGLE2 would be 180.

Arcs are filled as pie pieces to the center of the oval when a colored filling style is provided.

4.5.4 Am_Roundtangle

Instances of the Am_Roundtangle prototype are rectangles with rounded corners.

The slots in this object are the same as Am_Rectangle, with the additional slot Am_RADIUS, which specifies the curvature of the corners. The value of the Am_RADIUS slot can either be an integer, indicating an absolute pixel radius for the corners, or an element of the enumerated type Am_Radius_Flag, indicating a small, medium, or large radius (see table below). The keyword values do not correspond directly to pixel values, but rather compute a pixel value as a fraction of the length of the shortest side of the bounding box.

Figure 4-1 shows the meanings of the slots of Am_Roundtangle. If the value of Am_RADIUS is 0, the roundtangle looks just like a rectangle. If the value of Am_RADIUS is more than half the shortest side (which would mean there is not room to draw a corner of that size), then the corners are drawn as large as possible, as if the value of Am_RADIUS were half the shortest side.

4.5.5 Am_Polygon

The interface to the Am_Polygon object is more complicated than other Opal objects. To specify the set of points that should be drawn for the polygon, you must first create an instance of Am_Point_List with all your (x,y) coordinates, and then install the point list in the Am_POINT_LIST slot of your Am_Polygon object.

Section 4.5.5.1 lists all of the functions that are available for the Am_Point_List class, including how to create point lists and add points to the list. Section 4.5.5.2 provides an example of how to create a polygon using the Am_Polygon object and the Am_Point_List class.

A polygon's point list (Am_POINT_LIST) and bounding box (Am_LEFT, Am_TOP, Am_WIDTH, and Am_HEIGHT) are related by a web constraint that reevaluates whenever one of these slots changes. Whenever one of the bounding box slots is changed, all the points in the point list are translated or scaled so that the resulting polygon has that bounding box. The Am_LINE_STYLE slot is also involved in this constraint, since the polygon's bounding box may change when the thickness or cap style of its border changes.

Conversely, whenever a new point list is installed in the Am_POINT_LIST slot, the bounding box slots are recalculated. If you make a destructive modification to the point list class currently installed in the slot, such as adding a point to it with Add(), then you must call Note_Changed() on the polygon's Am_POINT_LIST slot to reevaluate and redraw the polygon. (For more details about destructive modification of slot values and formula evaluation, see Section 3.11.1.)

Because of the web in the polygons' bounding box and point list slots, the effects of certain Set operations will change depending on the order of the Sets. If you set the Am_POINT_LIST slot of a polygon, and then set its Am_WIDTH and Am_HEIGHT, the polygon will first get the correct point list, and then be scaled to the correct size. If you set the size first, however, that change will be masked by the calculate size of the point list. You should always make changes to the size and location of a polygon after you set its point list, to make sure those changes are actually seen.

The shape defined by an Am_Polygon object does not have to be a closed polygon. To draw a closed polygon, make the first and last points in the Am_Point_List the same. If they are not the same, and a fill style is provided, the border of the fill style will be along an invisible line between the first and last points.

Which sections of a self-intersecting polygon (such as a 5-pointed star) are displayed is controlled by the Am_Fill_Poly_Flag part of the Am_FILL_STYLE used for the polygon. See Section 4.6.3.6.

4.5.5.1 The Am_Point_List Class

Am_Point_List is a list of x-y coordinates, stored as pairs of floats, in the coordinate system of the polygon's owner. It is implemented as a wrapper class with an interface very similar to the standard Am_Value_List class (Section 3.8). Each Am_Point_List object contains a current-element pointer which can be moved forward and backward in the list. The current point is used for retrieving, replacing, inserting, and deleting points in the list.

In addition to the standard list-management functions, Am_Point_List also provides methods for finding the points' bounding box, translating all the points by an x-y offset, and scaling all the points relative to an origin.

The Am_Point_List is designed to be used to specify the vertices of a polygon object. Section 4.5.5.2 contains an example of how to use the Am_Point_List class with the Am_Polygon object

These methods are constructors for Am_Point_List:

These methods add new points to the list. In each case, if destructive modification is desired, pass false as an additional, final argument.

These methods move the current point:

These methods get the current point and other list information:

These methods modify the list at the current point. In each case, if destructive modification is desired, pass false as an additional, final argument.

These methods operate on the list as a set of points. For Translate() and Scale(), if destructive modification is desired, pass false as an additional, final argument.

The member functions of the Am_Point_List class are invoked using the standard C++ dot notation, as in ``my_point_list.Add (10, 20);''.

4.5.5.2 Using Point Lists with Am_Polygon

The list of points for a polygon should be installed in an instance of Am_Point_List, and then that point list should be set into the Am_POINT_LIST slot of your Am_Polygon object. The constructors for Am_Point_List allow you to initialize your point list with some, all, or none of the points that you will eventually use. After the point list has been created, you can add and remove points from it.

Here is an example of a triangle generated by adding points to an empty point list. The point list is then installed in an Am_Polygon object. To see the graphical result of this example, add the triangle_polygon object to a window.

Am_Point_List triangle_pl;
triangle_pl
	.Add (15, 50)
	.Add (45, 10)
	.Add (75, 50);

  Am_Object triangle_polygon = Am_Polygon.Create ("triangle_polygon")
    .Set (Am_POINT_LIST, triangle_pl)
    .Set (Am_LINE_STYLE, Am_Line_2)
    .Set (Am_FILL_STYLE, Am_Yellow);
Here is an example of a five-sided star generated from an array of integers. To see the graphical result of this example, add the star_polygon object to a window.

  static int star_ar[12] = {100, 0, 41, 181, 195, 69, 5, 69, 159, 181,
                            100, 0};

  Am_Point_List star_pl (star_ar, 12);

  Am_Object star_polygon = Am_Polygon.Create ("star_polygon")
    .Set (Am_POINT_LIST, star_pl)
    .Set (Am_FILL_STYLE, Am_No_Style);

4.5.6 Am_Text

The Am_Text object is used to display a single line of text in a specified font. The Am_TEXT slot holds the string to be displayed, and the Am_FONT slot holds an instance of Am_Font.

The Am_CURSOR_INDEX slot determines where a text insertion cursor (a vertical line) will be drawn. The slot contains an integer specifying the position of the cursor, measured in number of characters from the start of the string. A value of zero places the cursor before the text, and a value of Am_NO_CURSOR turns off the cursor.

The Am_WIDTH and Am_HEIGHT slots contain formulas that calculate the size of the object according to the string and the font being displayed. If the value of either of these slots is set, the formula will be removed, and the text object will no longer change size if the string or font is changed. In that case, a formula in the Am_X_OFFSET slot scrolls the text left and right to make sure the cursor is always visible.

The Am_LINE_STYLE slot controls the color of the text and the thickness of the cursor. If a style is provided in the Am_FILL_STYLE slot, then the background behind the text will be filled with that color, otherwise the background is transparent. Setting Am_INVERT to true causes the line and filling styles to be switched, which is useful for ``highlighting'' the text object. If Am_INVERT is true but no fill style is provided, Amulet draws the text as white against a background of the line style color.

4.5.6.1 Fonts

Am_Font is a C++ class defined in gdefs.h. Its creator functions are used to make customized instances describing the desired font. You can create fonts either with standard parameters that are more likely to be portable across different platforms, or by specifying the name of a specific font. The properties of fonts are: family (fixed, serif, or sans-serif), face (bold, italic, and/or underlined), and size. Am_Default_Font, exported from opal.h, is the fixed-width, medium-sized font you would get from calling the Am_Font constructor with its default values. Allowed values of the standard parameters appear below.

In the creator functions for Am_Font, the allowed values for the family parameter are:

The allowed values for the size parameter are:

4.5.6.2 Functions on Text and Fonts

There are additional functions that operate on Am_Text objects, strings, and fonts declared in the header file text_fns.h. These functions are included in the standard Amulet library, but are not automatically included by amulet.h because of their infrequent use. The Am_Text_Interactor uses these functions to edit strings. To access these functions directly, add the following lines to the top of your Amulet program:

#include <am_inc.h> // defines TEXT_FNS__H for machine independance
#include TEXT_FNS__H

4.5.6.3 Editing Text

Text editing is a feature provided by the Interactors module. To make a text object respond to mouse clicks and the keyboard, you need to use an Am_Text_Interactor. For details on this and other Interactors, see chapter 5.

4.5.7 Am_Bitmap

To specify the image that should be drawn by the Am_Bitmap object, you must first create an instance of Am_Image_Array containing the image data, and then install the image in the Am_IMAGE slot of your Am_Bitmap object.

The formulas in the Am_WIDTH and Am_HEIGHT slots reevaluate whenever a new image is installed in the Am_IMAGE slot. If a destructive modification is made to the image class currently installed in the slot, then Note_Changed() will have to be called to cause the formulas to reevaluate and to cause the object to be redrawn (for details about destructive modification of slot values and formula evaluation, see the ORE chapter).

Am_Image_Array is a regular C++ class, defined in gdefs.h. Here is a list of the member functions that are available for the Am_Image_Array class. The creator functions are used to make customized instances containing images described by data arrays or data stored in files. The other functions are for accessing, changing, and saving images to a file. Section 4.5.7.2 contains an example of how to use the Am_Image_Array class with the Am_Bitmap object. Section 4.6.3.7 discusses how to use the Am_Image_Array class with Am_Style.

Am_Image_Array (int percent);            // Halftone pattern (see Section 4.6.2.2)

Am_Image_Array (unsigned int width,      // Solid rectangular image
                unsigned int height,
                int depth,
                Am_Style intial_color);
Am_Image_Array (const char* file_name);  // Image read from a file.

//  The size of an image will be zero until drawn, & depends on the window in which the image is displayed.
void Get_Size (int& width, int& height);

4.5.7.1 Loading Am_Image_Arrays From a File

Am_Image_Array images can be loaded from several different image file formats depending on what platform you're using. Amulet decides which type of image to load based on the filename's suffix.

Full color GIF images are available on all three platforms. Files with names ending in `gif' will be loaded as GIF images. Both 87a and 89a formats are supported, but only the first image in a file is loaded. Amulet reads the GIF colormap from the file, and provides the closest colors available on the display's current color map. Amulet does not install its own colormap, and Amulet does not support changing colormaps or image colors.

On Unix/ X machines, all GIF images are treated as multiple bit depth pixmaps (except on monochrome displays). You cannot specify the foreground and background pixel values for these images, and you cannot draw them transparent. You cannot specify them as stipple fill styles.

PC BMP bitmap images are available only on Windows NT/ `95 machines. On the PC, files with names ending in `bmp' are loaded as Windows bitmap files.

XWindows XBM bitmap format is available only on Unix/ X machines. Under Unix, files with names that do not end in `gif' are assumed to be XBM format bitmaps. These bitmaps are loaded as single bit pixmaps under X, and can be used as stipple fill styles. Specifying foreground and background colors explicitly, and creating transparent bitmaps are possible with XBM format images.

To make your code as machine-independant as possible, you should use GIF image format whenever feasible to prevent conditional compilation of different image filenames. For code examples of using Am_Bitmap with these file formats, see the space sample program in your Amulet directory.

4.5.7.2 Using Images with Am_Bitmap

To display an image whose description is stored in a file, you must first create an instance of Am_Image_Array initialized with the name of the file, and then install that image in the Am_IMAGE slot of your Am_Bitmap object.

4.6 Styles

The C++ class Am_Style is used to specify a set of graphical properties, such as color and line thickness. The Am_LINE_STYLE and Am_FILL_STYLE slots present in all graphical objects hold instances of Am_Style. The definition of the Am_Style class is in gdefs.h, and the predefined styles are listed in opal.h.

There are many predefined styles, like Am_Red, that provide the most common colors and thicknesses. You can also create your own custom styles by calling the constructor functions for Am_Style with your desired parameters. Whether you use a predefined style or create your own, you can set it directly into the appropriate slot of a graphical object. The style placed in the Am_LINE_STYLE slot of a graphical object controls the drawing of lines and outlines, and the style in the Am_FILL_STYLE slot controls the inside of the object.

Instances of Am_Style are immutable, and cannot be changed after they are created.

4.6.1 Predefined Styles

The most frequently used styles are predefined by Amulet. You can use any of the styles listed in this section directly in the Am_LINE_STYLE or Am_FILL_STYLE slot of an object.

The following color styles have line thickness zero (which really means 1, as explained in Section 4.6.3.2)

The following styles are black.

The following styles are all black transparent or black and white opaque fills

4.6.2 Creating Simple Line and Fill Styles

4.6.2.1 Thick Lines

To quickly create black line styles of a particular thickness, you can use the following special Am_Style creator function:

Am_Style::Thick_Line (unsigned short thickness);
For example, if you wanted to create a black line style 5 pixels thick, you could say ``black5 = Am_Style::Thick_Line (5)''. To specify the color or any other property simultaneously with the thickness, you have to use the full Am_Style creator functions discussed in Section 4.6.3.

4.6.2.2 Halftone Stipples

Stippled styles repeat a pattern of ``on'' and ``off'' pixels throughout a line style or fill style. A halftone is the most common type of stipple pattern, where the ``on'' and ``off'' bits are regularly spaced to create darker or lighter shades of a color. When mixing black and white pixels, for example, a 50% stipple of black and white bits will look gray. A 75% stipple will look darker, and a 25% stipple will look lighter. Some gray stipples are predefined in Amulet, and listed in Section 4.6.1. More complicated stipples, such as diamond patterns, are discussed in Section 4.6.3.7.

To create a simple halftone style with a regular stipple pattern, use this special Am_Style creator function:

Am_Style::Halftone_Stipple (int percent,
Am_Fill_Solid_Flag fill_flag = Am_FILL_STIPPLED);
The percent parameter determines the shade of the halftone (0 is white and 100 is black). The fill_flag determines whether the pattern is transparent or opaque (see Section 4.6.3.6). In order to create a halftone that is one-third black and two-thirds white, you could say ``gray33 = Am_Style::Halftone_Stipple (33)''. There are only 17 different halftone shades available in Amulet, so several values of percent will map onto each built-in shade.

4.6.3 Customizing Line and Fill Style Properties

Any property of a style can be specified by creating an instance of Am_Style. The properties are provided as parameters to the Am_Style constructor functions. All the parameters have convenient defaults, so you only have to specify values for the parameters you are interested in. Since styles are used for both line styles and fill styles, some of the parameters only make sense for one kind of style or the other. The parameters that do not apply in a particular drawing situation are silently ignored.

Am_Style (float red, float green, float blue,         //color part
		short thickness = 0,
		Am_Line_Cap_Style_Flag cap_flag = Am_CAP_BUTT,
		Am_Join_Style_Flag join_flag = Am_JOIN_MITER,
		Am_Line_Solid_Flag line_flag = Am_LINE_SOLID,
		const char* dash_list = Am_DEFAULT_DASH_LIST,
		int dash_list_length = Am_DEFAULT_DASH_LIST_LENGTH,
		Am_Fill_Solid_Flag fill_flag = Am_FILL_SOLID,
		Am_Fill_Poly_Flag poly_flag = Am_FILL_POLY_EVEN_ODD,
		Am_Image_Array stipple = Am_No_Image)
  
Am_Style (const char* color_name,                     //color part
		short thickness = 0,
		Am_Line_Cap_Style_Flag cap_flag = Am_CAP_BUTT,
		Am_Join_Style_Flag join_flag = Am_JOIN_MITER,
		Am_Line_Solid_Flag line_flag = Am_LINE_SOLID,
		const char *dash_list = Am_DEFAULT_DASH_LIST,
		int dash_list_length = Am_DEFAULT_DASH_LIST_LENGTH,
		Am_Fill_Solid_Flag fill_flag = Am_FILL_SOLID,
		Am_Fill_Poly_Flag poly_flag = Am_FILL_POLY_EVEN_ODD,
		Am_Image_Array stipple = Am_No_Image)

static Am_Style Thick_Line (unsigned short thickness); 
static Am_Style Halftone_Stipple (int percent,
		Am_Fill_Solid_Flag fill_flag = Am_FILL_STIPPLED); 
The only required parameters for these style constructors are for the colors. Before you read the details below about what all the other parameters mean, be aware that most applications will just use the default values.

4.6.3.1 Color Parameter

The Am_Style constructor functions allow color to be specified in two ways: either with red, green, and blue values, or with a color_name such as ``pink''. The RGB values should be floats between 0.0f and 1.0f, where 1.0f is full on. Gem maintains a table of color names and their corresponding red, green, and blue values. In X, all standard X color names on your server are supported. A small common subset was chosen on the Mac and PC to get color indices.

Amulet does not install its own colormap, to be friendlier to other applications on your system. Not all colors will be available on your platform. When a color is not available on your machine, Amulet finds the closest available color, and uses that instead. On Unix/ X, a warning is printed to your console saying that a color cell could not be allocated. Quitting applications that use many colors will help Amulet applications get the colors they need. On the PC, Windows automatically dithers solid colors together to get a closer match than possible with a single color. This sometimes produces undesirable results, but it is unavoidable.

4.6.3.2 Thickness Parameter

The thickness parameter holds the integer line thickness in pixels. Zero thickness lines are drawn with line thickness one. On some platforms, there may be a subtle difference between lines with thickness zero and lines with thickness one. Zero thickness lines might be drawn on some platforms with a more efficient device-dependent line drawing algorithm than is used for lines with thickness of one or greater. For this reason, a thickness zero line parallel to a thick line may not be as aesthetically pleasing as a line with thickness one.

For instances of Am_Rectangle, Am_Roundtangle, and Am_Arc, increasing the thickness of the line style will not increase the width or height of the object. The object will stay the same size, but the colored boundary of the object will extend inwards to occupy more of the object. Increasing the thickness of the line style of an Am_Line or Am_Polygon (objects which calculate their bounding box, instead of having it provided by the user) will increase the object's width and height. For these objects the thickness will extend outward on both sides of the line or polyline.

4.6.3.3 Cap_Flag Style Parameter

The cap_flag parameter determines how the end caps of line segments are drawn in X11. This parameter is ignored on the PC and Mac. Allowed values are elements of the enumerated type Am_Line_Cap_Style_Flag:

4.6.3.4 Join_Flag Style Parameter

The join_flag parameter determines how corners (where multiple lines come together) are drawn for thick lines as part of rectangle and polygon objects in X11. This parameter is ignored on the PC. This does not affect individual lines (instances of Am_Line) that are part of a group, even if they happen to have the same endpoints. Allowed values are elements of the enumerated type Am_Join_Style_Flag:

4.6.3.5 Dash Style Parameters

The line_flag parameter determines whether the line is solid or dashed, and how the spaces between the dashes should be drawn. Valid values are elements of the enumerated type Am_Line_Solid_Flag:

The dash_list and dash_list_length parameters describe the pattern for dashed lines. The dash_list should be a const char* array that holds numbers corresponding to the pixel length of the ``on'' and ``off'' pixels. The default Am_DEFAULT_DASH_LIST value is {4 4}. A dash_list of {1 1 1 1 3 1} is a typical dot-dot-dash line. A list with an odd number of elements is equivalent to the list being appended to itself. Thus, the dash_list {3 2 1} is equivalent to {3 2 1 3 2 1}.

The following code defines a dash pattern with each ``on'' and ``off'' dash 15 pixels long. To see the result of this code, store the thick_dash style in the Am_LINE_STYLE slot of a graphical object.

  static char thick_dash_list[2] = {15, 15};
  Am_Style thick_dash ("black", 8, Am_CAP_BUTT, Am_JOIN_MITER,
		       Am_LINE_ON_OFF_DASH, thick_dash_list);
Dashed and dotted lines are not supported on the Macintosh. In a future version of Amulet, we will either support the current implementation of dashed and dotted lines on all platforms, or more likely, we'll provide a few predefined dashed and dotted line styles. The dotted and dashed line styles are guaranteed to look different from each other and from normal lines, but we will not provide as much support for complex dashed lines. To maintain forward compatability, you should only use the predefined dotted and dashed line styles Am_Dashed_Line and Am_Dotted_Line.

4.6.3.6 Fill Style Parameters

The fill_flag determines the way ``off'' pixels in the stippled pattern (see Section 4.6.3.7) will be drawn. The ``on'' pixels are always drawn with the color of the style. Allowed values are elements of the enumerated type Am_Fill_Solid_Flag:

The value of the poly_flag parameter should be an element of the enumerated type Am_Fill_Poly_Flag, either Am_FILL_POLY_EVEN_ODD or Am_FILL_POLY_WINDING. This parameter controls the filling for self-intersecting polygons, like the five-pointed star example in Section 4.5.5.2.

4.6.3.7 Stipple Parameters

A stippled style consists of a small pattern of ``on'' and ``off'' pixels that is repeated throughout the border or filling of an object. The simplest stipple pattern is the halftone, discussed in Section 4.6.2.2. You should only need to specify the stipple parameter in the full Am_Style creator functions when you are specifying some other property (like color) along with a non-solid stipple, or you are specifying an unconventional image for your stipple pattern.

The value of the stipple parameter should be an instance of Am_Image_Array. An image array holds the pattern of bits, which can either be a standard halftone pattern or something more exotic. The creator functions and other member functions for Am_Image_Array are discussed in Section 4.5.7.1.

On Unix/X, only XBM images can be used as stipples. GIF stipples and transparent GIFs are not currently supported on Unix.

Here is an example of a colored style with a 50% halftone stipple, created using the halftone initializer for Am_Image_Array:

Am_Style red_stipple ("red", 8, Am_CAP_BUTT, Am_JOIN_MITER, Am_LINE_SOLID,
				  Am_DEFAULT_DASH_LIST, Am_DEFAULT_DASH_LIST_LENGTH,
				  Am_FILL_STIPPLED, Am_FILL_POLY_EVEN_ODD,
				  (Am_Image_Array (50)) );
Here is an example of a stipple read from a file. The ``stripes'' file contains a description of a bitmap image. On Unix, the ``stripes'' file must be in X11 bitmap format (i.e., generated with the Unix bitmap utility). On the PC, the ``stripes'' file must be in either BMP or GIF format. This means that for portable code, you will need to use the #ifdef macro to load different files depending on your platform.

Am_Style striped_style ("black", 8, Am_CAP_BUTT, Am_JOIN_MITER,
				    Am_LINE_SOLID, Am_DEFAULT_DASH_LIST,
				    Am_DEFAULT_DASH_LIST_LENGTH, Am_FILL_STIPPLED,
				    Am_FILL_POLY_EVEN_ODD,
				    (Am_Image_Array ("stripes")) );

4.6.4 Getting Style Properties

You can query certain properties of an already-created style. This is useful if you want to create a style with the same color as another style, but with a different line thickness, for example.

Here are the Am_Style methods available for getting the values of a style's properties:

void Get_Values (float& red, float& green, float& blue) const;
void Get_Values (short& thickness,
Am_Line_Cap_Style_Flag& cap, Am_Join_Style_Flag& join,
Am_Line_Solid_Flag& line_flag,const char*& dash_l,
int& dash_l_length, Am_Fill_Solid_Flag& fill_flag,
Am_Fill_Poly_Flag& poly, Am_Image_Array& stipple) const;

void Get_Values (float& r, float& g, float& b, short& thickness,
Am_Line_Cap_Style_Flag& cap, Am_Join_Style_Flag& join,
Am_Line_Solid_Flag& line_flag, const char*& dash_l,
int& dash_l_length,Am_Fill_Solid_Flag& fill_flag,
Am_Fill_Poly_Flag& poly,Am_Image_Array& stipple) const;

Am_Fill_Solid_Flag Get_Fill_Flag() const;
Am_Image_Array Get_Stipple() const;
Am_Fill_Poly_Flag Get_Fill_Poly_Flag () const;

//Get the properties needed to calculate the line width
void Get_Line_Thickness_Values (short& thickness,
Am_Line_Cap_Style_Flag& cap) const;

const char* Get_Color_Name () const; //returns a pointer to the string, don't dealloc

4.7 Groups

Groups hold a collection of graphical objects (possibly including other groups). The objects in a group are called its parts, and the group is the owner of each of its parts. The concept of part/owner relationships was introduced in the ORE chapter, but groups treat their parts specially, by drawing them in windows. In Opal, the part-owner hierarchy corresponds to a graphical hierarchy, when graphical objects are involved.

Here are the slots of Am_Group:

Groups define their own coordinate system, meaning that the left and top of their parts is offset from the origin of the group. Changing the position of the group translates the position of all its parts. By using a special group called Am_Resize_Parts_Group, you can also have the group scale the size of its parts when the group's size changes.

Groups also clip their parts to the bounding box of the group. Parts of objects that extend outside the left, top, width, or height of the group are not drawn. The default width and height of a group is 10x10, so you must be careful to set the Am_WIDTH and Am_HEIGHT slot of your instances of Am_Group. The predefined constraints Am_Width_Of_Parts and Am_Height_Of_Parts may be used to compute the size of a group based on its parts

4.7.1 Adding and Removing Graphical Objects

The read only Am_GRAPHICAL_PARTS slot of a group contains an Am_Value_List of objects. It can be used to iterate over the graphical parts of an object. Parts of a group should be manipulated with the following member functions defined on Am_Object:

Am_Object Add_Part (Am_Object new_part, bool inherit = true);
Am_Object Add_Part (Am_Slot_Key key, Am_Object new_part);

void Remove_Part (Am_Slot_Key key);
void Remove_Part (Am_Object part);
Graphical parts added to an object with Add_Part will automatically be added to the Am_GRAPHICAL_PARTS list of the object.

Parts of an object may or may not be inherited (instantiated) in an instance of that object. All parts added with a slot key (using the second form of Add_Part, above) to an owner are instantiated in instances of the owner. Parts added without a slot key (using the first form of Add_Part, above) are only instantiated in instances of their owner if the second parameter, inherit, is true (the default is true).

Parts that are added with a slot key can be accessed with Get_Part() or Get(). It is often convenient to provide slot keys for parts so that functions and formulas can easily access these objects in their groups. All graphical parts can be accessed through the Am_GRAPHICAL_PARTS list.

You can add any kind of object (graphical, non-graphical, windows, screens) as a part of any other object. However, things only ``work right'' if graphical objects are added to groups or windows, which are added to windows or the screen.

4.7.2 Layout

Most groups do not use a layout procedure. In these groups, each part has its own left and top, which places it at some user-defined position relative to the left and top of the group.

It is sometimes convenient for the group itself to decide where its graphical parts should be located. If the parts should all be in a row or column, it's often easier and more extensible to tell the group that all of its parts should be arranged in a specific way, than to try to keep track of the locations of all of the objects in the group individually. To support this, a formula in the Am_LAYOUT slot of an Am_Group object can lay out all of the parts of the group. This formula operates by directly setting the Am_LEFT and Am_TOP of the parts.

4.7.2.1 Vertical and Horizontal Layout

Amulet provides two built-in layout procedures:

Am_Formula  Am_Vertical_Layout
Am_Formula  Am_Horizontal_Layout
These layout procedures arrange the parts of a group according to the values in the slots listed below. To arrange the parts of a group in a vertical list (like a menu), set the Am_LAYOUT slot to Am_Vertical_Layout. You may then want to set other slots of the group, like Am_V_SPACING, to control things like the spacing between parts or the number of columns.

These procedures set values in the Am_LEFT and Am_TOP slots of the graphical parts of the group, overriding whatever values (or formulas) were there before.

The slots that control layout when using the standard vertical or horizontal layout procedures are:

The following will create a group with a column containing a rectangle and a circle:

Am_Object my_group = Am_Group.Create ("my_group")
    .Set (Am_LEFT, 10)
    .Set (Am_TOP, 10)
    .Set (Am_LAYOUT, Am_Vertical_Layout)
    .Set (Am_V_SPACING, 5)
    .Add_Part(Am_Rectangle.Create())
    .Add_Part(Am_Circle.Create());

4.7.2.2 Custom Layout Procedures

You can provide a customized layout procedure for arranging the parts of a group. The procedure should be defined as a constraint, using Am_Define_Formula or a related function, and the constraint should be installed in the Am_LAYOUT slot of the group. The parts of the group should be arranged as a side effect of evaluating the formula (the return value is ignored). To do this, GV the list in the Am_GRAPHICAL_PARTS slot (which is in Z-order) and iterate through it, setting each part's Am_LEFT and Am_TOP slots appropriately.

4.7.3 Am_Resize_Parts_Group

This group operates like a regular Am_Group except that if the width or height of the Am_Resize_Parts_Group is changed, then the width and height of all the components is scaled proportionately. The width and height of the Am_Resize_Parts_Group should not be a formula depending on the parts, and the parts should not have formulas in their width and height slots. Instead, the width and heights will usually be integers, with the original size of the group set to be the correct size based on the parts. Be sure to adjust the width and height of a Am_Resize_Parts_Group when new parts are added or removed. Since you cannot use formulas that depend on the parts' sizes, you must explicitly make sure the group is always big enough to hold its parts.

All of the parts of a Am_Resize_Parts_Group are expected to be able to resize themselves when their width and heights are set. It is fine to use a Am_Resize_Parts_Group in another one, but it would usually be an error to include a regular Am_Group inside a Am_Resize_Parts_Group.

The Am_Resize_Parts_Group is created automatically by the Am_Graphics_Group_Command when the user selects a number of objects and executes a ``Group'' command (see Section 6.4 in the Widgets chapter).

Note: the Am_Resize_Parts_Group currently scales the parts using integers, so if the size gets very small, the parts will not retain their original proportions, and if the size of the group goes to zero, the objects will stay small forever.

4.8 Maps

The Am_Map object is a special kind of group that generates multiple graphical parts from a single prototype object. Maps should be used when all the parts of a group are similar enough that they can be generated from one prototype object (for example, they are all rectangles, or all the same kind of group.). This part-generating feature of maps is often used in conjunction with the layout feature of groups, in a situation such as arranging the selectable text items in a menu. For details on laying out the components of groups and maps, see Section 4.7.2.

You must set two slots in the map to control the parts that are generated:

There are two slots automatically installed in each of the generated parts, that are useful for distinguishing the parts from each other. These slots can be referenced by formulas in the item prototype to make each part different.

The Am_RANK of each created part is set with the count of this part. The Am_RANK of the first part created is set to 0, the second part's Am_RANK is set to 1, and so on. If the Am_ITEMS slot of the map contains an Am_Value_List, then the Am_ITEM (note: singular) slot of each created part is set with the corresponding element of the list.

The following code defines a map whose Am_ITEMS slot is a number. The map generates 4 rectangles, whose fill styles are determined by the formula map_fill_from_rank. The formula computes a halftone fill from the value stored in the Am_RANK slot of the part, which was installed by the map as the part was created. This uses a horizontal layout formula so the rectangles will be in a row.

// Formulas are defined in the global scope, outside of main()
Am_Define_Style_Formula (map_fill_from_rank) {
int rank = self.GV (Am_RANK);
return Am_Style::Halftone_Stipple (20 * rank);
}
...

// This code is inside main()
Am_Object my_map = Am_Map.Create ("my_map")
.Set (Am_LEFT, 10)
.Set (Am_TOP, 10)
.Set (Am_LAYOUT, Am_Horizontal_Layout)
.Set (Am_H_SPACING, 5)
.Set (Am_ITEMS, 4)
.Set (Am_ITEM_PROTOTYPE, Am_Rectangle.Create ("map item")
.Set (Am_FILL_STYLE, map_fill_from_rank)
.Set (Am_WIDTH, 20)
.Set (Am_HEIGHT, 20));
The next example defines a map whose Am_ITEMS slot contains a list of strings. The map generates 4 text objects, whose text strings are determined by the object's Am_ITEM slot.

// This code is inside main()
Am_Object my_map = Am_Map.Create ("my_map")
  .Set (Am_LEFT, 10)
  .Set (Am_TOP, 10)
  .Set (Am_LAYOUT, Am_Vertical_Layout)
  .Set (Am_V_SPACING, 5)
  .Set (Am_ITEMS, Am_Value_List ()
        .Add (``This is the first item in the map.'')
        .Add (``I'm number two'')
        .Add (``Three'')
        .Add (``The last item in the list.''))
  .Set (Am_ITEM_PROTOTYPE, Am_Text.Create ("map text item")
        .Set (Am_ITEM, ````) // initialize the slot so the formula won't crash
        .Set (Am_TEXT, Am_Same_As(Am_ITEM))
       );
To add another item to the map in the second example, you could install a new list in the Am_ITEMS slot containing all the old items plus the new one:

my_map.Make_Unique (Am_ITEMS); //in case slot shared with another object
Am_Value_List map_items = (Am_Value_List) my_map.Get (Am_ITEMS);
map_items.Add (``A new item!'');
my_map.Set (Am_ITEMS, map_items);
A more efficient way to add an item to the list is to destructively modify the list that is already installed (note the use of the false parameter in the Add method for Am_Value_List):

Am_Value_List map_items = (Am_Value_List) my_map.Get (Am_ITEMS);
map_items.Add (``A new item!'', false); // false means destructively modify, don't copy.
my_map.Note_Changed (Am_ITEMS);
The list in the Am_ITEMS slot can also be calculated with a formula, and the items in the map will change whenever the formula is reevaluated.

4.9 Methods on all Graphical Objects

4.9.1 Reordering Objects

As you add objects to a group or window, each new object by default is on top of the previous one. This is called the ``Z'' or ``stacking'' or ``covering'' order.

The following functions are useful for changing the Z order of an object among its siblings. For example, Am_To_Top(obj) will bring an object to the front of all of the other objects in the same group or window. To promote an object just above a certain target object, use Am_Move_Object (obj, target_obj, true). These functions work for windows as well as for regular graphical objects.

void Am_To_Top (Am_Object object);
void Am_To_Bottom (Am_Object object);
void Am_Move_Object (Am_Object object, Am_Object ref_object,
                      bool above = true); // false means below ref_object

4.9.2 Finding Objects from their Location

The following functions are useful for determining whether an object is under a given (x,y) coordinate:

bool Am_Point_In_All_Owners(Am_Object in_obj, int x, int y,
				   	Am_Object ref_obj);

Am_Object Am_Point_In_Obj (Am_Object in_obj, int x, int y,
				       Am_Object ref_obj);

Am_Object Am_Point_In_Part (Am_Object in_obj, int x, int y,
				        Am_Object ref_obj,
					  bool want_self = false,
				 	  bool want_groups = true);

Am_Object Am_Point_In_Leaf (Am_Object in_obj, int x, int y,
				        Am_Object ref_obj,
				   	  bool want_self = true,
				   	  bool want_groups = true);
Am_Point_In_All_Owners checks whether the point is inside all the owners of object, up to the window. Also validates that all of the owners are visible. If not, returns false. Use this to make sure that the user is not pressing outside of an owner since the other operations do not check this.

Am_Point_In_Obj checks whether the point is inside the object. It ignores covering (i.e., it just checks whether point is inside the object, even if the object is behind another object). If the point is inside, the object is returned; otherwise the function returns Am_No_Object. The coordinate system of x and y is defined with respect to ref_obj, that is, the origin of x and y is the left and top of ref_obj.

Am_Point_In_Part() finds the front-most (least covered) immediate part of in_obj at the specified location. If there is no part at that point, it returns Am_No_Object. The coordinate system of x and y is defined with respect to ref_obj. If there is no part at that point, then if want_self then if inside in_obj, returns in_obj. If not want_self or not inside in_obj, returns Am_No_Object. The coordinate system of x and y is defined with respect to ref_obj. If want_groups is true, then returns the part even if it is a group. If want_groups is false, then will not return a group (so if x,y is not over a ``primitive'' object, returns Am_No_Object).

Am_Point_In_Leaf() is similar to Am_Point_In_Part(), except that the search continues to the deepest part in the group hierarchy (i.e., it finds the leaf-most object at the specified location). If (x,y) is inside the bounding box of in_obj but not over a leaf, it returns in_obj. The coordinate system of x and y is defined with respect to ref_obj. Sometimes you will want a group to be treated as a leaf in this search even though it isn't really a leaf. In this case, you should set the Am_PRETEND_TO_BE_LEAF slot to true for each group that should be treated like a leaf. The search will not look through the parts of such a group, but will return the group itself. The slots want_self and want_groups work the same as for Am_Point_In_Part.

Am_Point_In_Part() and Am_Point_In_Leaf() use the function Am_Point_In_Obj() on the parts.

4.9.3 Beeping

void Am_Beep (Am_Object window = Am_No_Object);
This function causes the computer to emit a ``beep'' sound. Passing a specific window is useful in Unix, when several different screens might be displaying windows, and you only want a particular screen displaying a particular window to beep.

4.9.4 Filenames

char *Am_Merge_Pathname (char *name);
Am_Merge_Pathname() takes a filename as a parameter, and returns the full Amulet directory pathname prepended to that argument. For example, ``Am_Merge_Pathname (``lib/images/ent.bmp'')'' will return the full pathname to the PC compatible Enterprise bitmap included with the Amulet source files.

On the Macintosh, Am_Merge_Pathname automatically converts the Unix-standard path separation character ``/'' into the Mac-specific path separator ``:''. In Windows NT/'95, this conversion is done automatically by the OS. On all systems, you should specify pathnames with slashes (``/'') as the path separator to avoid machine dependancy.

4.9.5 Translate Coordinates

bool Am_Translate_Coordinates (Am_Object src_obj, int src_x, int src_y,
                Am_Object dest_obj, int& dest_x, int& dest_y,
                Am_Constraint_Context& cc = *Am_Empty_Constraint_Context);
Am_Translate_Coordinates() converts a point in one object's coordinate system to that of another object. It works for windows and all graphical objects. If the objects are not comparable (windows on separate screens, or windows not attached to any screen) then the function will return false. Otherwise, it will return true and dest_x and dest_y will contain the converted coordinates.

The destination coordinates are for the inside of dest_obj. This means that if obj was at src_x, src_y with respect to the left and top of src_obj, and you remove it from src_obj and add it to dest_obj at dest_x, dest_y then it will be at the same physical screen position. You can provide an Am_Constraint_Context parameter inside a formula to make the formula dependent on the relative positions of the objects.

Since each group and window defines its own coordinate system, you must use Am_Translate_Coordinates whenever you define a formula that depends on the left or top of an object that might be in a different group or window.

4.10 Windows

Objects are added to windows in the same way they're added to groups, with Add_Part(). All graphical objects added to a window will be displayed in that window. When a window is added as a part to another window, it becomes a subwindow. Subwindows do not have any window manager decoration (title bars).

4.10.1 Slots of Am_Window

The initial values of Am_LEFT, Am_TOP, Am_WIDTH, and Am_HEIGHT determine the size and position of the window when it appears on the screen. These slots can be set later to change the window's size and position. If the user changes the size or position of a window using the window manager (e.g., using the mouse), this will be reflected in the values for these slots.

Note that under Unix/X, it's not always possible to know exactly where a window is on the screen. Some window managers specify screen position as the location of the titlebar, some specify it as the location of the client region, and some allow the user to choose the coordinate reference system. It's impossible for Amulet to enumerate all the possible things that a window manager might do, and take them into account. In this case, our goal is to have code that never breaks and that maintains internal consistency.

The Am_FILL_STYLE determines the background color of the window. All parameters of Am_Style that affect fillings, including stipples, affect the fill style of windows. Using the fill style of a window is more efficient than putting a window-sized rectangle behind all the other objects in the window.

When values are installed in the Am_MAX_WIDTH, Am_MAX_HEIGHT, Am_MIN_WIDTH, or Am_MIN_HEIGHT slots, and the corresponding Am_USE_MAX_WIDTH, Am_USE_MAX_HEIGHT, Am_USE_MIN_WIDTH, or Am_USE_MIN_HEIGHT slot is set to true, then the window manager will make sure the user is not allowed to change the window's size to be outside of those ranges. You can still set the Am_WIDTH and Am_HEIGHT to be any value, but the window manager will eventually clip them back into the allowed range.

When Am_QUERY_POSITION or Am_QUERY_SIZE are set to true, then the user will have the opportunity to place the window on the screen when the window is first added to the screen, clicking the left mouse button to position the left and top of the window, and dragging the mouse to the desired width and height.

The border widths applied to the window by the window manager are stored in the Am_LEFT_BORDER_WIDTH, Am_TOP_BORDER_WIDTH, Am_RIGHT_BORDER_WIDTH, and Am_BOTTOM_BORDER_WIDTH. These slots are read only, set by Amulet when the window becomes visible on the screen.

The Am_OMIT_TITLE_BAR slot tells whether the Amulet window should have a title bar. If the slot has value false (the default), and the window manager permits it, then the window will have a title bar; otherwise the window will not have a title bar.

In the rare case when you want to have graphics drawn on a parent window appear over the enclosed (child) windows, you can set the Am_CLIP_CHILDREN slot of the parent to be true. Then any objects that belong to that window will appear on top of the window's subwindows (rather than being hidden by the subwindows).

When the Am_DOUBLE_BUFFER slot is set to true (the default), Opal updates the window by first drawing everything offscreen, and then copying the region over the old contents of the window. This is slightly slower than updating without double buffering and it uses a lot of memory, because you need to copy the contents of the offscreen buffer back to the screen. However, using double buffering is much more visually pleasing, and flicker is reduced tremendously, because you don't see each object being drawn to the screen in succession; it all appears at once.

4.10.2 Destroying windows

Users might destroy Amulet windows using the window manager's Kill-Window command or close box. This action is converted into a destroy message to the window. By default this deletes the window, but you can override the default with other behaviors by providing a new destroy method. The Am_DESTROY_WINDOW_METHOD slot of Am_WINDOW holds an Am_Object_Method that is called when the window is destroyed. The default method is:

Other predefined methods may be useful for various applications (defined in opal.h):

To actually destroy a window object, just use the regular object destroy method: win.Destroy(). This does not cause the Am_DESTROY_WINDOW_METHOD to be called. That is only called when the user destroys the window using the window manager's commands.

4.11 Am_Screen

Windows are not visible until they are added to the screen. The Am_Screen object can be thought of as a root window to which all top-level windows are added. In the ``hello world'' example of Section 4.3.2, the top-level window is added to Am_Screen with a call to Add_Part().

Am_Screen can be used in calls to Am_Translate_Coordinates() to convert from window coordinates to screen coordinates and back again.

4.12 Predefined formula constraints

Opal provides a number of constraints that can be put into slots of objects that might be useful. Some of these constraints were described in previous sections.

Am_Fill_To_Bottom - Put in an object's Am_HEIGHT slot, causes the object to size itself so it's tall enough to fill to the bottom of its owner. Am_Fill_To_Bottom leaves a border below the object, with a size equal to the object's Am_Y_OFFSET slot.
Am_Fill_To_Right - Analogous to Am_Fill_To_Bottom, used in the Am_WIDTH slot of an object. The Am_X_OFFSET slot of the object is used to measure the border to the right of the object.
Am_Width_Of_Parts - Useful for computing the width of a group: returns the distance between the group's left and the right of its rightmost part. You might put this into a group's Am_WIDTH slot.
Am_Height_Of_Parts - Analogous to Am_Width_Of_Parts, but for the Am_HEIGHT of a group.
Am_Right_Is_Right_Of_Owner - Useful for keeping a part at the right of its owner. Put this formula in the Am_LEFT slot of the part.
Am_Bottom_Is_Bottom_Of_Owner - Useful for keeping a part at the bottom of its owner. Put this formula in the Am_TOP slot of the part.
Am_Center_X_Is_Center_Of_Owner - Useful for centering a part horizontally within its owner. Put this formula in the Am_LEFT slot of the part.
Am_Center_Y_Is_Center_Of_Owner - Useful for centering a part vertically within its owner. Put this formula in the Am_TOP slot of the part.
Am_Center_X_Is_Center_Of - Useful for horizontally centering obj1 inside obj2. Put this formula in the Am_LEFT slot of obj1, and put obj2 in the Am_CENTER_X_OBJ slot of obj1.
Am_Center_Y_Is_Center_Of - Useful for vertically centering obj1 inside obj2. Put this formula in the Am_TOP slot of obj1, and put obj2 in the Am_CENTER_Y_OBJ slot of obj1.
Am_Horizontal_Layout - Constraint which lays out the parts of a group horizontally in one or more rows. Put this into the Am_LAYOUT slot of a group.
Am_Vertical_Layout - Constraint which lays out the parts of a group vertically in one or more columns. Put this into the Am_LAYOUT slot of a group.
Am_Same_As (Am_Slot_Key key) - This slot gets its value from the specified slot (key) in the same object. Equivalent to { return self.GV(key); }
Am_From_Owner (Am_Slot_Key key) - This slot gets its value from the specified slot (key) in the object's owner. Equivalent to { return self.GV_Owner().GV(key); }
Am_From_Part (Am_Slot_Key part, Am_Slot_Key key) - This slot gets its value from the specified slot (key) in the specified part (part) of this object. Equivalent to
{ return self.GV_Part(part).GV(key); }
Am_From_Sibling (Am_Slot_Key sibling, Am_Slot_Key key) - This slot gets its value from the specified slot (key) in the specified sibling (sibling) of this object. Equivalent to { return self.GV_Sibling(sibling).GV(key); }


Last Modified: 03:21pm EDT, May 24, 1996