Drawing Objects
***************

General Remarks
===============

An important aspect of a new object class (or a free object) is how to draw it. As indicated above this should happen when the event FL_DRAW is received by the object. The place and size, i.e., the bounding box, of the object are indicated by the object tructure fields obj->x, obj->y, obj->w and obj->h. Forms are drawn in the Forms Library default visual or the user requested visual, which could be any of the X supported visuals. Hence, preferably your classes should run well in all visuals. The Forms Library tries to hide as much as possible the information about graphics mode and, in general, using the built-in drawing routines is the best approach. Here are some details about graphics state in case such information is needed.

All state information is kept in a global structure of type FL_State and there is a total of six such structures, fl_state[6], each for every visual class.

The structure contains among others the following members:

XVisualInfo *xvinfo
     Many properties of the current visual can be obtained from this member.

int depth
     The depth of the visual. Same as what you get from xvinfo.

int vclass
     The visual class, PseudoColor, TrueColor etc.

Colormap colormap
     Current active colormap valid for the current visual for the entire Forms Library (except FL_CANVAS). You can allocate colors from this colormap, but you should never free it.

Window trailblazer
     This is a valid window resource ID created in the current visual with the colormap mentioned above. This member is useful if you have to call, before the form becomes active (thus does not have a window ID), some Xlib routines that require a valid window. A macro, fl_default_window(), is defined to return this member and use of the macro is encouraged.

GC gc[16]
     A total of 16 GCs appropriate for the current visual and depth. The first (gc[0]) is the default GC used by many internal routines and should be modified with care. It is a good idea to use only the top 8 GCs (8-15) for your free object so that future Forms Library extensions won't interfere with your program. Since many internal drawing routines use the Forms Library's default GC (gc[0]), it can change anytime whenever drawing occurs. Therefore, if you are using this GC for some of your own drawing routines make sure to always set the proper value before using it.

The currently active visual class (TrueColor, PseudoColor etc.) can be obtained by the following function/macro:

     int fl_get_form_vclass(FL_FORM *form);
     int fl_get_vclass(void);

The value returned can be used as an index into the array fl_state of FL_State structures. Note that fl_get_vclass() should only be used within a class/new object module where there can be no confusion what the "current" form is.

Other information about the graphics mode can be obtained by using visual class as an index into the fl_state structure array. For example, to print the current visual depth, code similar to the following can be used:

     int vmode = fl_get_vclass();
     printf("depth: %d\n", fl_state[vmode].depth);

Note that fl_state[] for indices other than the currently active visual class might not be valid. In almost all Xlib calls, the connection to the X server and current window ID are needed. The Forms Library comes with some utility functions/macros to facilitate easy utilization of Xlib calls. Since the current version of Forms Library only maintains a single connection, the global variable fl_display can be used where required. However, it is recommended that you use fl_get_display() or FL_FormDisplay(Form *form) instead since the function/macro version has the advantage that your program will remain compatible with future (possibly multi-connection) versions of the Forms Library.

There are a couple of ways to find out the "current" window ID, defined as the window ID the object receiving dispatcher's messages like FL_DRAW etc. belongs to. If the object's address is available, FL_ObjWin(obj) will suffice. Otherwise the function fl_winget() (see below) can be used.

There are other routines that might be useful:

     FL_FORM *fl_win_to_form(Window win);

This function takes a window ID win and returns the form the window belongs to or None on failure.

Color Handling
==============

As mentioned earlier, Forms Library keeps an internal colormap, initialized to predefined colors. The predefined colors do not correspond to pixel values the server understands but are indexes into the colormap. Therefore, they can't be used in any of the GC altering or Xlib routines. To get the actual pixel value the X server understands, use the following routine

     unsigned long fl_get_pixel(FL_COLOR col);

To e.g., get the pixel value of the red color, use

     unsigned long red_pixel;
     red_pixel = fl_get_pixel(FL_RED);

To change the foreground color in the Forms Library's default GC (gc[0]) use

     void fl_color(FL_COLOR col);

To set the background color in the default GC use instead

     void fl_bk_color(FL_COLOR col);

To set foreground or background in GCs other than the Forms Library's default, the following functions exist:

     void fl_set_foreground(GC gc, FL_COLOR col);
     void fl_set_background(GC gc, FL_COLOR col);

which is equivalent to the following Xlib calls

     XSetForeground(fl_get_display(), gc, fl_get_pixel(color));
     XSetBackground(fl_get_display(), gc, fl_get_pixel(color));

To free allocated colors from the default colormap, use the following routine

     void fl_free_colors(FL_COLOR *cols, int n);

This function frees the n colors stored in the array of colormap indices cols. You shouldn't do that for the reserved colors, i.e., colors with indices below FL_FREE_COL1.

In case the pixel values (instead of the index into the colormap) are known, the following routine can be used to free the colors from the default colormap

     void fl_free_pixels(unsigned long *pixels, int n);

Note that the internal colormap maintained by the Forms Library is not updated. This is in general harmless.

To modify or query the internal colormap, use the following routines:

     unsigned long fl_mapcolor(FL_COLOR col, int red, int green, int blue)
     long fl_mapcolorname(FL_COLOR col, const char *name);
     unsigned long fl_getmcolor(FL_COLOR col,
                                int *red, int *green, int *blue);

The first function, fl_mapcolor() sets a the color indexed by color to the color given by the red, green and blue, returning the colors pixel value.

The second function, fl_mapcolorname(), sets the color in the colormap indexed by color to the color named name, where name must be a valid name from the system's color database file rgb.txt. It also returns the colors pixel value or -1 on failure.

The last function, fl_getmcolor(), returns the RGB values of the color indexed by color in the second to third argument pointers and the pixel value as the return value (or -1, cast to unsigned long, on failure).

Mouse Handling
==============

The coordinate system used corresponds directly to that of the screen. But object coordinates are relative to the upper-left corner of the form the object belongs to.

To obtain the position of the mouse relative to a certain form or window, use the routines

     Window fl_get_form_mouse(FL_FORM *form, FL_Coord *x, FL_Coord *y,
                              unsigned *keymask)
     Window fl_get_win_mouse(Window win, FL_Coord *x, FL_Coord *y,
                             unsigned *keymask);

The functions return the ID of the window the mouse is in. Upon return x and y are set to the mouse position relative to the form or window and keymask contains information on modifier keys (same as the the corresponding XQueryPointer() argument).

A similar routine exists that can be used to obtain the mouse location relative to the root window

     Window fl_get_mouse(FL_Coord *x, FL_Coord *y, unsigned *keymask);

The function returns the ID of the window the mouse is in.

To move the mouse to a specific location relative to the root window, use the following routine

     void fl_set_mouse(FL_Coord x, FL_Coord y);

Use this function sparingly, it can be extremely annoying for the user if the mouse position is changed by a program.

Clipping
========

To avoid drawing outside a box the following routine exists:

     void fl_set_clipping(FL_Coord x, FL_Coord y, FL_Coord w, FL_Coord h);

It sets a clipping region in the Forms Library's default GC used for drawing (but not for output of text, see below). x, y, w and h define the area drawing is to restrict to and are relative to the window/form that will be drawn to. In this way you can prevent drawing over other objects.

Under some circumstances XForms also does it's own clipping, i.e., while drawing due to a exposure event. This is called "global clipping". Thus the clipping area you have set via a call of fl_set_clipping() may get restricted even further due this global clipping.

You can check if there's clipping set for the default GC using the function

     int fl_is_clipped(int include_global);

which returns 1 if clipping is switched on and 0 otherwise. The include_global argument tells the function if global clipping is to be included in the answer or not (i.e., if the argument is 0 only clipping set via fl_set_clipping() is reported).

The area currently clipped to is returned by the function

     int fl_get_clipping(int include_global, FL_Coord *x,FL_Coord *y,
                             FL_Coord *width, FL_Coord *height);

On return the four pointer arguments are set to the position and size of the clipping rectangle (at least if clipping is switched on) and the qreturn value of this function is the same as that of fl_is_clipped(). The include_global argument has the same meaning as for fl_is_clipped(), i.e., it controls if the effects of global clipping is included in the results.

When finished with drawing always use

     void fl_unset_clipping(void);

to switch clipping of again.

You also can check and obtain the current settings for global clipping using the functions

     int fl_is_global_clipped(void);
     int fl_get_global_clipping(FL_Coord *x,FL_Coord *y,
                                FL_Coord *width, FL_Coord *height);

Clipping for text is controlled via a different GC and thus needs to be set, tested for and unset using a different set of functions:

     void fl_set_text_clipping(FL_Coord x,FL_Coord y,FL_Coord w,FL_Coord h);
     int fl_is_text_clipped(int include_global);
     int fl_get_text_clipping(int include_global, FL_Coord *x,FL_Coord *y,
                              FL_Coord *width, FL_Coord *height);
     void fl_unset_text_clipping(void);

Finally, there are functions to set and unset the clipping for a specific  GC:

     void fl_set_gc_clipping(GC gc, FL_Coord x, FL_Coord y,
                             FL_Coord width, FL_Coord height);
     void fl_unset_gc_clipping(GC gc);

Please note that setting clipping for a GC will always further restrict the region to the region of global clipping (if it is on at the moment the function is called) and unsetting clipping will still retain global clipping if this is on at the moment the second function is invoked (if it is currently on can be checked using the fl_is_global_clipped()).

Getting the Size
================

To obtain the bounding box of an object with the label taken into account (in contrast to the result of the fl_get_object_geometry() function which doesn't include a label that isn't inside the object the following routine exists:

     void fl_get_object_bbox(FL_OBJECT *obj, FL_Coord *x, FL_Coord *y,
                             FL_Coord *w, FL_Coord *h);

For drawing text at the correct places you will need some information about the sizes of characters and strings. The following routines are provided:

     int fl_get_char_height(int style, int size, int *ascent, int *descent)
     int fl_get_char_width(int style, int size);

These two routines return the maximum height and width of the font used, where size indicates the point size for the font and style is the style in which the text is to be drawn. The first function, fl_get_char_height(), also returns the height above and below the baseline of the font via the ascent and descent arguments (if they aren't NULL pointers). A list of valid styles can be found in Section 3.11.3.

To obtain the width and height information for a specific string use the following routines:

     int fl_get_string_width(int style, int size, const char *str,
                             int len);
     int fl_get_string_height(int style, int size, const char *str,
                              int len, int *ascent, int *descent);

where len is the length of the string str. The functions return the width and height of the string, respectively. The second function also returns the height above and below the fonts baseline if ascent and descent aren't NULL pointers. Note that the string may not contain newline characters '\n' and that the height calculated from the ascent and descent of those characters in the string that extend the most above and below the fonts baseline. It thus may not be suitable for calculating line spacings, for that use the fl_get_char_height() or fl_get_string_dimension() function.

There exists also a routine that returns the width and height of a string in one call. In addition, the string passed can contain embedded newline characters '\n' and the routine will make proper adjustment so the values returned are large enough to contain the multiple lines of text. The height of each of the lines is the fonts height.

     void fl_get_string_dimension(int style, int size, const char *str,
                                  int len, int *width, int *height);

Font Handling
=============

Sometimes it can be useful to get the X font structure for a particular size and style as used in the Forms Library. For this purpose, the following routine exists:

     [const] XFontStruct *fl_get_fontstruct(int style, int size);

The structure returned can be used in, say, setting the font in a particular GC:

     XFontStruct *xfs = fl_get_fontstruct(FL_TIMESBOLD_STYLE, FL_HUGE_SIZE);
     XSetFont(fl_get_display(), mygc, xfs->fid);

The caller is not allowed to free the structure returned by fl_get_fontstruct(), it's just a pointer to an internal structure!

Drawing Functions
=================

There are a number of routines that help you draw objects on the screen. All XForms's internal drawing routine draws into the "current window", defined as the window the object that uses the drawing routine belongs to. If that's not what you need, the following routines can be used to set or query the current window:

     void fl_winset(Window win);
     Window fl_winget(void);

One caveat about fl_winget() is that it can return None if called outside of an object's event handler, depending on where the mouse is. Thus, the return value of this function should be checked when called outside of an object's event handler.

It is important to remember that unless the following drawing commands are issued while handling the FL_DRAW or FL_DRAWLABEL event (which is not generally recommended), it is the application's responsibility to set the proper drawable using fl_winset().

The most basic drawing routines are for drawing rectangles:

     void fl_rectf(FL_Coord x, FL_Coord y, FL_Coord w, FL_Coord h,
                   FL_COLOR col);
     void fl_rect(FL_Coord x, FL_Coord y, FL_Coord w, FL_Coord h,
                  FL_COLOR col);

Both functions draw a rectangle on the screen in color col. While fl_rectf() draws a filled rectangle, fl_rect() just draws the outline in the given color.

To draw a filled (with color col) rectangle with a black border use

     void fl_rectbound(FL_Coord x, FL_Coord y, FL_Coord w, FL_Coord h,
                       FL_COLOR col);

To draw a rectangle with rounded corners (filled or just the outlined) employ

     void fl_roundrectf(FL_Coord x, FL_Coord y, FL_Coord w, FL_Coord h,
                        FL_COLOR col);
     void fl_roundrect(FL_Coord x, FL_Coord y, FL_Coord w, FL_Coord h,
                       FL_COLOR col);

To draw a general polygon, use one of the following routines

     typedef struct {
         short x,
               y;
     } FL_POINT;

     void fl_polyf(FL_POINT *xpoint, int n, FL_COLOR col);
     void fl_polyl(FL_POINT *xpoint, int n, FL_COLOR col);
     void fl_polybound(FL_POINT *xpoint, int n, FL_COLOR col);

fl_polyf() draws a filled polygon defined by n points, fl_polyl() the ouline of a polygon and fl_polybound() a filled polygon with a black outline.

Note: all polygon routines require that the array xpoint has spaces for n+1 points, i.e., one more than then number of points you intend to draw!

To draw an ellipse. either filled, open (with the outline drawn in the given color), or filled with a black border the following routines can be used (use w equal to h to get a circle):

     void fl_ovalf(FL_Coord x, FL_Coord y, FL_Coord w, FL_Coord h,
                   FL_COLOR col);
     void fl_ovall(FL_Coord x, FL_Coord y, FL_Coord w, FL_Coord h,
                   FL_COLOR col);
     void fl_ovalbound(FL_Coord x, FL_Coord y, FL_Coord w, FL_Coord h,
                       FL_COLOR col);

The x and y arguments are the upper left hand corner of the ellipse, while w and h are its width and height.

Note: fl_ovall() (with two 'l') isn't a typo, the trailing 'l' it's meant indicate that only a line will be drawn. And there's also the function

     void fl_ovalf(int fill, FL_Coord x, FL_Coord y, FL_Coord w,
                   FL_Coord h, FL_COLOR col);

which is invoked by both (the macros) fl_ovalf() and fl_ovall() with the first argument fill set to either 1 or 0.

To simplify drawing circles there are three additional functions. The first one draws an (open) circle (with the circumfence in the given color), the second one a filled circle, and the last one a filled circle with a black circumfence:

     void fl_circ(FL_Coord x, FL_Coord y, FL_Coord r, FL_COLOR col);
     void fl_circf(FL_Coord x, FL_Coord y, FL_Coord r, FL_COLOR col);
     void fl_circbound(FL_Coord x, FL_Coord y, FL_Coord r, FL_COLOR col);

Here x and y are the coordinates of the center of the circle, r is its radius and col the color to be used.

To draw circular arcs, either open or filled, the following routines can be used

     void fl_arc(FL_Coord x, FL_Coord y, FL_Coord radius,
                 int start_theta, int end_theta, FL_COLOR col);
     void fl_arcf(FL_Coord x, FL_Coord y, FL_Coord radius,
                  int start_theta, int end_theta, FL_COLOR col);

x and y are the coordinates of the center and r is the radius. start_theta and end_theta are the starting and ending angles of the arc in units of tenths of a degree (where 0 stands for a direction of 3 o'clock, i.e., the right-most point of a circle), and x and y are the center of the arc. If the difference between theta_end and theta_start is larger than 3600 (360 degrees), drawing is truncated to 360 degrees.

To draw elliptical arcs the following routine can be used:

     void fl_pieslice(int fill, FL_Coord x, FL_Coord y, FL_Coord w,
                      FL_Coord h, int start_theta, int end_theta,
                      FL_COLOR col);

x and y are the upper left hand corner of the box enclosing the ellipse that the pieslice is part of and w and h the width and height of that box. start_theta and end_theta, to be given in tenth of a degree, specify the starting and ending angles measured from zero degrees (3 o'clock).

Depending on circumstance, elliptical arc may be more easily drawn using the following routine

     void fl_ovalarc(int fill, FL_Coord x, FL_Coord y, FL_Coord w,
                     FL_Coord h, int theta, int dtheta, FL_COLOR col);

Here theta specifies the starting angle (again measured in tenth of a degree and with 0 at the 3 o'clock position), and dtheta specifies both the direction and extent of the arc. If dtheta is positive the arc is drawn in counter-clockwise direction from the starting point defined by theta, otherwise in clockwise direction. If dtheta is larger than 3600 it is truncated to 3600.

To connect two points with a straight line, use

     void fl_line(FL_Coord x1, FL_Coord y1,
                  FL_Coord x2, FL_Coord y2, FL_COLOR col);

There is also a macro for drawing a line along the diagonal of a box (to draw a horizontal line set h to 1, not to 0):

     void fl_diagline(FL_Coord x, FL_Coord y, FL_Coord w, FL_Coord h,
                      FL_COLOR col);

To draw connected line segments between n points use

     void fl_lines(FL_POINT *points, int n, FL_COLOR col);

All coordinates in points are relative to the origin of the drawable.

There are also routines to draw one or more pixels

     void fl_point(FL_Coord x, FL_Coord y, FL_COLOR col);
     void fl_points(FL_POINT *p, int np, FL_COLOR col);

As usual, all coordinates are relative to the origin of the drawable. Note that these routines are meant for you to draw a few pixels, not images consisting of tens of thousands of pixels of varying colors. For that kind of drawing XPutImage(3) should be used. Or better yet, use the image support in the Forms Library (see Images). Also it's usually better when drawing multiple points to use fl_points(), even if that means that the application program has to pre-sort and group the pixels of the same color.

To change the line width or style, the following convenience functions are available

     void fl_linewidth(int lw);
     void fl_linestyle(int style);

Set lw to 0 to reset the line width to the servers default. Line styles can take on the following values (also see XChangeGC(3))

FL SOLID
     Solid line. Default and most efficient.

FL DOT
     Dotted line.

FL DASH
     Dashed line.

FL DOTDASH
     Dash-dot-dash line.

FL LONGDASH
     Long dashed line.

FL USERDASH
     Dashed line, but the dash pattern is user definable via fl_dashedlinestyle(). Only the odd numbered segments are drawn with the foreground color.

FL USERDOUBLEDASH
     Similar to FL_LINE_USERDASH but both even and odd numbered segments are drawn, with the even numbered segments drawn in the background color (as set by fl_bk_color()).

The following routine can be used to change the dash pattern for FL_USERDASH and FL USERDOUBLEDASH:

     void fl_dashedlinestyle(const char *dash, int ndashes)

Each element of the array dash is the length of a segment of the pattern in pixels (0 is not allowed). Dashed lines are drawn as alternating segments, each with the length of an element in dash. Thus the overall length of the dash pattern, in pixels, is the sum of all elements of dash. When the pattern is used up but the line to draw is longer it used from the start again. The following example code specifies a long dash (9 pixels) to come first, then a skip (3 pixels), a short dash (2 pixels) and then again a skip (3 pixels). After this sequence, the pattern repeats.

     char ldash_sdash[] = {9, 3, 2, 3};
     fl_dashedlinestyle(ldash_sdash, 4);

If dash is NULL or ndashes is 0 (or the dash array contains an element set to 0) a default pattern of 4 pixels on and 4 fixels off is set.

It is important to remember to call fl_dashedlinestyle() whenever FL_USERDASH is used to set the dash pattern, otherwise whatever the last pattern was will be used. To use the default dash pattern you can pass NULL as the dash parameter to fl_dashedlinestyle().

By default, all lines are drawn so they overwrite the destination pixel values. It is possible to change the drawing mode so the destination pixel values play a role in the final pixel value.

     void fl_drawmode(int mode);

There are 16 different possible settings for mode (see a Xlib programming manual for all the gory details). A of the more useful ones are

GXcopy
     Default overwrite mode. Final pixel value = Src

GXxor
     Bitwise XOR (exclusive-or) of the pixel value to be drawn with the pixel value already on the screen. Useful for rubber-banding.

GXand
     Bitwise AND of the pixel value to be drawn with the pixel value already on the screen.

GXor
     Bitwise OR of the pixel value to be drawn with the pixel value already on the screen.

GXinvert
     Just invert the pixel values already on the screen.

To obtain the current settings of the line drawing attributes use the following routines

     int fl_get_linewidth(void);
     int fl_get_linestyle(void);
     int fl_get_drawmode(void);

There are also a number of high-level drawing routines available. To draw boxes the following routine exists. Almost any object class will use it to draw the bounding box of the object.

     void fl_draw_box(int style, FL_Coord x, FL_Coord y,
                      FL_Coord w, FL_Coord h,
                      FL_COLOR col, int bw);

style is the type of the box, e.g., FL_DOWN_BOX. x, y, w, and h indicate the size of the box. col is the color and bw is the width of the boundary, which typically should be given the value obj->bw or FL_BOUND_WIDTH. Note that a negative border width indicates a "softer" up box. See the demo program borderwidth.c for the visual effect of different border widths.

There is also a routine for drawing a frame:

     void fl_draw_frame(int style, FL_Coord x, FL_Coord y,
                        FL_Coord w, FL_Coord h, FL_COLOR col, int bw)

All parameters have the usual meaning except that the frame is drawn outside of the bounding box specified.

For drawing text there are two routines:

     void fl_draw_text(int align, FL_Coord x, FL_Coord y, FL_Coord w,
                       FL_Coord h, FL_COLOR col, int style, int size,
                       const char *str);
     void fl_draw_text_beside(int align, FL_Coord x, FL_Coord y,
                              FL_Coord w, FL_Coord h, FL_COLOR col,
                              int style, int size, const char *str);

where align is the alignment, namely, FL ALIGN LEFT, FL ALIGN CENTER etc. x, y, w and h indicate the bounding box, col is the color of the text, size is the size of the font to use (in points) and style is the font style to be used (see Label Attributes and Fonts, for valid styles). Finally, str is the string itself, possibly containing embedded newline characters.

fl_draw_text() draws the text inside the bounding box according to the alignment requested while fl_draw_text_beside() draws the text aligned outside of the box. These two routines interpret a text string starting with the character @ differently in drawing some symbols instead. Note that fl_draw_text() puts a padding of 5 pixels in vertical direction and 4 in horizontal around the text. Thus the bounding box should be 10 pixels wider and 8 pixels higher than required for the text to be drawn.

The following routine can also be used to draw text and, in addition, a cursor can optionally be drawn

     void fl_draw_text_cursor(int align, FL_Coord x, FL_Coord y,
                              FL_Coord w, FL_Coord h, FL_COLOR col,
                              int style, int size, char *str,
                              FL_COLOR ccol, int pos);

where ccol is the color of the cursor and pos is its position which indicates the index of the character in str before which to draw the cursor (-1 means show no cursor). This routine does no interpretion of the special character @ nor does it add padding around the text.

Given a bounding box and the size of an object (e.g., a label) to draw, the following routine can be used to obtain the position of where to draw it with a certain alignment and including padding:

     void fl_get_align_xy(int align, int x, int y, int w, int h,
                          int obj_xsize, int obj_ysize,
                          int xmargin, int ymargin,
                          int *xpos, int *ypos);

This routine works regardless if the object is to be drawn inside or outside of the bounding box specified by x, y, w and h. obj_xsize and obj->ysize are the width and height of the object to be drawn and xmargin and ymargin is the additional padding to use. xpos and ypos return the position to be used for drawing the object.

For drawing object labels the following routines might be more convenient:

     void fl_draw_object_label(FL_OBJECT *obj)
     void fl_draw_object_label_outside(FL_OBJECT *obj);

Both routines assume that the alignment is relative to the full bounding box of the object. The first routine draws the label according to the alignment, which could be inside or outside of the bounding box. The second routine will always draw the label outside of the bounding box.

An important aspect of (re)drawing an object is efficiency which can result in flicker and non-responsiveness if not handled with care. For simple objects like buttons or objects that do not have "movable parts", drawing efficiency is not a serious issue although you can never be too fast. For complex objects, especially those that a user can interactively change, special care should be taken.

The most important rule for efficient drawing is not to draw if you don't have to, regardless how simple the drawing is. Given the networking nature of X, simple or not depends not only on the host/server speed but also the connection. What this strategy entails is that the drawing should be broken into blocks and depending on the context, draw/update only those parts that need to.