/*
* $XConsortium: Layout.c,v 1.1 91/09/13 18:51:44 keith Exp $
*
* Copyright 1991 Massachusetts Institute of Technology
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation, and that the name of M.I.T. not be used in advertising or
* publicity pertaining to distribution of the software without specific,
* written prior permission. M.I.T. makes no representations about the
* suitability of this software for any purpose. It is provided "as is"
* without express or implied warranty.
*
* M.I.T. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL M.I.T.
* BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* Author: Keith Packard, MIT X Consortium
*/
#include <X11/IntrinsicP.h>
#include <X11/StringDefs.h>
#include <X11/Xmu/Misc.h>
#include <X11/Xmu/Converters.h>
/*#include <X11/Xaw3d/LayoutP.h>*/
#include "LayoutP.h"
#include <ctype.h>
#include <stdio.h>
/*****************************************************************************
*
* Full instance record declaration
*
****************************************************************************/
#define offset(field) XtOffsetOf(LayoutRec, layout.field)
static XtResource resources[] = {
{XtNlayout, XtCLayout, XtRLayout, sizeof (BoxPtr),
offset(layout), XtRLayout, NULL },
{XtNdebug, XtCBoolean, XtRBoolean, sizeof(Boolean),
offset(debug), XtRImmediate, (XtPointer) FALSE},
};
#undef offset
static void ClassInitialize(), Initialize();
static void Resize();
static Boolean SetValues();
static XtGeometryResult GeometryManager();
static void ChangeManaged();
static void InsertChild();
static XtGeometryResult QueryGeometry ();
static void GetDesiredSize ();
static void LayoutLayout ();
static void LayoutGetNaturalSize ();
static void LayoutFreeLayout ();
extern void LayYYsetsource(), LayYYsetdest();
extern int LayYYparse();
#define SuperClass ((ConstraintWidgetClass)&constraintClassRec)
LayoutClassRec layoutClassRec = {
{
/* core class fields */
/* superclass */ (WidgetClass) SuperClass,
/* class name */ "Layout",
/* size */ sizeof(LayoutRec),
/* class_initialize */ ClassInitialize,
/* class_part init */ NULL,
/* class_inited */ FALSE,
/* initialize */ Initialize,
/* initialize_hook */ NULL,
/* realize */ XtInheritRealize,
/* actions */ NULL,
/* num_actions */ 0,
/* resources */ resources,
/* resource_count */ XtNumber(resources),
/* xrm_class */ NULLQUARK,
/* compress_motion */ 0, /*NULL,*/
/* compress_exposure */ 0, /*NULL,*/
/* compress_enterleave*/ 0, /*NULL,*/
/* visible_interest */ FALSE,
/* destroy */ NULL,
/* resize */ Resize,
/* expose */ NULL,
/* set_values */ SetValues,
/* set_values_hook */ NULL,
/* set_values_almost */ XtInheritSetValuesAlmost,
/* get_values_hook */ NULL,
/* accept_focus */ NULL,
/* version */ XtVersion,
/* callback_private */ NULL,
/* tm_table */ NULL,
/* query_geometry */ QueryGeometry,
/* display_accelerator*/ XtInheritDisplayAccelerator,
/* extension */ NULL
}, {
/* composite class fields */
/* geometry_manager */ GeometryManager,
/* change_managed */ ChangeManaged,
/* insert_child */ InsertChild,
/* delete_child */ XtInheritDeleteChild,
/* extension */ NULL
}, {
/* constraint class fields */
/* subresources */ NULL,
/* subresource_count */ 0,
/* constraint_size */ sizeof(LayoutConstraintsRec),
/* initialize */ NULL,
/* destroy */ NULL,
/* set_values */ NULL,
/* extension */ NULL
}
};
WidgetClass layoutWidgetClass = (WidgetClass) &layoutClassRec;
#define ForAllChildren(pw, childP) \
for ( (childP) = (pw)->composite.children ; \
(childP) < (pw)->composite.children + (pw)->composite.num_children ; \
(childP)++ ) if (!XtIsManaged(*childP)) ; else
/************************************************************
*
* Semi-public routines.
*
************************************************************/
/* Function Name: ClassInitialize
* Description: The Layout widgets class initialization proc.
* Arguments: none.
* Returns: none.
*/
/*ARGSUSED*/
static Boolean
CvtStringToLayout (dpy, args, num_args, from, to, converter_data)
Display *dpy;
XrmValue *args;
Cardinal *num_args;
XrmValue *from, *to;
XtPointer *converter_data;
{
LayYYsetsource ((char *) from->addr);
LayYYsetdest ((BoxPtr *) to->addr);
if (LayYYparse () == 0)
return TRUE;
else
return FALSE;
}
/*ARGSUSED*/
static void
DisposeLayout (app, to, data, args, num_args)
XtAppContext app;
XrmValue *to;
XtPointer data;
XrmValuePtr args;
Cardinal *num_args;
{
LayoutFreeLayout (* (LayoutPtr *) to->addr);
}
static void
ClassInitialize()
{
XtSetTypeConverter ( XtRString, XtRLayout, CvtStringToLayout,
(XtConvertArgList)NULL, (Cardinal)0, XtCacheNone,
DisposeLayout );
}
/*ARGSUSED*/
static XtGeometryResult GeometryManager(child, request, reply)
Widget child;
XtWidgetGeometry *request, *reply;
{
LayoutWidget w = (LayoutWidget) XtParent(child);
SubInfoPtr p = SubInfo(child);
int bw;
Bool changed, bwChanged;
bw = p->naturalBw;
changed = FALSE;
bwChanged = FALSE;
if (request->request_mode & CWBorderWidth &&
request->border_width != child->core.border_width)
{
p->naturalBw = bw;
bw = request->border_width;
changed = TRUE;
bwChanged = TRUE;
}
if (bwChanged || request->request_mode & CWWidth &&
request->width != child->core.width)
{
p->naturalSize[LayoutHorizontal] = request->width + bw * 2;
changed = TRUE;
}
if (bwChanged || request->request_mode & CWHeight &&
request->height != child->core.height)
{
p->naturalSize[LayoutVertical] = request->height + bw * 2;
changed = TRUE;
}
if (changed)
LayoutLayout (w, TRUE);
return XtGeometryDone;
}
/* ARGSUSED */
static void Initialize(request, new, args, num_args)
Widget request, new;
ArgList args;
Cardinal *num_args;
{
/* LayoutWidget w = (LayoutWidget)new; */
}
static void ChangeManaged(gw)
Widget gw;
{
LayoutWidget w = (LayoutWidget) gw;
Widget *children;
ForAllChildren (w, children)
GetDesiredSize (*children);
LayoutLayout ((LayoutWidget) w, TRUE);
}
static void
GetDesiredSize (child)
Widget child;
{
XtWidgetGeometry desired;
SubInfoPtr p;
XtQueryGeometry (child, (XtWidgetGeometry *) NULL, &desired);
p = SubInfo (child);
p->naturalBw = desired.border_width;
p->naturalSize[LayoutHorizontal] = desired.width + desired.border_width * 2;
p->naturalSize[LayoutVertical] = desired.height + desired.border_width * 2;
}
static void InsertChild (child)
Widget child;
{
(*SuperClass->composite_class.insert_child) (child);
GetDesiredSize (child);
}
static void
Resize(gw)
Widget gw;
{
LayoutLayout ((LayoutWidget) gw, FALSE);
}
/* ARGSUSED */
static Boolean
SetValues(gold, greq, gnew, args, num_args)
Widget gold, greq, gnew;
ArgList args;
Cardinal *num_args;
{
LayoutWidget old = (LayoutWidget) gold,
new = (LayoutWidget) gnew;
if (old->layout.layout != new->layout.layout)
LayoutLayout (new, TRUE);
return FALSE;
} /* SetValues */
static XtGeometryResult
QueryGeometry (gw, request, prefered_return)
Widget gw;
XtWidgetGeometry *request, *prefered_return;
{
LayoutWidget w = (LayoutWidget) gw;
XtGeometryResult result;
XtWidgetGeometry prefered_size;
if (request && !(request->request_mode & CWWidth|CWHeight))
return XtGeometryYes;
LayoutGetNaturalSize (w, &prefered_size.width, &prefered_size.height);
prefered_return->request_mode = 0;
result = XtGeometryYes;
if (!request) {
prefered_return->width = prefered_size.width;
prefered_return->height= prefered_size.height;
if (prefered_size.width != w->core.width) {
prefered_return->request_mode |= CWWidth;
result = XtGeometryAlmost;
}
if (prefered_size.height != w->core.height) {
prefered_return->request_mode |= CWHeight;
result = XtGeometryAlmost;
}
} else {
if (request->request_mode & CWWidth) {
if (prefered_size.width > request->width)
{
if (prefered_size.width == w->core.width)
result = XtGeometryNo;
else if (result != XtGeometryNo) {
result = XtGeometryAlmost;
prefered_return->request_mode |= CWWidth;
prefered_return->width = prefered_size.width;
}
}
}
if (request->request_mode & CWHeight) {
if (prefered_size.height > request->height)
{
if (prefered_size.height == w->core.height)
result = XtGeometryNo;
else if (result != XtGeometryNo) {
result = XtGeometryAlmost;
prefered_return->request_mode |= CWHeight;
prefered_return->height = prefered_size.height;
}
}
}
}
return result;
}
/*
* Layout section. Exports LayoutGetNaturalSize and
* LayoutLayout to above section
*/
static void
PrintGlue (g)
GlueRec g;
{
if (g.order == 0 || g.value != 1.0)
(void) printf ("%g", g.value);
if (g.order > 0)
{
(void) printf ("%s", " inf");
if (g.order > 1);
(void) printf (" %d", g.order);
}
}
static void
PrintDirection (dir)
LayoutDirection dir;
{
switch (dir) {
case LayoutHorizontal:
(void) printf ("%s", "horizontal");
break;
case LayoutVertical:
(void) printf ("%s", "vertical");
break;
default:
(void) printf ("Unknown layout direction %d\n", dir);
break;
}
}
static void
TabTo(level)
int level;
{
while (level--)
(void) printf ("%s", " ");
}
static void
PrintBox (box, level)
BoxPtr box;
int level;
{
BoxPtr child;
TabTo (level);
(void) printf ("%s", "< ");
(void) printf ("%s", " + ");
PrintGlue (box->params.stretch[LayoutHorizontal]);
(void) printf ("%s", " - ");
PrintGlue (box->params.shrink[LayoutHorizontal]);
(void) printf ("%s", " * ");
(void) printf ("%s", " + ");
PrintGlue (box->params.stretch[LayoutVertical]);
(void) printf ("%s", " - ");
PrintGlue (box->params.shrink[LayoutVertical]);
(void) printf ("%s", " >");
(void) printf (" size: %d x %d", box->size[0], box->size[1]);
(void) printf (" natural: %d x %d ", box->natural[0], box->natural[1]);
switch (box->type) {
case BoxBox:
PrintDirection (box->u.box.dir);
(void) printf ("%s\n", "");
for (child = box->u.box.firstChild; child; child = child->nextSibling)
PrintBox (child, level+1);
break;
case WidgetBox:
(void) printf (" %s\n", XrmQuarkToString (box->u.widget.quark));
break;
case GlueBox:
(void) printf ("%s\n", " glue");
break;
case VariableBox:
(void) printf (" variable %s\n", XrmQuarkToString (box->u.variable.quark));
break;
}
}
ExprPtr
LookupVariable (child, quark)
BoxPtr child;
XrmQuark quark;
{
BoxPtr parent, box;
while (parent = child->parent)
{
for (box = parent->u.box.firstChild;
box != child;
box = box->nextSibling)
{
if (box->type == VariableBox && box->u.variable.quark == quark)
return box->u.variable.expr;
}
child = parent;
}
return 0;
}
static double
Evaluate (l, box, expr, natural)
LayoutWidget l;
BoxPtr box;
ExprPtr expr;
double natural;
{
double left, right, down;
Widget widget;
SubInfoPtr info;
switch (expr->type) {
case Constant:
return expr->u.constant;
case Binary:
left = Evaluate (l, box, expr->u.binary.left, natural);
right = Evaluate (l, box, expr->u.binary.right, natural);
switch (expr->u.binary.op) {
case Plus:
return left + right;
case Minus:
return left - right;
case Times:
return left * right;
case Divide:
return left / right;
case Percent:
return right * left / 100.0;
}
case Unary:
down = Evaluate (l, box, expr->u.unary.down, natural);
switch (expr->u.unary.op) {
case Percent:
return natural * down / 100.0;
case Minus:
return -down;
}
case Width:
widget = QuarkToWidget (l, expr->u.width);
if (!widget)
return 0;
info = SubInfo (widget);
return info->naturalSize[LayoutHorizontal];
case Height:
widget = QuarkToWidget (l, expr->u.height);
if (!widget)
return 0;
info = SubInfo (widget);
return info->naturalSize[LayoutVertical];
case Variable:
expr = LookupVariable (box, expr->u.variable);
if (!expr)
{
char buf[256];
(void) sprintf (buf, "Layout: undefined variable %s\n",
XrmQuarkToString (expr->u.variable));
XtError (buf);
return 0.0;
}
return Evaluate (l, box, expr, natural);
}
}
static void
DisposeExpr (expr)
ExprPtr expr;
{
if (!expr)
return;
switch (expr->type) {
case Constant:
break;
case Binary:
DisposeExpr (expr->u.binary.left);
DisposeExpr (expr->u.binary.right);
break;
case Unary:
DisposeExpr (expr->u.unary.down);
break;
case Width:
break;
case Height:
break;
}
Dispose (expr);
}
#define CheckGlue(l, box, glue, n) { \
if (glue.expr) \
glue.value = Evaluate (l, box, glue.expr, n); \
if (glue.order == 0 && glue.value == 0) \
glue.order = -1; \
else if (glue.order == -1 && glue.value != 0) \
glue.order = 0; \
}
#define DoStretch(l, box, dir) \
CheckGlue (l, box, box->params.stretch[dir], (double) box->natural[dir]);
#define DoShrink(l, box, dir) \
CheckGlue (l, box, box->params.shrink[dir], (double) box->natural[dir])
/* compute the natural sizes of a box */
static void
ComputeNaturalSizes (l, box, dir)
LayoutWidget l;
BoxPtr box;
LayoutDirection dir;
{
BoxPtr child;
Widget w;
SubInfoPtr info;
int minStretchOrder, minShrinkOrder;
LayoutDirection thisDir;
switch (box->type) {
case WidgetBox:
w = box->u.widget.widget = QuarkToWidget (l, box->u.widget.quark);
if (!w)
{
box->natural[LayoutHorizontal] = 0;
box->natural[LayoutVertical] = 0;
}
else
{
info = SubInfo (w);
box->natural[LayoutHorizontal] = info->naturalSize[LayoutHorizontal];
box->natural[LayoutVertical] = info->naturalSize[LayoutVertical];
}
DoStretch (l, box, dir);
DoShrink (l, box, dir);
DoStretch (l, box, !dir);
DoShrink (l, box, !dir);
break;
case GlueBox:
box->natural[dir] = Evaluate (l, box, box->u.glue.expr, 0.0);
box->natural[!dir] = 0;
DoStretch (l, box, dir);
DoShrink (l, box, dir);
break;
case BoxBox:
thisDir = box->u.box.dir;
box->natural[0] = 0;
box->natural[1] = 0;
minStretchOrder = 100000;
minShrinkOrder = 100000;
ZeroGlue (box->params.shrink[thisDir]);
ZeroGlue (box->params.stretch[thisDir]);
box->params.shrink[!thisDir].order = 100000;
box->params.stretch[!thisDir].order = 100000;
for (child = box->u.box.firstChild; child; child = child->nextSibling)
{
ComputeNaturalSizes (l, child, thisDir);
/*
* along box axis:
* normal size += child normal size
* shrink += child shrink
* stretch += child stretch
*/
box->natural[thisDir] += child->natural[thisDir];
AddGlue (box->params.shrink[thisDir],
box->params.shrink[thisDir],
child->params.shrink[thisDir]);
AddGlue (box->params.stretch[thisDir],
box->params.stretch[thisDir],
child->params.stretch[thisDir]);
/*
* normal to box axis:
* normal size = maximum child normal size of minimum shrink order
* shrink = difference between normal size and minimum shrink
* stretch = minimum child stretch
*/
if (box->natural[!thisDir] >= child->natural[!thisDir])
{
if (child->params.stretch[!thisDir].order < minShrinkOrder)
{
box->natural[!thisDir] = child->natural[!thisDir];
minStretchOrder = child->params.stretch[!thisDir].order;
if (child->params.shrink[!thisDir].order < minShrinkOrder)
minShrinkOrder = child->params.shrink[!thisDir].order;
}
}
else
{
if (child->params.shrink[!thisDir].order <= minStretchOrder)
{
box->natural[!thisDir] = child->natural[!thisDir];
minShrinkOrder = child->params.shrink[!thisDir].order;
if (child->params.stretch[!thisDir].order < minStretchOrder)
minStretchOrder = child->params.stretch[!thisDir].order;
}
}
MinGlue (box->params.stretch[!thisDir],
box->params.stretch[!thisDir],
child->params.stretch[!thisDir]);
MinGlue (box->params.shrink[!thisDir],
box->params.shrink[!thisDir],
child->params.shrink[!thisDir]);
}
if (box->params.shrink[!thisDir].order <= 0)
{
int minSize;
int largestMinSize;
largestMinSize = 0;
for (child = box->u.box.firstChild; child; child = child->nextSibling)
{
if (child->params.shrink[!thisDir].order <= 0)
{
minSize = child->natural[!thisDir] -
child->params.shrink[!thisDir].value;
if (minSize > largestMinSize)
largestMinSize = minSize;
}
}
box->params.shrink[!thisDir].value = box->natural[!thisDir] -
largestMinSize;
if (box->params.shrink[!thisDir].value == 0)
box->params.shrink[!thisDir].order = -1;
else
box->params.shrink[!thisDir].order = 0;
}
}
}
/* given the boxs geometry, set the geometry of the pieces */
#define GluePart(a,b,dist) ((a) ? ((int) (((a) * (dist)) / (b) + \
((dist >= 0) ? 0.5 : -0.5))) : 0)
static Bool
ComputeSizes (box)
BoxPtr box;
{
LayoutDirection dir;
BoxPtr child;
GlueRec stretch;
GlueRec shrink;
GlueRec totalGlue[2];
double remainingGlue;
GluePtr glue;
int size;
int totalSizes;
int totalChange[2];
int change;
int remainingChange;
Bool shrinking;
Bool happy;
int i;
int maxGlue;
dir = box->u.box.dir;
size = box->size[dir];
stretch = box->params.stretch[dir];
shrink = box->params.shrink[dir];
/* pick the correct adjustment parameters based on the change direction */
totalChange[0] = size - box->natural[dir];
shrinking = totalChange[0] < 0;
totalChange[1] = 0;
totalGlue[1].order = 100000;
totalGlue[1].value = 0;
maxGlue = 1;
if (shrinking)
{
totalGlue[0] = shrink;
/* for first-order infinites, shrink it to zero and then
* shrink the zero-orders
*/
if (shrink.order == 1) {
totalSizes = 0;
remainingGlue = 0;
for (child = box->u.box.firstChild;
child;
child = child->nextSibling)
{
switch (child->params.shrink[dir].order) {
case 0:
remainingGlue += child->params.shrink[dir].value;
break;
case 1:
totalSizes += child->natural[dir];
break;
}
}
if (totalSizes < -totalChange[0])
{
totalGlue[1] = shrink;
totalGlue[0].order = 0;
totalGlue[0].value = remainingGlue;
totalChange[1] = -totalSizes;
totalChange[0] = totalChange[0] - totalChange[1];
maxGlue = 2;
}
}
if (totalGlue[0].order <= 0 &&
totalChange[0] > totalGlue[0].value)
{
totalChange[0] = totalGlue[0].value;
}
}
else
totalGlue[0] = stretch;
/* adjust each box */
totalSizes = 0;
remainingGlue = totalGlue[0].value + totalGlue[1].value;
remainingChange = totalChange[0] + totalChange[1];
happy = True;
for (child = box->u.box.firstChild; child; child = child->nextSibling)
{
if (shrinking)
glue = &child->params.shrink[dir];
else
glue = &child->params.stretch[dir];
child->size[dir] = child->natural[dir];
for (i = 0; i < maxGlue; i++)
{
if (glue->order == totalGlue[i].order)
{
remainingGlue -= glue->value;
if (remainingGlue <= 0)
change = remainingChange;
else
change = GluePart (glue->value,
totalGlue[i].value,
totalChange[i]);
child->size[dir] += change;
remainingChange -= change;
}
}
child->size[!dir] = box->size[!dir];
totalSizes += child->size[dir];
if (child->type == BoxBox)
if (!ComputeSizes (child))
happy = False;
}
return totalSizes == box->size[dir] && happy;
}
static void
SetSizes (box, x, y)
BoxPtr box;
Position x, y;
{
BoxPtr child;
int width, height;
int bw;
Widget w;
SubInfoPtr info;
switch (box->type) {
case WidgetBox:
w = box->u.widget.widget;
if (w)
{
info = (SubInfoPtr) w->core.constraints;
width = box->size[LayoutHorizontal];
height = box->size[LayoutVertical];
bw = info->naturalBw;
width -= bw * 2;
height -= bw * 2;
/* Widgets which grow too small are placed off screen */
if (width <= 0 || height <= 0)
{
width = 1;
height = 1;
bw = 0;
x = -1;
y = -1;
}
XtConfigureWidget (w, x, y,
(Dimension)width, (Dimension)height,
(Dimension)bw);
}
break;
case GlueBox:
break;
case BoxBox:
for (child = box->u.box.firstChild; child; child = child->nextSibling)
{
SetSizes (child, x, y);
if (box->u.box.dir == LayoutHorizontal)
x += child->size[LayoutHorizontal];
else
y += child->size[LayoutVertical];
}
break;
}
}
static void
LayoutFreeLayout (box)
BoxPtr box;
{
BoxPtr child, next;
switch (box->type) {
case BoxBox:
for (child = box->u.box.firstChild; child; child = next)
{
next = child->nextSibling;
LayoutFreeLayout (child);
}
break;
case GlueBox:
DisposeExpr (box->u.glue.expr);
break;
}
DisposeExpr (box->params.stretch[LayoutHorizontal].expr);
DisposeExpr (box->params.shrink[LayoutHorizontal].expr);
DisposeExpr (box->params.stretch[LayoutVertical].expr);
DisposeExpr (box->params.shrink[LayoutVertical].expr);
Dispose (box);
}
static void
LayoutGetNaturalSize (l, widthp, heightp)
LayoutWidget l;
Dimension *widthp, *heightp;
{
BoxPtr box;
box = l->layout.layout;
if (box)
{
ComputeNaturalSizes (l, box, LayoutHorizontal);
*widthp = box->natural[LayoutHorizontal];
*heightp = box->natural[LayoutVertical];
}
else
{
*widthp = 0;
*heightp = 0;
}
}
static void
LayoutLayout (l, attemptResize)
LayoutWidget l;
Bool attemptResize;
{
BoxPtr box;
Dimension width, height;
Dimension prefered_width, prefered_height;
box = l->layout.layout;
if (!box)
return;
LayoutGetNaturalSize (l, &prefered_width, &prefered_height);
if (l->core.width == 0 || l->core.height == 0)
{
l->core.width = prefered_width;
l->core.height = prefered_height;
}
box->size[LayoutHorizontal] = l->core.width;
box->size[LayoutVertical] = l->core.height;
if (!ComputeSizes (box) && attemptResize)
{
XtMakeResizeRequest ((Widget) l,
prefered_width, prefered_height,
&width, &height);
if (width != box->size[LayoutHorizontal] ||
height != box->size[LayoutVertical])
{
box->size[LayoutHorizontal] = width;
box->size[LayoutVertical] = height;
ComputeSizes (box);
}
}
if (l->layout.debug)
{
PrintBox (box, 0);
fflush (stdout);
}
SetSizes (box, 0, 0);
}
syntax highlighted by Code2HTML, v. 0.9.1