home / libGx

Overview

Most of libGx's owner widgets can have multiple children. The order in which the children are constructed is important, as child widgets are placed in the same order as they are constructed.

LibGx has a variety of special purpose geometry control classes that override the default algorithm like GxRow GxColumn, GxTable, etc.

Default Placement Algorithm

The default placement algorithm for each parent works by starting with four integers initialized as follows:

int lX = 0;        /* left X */
int rX = width; /* right X */
int tY = 0; /* top Y */
int bY = height; /* bottom Y */

These integers represent the area available for placement within the parent window (Parent's Available Area). The parent then traverses its list of children, passing references to these four integers into each of its children's Place() functions. The children (may) then pass these four integers into a pluggable GxGeomControl module that place the child within the parent's available area. The children then (can) update the four integers to guide subsequent children's placement in the reduced parent's area.

If a child was not given a GxGeomControl module, its default action is to size and place itself to consume the full parent's available area . This of course leaves no area to place other children in the parents children list.  This is ok much of the time as the default behavior can be exploited.

Two important GxGeomControl modules that implement the default placing behavior are the GxBasic and the GxSIBasic classes. Both modules work similarly, except the GxSIBasic module can specify an additional 'whitespace' border to be defined around the child it is plugged into.

The GxBasic's constructor is as follows.

GxBasic::GxBasic(GX_WD_STAT tPWidth, GX_HT_STAT tPHeight, GX_H_PLACEMENT tHPlace, GX_V_PLACEMENT tVPlace,
bool tHReset = false, bool tVReset = false);

where the enums used as parameters are defined as:

enum GX_WD_STAT{GX_WD_FIXED, GX_WD_FILL, GX_WD_INT};
enum GX_HT_STAT{GX_HT_FIXED, GX_HT_FILL, GX_HT_INT};
enum GX_H_PLACEMENT{GX_H_FIXED, GX_H_CENTERED, GX_FLOW_LEFT, GX_FLOW_RIGHT};
enum GX_V_PLACEMENT{GX_V_FIXED, GX_V_CENTERED, GX_FLOW_UP, GX_FLOW_DOWN};

The child's width is established by the tPWidth variable which may have one of the following values:

GX_WD_FIXED 
The Width() of the child is not modified
GX_WD_FILL 
The child is expanded in width until it fills the area of the parent
GX_WD_INT 
The child's width is set to the value returned by pChild->GetDesiredWidth()

The child is placed in X (after it has been sized for width) based on the value of tHPlace which may have one of the following (self explanatory) values: GX_FLOW_LEFT, GX_FLOW_RIGHT, GX_H_CENTERED.

When an child is placed so that it flows to the left, it is moved to the minimum x value in the parents specified area ie. pChild->X(lX). When a child is placed so that it flows to the right it is moved so that its right edge is aligned with the right edge in the parents available area I.e. pChild->X( rX – pChild->Width();

The child's height is established by the tPHeight variable which may have one of the following values:

GX_HT_FIXED 
The Height() of the child is not modified
GX_HT_INT 
The Height() of the child is set to the value returned by pChild->GetDesiredHeight();
GX_HT_FILL 
The child's height is expanded until it fills the area of the parent.

The child is placed in Y (after it has been sized for width) based on the value of tVPlace which may have one of the following (self explanatory) values: GX_FLOW_TOP, GX_FLOW_BOTTOM, GX_V_CENTERED.

When a child is placed so that it flows up, it is moved to the minimum y value in the parents specified area, i.e. pChild->Y(tY). When a child is placed so that it flows down, it is moved so that its bottom edge is aligned with the bottom edge in the parents available area, i.e. pChild->Y(bY – pChild->Height() );

If a child flows up or down (or left or right) it can reset its parent's available area (remember through the lX, rX, tY, bY). The GxBasic and GxSIBasic plugins will do so by default if they know the child will span the width or height of the parent's available area by looking for GX_WD_FILL and/or GX_HT_FILL. There are two boolean parameters that can force the reset of the parent's available area that can be useful in the cases where the child is sized as GX_WD_INT or GX_HT_INT.

The GxSIBasic geometry control module enhances the GxBasic module by adding the ability to add a spacing border around the child. The child is placed identically to the GxBasic case, but it is considered to be larger than its actual size by the widths of the defined border. The spacing increment is not in pixels, but is in a theoretically 'display independent' variable based on the height of the default font. (It is actually a dumb constant at the moment)

Example

As an example we'll create a dialog box.

class MyDialog : public GxPopupWin
{
public:
MyDialog(GxTopLevelWin *pOwner);
virtual ~MyDialog(void);

bool DoDialog(void);

protected:
void Ok(void);
void Cancel(void);
bool retStatus;

/* the order of construction matters here, the placement algorithm depends on it */
GxRow buttonRow; //the ok/cancel button row

GxButton ok, cancel; //could be constructed any point after the button row, but makes sense here

GxHLine hLine;

GxLabel textLabel;
GxTextWin textWin;
};

The constructor for MyDialog finishes up specifying widget placement

MyDialog::MyDialog(GxTopLevelWin *pOwner) :
GxPopupWin(pOwner),
buttonRow(this), ok(&buttonRow,"Ok"), cancel(&buttonRow, "Cancel"),
hLine(this),
  textLabel(this, "Text is here:"),
  textWin(this)
{
 //set a default size for the top level window (it will be resize-able by the window manager)
 width = 500;
 height = 400;

//adding this just means the top window will not try to shrink down to the size of its children
//i.e. it will respect the above values (but again the Window Manager is free to override)
SetGeomControl( GxFixed() );

 //set the widths and heights of the button row's children to be identical.
 buttonRow.SetWidthIdentical(true);
 buttonRow.SetHeightIdentical(true);

 //the hline widget has a little border on its top and bottom, so we
//don't need to add one to the button row.
buttonRow.SetGeomControl( GxSIBasic(GX_WD_FILL, GX_HT_INT, GX_FLOW_LEFT, GX_FLOW_DOWN, 1,1,0,1) );

//the hline has only one vertical size, so it can have GX_HT_FIXED (or GX_HT_INT)
hLine.SetGeomControl( GxSIBasic(GX_WD_FILL, GX_HT_FIXED, GX_FLOW_LEFT, GX_FLOW_DOWN, 1,1,0,0) );

 //let the text label flow to the top left of the window
 //the final false, true forces the remaining parent area to not be changed in X, but to be reset in Y to include
 //the height of this widget. If the desired width specifier would be set to GX_WD_FILL instead of GX_WD_INT, the
 //final true false could be excluded because the placement action that would occur would
//be the same as what we are forcing to happen
 textLabel.SetGeomControl( GxSIBasic(GX_WD_INT, GX_HT_INT, GX_FLOW_LEFT, GX_FLOW_UP, 1,1,1,1, false, true) );

 //the text window does not need a geom control as it will fill the available area left in the window.
 //textWin.SetGeomControl();

 //we can set some callbacks
 ok.cb.Assign( CbVoidMember<MyDialog>(this, &MyDialog::Ok) );
 cancel.cb.Assign( CbVoidMember<MyDialog>(this, &MyDialog::Cancel) );

Place(); //compute sizes and positions of x11 windows
Create(); //create all x11 windows (and necessary gc, colors, etc)
};

MyDialog::~MyDialog(void)
{}

bool MyDialog::DoDialog(void)
{
 Display(); //recursively map the dialog's window tree
 EventLoop();
 return retStatus;
}

void MyDialog::Ok(void)
{
retStatus = true;
 processEvents = false;
}

void MyDialog::Cancel(void)
{
 retStatus = false;
 processEvents = false;
};

Note:

If the c++ force is strong with you, you have noticed that the compiler knows the variable sizes of all widgets, and that to create the dialog class itself requires only a single new call (or even just a simple push to the stack). The libGx design is not perfectly optimal in this respect as specifying the geom control plugins and the function callbacks still need to use new() internally (This happens when they are Clone()'ed by the SetGeomControl() or Assign() functions). This compromise was felt to be necessary in order to keep the libGx API simple.