// $Id: GraphEdit.C,v 1.71 2001/04/26 11:08:06 zeller Exp $
// GraphEdit Widget

// Copyright (C) 1995-1998 Technische Universitaet Braunschweig, Germany.
// Written by Andreas Zeller <zeller@gnu.org>.
// 
// This file is part of DDD.
// 
// DDD is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
// 
// DDD is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public
// License along with DDD -- see the file COPYING.
// If not, write to the Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
// 
// DDD is the data display debugger.
// For details, see the DDD World-Wide-Web page, 
// `http://www.gnu.org/software/ddd/',
// or send a mail to the DDD developers <ddd@gnu.org>.

char GraphEdit_rcsid[] = 
    "$Id: GraphEdit.C,v 1.71 2001/04/26 11:08:06 zeller Exp $";

#ifdef __GNUG__
#pragma implementation
#endif

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <iostream.h>
#include <strstream.h>

#include <Xm/Xm.h>
#include <Xm/ScrolledW.h>
#include <X11/Xlib.h>
#include <X11/cursorfont.h>
#include <X11/Intrinsic.h>
#include <X11/IntrinsicP.h>
#include <X11/StringDefs.h>

#include "events.h"
#include "Graph.h"
#include "GraphEditP.h"
#include "GraphEdit.h"
#include "HintGraphN.h"
#include "LineGraphE.h"
#include "layout.h"
#include "misc.h"
#include "cook.h"
#include "strtoul.h"
#include "TimeOut.h"
#include "EdgeAPA.h"
#include "GraphNPA.h"


#if XmVersion < 1002
// Motif 1.1 backwards compatibility
#ifndef XmInheritBorderHighlight
#define XmInheritBorderHighlight (XtWidgetProc)_XtInherit
#endif

#ifndef XmInheritBorderUnhighlight
#define XmInheritBorderUnhighlight (XtWidgetProc)_XtInherit
#endif

#ifndef XmInheritArmAndActivate
#define XmInheritArmAndActivate (XmArmAndActivate)_XtInherit
#endif
#endif // XmVersion


static BoxRegion EVERYWHERE(BoxPoint(0,0), BoxSize(INT_MAX, INT_MAX));


// Compute default foreground

static void defaultForeground(Widget w, int, XrmValue *value)
{
    const GraphEditWidget _w = GraphEditWidget(w);
    value->addr = XPointer(&_w->primitive.foreground);
}


// Resource list

static XtResource resources[] = {
#define offset(field) XtOffsetOf(GraphEditRec, graphEdit.field)
    // {name, class, type, size, offset, default_type, default_addr}
    { (char *)XtNgraph, (char *)XtCGraph, XtRPointer, sizeof(Graph *),
        offset(graph), XtRImmediate, XtPointer(0) },
    { (char *)XtNmoveDelta, (char *)XtCMoveDelta, XtRDimension, sizeof(Dimension),
	offset(moveDelta), XtRImmediate, XtPointer(4) },
    { (char *)XtNrubberEdges, (char *)XtCRubberEdges, XtRBoolean, sizeof(Boolean),
	offset(rubberEdges), XtRImmediate, XtPointer(True) },
    { (char *)XtNrubberArrows, (char *)XtCRubberEdges, XtRBoolean, sizeof(Boolean),
	offset(rubberArrows), XtRImmediate, XtPointer(False) },
    { (char *)XtNrubberAnnotations, (char *)XtCRubberAnnotations, XtRBoolean, sizeof(Boolean),
	offset(rubberAnnotations), XtRImmediate, XtPointer(False) },
    { (char *)XtNopaqueMove, (char *)XtCOpaqueMove, XtRBoolean, sizeof(Boolean),
	offset(opaqueMove), XtRImmediate, XtPointer(False) },

    { (char *)XtNautoRaise, (char *)XtCAutoRaise, XtRBoolean, sizeof(Boolean),
	offset(autoRaise), XtRImmediate, XtPointer(True) },

    {(char *) XtNshowHints, (char *)XtCShowHints, XtRBoolean, sizeof(Boolean),
	offset(showHints), XtRImmediate, XtPointer(False) },
    { (char *)XtNhintSize, (char *)XtCHintSize, XtRDimension, sizeof(Dimension),
	offset(hintSize), XtRImmediate, XtPointer(6) },

    { (char *)XtNshowAnnotations, (char *)XtCShowAnnotations, XtRBoolean, sizeof(Boolean),
	offset(showAnnotations), XtRImmediate, XtPointer(True) },

    { (char *)XtNgridWidth, (char *)XtCGridSize, XtRDimension, sizeof(Dimension),
	offset(gridWidth), XtRImmediate, XtPointer(16) },
    { (char *)XtNgridHeight, (char *)XtCGridSize, XtRDimension, sizeof(Dimension),
	offset(gridHeight), XtRImmediate, XtPointer(16) },

    { (char *)XtNshowGrid, (char *)XtCShowGrid, XtRBoolean, sizeof(Boolean),
	offset(showGrid), XtRImmediate, XtPointer(False) },
    { (char *)XtNsnapToGrid, (char *)XtCSnapToGrid, XtRBoolean, sizeof(Boolean),
	offset(snapToGrid), XtRImmediate, XtPointer(False) },

    { (char *)XtNautoLayout, (char *)XtCAutoLayout, XtRBoolean, sizeof(Boolean),
	offset(autoLayout), XtRImmediate, XtPointer(False) },

    { (char *)XtNrotation, (char *)XtCRotation, XtRCardinal, sizeof(Cardinal),
	offset(rotation), XtRImmediate, XtPointer(0)},

    { (char *)XtNedgeWidth, (char *)XtCEdgeWidth, XtRDimension, sizeof(Dimension),
	offset(edgeWidth), XtRImmediate, XtPointer(0) },
    { (char *)XtNarrowAngle, (char *)XtCArrowAngle, XtRDimension, sizeof(Dimension),
	offset(arrowAngle), XtRImmediate, XtPointer(30) },
    { (char *)XtNarrowLength, (char *)XtCArrowLength, XtRDimension, sizeof(Dimension),
	offset(arrowLength), XtRImmediate, XtPointer(10) },
    { (char *)XtNselfEdgeDiameter, (char *)XtCSelfEdgeDiameter, XtRDimension, 
        sizeof(Dimension), offset(selfEdgeDiameter), 
        XtRImmediate, XtPointer(32) },

    { (char *)XtNextraWidth, (char *)XtCExtraSize, XtRDimension, sizeof(Dimension),
	offset(extraWidth), XtRImmediate, XtPointer(0) },
    { (char *)XtNextraHeight, (char *)XtCExtraSize, XtRDimension, sizeof(Dimension),
	offset(extraHeight), XtRImmediate, XtPointer(0) },

    { (char *)XtNrequestedWidth, (char *)XtCRequestedSize, XtRDimension, sizeof(Dimension),
	offset(requestedWidth), XtRImmediate, XtPointer(0) },
    { (char *)XtNrequestedHeight, (char *)XtCRequestedSize, XtRDimension, sizeof(Dimension),
	offset(requestedHeight), XtRImmediate, XtPointer(0) },

    { (char *)XtNselectTile, (char *)XtCBitmap, XtRBitmap, sizeof(Pixmap),
	offset(selectTile), XtRImmediate, XtPointer(0)},

    { (char *)XtNedgeAttachMode, (char *)XtCEdgeAttachMode, (char *)XtREdgeAttachMode,
	sizeof(EdgeAttachMode), offset(edgeAttachMode), 
        XtRImmediate, XtPointer(Straight) },
    { (char *)XtNlayoutMode, (char *)XtCLayoutMode, (char *)XtRLayoutMode,
	sizeof(LayoutMode), offset(layoutMode), 
        XtRImmediate, XtPointer(RegularLayoutMode) },
    { (char *)XtNselfEdgePosition, (char *)XtCSelfEdgePosition, (char *)XtRSelfEdgePosition,
	sizeof(SelfEdgePosition), offset(selfEdgePosition), 
        XtRImmediate, XtPointer(NorthEast) },
    { (char *)XtNselfEdgeDirection, (char *)XtCSelfEdgeDirection, (char *)XtRSelfEdgeDirection,
	sizeof(SelfEdgeDirection), offset(selfEdgeDirection), 
        XtRImmediate, XtPointer(Counterclockwise) },
    { (char *)XtNdashedLines, (char *)XtCDashedLines, XtRBoolean,
	sizeof(Boolean), offset(dashedLines), 
        XtRImmediate, XtPointer(False) },

    { (char *)XtNdefaultCursor, (char *)XtCCursor, XtRCursor, sizeof(Cursor),
	offset(defaultCursor), XtRImmediate, XtPointer(0)},
    { (char *)XtNmoveCursor, (char *)XtCCursor, XtRCursor, sizeof(Cursor),
	offset(moveCursor), XtRImmediate, XtPointer(0)},
    { (char *)XtNselectCursor, (char *)XtCCursor, XtRCursor, sizeof(Cursor),
	offset(selectCursor), XtRImmediate, XtPointer(0)},
    { (char *)XtNselectBottomLeftCursor, (char *)XtCCursor, XtRCursor, sizeof(Cursor),
	offset(selectBottomLeftCursor), XtRImmediate, XtPointer(0)},
    { (char *)XtNselectBottomRightCursor, (char *)XtCCursor, XtRCursor, sizeof(Cursor),
	offset(selectBottomRightCursor), XtRImmediate, XtPointer(0)},
    { (char *)XtNselectTopLeftCursor, (char *)XtCCursor, XtRCursor, sizeof(Cursor),
	offset(selectTopLeftCursor), XtRImmediate, XtPointer(0)},
    { (char *)XtNselectTopRightCursor, (char *)XtCCursor, XtRCursor, sizeof(Cursor),
	offset(selectTopRightCursor), XtRImmediate, XtPointer(0)},

    { (char *)XtNnodeColor, (char *)XtCColor, XtRPixel, sizeof(Pixel),
	offset(nodeColor), XtRCallProc, XtPointer(defaultForeground) },
    { (char *)XtNedgeColor, (char *)XtCColor, XtRPixel, sizeof(Pixel),
	offset(edgeColor), XtRCallProc, XtPointer(defaultForeground) },
    { (char *)XtNframeColor, (char *)XtCColor, XtRPixel, sizeof(Pixel),
	offset(frameColor), XtRCallProc, XtPointer(defaultForeground) },
    { (char *)XtNoutlineColor, (char *)XtCColor, XtRPixel, sizeof(Pixel),
	offset(outlineColor), XtRCallProc, XtPointer(defaultForeground) },
    { (char *)XtNgridColor, (char *)XtCColor, XtRPixel, sizeof(Pixel),
	offset(gridColor), XtRCallProc, XtPointer(defaultForeground) },
    { (char *)XtNselectColor, (char *)XtCColor, XtRPixel, sizeof(Pixel),
	offset(selectColor), XtRCallProc, XtPointer(defaultForeground) },

    { (char *)XtNnodePrintColor, (char *)XtCColor, XtRString, sizeof(String),
	offset(nodePrintColor), XtRImmediate, 0 },
    { (char *)XtNedgePrintColor, (char *)XtCColor, XtRString, sizeof(String),
	offset(edgePrintColor), XtRImmediate, 0 },

    { (char *)XtNpositionChangedCallback, (char *)XtCCallback, XtRCallback, sizeof(XtPointer),
	offset(positionChangedProc), XtRCallback, XtPointer(0) },
    { (char *)XtNselectionChangedCallback, (char *)XtCCallback, XtRCallback, sizeof(XtPointer),
	offset(selectionChangedProc), XtRCallback, XtPointer(0) },
    { (char *)XtNsizeChangedCallback, (char *)XtCCallback, XtRCallback, sizeof(XtPointer),
	offset(sizeChangedProc), XtRCallback, XtPointer(0) },
    { (char *)XtNcompareNodesCallback, (char *)XtCCallback, XtRCallback, sizeof(XtPointer),
	offset(compareNodesProc), XtRCallback, XtPointer(0) },
    { (char *)XtNpreLayoutCallback, (char *)XtCCallback, XtRCallback, sizeof(XtPointer),
	offset(preLayoutProc), XtRCallback, XtPointer(0) },
    { (char *)XtNpostLayoutCallback, (char *)XtCCallback, XtRCallback, sizeof(XtPointer),
	offset(postLayoutProc), XtRCallback, XtPointer(0) },
    { (char *)XtNpreSelectionCallback, (char *)XtCCallback, XtRCallback, sizeof(XtPointer),
	offset(preSelectionProc), XtRCallback, XtPointer(0) },

#undef offset
};


// Action function declarations

static void Select      (Widget, XEvent *, String *, Cardinal *);
static void Extend      (Widget, XEvent *, String *, Cardinal *);
static void Toggle      (Widget, XEvent *, String *, Cardinal *);
static void SelectOrMove(Widget, XEvent *, String *, Cardinal *);
static void ExtendOrMove(Widget, XEvent *, String *, Cardinal *);
static void ToggleOrMove(Widget, XEvent *, String *, Cardinal *);
static void MoveSelected(Widget, XEvent *, String *, Cardinal *);
static void Follow      (Widget, XEvent *, String *, Cardinal *);
static void End         (Widget, XEvent *, String *, Cardinal *);
static void ShowEdges   (Widget, XEvent *, String *, Cardinal *);
static void HideEdges   (Widget, XEvent *, String *, Cardinal *);
static void SelectAll   (Widget, XEvent *, String *, Cardinal *);
static void SelectFirst (Widget, XEvent *, String *, Cardinal *);
static void SelectNext  (Widget, XEvent *, String *, Cardinal *);
static void SelectPrev  (Widget, XEvent *, String *, Cardinal *);
static void UnselectAll (Widget, XEvent *, String *, Cardinal *);
static void SnapToGrid  (Widget, XEvent *, String *, Cardinal *);
static void _SnapToGrid (Widget, XEvent *, String *, Cardinal *);
static void Rotate      (Widget, XEvent *, String *, Cardinal *);
static void _Rotate     (Widget, XEvent *, String *, Cardinal *);
static void DoLayout    (Widget, XEvent *, String *, Cardinal *);
static void _Layout     (Widget, XEvent *, String *, Cardinal *);
static void Normalize   (Widget, XEvent *, String *, Cardinal *);
static void _Normalize  (Widget, XEvent *, String *, Cardinal *);


// Actions table

static XtActionsRec actions[] = {
    { (char *)"select",         Select },        // select()
    { (char *)"extend",         Extend },        // extend()
    { (char *)"toggle",         Toggle },        // toggle()
    { (char *)"select-or-move", SelectOrMove },	 // select-or-move()
    { (char *)"extend-or-move", ExtendOrMove },	 // extend-or-move()
    { (char *)"toggle-or-move", ToggleOrMove },	 // toggle-or-move()
    { (char *)"move-selected",  MoveSelected },	 // move-selected(X, Y)
    { (char *)"follow",		Follow },        // follow()
    { (char *)"end",		End },           // end()
    { (char *)"show-edges",	ShowEdges },     // show-edges([any|both|from|to])
    { (char *)"hide-edges", 	HideEdges },     // hide-edges([any|both|from|to])
    { (char *)"select-all", 	SelectAll },     // select-all()
    { (char *)"select-first", 	SelectFirst },   // select-first()
    { (char *)"select-next", 	SelectNext },    // select-next()
    { (char *)"select-prev", 	SelectPrev },    // select-prev()
    { (char *)"unselect-all", 	UnselectAll },   // unselect-all()
    { (char *)"snap-to-grid",	SnapToGrid },    // snap-to-grid()
    { (char *)"_snap-to-grid",	_SnapToGrid },
    { (char *)"rotate",		Rotate },        // rotate([[+|-]DEGREES])
    { (char *)"_rotate",	_Rotate },
    { (char *)"layout",		DoLayout },      // layout([regular|compact],
    { (char *)"_layout",	_Layout },       //         [[+|-]DEGREES]])
    { (char *)"normalize",	Normalize },     // normalize()
    { (char *)"_normalize",	_Normalize },
};


// Default translation table

static char defaultTranslations[] =
    "Shift<Btn1Down>:	       toggle()\n"
    "~Shift<Btn1Down>:	       select-or-move()\n"
    "<Btn1Motion>:	       follow()\n"
    "<Btn1Up>:		       end()\n"
    "<Btn2Down>:	       toggle()\n"
    "<Btn2Motion>:	       follow()\n"
    "<Btn2Up>:		       end()\n"
    "~Shift Ctrl<Key>KP_1:     move-selected(-grid, +grid)\n"
    "~Shift Ctrl<Key>KP_2:     move-selected(    0, +grid)\n"
    "~Shift Ctrl<Key>KP_3:     move-selected(+grid, +grid)\n"
    "~Shift Ctrl<Key>KP_4:     move-selected(-grid,     0)\n"
    "~Shift Ctrl<Key>KP_6:     move-selected(+grid,     0)\n"
    "~Shift Ctrl<Key>KP_7:     move-selected(-grid, -grid)\n"
    "~Shift Ctrl<Key>KP_8:     move-selected(    0, -grid)\n"
    "~Shift Ctrl<Key>KP_9:     move-selected(+grid, -grid)\n"
    "~Shift Ctrl<Key>B:        move-selected(-grid,     0)\n"
    "~Shift Ctrl<Key>F:        move-selected(+grid,     0)\n"
    "~Shift Ctrl<Key>P:        move-selected(    0, -grid)\n"
    "~Shift Ctrl<Key>N:        move-selected(    0, +grid)\n"
    "~Ctrl Shift<Key>KP_1:     move-selected(-1, +1)\n"
    "~Ctrl Shift<Key>KP_2:     move-selected( 0, +1)\n"
    "~Ctrl Shift<Key>KP_3:     move-selected(+1, +1)\n"
    "~Ctrl Shift<Key>KP_4:     move-selected(-1,  0)\n"
    "~Ctrl Shift<Key>KP_6:     move-selected(+1,  0)\n"
    "~Ctrl Shift<Key>KP_7:     move-selected(-1, -1)\n"
    "~Ctrl Shift<Key>KP_8:     move-selected( 0, -1)\n"
    "~Ctrl Shift<Key>KP_9:     move-selected(+1, -1)\n"
    "~Ctrl Shift<Key>B:        move-selected(-1,  0)\n"
    "~Ctrl Shift<Key>F:        move-selected(+1,  0)\n"
    "~Ctrl Shift<Key>P:        move-selected( 0, -1)\n"
    "~Ctrl Shift<Key>N:        move-selected( 0, +1)\n"
    "~Meta ~Ctrl<Key>KP_4:     select-prev()\n"
    "~Meta ~Ctrl<Key>KP_6:     select-next()\n"
    "~Meta ~Ctrl<Key>KP_7:     select-first()\n"
    "~Meta ~Ctrl<Key>KP_0:     select-first()\n"
    "~Meta ~Ctrl<Key>B:        select-prev()\n"
    "~Meta ~Ctrl<Key>F:        select-next()\n"
    "~Meta ~Ctrl<Key>P:        select-prev()\n"
    "~Meta ~Ctrl<Key>N:        select-next()\n"
    "~Meta ~Ctrl<Key>A:        select-first()\n"
    "~Meta Ctrl<Key>A:         select-all()\n"
    ;

// These translations override the XmPrimitive base translations
static char extraTranslations[] =
    "~Meta ~Shift Ctrl<Key>osfLeft:       move-selected(-grid,     0)\n"
    "~Meta ~Shift Ctrl<Key>osfRight:      move-selected(+grid,     0)\n"
    "~Meta ~Shift Ctrl<Key>osfUp:         move-selected(    0, -grid)\n"
    "~Meta ~Shift Ctrl<Key>osfDown:       move-selected(    0, +grid)\n"
    "~Meta ~Shift Ctrl<Key>osfBeginLine:  move-selected(-grid, -grid)\n"
    "~Meta ~Ctrl Shift<Key>osfLeft:       move-selected(-1,  0)\n"
    "~Meta ~Ctrl Shift<Key>osfRight:      move-selected(+1,  0)\n"
    "~Meta ~Ctrl Shift<Key>osfUp:         move-selected( 0, -1)\n"
    "~Meta ~Ctrl Shift<Key>osfDown:       move-selected( 0, +1)\n"
    "~Meta ~Ctrl Shift<Key>osfBeginLine:  move-selected(-1, -1)\n"
    "~Meta ~Shift ~Ctrl<Key>osfLeft:      select-prev()\n"
    "~Meta ~Shift ~Ctrl<Key>osfRight:     select-next()\n"
    "~Meta ~Shift ~Ctrl<Key>osfUp:        select-prev()\n"
    "~Meta ~Shift ~Ctrl<Key>osfDown:      select-next()\n"
    "~Meta ~Shift ~Ctrl<Key>osfBeginLine: select-first()\n"
    "~Meta ~Shift Ctrl<Key>Left:          move-selected(-grid,     0)\n"
    "~Meta ~Shift Ctrl<Key>Right:         move-selected(+grid,     0)\n"
    "~Meta ~Shift Ctrl<Key>Up:            move-selected(    0, -grid)\n"
    "~Meta ~Shift Ctrl<Key>Down:          move-selected(    0, +grid)\n"
    "~Meta ~Ctrl Shift<Key>Left:          move-selected(-1,  0)\n"
    "~Meta ~Ctrl Shift<Key>Right:         move-selected(+1,  0)\n"
    "~Meta ~Ctrl Shift<Key>Up:            move-selected( 0, -1)\n"
    "~Meta ~Ctrl Shift<Key>Down:          move-selected( 0, +1)\n"
    "~Meta ~Shift ~Ctrl<Key>Left:         select-prev()\n"
    "~Meta ~Shift ~Ctrl<Key>Right:        select-next()\n"
    "~Meta ~Shift ~Ctrl<Key>Up:           select-prev()\n"
    "~Meta ~Shift ~Ctrl<Key>Down:         select-next()\n"
;

// Method function declarations

static void ClassInitialize();

static void Initialize(Widget request, 
		       Widget w, 
		       ArgList args,
		       Cardinal *num_args);

static void Redisplay(Widget w, XEvent *event, Region region);

static void Realize(Widget w, 
		    XtValueMask *value_mask,
		    XSetWindowAttributes *attributes);

static Boolean SetValues(Widget old, 
			 Widget request, 
			 Widget new_w,
			 ArgList args, 
			 Cardinal *num_args);

static void Destroy(Widget w);


// Class record initialization

GraphEditClassRec graphEditClassRec = {
  {	 /* core fields */
    /* superclass               */  (WidgetClass) &xmPrimitiveClassRec,
    /* class_name               */  (char *)"GraphEdit",
    /* widget_size              */  sizeof(GraphEditRec),
    /* class_initialize         */  ClassInitialize,
    /* class_part_initialize    */  XtWidgetClassProc(0),
    /* class_inited             */  False,
    /* initialize               */  Initialize,
    /* initialize_hook          */  XtArgsProc(0),
    /* realize                  */  Realize,
    /* actions                  */  actions,
    /* num_actions              */  XtNumber(actions),
    /* resources                */  resources,
    /* num_resources            */  XtNumber(resources),
    /* xrm_class                */  NULLQUARK,
    /* compress_motion          */  True,
    /* compress_exposure        */  True,
    /* compress_enterleave      */  True,
    /* visible_interest         */  False,
    /* destroy                  */  Destroy,
    /* resize                   */  XtInheritResize,
    /* expose                   */  Redisplay,
    /* set_values               */  SetValues,
    /* set_values_hook          */  XtArgsFunc(0),
    /* set_values_almost        */  XtInheritSetValuesAlmost,
    /* get_values_hook          */  XtArgsProc(0),
    /* accept_focus             */  XtAcceptFocusProc(0),
    /* version                  */  XtVersion,
    /* callback_private         */  XtPointer(0),
    /* tm_table                 */  defaultTranslations,
    /* query_geometry           */  XtInheritQueryGeometry,
    /* display_accelerator      */  XtInheritDisplayAccelerator,
    /* extension                */  XtPointer(0)
  },
  {	 /* Primitive fields */
    /* border_highlight         */ XmInheritBorderHighlight,
    /* border_unhighlight       */ XmInheritBorderUnhighlight,
    /* translations             */ XtInheritTranslations,
    /* arm_and_activate         */ XtActionProc(0), // XmInheritArmAndActivate?
    /* syn_resources            */ (XmSyntheticResource *)0,
    /* num_syn_resources        */ 0,
    /* extension                */ XtPointer(0)
#if defined(__sgi) && !defined(LesstifVersion)
	// Paul Sydney <sydney@ulua.mhpcc.af.mil> reports that OSF/Motif
	// on an SGI Indy running IRIX 6.5 has an extra
	// `_SG_vendorExtension' field.  If this is not initialized
	// explicitly, then EGCS 1.1 gives a warning.
	, 0
#endif
  },
  {	/* GraphEdit fields */
    /* extension                */ XtPointer(0)
  },
};

WidgetClass graphEditWidgetClass = (WidgetClass)&graphEditClassRec;


// Method function definitions

// Set widget to minimal size
void graphEditSizeChanged(Widget w)
{
    XtCheckSubclass(w, GraphEditWidgetClass, "Bad widget class");

    Arg args[10];
    int arg;

    const GraphEditWidget _w            = GraphEditWidget(w);
    const Graph* graph                  = _w->graphEdit.graph;
    const Dimension highlight_thickness = _w->primitive.highlight_thickness;
    Boolean& sizeChanged                = _w->graphEdit.sizeChanged;
    const GraphGC& graphGC              = _w->graphEdit.graphGC;
    const Dimension extraWidth          = _w->graphEdit.extraWidth;
    const Dimension extraHeight         = _w->graphEdit.extraHeight;

    // Could it be this is invoked without any graph yet?
    if (graph == 0)
	return;

    sizeChanged = False;

    BoxRegion r = graph->region(graphGC);

    Dimension myWidth  = r.origin(X) + r.space(X) + highlight_thickness * 2;
    Dimension myHeight = r.origin(Y) + r.space(Y) + highlight_thickness * 2;

    Dimension parentWidth  = 0;
    Dimension parentHeight = 0;

    Widget parent = XtParent(w);
    if (!XmIsScrolledWindow(parent))
	parent = XtParent(parent);	// Skip clipping window
    if (XmIsScrolledWindow(parent))
    {
	// Get the size allowed by our parent
	Dimension parentSpacing;

	arg = 0;
	XtSetArg(args[arg], XtNwidth,   &parentWidth);   arg++;
	XtSetArg(args[arg], XtNheight,  &parentHeight);  arg++;
	XtSetArg(args[arg], XmNspacing, &parentSpacing); arg++;
	XtGetValues(parent, args, arg);

	if (parentWidth >= parentSpacing)
	    parentWidth -= parentSpacing;
	if (parentHeight >= parentSpacing)
	    parentHeight -= parentSpacing;
    }

    Dimension width  = max(parentWidth, myWidth);
    Dimension height = max(parentHeight, myHeight);

    width  += extraWidth;
    height += extraHeight;

    Dimension width_return;
    Dimension height_return;
    XtGeometryResult result = 
	XtMakeResizeRequest(w, width, height, &width_return, &height_return);
    if (result == XtGeometryAlmost)
	result = XtMakeResizeRequest(w, width_return, height_return,
				     &width_return, &height_return);

    if (result == XtGeometryYes)
    {
	// Normally, we should let our manager resize ourselves.
	// But LessTif 0.87 wants it this way.
	XtResizeWidget(w, width_return, height_return, 0);

	graphEditRedraw(w);
    }
}

// Return the graph's Graphic Context
const GraphGC& graphEditGetGraphGC(Widget w)
{
    XtCheckSubclass(w, GraphEditWidgetClass, "Bad widget class");

    const GraphEditWidget _w = GraphEditWidget(w);
    const GraphGC& graphGC   = _w->graphEdit.graphGC;

    return graphGC;
}

// Return the graph
Graph *graphEditGetGraph(Widget w)
{
    XtCheckSubclass(w, GraphEditWidgetClass, "Bad widget class");

    GraphEditWidget _w = GraphEditWidget(w);
    return _w->graphEdit.graph;
}


// Set grid pixmap
static void setGrid(Widget w, Boolean reset = False)
{
    const GraphEditWidget _w   = GraphEditWidget(w);
    const Pixel     gridColor  = _w->graphEdit.gridColor;
    const Pixel     background = _w->core.background_pixel;
    const Boolean   showGrid   = _w->graphEdit.showGrid;
    Dimension& gridHeight      = _w->graphEdit.gridHeight;
    Dimension& gridWidth       = _w->graphEdit.gridWidth;
    Pixmap& gridPixmap         = _w->graphEdit.gridPixmap;

    gridWidth  = max(gridWidth,  2);
    gridHeight = max(gridHeight, 2);

    if (reset && gridPixmap != None)
    {
	// delete old pixmap
	XSetWindowBackgroundPixmap(XtDisplay(w), XtWindow(w), ParentRelative);
	XClearArea(XtDisplay(w), XtWindow(w), 0, 0, 0, 0, True);

	XFreePixmap(XtDisplay(w), gridPixmap);
	gridPixmap = None;
    }

    if (gridPixmap == None)
    {
	// Create grid pixmap
	int gridDataSize = ((gridWidth + 7) / 8) * gridHeight + 1;
	char *gridData = new char [gridDataSize];
	for (int i = 0; i < gridDataSize; i++)
	    gridData[i] = 0x00;
	if (showGrid)
	    gridData[0] = 0x01;

	int depth = PlanesOfScreen(XtScreen(w));
	gridPixmap = 
	    XCreatePixmapFromBitmapData(XtDisplay(w), 
					XtWindow(w),
					gridData, 
					gridWidth, gridHeight,
					gridColor, background,
					depth);

 	XSetWindowBackgroundPixmap(XtDisplay(w), XtWindow(w), gridPixmap);
	XClearArea(XtDisplay(w), XtWindow(w), 0, 0, 0, 0, True);

	delete[] gridData;
    }
}



// Redraw
static void RedrawCB(XtPointer client_data, XtIntervalId *id)
{
    const Widget w                      = Widget(client_data);
    const GraphEditWidget _w            = GraphEditWidget(w);
    const Graph* graph                  = _w->graphEdit.graph;
    const GraphGC& graphGC              = _w->graphEdit.graphGC;
    const Boolean sizeChanged           = _w->graphEdit.sizeChanged;
    const Boolean redisplayEnabled      = _w->graphEdit.redisplayEnabled;
    const Boolean highlight_drawn       = _w->primitive.highlight_drawn;
    const Dimension highlight_thickness = _w->primitive.highlight_thickness;
    XtIntervalId& redrawTimer           = _w->graphEdit.redrawTimer;

    (void) id;			// Use it
    assert(redrawTimer == *id);
    redrawTimer = 0;

    if (graph == 0)
	return;			// No graph to draw

    if (!redisplayEnabled)
	return;			// Display disabled

    setGrid(w);

    if (sizeChanged)
	graphEditSizeChanged(w);

    // Redraw XmPrimitive border
    if (highlight_drawn)
	graphEditClassRec.primitive_class.border_highlight(w);

    // Check for pending redrawings
    Boolean redraw_all = True;
    GraphNode *node;
    for (node = graph->firstVisibleNode(); 
	 node != 0;
	 node = graph->nextVisibleNode(node))
    {
	if (!node->redraw())
	{
	    redraw_all = False;
	    break;
	}
    }

    setGrid(w);

    if (redraw_all)
    {
	XClearArea(XtDisplay(w), XtWindow(w),
		   highlight_thickness, highlight_thickness, 
		   _w->core.width  - highlight_thickness * 2, 
		   _w->core.height - highlight_thickness * 2,
		   False);

	graph->draw(w, EVERYWHERE, graphGC);
    }

    for (node = graph->firstVisibleNode(); 
	 node != 0;
	 node = graph->nextVisibleNode(node))
    {
	if (!redraw_all && node->redraw())
	{
	    BoxRegion r = node->region(graphGC);
	    XClearArea(XtDisplay(w), XtWindow(w), r.origin(X), r.origin(Y),
		       r.space(X), r.space(Y), False);

	    graph->draw(w, r, graphGC);
	}

	node->redraw() = False;
    }
}

// Launch redrawing procedure
static void StartRedraw(Widget w)
{
    const GraphEditWidget _w  = GraphEditWidget(w);
    XtIntervalId& redrawTimer = _w->graphEdit.redrawTimer;

    if (redrawTimer != 0)
	return;			// Redraw pending

    // Redraw after we are back in the event loop
    redrawTimer = XtAppAddTimeOut(XtWidgetToApplicationContext(w),
				  0, RedrawCB, XtPointer(w));
}

// Redraw entire graph
void graphEditRedraw(Widget w)
{
    XtCheckSubclass(w, GraphEditWidgetClass, "Bad widget class");

    const GraphEditWidget _w   = GraphEditWidget(w);
    const Graph* graph         = _w->graphEdit.graph;

    for (GraphNode *node = graph->firstVisibleNode(); 
	 node != 0;
	 node = graph->nextVisibleNode(node))
    {
	graphEditRedrawNode(w, node);
    }
}

// Redraw a specific region
void graphEditRedrawNode(Widget w, GraphNode *node)
{
    XtCheckSubclass(w, GraphEditWidgetClass, "Bad widget class");

    if (!node->hidden())
    {
	node->redraw() = True;
	StartRedraw(w);
    }
}

// Disable redrawing for a while; return old state
Boolean graphEditEnableRedisplay(Widget w, Boolean state)
{
    XtCheckSubclass(w, GraphEditWidgetClass, "Bad widget class");

    const GraphEditWidget _w   = GraphEditWidget(w);
    Boolean& redisplayEnabled  = _w->graphEdit.redisplayEnabled;

    Boolean old_state = redisplayEnabled;
    redisplayEnabled = state;

    if (redisplayEnabled)
	StartRedraw(w);

    return old_state;
}




// Converters

#define done(type, value) \
    {							\
	if (toVal->addr != 0) {			\
	    if (toVal->size < sizeof(type)) {		\
		toVal->size = sizeof(type);		\
		return False;				\
	    }						\
	    *(type *)(toVal->addr) = (value);		\
	}						\
	else {						\
	    static type static_val;			\
	    static_val = (value);			\
	    toVal->addr = (XPointer)&static_val;	\
	}						\
							\
	toVal->size = sizeof(type);			\
	return True;					\
    }



// Convert String to EdgeAttachMode and vice versa

static Boolean CvtStringToEdgeAttachMode (Display *display, XrmValue *,
    Cardinal *num_args, XrmValue *fromVal, XrmValue *toVal,
    XtPointer *)
{
    EdgeAttachMode mode = Straight;

    if (*num_args != 0)
	XtAppWarningMsg(XtDisplayToApplicationContext(display),
	    "CvtStringToEdgeAttachMode", "wrongParameters",
	    "XtToolkitError",
	    "String to EdgeAttachMode conversion needs no extra arguments",
	    (String *)0, (Cardinal *)0);
    
    string s = downcase((char *)fromVal->addr);

    if (s == "straight")
	mode = Straight;
    else if (s == "circle")
	mode = Circle;
    else if (s == "centered")
	mode = Centered;
    else
	XtDisplayStringConversionWarning(display, (String)fromVal->addr,
	    XtREdgeAttachMode);

    done(EdgeAttachMode, mode);
}

static Boolean CvtEdgeAttachModeToString (Display *display, XrmValue *,
    Cardinal *num_args, XrmValue *fromVal, XrmValue *toVal,
    XtPointer *)
{
    if (*num_args != 0)
	XtAppWarningMsg(XtDisplayToApplicationContext(display),
	    "CvtEdgeAttachModeToString", "wrongParameters",
	    "XtToolkitError",
	    "EdgeAttachMode to String conversion needs no extra arguments",
	    (String *)0, (Cardinal *)0);

    EdgeAttachMode mode = *((EdgeAttachMode *)fromVal->addr);

    const _XtString s = "unknown";
    switch (mode)
    {
    case Straight:
	s = "straight";
	break;
    case Circle:
	s = "circle";
	break;
    case Centered:
	s = "centered";
	break;
    default:
	XtDisplayStringConversionWarning(display, s, XtRString);
	break;
    }

    done(const _XtString, s);
}


// Convert String to LayoutMode and vice versa

static Boolean CvtStringToLayoutMode (Display *display, XrmValue *,
    Cardinal *num_args, XrmValue *fromVal, XrmValue *toVal,
    XtPointer *)
{
    LayoutMode mode = RegularLayoutMode;

    if (*num_args != 0)
	XtAppWarningMsg(XtDisplayToApplicationContext(display),
	    "CvtStringToLayoutMode", "wrongParameters",
	    "XtToolkitError",
	    "String to LayoutMode conversion needs no extra arguments",
	    (String *)0, (Cardinal *)0);
    
    string s = downcase((char *)fromVal->addr);

    if (s == "regular")
	mode = RegularLayoutMode;
    else if (s == "compact")
	mode = CompactLayoutMode;
    else
	XtDisplayStringConversionWarning(display, (String)fromVal->addr,
	    XtRLayoutMode);

    done(LayoutMode, mode);
}


static Boolean CvtLayoutModeToString (Display *display, XrmValue *,
    Cardinal *num_args, XrmValue *fromVal, XrmValue *toVal,
    XtPointer *)
{
    if (*num_args != 0)
	XtAppWarningMsg(XtDisplayToApplicationContext(display),
	    "CvtLayoutModeToString", "wrongParameters",
	    "XtToolkitError",
	    "LayoutMode to String conversion needs no extra arguments",
	    (String *)0, (Cardinal *)0);

    LayoutMode mode = *((LayoutMode *)fromVal->addr);

    const _XtString s = "unknown";
    switch (mode)
    {
    case RegularLayoutMode:
	s = "regular";
	break;
    case CompactLayoutMode:
	s = "compact";
	break;
    default:
	XtDisplayStringConversionWarning(display, s, XtRString);
	break;
    }

    done(const _XtString, s);
}


// Convert String to SelfEdgePosition and vice versa

static Boolean CvtStringToSelfEdgePosition (Display *display, XrmValue *,
    Cardinal *num_args, XrmValue *fromVal, XrmValue *toVal,
    XtPointer *)
{
    if (*num_args != 0)
	XtAppWarningMsg(XtDisplayToApplicationContext(display),
	    "CvtStringToSelfEdgePosition", "wrongParameters",
	    "XtToolkitError",
	    "String to SelfEdgePosition conversion needs no extra arguments",
	    (String *)0, (Cardinal *)0);
    
    string s = downcase((char *)fromVal->addr);

    SelfEdgePosition pos = NorthWest;
    if (s == "northwest")
	pos = NorthWest;
    else if (s == "northeast")
	pos = NorthEast;
    else if (s == "southwest")
	pos = SouthWest;
    else if (s == "southeast")
	pos = SouthEast;
    else
	XtDisplayStringConversionWarning(display, (String)fromVal->addr,
	    XtRSelfEdgePosition);

    done(SelfEdgePosition, pos);
}

static Boolean CvtSelfEdgePositionToString (Display *display, XrmValue *,
    Cardinal *num_args, XrmValue *fromVal, XrmValue *toVal,
    XtPointer *)
{
    if (*num_args != 0)
	XtAppWarningMsg(XtDisplayToApplicationContext(display),
	    "CvtSelfEdgePositionToString", "wrongParameters",
	    "XtToolkitError",
	    "SelfEdgePosition to String conversion needs no extra arguments",
	    (String *)0, (Cardinal *)0);

    SelfEdgePosition pos = *((SelfEdgePosition *)fromVal->addr);

    const _XtString s = "unknown";
    switch (pos)
    {
    case NorthWest:
	s = "northwest";
	break;
    case NorthEast:
	s = "northeast";
	break;
    case SouthWest:
	s = "southwest";
	break;
    case SouthEast:
	s = "southwest";
	break;
    default:
	XtDisplayStringConversionWarning(display, s, XtRString);
	break;
    }

    done(const _XtString, s);
}


// Convert String to SelfEdgeDirection and vice versa

static Boolean CvtStringToSelfEdgeDirection (Display *display, XrmValue *,
    Cardinal *num_args, XrmValue *fromVal, XrmValue *toVal,
    XtPointer *)
{
    if (*num_args != 0)
	XtAppWarningMsg(XtDisplayToApplicationContext(display),
	    "CvtStringToSelfEdgeDirection", "wrongParameters",
	    "XtToolkitError",
	    "String to SelfEdgeDirection conversion needs no extra arguments",
	    (String *)0, (Cardinal *)0);
    
    string s = downcase((char *)fromVal->addr);

    SelfEdgeDirection dir = Counterclockwise;
    if (s == "counterclockwise")
	dir = Counterclockwise;
    else if (s == "clockwise")
	dir = Clockwise;
    else
	XtDisplayStringConversionWarning(display, (String)fromVal->addr,
	    XtRSelfEdgeDirection);

    done(SelfEdgeDirection, dir);
}

static Boolean CvtSelfEdgeDirectionToString (Display *display, XrmValue *,
    Cardinal *num_args, XrmValue *fromVal, XrmValue *toVal,
    XtPointer *)
{
    if (*num_args != 0)
	XtAppWarningMsg(XtDisplayToApplicationContext(display),
	    "CvtSelfEdgeDirectionToString", "wrongParameters",
	    "XtToolkitError",
	    "SelfEdgeDirection to String conversion needs no extra arguments",
	    (String *)0, (Cardinal *)0);

    SelfEdgeDirection pos = *((SelfEdgeDirection *)fromVal->addr);

    const _XtString s = "unknown";
    switch (pos)
    {
    case Clockwise:
	s = "clockwise";
	break;
    case Counterclockwise:
	s = "counterclockwise";
	break;
    default:
	XtDisplayStringConversionWarning(display, s, XtRString);
	break;
    }

    done(const _XtString, s);
}




// Standard Converters

static Boolean CvtBooleanToString (Display *display, XrmValue *,
    Cardinal *num_args, XrmValue *fromVal, XrmValue *toVal,
    XtPointer *)
{
    if (*num_args != 0)
	XtAppWarningMsg(XtDisplayToApplicationContext(display),
	    "CvtBooleanToString", "wrongParameters",
	    "XtToolkitError",
	    "Boolean to String conversion needs no extra arguments",
	    (String *)0, (Cardinal *)0);

    Boolean mode = *((Boolean *)fromVal->addr);

    const _XtString s = "unknown";
    switch (mode)
    {
    case True:
	s = "on";
	break;
    case False:
	s = "off";
	break;
    default:
	XtDisplayStringConversionWarning(display, s, XtRString);
	break;
    }

    done(const _XtString, s);
}

static Boolean CvtDimensionToString (Display *display, XrmValue *,
    Cardinal *num_args, XrmValue *fromVal, XrmValue *toVal,
    XtPointer *)
{
    if (*num_args != 0)
	XtAppWarningMsg(XtDisplayToApplicationContext(display),
	    "CvtDimensionToString", "wrongParameters",
	    "XtToolkitError",
	    "Dimension to String conversion needs no extra arguments",
	    (String *)0, (Cardinal *)0);

    Dimension d = *((Dimension *)fromVal->addr);

    ostrstream os;
    os << d;
    string os_s(os);
    String s = (String)XtNewString((String)os_s);

    done(String, s);
}

static Boolean CvtCardinalToString (Display *display, XrmValue *,
    Cardinal *num_args, XrmValue *fromVal, XrmValue *toVal,
    XtPointer *)
{
    if (*num_args != 0)
	XtAppWarningMsg(XtDisplayToApplicationContext(display),
	    "CvtCardinalToString", "wrongParameters",
	    "XtToolkitError",
	    "Cardinal to String conversion needs no extra arguments",
	    (String *)0, (Cardinal *)0);

    Cardinal d = *((Cardinal *)fromVal->addr);

    ostrstream os;
    os << d;
    string os_s(os);
    String s = (String)XtNewString((String)os_s);

    done(String, s);
}


// Initialize class
static void ClassInitialize()
{
    // Register own string -> type converters
    XtSetTypeConverter(XtRString, XtREdgeAttachMode, 
		       CvtStringToEdgeAttachMode,
		       XtConvertArgList(0), 0, 
		       XtCacheAll, XtDestructor(0));
    XtSetTypeConverter(XtRString, XtRLayoutMode, 
		       CvtStringToLayoutMode,
		       XtConvertArgList(0), 0, 
		       XtCacheAll, XtDestructor(0));
    XtSetTypeConverter(XtRString, XtRSelfEdgePosition, 
		       CvtStringToSelfEdgePosition,
		       XtConvertArgList(0), 0, 
		       XtCacheAll, XtDestructor(0));
    XtSetTypeConverter(XtRString, XtRSelfEdgeDirection, 
		       CvtStringToSelfEdgeDirection,
		       XtConvertArgList(0), 0, 
		       XtCacheAll, XtDestructor(0));

    // Register own type -> string converters
    XtSetTypeConverter(XtREdgeAttachMode, XtRString, 
		       CvtEdgeAttachModeToString,
		       XtConvertArgList(0), 0, 
		       XtCacheAll, XtDestructor(0));
    XtSetTypeConverter(XtRLayoutMode, XtRString, 
		       CvtLayoutModeToString,
		       XtConvertArgList(0), 0, 
		       XtCacheAll, XtDestructor(0));
    XtSetTypeConverter(XtRSelfEdgePosition, XtRString, 
		       CvtSelfEdgePositionToString,
		       XtConvertArgList(0), 0, 
		       XtCacheAll, XtDestructor(0));
    XtSetTypeConverter(XtRSelfEdgeDirection, XtRString, 
		       CvtSelfEdgeDirectionToString,
		       XtConvertArgList(0), 0, 
		       XtCacheAll, XtDestructor(0));

    // Register standard type -> string converters
    XtSetTypeConverter(XtRBoolean, XtRString, 
		       CvtBooleanToString,
		       XtConvertArgList(0), 0, 
		       XtCacheAll, XtDestructor(0));
    XtSetTypeConverter(XtRDimension, XtRString, 
		       CvtDimensionToString,
		       XtConvertArgList(0), 0, 
		       XtCacheAll, XtDestructor(0));
    XtSetTypeConverter(XtRCardinal, XtRString, 
		       CvtCardinalToString,
		       XtConvertArgList(0), 0, 
		       XtCacheAll, XtDestructor(0));
}


// Initialize widget

inline void createCursor(Widget w, Cursor& cursor, unsigned int shape)
{
    if (cursor == 0)
	cursor = XCreateFontCursor(XtDisplay(w), shape);
}

static void setGCs(Widget w)
{
    const GraphEditWidget _w        = GraphEditWidget(w);

    // read-only
    const Dimension edgeWidth       = _w->graphEdit.edgeWidth;
    const Pixmap selectTile         = _w->graphEdit.selectTile;
    const Pixel background          = _w->core.background_pixel;
    const Pixel nodeColor           = _w->graphEdit.nodeColor;
    const Pixel edgeColor           = _w->graphEdit.edgeColor;
    const Pixel frameColor          = _w->graphEdit.frameColor;
    const Pixel outlineColor        = _w->graphEdit.outlineColor;
    const Pixel selectColor         = _w->graphEdit.selectColor;
    const Boolean dashedLines       = _w->graphEdit.dashedLines;

    // write-only
    GC& nodeGC                      = _w->graphEdit.nodeGC;
    GC& edgeGC                      = _w->graphEdit.edgeGC;
    GC& invertGC                    = _w->graphEdit.invertGC;
    GC& clearGC                     = _w->graphEdit.clearGC;
    GC& frameGC                     = _w->graphEdit.frameGC;
    GC& outlineGC                   = _w->graphEdit.outlineGC;

    int line_style = dashedLines ? LineOnOffDash : LineSolid;

    // set nodeGC
    XGCValues gcv;
    gcv.foreground = nodeColor;
    gcv.line_width = 1;
    gcv.line_style = line_style;
    nodeGC = XtGetGC(w, GCForeground | GCLineWidth | GCLineStyle, &gcv);

    // set edgeGC
    gcv.foreground = edgeColor;
    gcv.line_width = edgeWidth;
    gcv.line_style = line_style;
    edgeGC = XtGetGC(w, GCForeground | GCLineWidth | GCLineStyle, &gcv);

    // set invertGC
    if (selectTile)
    {
	gcv.foreground = selectColor;
	gcv.function   = GXcopy;
	gcv.fill_style = FillStippled;
	gcv.stipple    = selectTile;
	invertGC = XtGetGC(w, GCForeground | GCFunction | 
			   GCFillStyle | GCStipple, &gcv);
    }
    else
    {
	gcv.foreground = selectColor;
	gcv.function   = GXinvert;
	gcv.plane_mask = selectColor ^ background;
	invertGC = XtGetGC(w, GCForeground | GCFunction | GCPlaneMask, &gcv);
    }

    // set clearGC
    gcv.foreground = background;
    gcv.function   = GXcopy;
    clearGC = XtGetGC(w, GCForeground | GCFunction, &gcv);

    // set frameGC
    gcv.foreground = frameColor;
    gcv.function   = GXinvert;
    gcv.line_width = 1;
    gcv.line_style = LineSolid;
    gcv.plane_mask = frameColor ^ background;
    frameGC = XtGetGC(w, GCForeground | GCFunction | 
		      GCLineWidth | GCLineStyle | GCPlaneMask, &gcv);

    // set outlineGC
    gcv.foreground = outlineColor;
    gcv.function   = GXinvert;
    gcv.line_width = 1;
    gcv.line_style = LineSolid;
    gcv.plane_mask = outlineColor ^ background;
    outlineGC = XtGetGC(w, GCForeground | GCFunction | 
			GCLineWidth | GCLineStyle | GCPlaneMask, &gcv);
}


static void setGraphGC(Widget w)
{
    const GraphEditWidget _w        = GraphEditWidget(w);

    // Read only
    const Dimension arrowAngle      = _w->graphEdit.arrowAngle;
    const Dimension arrowLength     = _w->graphEdit.arrowLength;
    const Dimension hintSize        = _w->graphEdit.hintSize;
    const Boolean showHints         = _w->graphEdit.showHints;
    const Boolean showAnnotations   = _w->graphEdit.showAnnotations;
    const GC nodeGC                 = _w->graphEdit.nodeGC;
    const GC edgeGC                 = _w->graphEdit.edgeGC;
    const GC invertGC               = _w->graphEdit.invertGC;
    const GC clearGC                = _w->graphEdit.clearGC;

    const Dimension selfEdgeDiameter = 
	_w->graphEdit.selfEdgeDiameter;
    const SelfEdgePosition selfEdgePosition = 
	_w->graphEdit.selfEdgePosition;
    const SelfEdgeDirection selfEdgeDirection = 
	_w->graphEdit.selfEdgeDirection;
    const EdgeAttachMode edgeAttachMode = 
	EdgeAttachMode(_w->graphEdit.edgeAttachMode);

    // Write only
    GraphGC& graphGC                = _w->graphEdit.graphGC;

    // Set graphGC
    graphGC = GraphGC(nodeGC, edgeGC, invertGC, clearGC);
    graphGC.arrowAngle        = arrowAngle;
    graphGC.arrowLength       = arrowLength;
    graphGC.edgeAttachMode    = EdgeAttachMode(edgeAttachMode);
    graphGC.drawHints         = showHints;
    graphGC.drawAnnotations   = showAnnotations;
    graphGC.hintSize          = hintSize;
    graphGC.selfEdgeDiameter  = selfEdgeDiameter;
    graphGC.selfEdgePosition  = selfEdgePosition;
    graphGC.selfEdgeDirection = selfEdgeDirection;

    // Get print colors

    if (_w->graphEdit.nodePrintColor != 0)
    {
	XColor exact_def;
	Status ok = 
	    XParseColor(XtDisplay(w), _w->core.colormap, 
			_w->graphEdit.nodePrintColor, &exact_def);

	if (ok)
	{
	    graphGC.node_red   = exact_def.red;
	    graphGC.node_green = exact_def.green;
	    graphGC.node_blue  = exact_def.blue;
	}
	else
	{
	    Cardinal one = 1;

	    XtAppWarningMsg(XtWidgetToApplicationContext(w),
			    "GraphEdit::Initialize", "badColor",
			    "XtToolkitError",
			    "Cannot parse " XtNnodePrintColor " \"%s\"",
			    (String *)&_w->graphEdit.nodePrintColor, &one);
	}
    }

    if (_w->graphEdit.edgePrintColor != 0)
    {
	XColor exact_def;
	Status ok = 
	    XParseColor(XtDisplay(w), _w->core.colormap, 
			_w->graphEdit.edgePrintColor, &exact_def);

	if (ok)
	{
	    graphGC.edge_red   = exact_def.red;
	    graphGC.edge_green = exact_def.green;
	    graphGC.edge_blue  = exact_def.blue;
	}
	else
	{
	    Cardinal one = 1;

	    XtAppWarningMsg(XtWidgetToApplicationContext(w),
			    "GraphEdit::Initialize", "badColor",
			    "XtToolkitError",
			    "Cannot parse " XtNedgePrintColor " spec \"%s\"",
			    (String *)&_w->graphEdit.edgePrintColor, &one);
	}
    }
}


static void Initialize(Widget request, Widget w, ArgList, Cardinal *)
{
    // read-only
    const GraphEditWidget _w        = GraphEditWidget(w);

    // write-only
    GraphEditState& state           = _w->graphEdit.state;
    Cursor& moveCursor              = _w->graphEdit.moveCursor;
    Cursor& selectCursor            = _w->graphEdit.selectCursor;
    Cursor& selectBottomLeftCursor  = _w->graphEdit.selectBottomLeftCursor;
    Cursor& selectBottomRightCursor = _w->graphEdit.selectBottomRightCursor;
    Cursor& selectTopLeftCursor     = _w->graphEdit.selectTopLeftCursor;
    Cursor& selectTopRightCursor    = _w->graphEdit.selectTopRightCursor;
    Pixmap& gridPixmap              = _w->graphEdit.gridPixmap;
    Boolean& sizeChanged            = _w->graphEdit.sizeChanged;
    Boolean& redisplayEnabled       = _w->graphEdit.redisplayEnabled;
    Time& lastSelectTime            = _w->graphEdit.lastSelectTime;
    XtIntervalId& redrawTimer       = _w->graphEdit.redrawTimer;
    Dimension& requestedWidth       = _w->graphEdit.requestedWidth;
    Dimension& requestedHeight      = _w->graphEdit.requestedHeight;

    // init state
    state = NopState;

    // init sizeChanged
    sizeChanged = False;

    // init redisplayEnabled
    redisplayEnabled = True;

    // init lastSelectTime
    lastSelectTime = 0;

    // init redrawTimer
    redrawTimer = 0;

    // set GCs
    setGCs(w);

    // set Graph GC
    setGraphGC(w);

    // set grid pixmap
    gridPixmap = None;

    // create cursors if not already set
    createCursor(w, moveCursor,              XC_fleur);
    createCursor(w, selectCursor,            XC_plus);
    createCursor(w, selectBottomLeftCursor,  XC_ll_angle);
    createCursor(w, selectBottomRightCursor, XC_lr_angle);
    createCursor(w, selectTopLeftCursor,     XC_ul_angle);
    createCursor(w, selectTopRightCursor,    XC_ur_angle);

    // save requested size
    requestedWidth  = request->core.width;
    requestedHeight = request->core.height;

    // set size
    graphEditSizeChanged(w);

    // Override XmPrimitive translations
    static XtTranslations translations = 
	XtParseTranslationTable(extraTranslations);

    XtOverrideTranslations(w, translations);
}

inline void defineCursor(Widget w, Cursor cursor)
{
    if (cursor != 0)
	XDefineCursor(XtDisplay(w), XtWindow(w), cursor);
    else
	XUndefineCursor(XtDisplay(w), XtWindow(w));
}

// Realize widget
static void Realize(Widget w, 
		    XtValueMask *value_mask,
		    XSetWindowAttributes *attributes)
{
    const GraphEditWidget _w = GraphEditWidget(w);
    Cursor defaultCursor     = _w->graphEdit.defaultCursor;

    // Call superclass realize method
    graphEditClassRec.core_class.superclass->
	core_class.realize(w, value_mask, attributes);
    
    // Setup default cursor
    defineCursor(w, defaultCursor);
}


// Redisplay widget
static void Redisplay(Widget w, XEvent *event, Region)
{
    const GraphEditWidget _w       = GraphEditWidget(w);
    const Graph* graph             = _w->graphEdit.graph;
    const GraphGC& graphGC         = _w->graphEdit.graphGC;
    const Boolean sizeChanged      = _w->graphEdit.sizeChanged;
    const Boolean redisplayEnabled = _w->graphEdit.redisplayEnabled;
    const Boolean highlight_drawn  = _w->primitive.highlight_drawn;

    if (!redisplayEnabled)
    {
	graphEditRedraw(w);
	return;
    }

    if (sizeChanged)
	graphEditSizeChanged(w);

    setGrid(w);

    // Redraw XmPrimitive border
    if (highlight_drawn)
	graphEditClassRec.primitive_class.border_highlight(w);

    graph->draw(w, BoxRegion(point(event), size(event)), graphGC);
}


// Set widget values
static Boolean SetValues(Widget old, Widget, Widget new_w, 
			 ArgList, Cardinal *)
{
    GraphEditWidget before = GraphEditWidget(old);
    GraphEditWidget after  = GraphEditWidget(new_w);

    Boolean redisplay = False;

    // redisplay graph if changed
    if (before->graphEdit.graph != after->graphEdit.graph)
    {
	redisplay = True;

	// Re-layout if auto-layout is enabled
	if (after->graphEdit.autoLayout)
	{
	    Cardinal zero = 0;
	    _Layout(new_w, 0, 0, &zero);
	}

	// Snap to grid if enabled
	if (after->graphEdit.snapToGrid)
	{
	    Cardinal zero = 0;
	    _SnapToGrid(new_w, 0, 0, &zero);
	}
    }

    Boolean new_gcs = False;

    // reset GCs if changed
    if (before->graphEdit.edgeWidth      != after->graphEdit.edgeWidth ||
	before->graphEdit.selectTile     != after->graphEdit.selectTile ||
	before->graphEdit.dashedLines    != after->graphEdit.dashedLines ||
	before->graphEdit.nodeColor      != after->graphEdit.nodeColor ||
	before->graphEdit.edgeColor      != after->graphEdit.edgeColor ||
	before->graphEdit.frameColor     != after->graphEdit.frameColor ||
	before->graphEdit.outlineColor   != after->graphEdit.outlineColor ||
	before->graphEdit.gridColor      != after->graphEdit.gridColor ||
	before->graphEdit.selectColor    != after->graphEdit.selectColor)
    {    
	setGCs(new_w);
	new_gcs = True;
	redisplay = True;
    }

    // reset GraphGC if changed
    if (new_gcs ||
	before->graphEdit.arrowAngle      != after->graphEdit.arrowAngle     ||
	before->graphEdit.arrowLength     != after->graphEdit.arrowLength    ||
	before->graphEdit.showHints       != after->graphEdit.showHints      ||
	before->graphEdit.hintSize        != after->graphEdit.hintSize       ||
	before->graphEdit.edgeAttachMode  != after->graphEdit.edgeAttachMode ||
	before->graphEdit.showAnnotations != after->graphEdit.showAnnotations)
    {
	setGraphGC(new_w);
	redisplay = True;
    }

    if (before->graphEdit.nodePrintColor != after->graphEdit.nodePrintColor ||
	before->graphEdit.edgePrintColor != after->graphEdit.edgePrintColor)
    {
	setGraphGC(new_w);
    }

    // reset grid pixmap if changed
    if (before->graphEdit.gridWidth  != after->graphEdit.gridWidth  ||
	before->graphEdit.gridHeight != after->graphEdit.gridHeight ||
	before->graphEdit.showGrid   != after->graphEdit.showGrid)
    {
	setGrid(new_w, True);
	redisplay = True;
    }

    // Always recompute size
    after->graphEdit.sizeChanged = True;

    return redisplay;
}

// Destroy widget
static void Destroy(Widget)
{
    // Delete graph?
}




// Action function definitions

// Helping stuff

// Find node at point
GraphNode *graphEditGetNodeAtPoint(Widget w, BoxPoint p)
{
    XtCheckSubclass(w, GraphEditWidgetClass, "Bad widget class");

    const GraphEditWidget _w  = GraphEditWidget(w);
    const Graph* graph        = _w->graphEdit.graph;
    GraphGC& graphGC          = _w->graphEdit.graphGC;
    GraphNode *found = 0;

    // Could it be this is invoked without any graph yet?
    if (graph == 0)
	return 0;

    // note that we return the last matching node in the list;
    // thus on overlapping nodes we select the top one

    for (GraphNode *node = graph->firstVisibleNode(); node != 0;
	node = graph->nextVisibleNode(node))
	if (p <= node->sensitiveRegion(graphGC))
	    found = node;

    return found;
}
    
GraphNode *graphEditGetNodeAtEvent(Widget w, XEvent *event)
{
    return graphEditGetNodeAtPoint(w, point(event));
}


// Get frame region
static BoxRegion frameRegion(Widget w)
{
    const GraphEditWidget _w    = GraphEditWidget(w);
    const BoxPoint& startAction = _w->graphEdit.startAction;
    const BoxPoint& endAction   = _w->graphEdit.endAction;

    BoxPoint origin(min(startAction[X], endAction[X]),
	min(startAction[Y], endAction[Y]));

    BoxSize space(abs(startAction[X] - endAction[X]),
	abs(startAction[Y] - endAction[Y]));

    return BoxRegion(origin, space);
}

// Get frame cursor
static void setRegionCursor(Widget w)
{
    const GraphEditWidget _w    = GraphEditWidget(w);
    const BoxPoint& startAction = _w->graphEdit.startAction;
    const BoxPoint& endAction   = _w->graphEdit.endAction;

    Cursor selectCursor            = _w->graphEdit.selectCursor;
    Cursor selectBottomLeftCursor  = _w->graphEdit.selectBottomLeftCursor;
    Cursor selectBottomRightCursor = _w->graphEdit.selectBottomRightCursor;
    Cursor selectTopLeftCursor     = _w->graphEdit.selectTopLeftCursor;
    Cursor selectTopRightCursor    = _w->graphEdit.selectTopRightCursor;

    Cursor cursor = selectCursor;

    if (endAction[X] < startAction[X])
    {
	if (endAction[Y] < startAction[Y])
	    cursor = selectTopLeftCursor;
	else if (endAction[Y] > startAction[Y])
	    cursor = selectBottomLeftCursor;
    }
    else if (endAction[X] > startAction[X])
    {
	if (endAction[Y] < startAction[Y])
	    cursor = selectTopRightCursor;
	else if (endAction[Y] > startAction[Y])
	    cursor = selectBottomRightCursor;
    }

    defineCursor(w, cursor);
}


inline void myXDrawLine(Display *display, 
			Drawable d, 
			GC gc,
			const BoxPoint& f, const BoxPoint& t)
{
    if (f != t)
	XDrawLine(display, d, gc, f[X], f[Y], t[X], t[Y]);
}
    

static void redrawSelectFrame(Widget w, const BoxRegion& r)
{
    const GraphEditWidget _w = GraphEditWidget(w);
    const GC frameGC = _w->graphEdit.frameGC;

    Display *display = XtDisplay(w);
    Window window    = XtWindow(w);

    // North
    myXDrawLine(display, window, frameGC, 
		r.origin() + BoxPoint(1, 0),
		r.origin() + BoxPoint(r.space(X) - 1, 0));

    // South
    myXDrawLine(display, window, frameGC,
		r.origin() + BoxPoint(0, r.space(Y)),
		r.origin() + BoxPoint(r.space(X), r.space(Y)));

    // East
    myXDrawLine(display, window, frameGC,
		r.origin(),
		r.origin() + BoxPoint(0, r.space(Y) - 1));

    // West
    myXDrawLine(display, window, frameGC,
		r.origin() + BoxPoint(r.space(X), 0),
		r.origin() + BoxPoint(r.space(X), r.space(Y) - 1));
}


static void drawSelectFrames(Widget w, 
			     const BoxRegion& r0, 
			     const BoxRegion& r1)
{
    // Clear old frame (by redrawing it)
    redrawSelectFrame(w, r0);

    // Draw new frame
    redrawSelectFrame(w, r1);

    // Set appropriate cursor
    setRegionCursor(w);
}


// Draw the selection frame
inline void drawSelectFrame(Widget w)
{
    drawSelectFrames(w, frameRegion(w),
	BoxRegion(BoxPoint(0, 0), BoxSize(0, 0)));
}


// Redraw selection frame
static void redrawSelectFrame(Widget w, BoxPoint& p)
{
    const GraphEditWidget _w = GraphEditWidget(w);
    BoxPoint& endAction      = _w->graphEdit.endAction;

    BoxRegion r0 = frameRegion(w);
    endAction = p;
    BoxRegion r1 = frameRegion(w);

    drawSelectFrames(w, r0, r1);
}


// Find min possible offset
static void getMinimalOffset(Widget w)
{
    const GraphEditWidget _w            = GraphEditWidget(w);
    const Graph* graph                  = _w->graphEdit.graph;
    const Dimension highlight_thickness = _w->primitive.highlight_thickness;
    const GraphGC& graphGC              = _w->graphEdit.graphGC;
    BoxPoint& minimalOffset             = _w->graphEdit.minimalOffset;

    const Dimension min_origin = highlight_thickness + 2;

    Boolean found[NDimensions];
    found[X] = False;
    found[Y] = False;

    for (GraphNode *node = graph->firstVisibleNode(); node != 0;
	node = graph->nextVisibleNode(node))
    {
	if (node->selected())
	{
	    BoxRegion r = node->region(graphGC);

	    for (BoxDimension d = X; d <= Y; d++)
	    {
		if (!found[d] || minimalOffset[d] < min_origin - r.origin(d))
		{
		    minimalOffset[d] = min_origin - r.origin(d);
		    found[d] = True;
		}
	    }
	}
    }
}

// Return current offset
static BoxPoint actionOffset(Widget w)
{
    const GraphEditWidget _w      = GraphEditWidget(w);
    const Dimension gridWidth     = _w->graphEdit.gridWidth;
    const Dimension gridHeight    = _w->graphEdit.gridHeight;
    const Boolean snapToGrid      = _w->graphEdit.snapToGrid;
    const BoxPoint& startAction   = _w->graphEdit.startAction;
    const BoxPoint& endAction     = _w->graphEdit.endAction;
    const BoxPoint& minimalOffset = _w->graphEdit.minimalOffset;

    BoxPoint offset = endAction - startAction;
    BoxPoint grid(gridWidth, gridHeight);

    for (BoxDimension d = X; d <= Y; d++)
    {
	// Offset must not move nodes out of area
	if (offset[d] < minimalOffset[d])
	    offset[d] = minimalOffset[d];

	// Offset must be a grid multiple
	if (snapToGrid && grid[d] > 0)
	{
	    offset[d] = ((offset[d] + grid[d] / 2) / grid[d]) * grid[d];
	    if (offset[d] < minimalOffset[d])
		offset[d] += grid[d];
	}
    }

    return offset;
}

// Draw moving frames and edges for nodes at (endAction - startAction)
static void drawOutlines(Widget w, const BoxPoint& offset)
{
    const GraphEditWidget _w            = GraphEditWidget(w);
    const Graph* graph                  = _w->graphEdit.graph;
    const Boolean rubberArrows          = _w->graphEdit.rubberArrows;
    const Boolean rubberAnnotations     = _w->graphEdit.rubberAnnotations;
    const Boolean rubberEdges           = _w->graphEdit.rubberEdges;
    const GraphGC& graphGC              = _w->graphEdit.graphGC;
    const GC& outlineGC                 = _w->graphEdit.outlineGC;

    for (GraphNode *node = graph->firstVisibleNode(); node != 0;
	node = graph->nextVisibleNode(node))
    {
	if (node->selected())
	{
	    // this should also handle opaqueMove (FIXME)...
	    BoxRegion r = node->region(graphGC);
	    XDrawRectangle(XtDisplay(w), XtWindow(w), outlineGC,
		r.origin(X) + offset[X], r.origin(Y) + offset[Y],
		r.space(X), r.space(Y));
	}
    }

    if (rubberEdges)
    {
	GraphGC gc(graphGC);
	gc.edgeGC           = outlineGC;
	gc.offsetIfSelected = offset;
	gc.drawArrowHeads   = rubberArrows;
	gc.drawAnnotations  = (gc.drawAnnotations && rubberAnnotations);

	for (GraphEdge *edge = graph->firstVisibleEdge(); edge != 0;
	    edge = graph->nextVisibleEdge(edge))
	{
	    // if (edge->from()->selected() || edge->to()->selected())
		edge->draw(w, EVERYWHERE, gc);
	}
    }
}

// Move Node to specified position and call callbacks
static void moveTo(Widget w, 
		   GraphNode *node, 
		   const BoxPoint& newPos,
		   Boolean isLast)
{
    const GraphEditWidget _w  = GraphEditWidget(w);
    Graph* graph              = _w->graphEdit.graph;

    if (node->pos() != newPos)
    {
	GraphEditPositionChangedInfo info;

	info.graph        = graph;
	info.node         = node;
	info.old_position = node->pos();
	info.new_position = newPos;
	info.is_last      = isLast;

	XtCallCallbacks(w, XtNpositionChangedCallback, XtPointer(&info));

	node->moveTo(newPos);
    }
}

// Call ``selection changed'' callbacks
static void selectionChanged(Widget w, XEvent *event, Boolean double_click)
{
    const GraphEditWidget _w  = GraphEditWidget(w);
    Graph* graph              = _w->graphEdit.graph;

    GraphEditSelectionChangedInfo info;
    info.graph        = graph;
    info.event        = event;
    info.double_click = double_click;

    XtCallCallbacks(w, XtNselectionChangedCallback, XtPointer(&info));
}


// Action functions

// Select all nodes
static Boolean _SelectAll(Widget w, XEvent *, String *, Cardinal *)
{
    const GraphEditWidget _w = GraphEditWidget(w);
    const Graph* graph       = _w->graphEdit.graph;

    Boolean changed = False;
    for (GraphNode *node = graph->firstVisibleNode(); node != 0;
	node = graph->nextVisibleNode(node))
    {
	if (!node->selected())
	{
	    changed = True;
	    node->selected() = True;
	    graphEditRedrawNode(w, node);
	}
    }

    return changed;
}

static void SelectAll(Widget w, XEvent *event, String *params,
    Cardinal *num_params)
{
    if (_SelectAll(w, event, params, num_params))
	selectionChanged(w, event, False);
}


// Unselect all nodes
static Boolean _UnselectAll(Widget w, XEvent *, String *, Cardinal *)
{
    const GraphEditWidget _w = GraphEditWidget(w);
    const Graph* graph       = _w->graphEdit.graph;

    Boolean changed = False;
    for (GraphNode *node = graph->firstNode(); node != 0;
	node = graph->nextNode(node))
    {
	if (node->selected())
	{
	    changed = True;
	    node->selected() = False;
	    graphEditRedrawNode(w, node);
	}
    }

    return changed;
}

static void UnselectAll(Widget w, XEvent *event, String *params,
    Cardinal *num_params)
{
    if (_UnselectAll(w, event, params, num_params))
	selectionChanged(w, event, False);
}

// Find nodes connected to ROOT
static void find_connected_nodes(GraphNode *root, GraphNodePointerArray& nodes)
{
    for (int i = 0; i < nodes.size(); i++)
	if (nodes[i] == root)
	    return;

    // if (!root->hidden()) 
	nodes += root;

    GraphEdge *edge;
    for (edge = root->firstFrom(); edge != 0; edge = root->nextFrom(edge))
	find_connected_nodes(edge->to(), nodes);

    for (edge = root->firstTo(); edge != 0; edge = root->nextTo(edge))
	find_connected_nodes(edge->from(), nodes);
}

// Select an entire subgraph
static Boolean select_graph(Widget w, GraphNode *root, Boolean set = True)
{
    // Find all connected nodes
    GraphNodePointerArray nodes;
    find_connected_nodes(root, nodes);

    // Select them
    Boolean changed = False;
    for (int i = 0; i < nodes.size(); i++)
    {
	GraphNode *node = nodes[i];
	if (node->selected() != set)
	{
	    node->selected() = set;
	    graphEditRedrawNode(w, node);
	    changed = True;
	}
    }

    return changed;
}

inline Boolean unselect_graph(Widget w, GraphNode *root)
{
    return select_graph(w, root, False);
}

// Raise node NODE such that it is placed on top of all others
void graphEditRaiseNode(Widget w, GraphNode *node)
{
    XtCheckSubclass(w, GraphEditWidgetClass, "Bad widget class");

    const GraphEditWidget _w = GraphEditWidget(w);
    Graph* graph             = _w->graphEdit.graph;

    // The last node in the list is drawn last (i.e. on top)
    graph->makeNodeLast(node);
}

// Same, but only if the autoRaise resource is set
static void raise_node(Widget w, GraphNode *node)
{
    const GraphEditWidget _w = GraphEditWidget(w);
    Boolean autoRaise        = _w->graphEdit.autoRaise;

    if (autoRaise)
	graphEditRaiseNode(w, node);
}

// Begin selecting or moving
static void _SelectOrMove(Widget w, XEvent *event, String *params,
    Cardinal *num_params, SelectionMode mode, Boolean follow)
{
    const GraphEditWidget _w = GraphEditWidget(w);

    Graph* graph             = _w->graphEdit.graph;
    Cursor moveCursor        = _w->graphEdit.moveCursor;

    GraphEditState& state    = _w->graphEdit.state;
    BoxPoint& startAction    = _w->graphEdit.startAction;
    BoxPoint& endAction      = _w->graphEdit.endAction;
    Time& lastSelectTime     = _w->graphEdit.lastSelectTime;

    // Get the input focus
    XmProcessTraversal(w, XmTRAVERSE_CURRENT);

    BoxPoint p = point(event);
    startAction = p;
    endAction   = p;

    Time t = time(event);
    Boolean double_click = 
	(Time(t - lastSelectTime) <= Time(XtGetMultiClickTime(XtDisplay(w))));
    lastSelectTime = t;

    GraphNode *node = graphEditGetNodeAtPoint(w, p);

    if (mode == SetSelection)
    {
	GraphEditPreSelectionInfo info;
	info.graph        = graph;
	info.node         = node;
	info.event        = event;
	info.doit         = True;
	info.double_click = double_click;

	XtCallCallbacks(w, XtNpreSelectionCallback, XtPointer(&info));

	if (!info.doit)
	    return;
    }

    if (node == 0 || node->hidden())
    {
	// On the background
	switch (mode)
	{
	case SetSelection:
	    if (double_click)
		SelectAll(w, event, params, num_params);
	    else
		UnselectAll(w, event, params, num_params);
	    break;

	case ExtendSelection:
	case ToggleSelection:
	    // Nothing to do on background
	    break;
	}

	if (follow)
	{
	    // Not on a node: draw a frame
	    state = SelectState;

	    // start drawing a frame
	    drawSelectFrame(w);
	}
    }
    else
    {
	// On a node
	Boolean changed = False;
	switch (mode)
	{
	case SetSelection:
	    if (!node->selected())
	    {
		// Create new selection
		changed = _UnselectAll(w, event, params, num_params);
	    }
	    // FALL THROUGH

	case ExtendSelection:
	    if (double_click)
	    {
		// Select all connected nodes
		changed = select_graph(w, node);
	    }
	    else
	    {
		// Select single node
		if (!node->selected())
		{		
		    node->selected() = True;
		    graphEditRedrawNode(w, node);
		    raise_node(w, node);
		    changed = True;
		}
	    }
	    break;

	case ToggleSelection:
	    if (double_click)
	    {
		// Toggle all connected modes
		if (node->selected())
		    changed = select_graph(w, node);
		else
		    changed = unselect_graph(w, node);
	    }
	    else
	    {
		// Toggle single node
		node->selected() = !node->selected();
		graphEditRedrawNode(w, node);
		raise_node(w, node);
		changed = True;
	    }
	    break;
	}

	if (changed)
	    selectionChanged(w, event, double_click);

	if (follow)
	{
	    // Wait for movement
	    state = DeltaState;

	    // Set moving cursor
	    defineCursor(w, moveCursor);
	}
    }
}

static void SelectOrMove(Widget w, XEvent *event, String *params,
    Cardinal *num_params)
{
    _SelectOrMove(w, event, params, num_params, SetSelection, True);
}

static void ExtendOrMove(Widget w, XEvent *event, String *params,
    Cardinal *num_params)
{
    _SelectOrMove(w, event, params, num_params, ExtendSelection, True);
}

static void ToggleOrMove(Widget w, XEvent *event, String *params,
    Cardinal *num_params)
{
    _SelectOrMove(w, event, params, num_params, ToggleSelection, True);
}

static void Select(Widget w, XEvent *event, String *params,
    Cardinal *num_params)
{
    _SelectOrMove(w, event, params, num_params, SetSelection, False);
}

static void Extend(Widget w, XEvent *event, String *params,
    Cardinal *num_params)
{
    _SelectOrMove(w, event, params, num_params, ExtendSelection, False);
}

static void Toggle(Widget w, XEvent *event, String *params,
    Cardinal *num_params)
{
    _SelectOrMove(w, event, params, num_params, ToggleSelection, False);
}

// Keep on acting...
static void Follow(Widget w, XEvent *event, String *, Cardinal *)
{
    const GraphEditWidget _w    = GraphEditWidget(w);
    const BoxPoint& startAction = _w->graphEdit.startAction;
    BoxPoint& endAction         = _w->graphEdit.endAction;
    GraphEditState& state       = _w->graphEdit.state;
    BoxPoint& lastOffset        = _w->graphEdit.lastOffset;
    const Dimension moveDelta   = _w->graphEdit.moveDelta;

    BoxPoint p = point(event);

    switch(state)
    {
	case SelectState:
	    // Draw new select frame
	    redrawSelectFrame(w, p);
	    break;

	case MoveState:
	{
	    // Draw new move frames
	    endAction = p;
	    BoxPoint newOffset = actionOffset(w);
	    if (newOffset != lastOffset)
	    {
		drawOutlines(w, lastOffset);
		drawOutlines(w, lastOffset = newOffset);
	    }
	    break;
	}

	case DeltaState:
	    // Wait for movement
	    if (abs(p[X] - startAction[X]) > moveDelta ||
		abs(p[Y] - startAction[Y]) > moveDelta)
	    {
		// start moving
		endAction = p;
		getMinimalOffset(w);
		graphEditSizeChanged(w);
		drawOutlines(w, lastOffset = actionOffset(w));
		state = MoveState;
	    }
	    break;

	default:
	    // Do nothing
	    break;
    }
}

// Now, all is done.

static void move_selected_nodes(Widget w, const BoxPoint& offset)
{
    const GraphEditWidget _w   = GraphEditWidget(w);
    const Graph* graph         = _w->graphEdit.graph;
    const GraphGC& graphGC     = _w->graphEdit.graphGC;

    if (offset == BoxPoint(0, 0))
	return;

    // Clear graph area
    BoxRegion r = graph->region(graphGC);
    XClearArea(XtDisplay(w), XtWindow(w), r.origin(X), r.origin(Y),
	       r.space(X), r.space(Y), False);

    // Move selected nodes
    GraphNode *lastNode = 0;
    for (GraphNode *node = graph->firstVisibleNode(); 
	 node != 0;
	 node = graph->nextVisibleNode(node))
    {
	if (node->selected())
	{
	    if (lastNode)
		moveTo(w, lastNode, lastNode->pos() + offset, False);
	    lastNode = node;
	}
    }
    if (lastNode)
	moveTo(w, lastNode, lastNode->pos() + offset, True);

    // resize widget to graph size and redraw graph
    graphEditSizeChanged(w);
    graphEditRedraw(w);
}

static void End(Widget w, XEvent *event, String *, Cardinal *)
{
    const GraphEditWidget _w   = GraphEditWidget(w);
    const Graph* graph         = _w->graphEdit.graph;
    const GraphGC& graphGC     = _w->graphEdit.graphGC;
    const BoxPoint& lastOffset = _w->graphEdit.lastOffset;
    Cursor defaultCursor       = _w->graphEdit.defaultCursor;

    BoxPoint& endAction        = _w->graphEdit.endAction;
    GraphEditState& state      = _w->graphEdit.state;

    Boolean changed = False;

    switch(state)
    {
	case SelectState:
	{
	    drawSelectFrame(w);
	    endAction = point(event);

	    BoxRegion selected = frameRegion(w);
	    Boolean have_unselected_nodes = False;

	    // Find all nodes in frame and select them
	    for (GraphNode *node = graph->firstVisibleNode(); node != 0;
		node = graph->nextVisibleNode(node))
	    {
		if (!node->selected())
		{
		    // Intersection must be non-empty
		    BoxRegion intersection = 
			selected & node->region(graphGC);

		    if (!intersection.isEmpty())
		    {
			have_unselected_nodes = True;
			node->selected() = True;
			graphEditRedrawNode(w, node);
			changed = True;
		    }
		}
	    }

	    if (!have_unselected_nodes)
	    {
		// All selected nodes are already selected - unselect them
		for (GraphNode *node = graph->firstVisibleNode(); node != 0;
		     node = graph->nextVisibleNode(node))
		{
		    if (node->selected())
		    {
			// Intersection must be non-empty
			BoxRegion intersection = 
			    selected & node->region(graphGC);

			if (!intersection.isEmpty())
			{
			    node->selected() = False;
			    graphEditRedrawNode(w, node);
			    changed = True;
			}
		    }
		}
	    }

	    state = NopState;
	    break;
	}

        case MoveState:
	{
	    // Move all selected nodes to new positions
		   
	    // clear graph area
	    drawOutlines(w, lastOffset);

            // move nodes
	    endAction = point(event);
	    BoxPoint offset = actionOffset(w);
	    move_selected_nodes(w, offset);

	    state = NopState;
	    break;
	}

	default:
	    // Do nothing
	    break;
    }

    if (changed)
	selectionChanged(w, event, False);

    defineCursor(w, defaultCursor);
}

// Key movement action
static void MoveSelected(Widget w, XEvent *, String *params, 
			 Cardinal *num_params)
{
    const GraphEditWidget _w      = GraphEditWidget(w);
    const Dimension gridWidth     = _w->graphEdit.gridWidth;
    const Dimension gridHeight    = _w->graphEdit.gridHeight;
    const BoxPoint& minimalOffset = _w->graphEdit.minimalOffset;

    BoxPoint grid(gridWidth, gridHeight);

    if (num_params == 0 || *num_params != 2)
    {
	cerr << "move-selected: usage: move-selected(X, Y)\n";
	return;
    }

    BoxPoint offset;

    string offset_s[2];
    offset_s[X] = params[0];
    offset_s[Y] = params[1];

    BoxDimension d;
    for (d = X; d <= Y; d++)
    {
	BoxCoordinate& c = offset[d];
	string& s = offset_s[d];

	if (s == "+grid" || s == "grid")
	    c = +grid[d];
	else if (s == "-grid")
	    c = -grid[d];
	else
	{
	    char *ptr;
	    char *str = (char *)s;
	    c = strtol(str, &ptr, 0);
	    if (ptr == str)
	    {
		cerr << "move-selected: illegal argument " << str << "\n";
		return;
	    }
	}
    }

    getMinimalOffset(w);
    
    for (d = X; d <= Y; d++)
    {
	// Offset must not move nodes out of area
	if (offset[d] < minimalOffset[d])
	    offset[d] = minimalOffset[d];
    }

    if (offset != BoxPoint(0, 0))
    {
	move_selected_nodes(w, offset);
	graphEditSizeChanged(w);
    }
}


// Select single node
static void select_single_node(Widget w, XEvent *event, GraphNode *selectNode)
{
    if (selectNode == 0)
	return;

    const GraphEditWidget _w   = GraphEditWidget(w);
    const Graph* graph         = _w->graphEdit.graph;

    Boolean changed = False;

    for (GraphNode *node = graph->firstVisibleNode(); 
	 node != 0;
	 node = graph->nextVisibleNode(node))
    {
	if (node != selectNode && node->selected())
	{
	    node->selected() = False;
	    changed = True;
	    graphEditRedrawNode(w, node);
	}
    }

    if (!selectNode->selected())
    {
	selectNode->selected() = True;
	changed = True;
	raise_node(w, selectNode);
	graphEditRedrawNode(w, selectNode);
    }

    if (changed)
	selectionChanged(w, event, False);
}

// Select first node
static void SelectFirst(Widget w, XEvent *event, String *, Cardinal *)
{
    const GraphEditWidget _w   = GraphEditWidget(w);
    const Graph* graph         = _w->graphEdit.graph;

    select_single_node(w, event, graph->firstVisibleNode());
}

// Select next node
static void SelectNext(Widget w, XEvent *event, String *, Cardinal *)
{
    const GraphEditWidget _w   = GraphEditWidget(w);
    const Graph* graph         = _w->graphEdit.graph;

    GraphNode *selectNode = 0;
    for (GraphNode *node = graph->firstVisibleNode(); 
	 node != 0;
	 node = graph->nextVisibleNode(node))
    {
	if (node->selected())
	{
	    selectNode = graph->nextVisibleNode(node);
	    break;
	}
    }

    if (selectNode == 0)
	selectNode = graph->firstVisibleNode();

    select_single_node(w, event, selectNode);
}

// Select previous node
static void SelectPrev(Widget w, XEvent *event, String *, Cardinal *)
{
    const GraphEditWidget _w = GraphEditWidget(w);
    const Graph* graph       = _w->graphEdit.graph;

    GraphNode *lastNode = 0;
    GraphNode *selectNode = 0;
    for (GraphNode *node = graph->firstVisibleNode(); 
	 node != 0;
	 node = graph->nextVisibleNode(node))
    {
	if (node->selected())
	    selectNode = lastNode;
	lastNode = node;
    }

    if (selectNode == 0)
	selectNode = lastNode;

    select_single_node(w, event, selectNode);
}

// Return nearest grid position near P
static BoxPoint NearestGridPosition(const BoxPoint& grid, const BoxPoint& p)
{
    BoxPoint pos(p);

    for (BoxDimension d = X; d <= Y; d++)
	if (grid[d] > 0)
	    pos[d] = ((pos[d] + grid[d] / 2) / grid[d]) * grid[d];

    return pos;
}


// Return final position (if snapToGrid is enabled, for example)
BoxPoint graphEditFinalPosition(Widget w, const BoxPoint& p)
{
    XtCheckSubclass(w, GraphEditWidgetClass, "Bad widget class");

    const GraphEditWidget _w   = GraphEditWidget(w);
    const Boolean snapToGrid   = _w->graphEdit.snapToGrid;
    const Dimension gridWidth  = _w->graphEdit.gridWidth;
    const Dimension gridHeight = _w->graphEdit.gridHeight;

    if (snapToGrid)
    {
	BoxPoint grid(gridWidth, gridHeight);
	return NearestGridPosition(grid, p);
    }
    else
	return p;
}

// Snap nodes to grid
static void _SnapToGrid(Widget w, XEvent *, String *params, 
			Cardinal *num_params)
{
    const GraphEditWidget _w   = GraphEditWidget(w);
    const Graph* graph         = _w->graphEdit.graph;
    const Dimension gridWidth  = _w->graphEdit.gridWidth;
    const Dimension gridHeight = _w->graphEdit.gridHeight;

    BoxPoint grid(gridWidth, gridHeight);

    if (*num_params >= 1)
	grid[X] = atoi(params[0]);
    if (*num_params >= 2)
	grid[Y] = atoi(params[1]);

    for (GraphNode *node = graph->firstVisibleNode(); node != 0;
	node = graph->nextVisibleNode(node))
    {
	BoxPoint pos = NearestGridPosition(grid, node->pos());
	if (pos != node->pos())
	{
            // Set new node position
	    moveTo(w, node, pos, graph->nextVisibleNode(node) == 0);
	}
    }
}

static void SnapToGrid(Widget w, XEvent *event, String *params,
    Cardinal *num_params)
{
    _SnapToGrid(w, event, params, num_params);
    graphEditRedraw(w);
}

static int get_new_rotation(Widget w, String *params, Cardinal *num_params,
			    const _XtString name, const _XtString default_param, 
			    const _XtString extra_args = "")
{
    const GraphEditWidget _w   = GraphEditWidget(w);
    const Cardinal rotation    = _w->graphEdit.rotation;

    string param = "";
    if (num_params && *num_params >= 1)
	param = params[0];
    if (param == "")
	param = default_param;

    int new_rotation = atoi(param);
    new_rotation = (new_rotation % 360 + 360) % 360;

    if (new_rotation % 90 != 0)
    {
	cerr << name << ": usage: " << name << "(" 
	     << extra_args << "[[+-]DEGREES]), "
	    "where DEGREES is a multiple of 90\n";
	return -1;
    }

    if (param[0] == '+')
	new_rotation = (rotation + new_rotation + 360) % 360;
    else if (param[0] == '-')
	new_rotation = (rotation - new_rotation + 360) % 360;

    return new_rotation;
}


// Rotate nodes
static void _Rotate(Widget w, XEvent *event, String *params,
    Cardinal *num_params)
{
    const GraphEditWidget _w   = GraphEditWidget(w);
    const Graph* graph         = _w->graphEdit.graph;
    Cardinal& rotation         = _w->graphEdit.rotation;

    int new_rotation = 
	get_new_rotation(w, params, num_params, "rotate", "+90");
    if (new_rotation < 0)
	return;

    int width  = _w->core.width;
    int height = _w->core.height;

    for (int offset  = (rotation - new_rotation + 360) % 360;
	 offset > 0; offset -= 90)
    {
	for (GraphNode *node = graph->firstNode(); 
	     node != 0;
	     node = graph->nextNode(node))
	{
	    BoxPoint pos = node->pos();
	    pos[X] = width - node->pos()[Y];
	    pos[Y] = node->pos()[X];

	    if (pos != node->pos())
	    {
		moveTo(w, node, pos, 
		       graph->nextNode(node) == 0 && new_rotation <= 90);
	    }
	}

	int tmp = width;
	width = height;
	height = tmp;
    }

    rotation = new_rotation;

    Cardinal zero = 0;
    _Normalize(w, event, 0, &zero);
    _SnapToGrid(w, event, 0, &zero);
}

static void Rotate(Widget w, XEvent *event, String *params,
    Cardinal *num_params)
{
    const GraphEditWidget _w   = GraphEditWidget(w);
    const Boolean autoLayout   = _w->graphEdit.autoLayout;

    _Rotate(w, event, params, num_params);
    if (autoLayout)
    {
	Cardinal zero = 0;
	_Layout(w, event, 0, &zero);
    }
    graphEditRedraw(w);
}



// Layout nodes

static Graph *layout_graph = 0;
static Widget layout_widget = 0;

static void LayoutNodeCB(char *node_name, int x, int y)
{
    GraphNode *node = (GraphNode *)strtoul(node_name, 0, 16);
    assert(node != 0);

    node->moveTo(BoxPoint(x, y));
}

static void LayoutHintCB(char *from_name, char *to_name, int x, int y)
{
    GraphNode *from = (GraphNode *)strtoul(from_name, 0, 16);
    assert(from != 0);
    GraphNode *to   = (GraphNode *)strtoul(to_name, 0, 16);
    assert(to != 0);

    for (GraphEdge *edge = from->firstFrom();
	 edge != 0;
	 edge = from->nextFrom(edge))
    {
	if (edge->hidden())
	    continue;

	GraphNode *n = edge->to();
	while (n->isHint())
	    n = n->firstFrom()->to();
	if (n == to)
	{
	    // We hide the original edge...
	    edge->hidden() = True;

	    // ... fetch its annotation ...
	    LineGraphEdge *ge = ptr_cast(LineGraphEdge, edge);
	    EdgeAnnotation *anno = ge->annotation();
	    if (anno != 0)
		anno = anno->dup();

	    // ... and insert an edge hint at the end
	    // of the path between FROM and TO.
	    HintGraphNode *hint = new HintGraphNode(BoxPoint(x, y));
	 
	    *layout_graph += hint;
	    *layout_graph += new LineGraphEdge(edge->from(), hint, anno);
	    *layout_graph += new LineGraphEdge(hint, edge->to());

	    return;
	}
    }
}

static int LayoutCompareCB(char *name1, char *name2)
{
    GraphNode *node1 = (GraphNode *)strtoul(name1, 0, 16);
    assert(node1 != 0);
    GraphNode *node2 = (GraphNode *)strtoul(name2, 0, 16);
    assert(node2 != 0);

    GraphEditCompareNodesInfo info;

    info.graph = layout_graph;
    info.node1 = node1;
    info.node2 = node2;

    XtCallCallbacks(layout_widget, XtNcompareNodesCallback, XtPointer(&info));

    return info.result;
}

string node_name(GraphNode *node)
{
    char buffer[BUFSIZ];
    sprintf(buffer, "0x%lx", (unsigned long) node);
    return string(buffer);
}

static void remove_all_hints(Graph *graph)
{
    // Find all hint nodes
    GraphNodePointerArray hints;

    for (GraphNode *node = graph->firstNode(); 
	 node != 0;
	 node = graph->nextNode(node))
    {
	if (node->isHint())
	    hints += node;
    }

    // Remove hint nodes
    for (int i = 0; i < hints.size(); i++)
	*graph -= hints[i];

    // Enable remaining edges
    for (GraphEdge *edge = graph->firstEdge(); 
	 edge != 0;
	 edge = graph->nextEdge(edge))
    {
	edge->hidden() = False;
    }
}

// Replace all paths A -> HINT_1 -> HINT_2 -> ... -> HINT_N -> B
// by A -> B, where B is placed at the position of HINT_1.

static void compact_layouted_graph(Graph *graph)
{
    for (GraphNode *node = graph->firstNode();
	 node != 0;
	 node = graph->nextNode(node))
    {
	if (!node->isHint())
	{
	    for (GraphEdge *edge = node->firstFrom();
		 edge != 0; edge = node->nextFrom(edge))
	    {
		if (edge->to()->isHint())
		{
		    GraphNode *hint = edge->to();
		    const BoxPoint& hintPos = hint->pos();

		    GraphEdge *hint_edge = hint->firstFrom();
		    while (hint_edge && hint_edge->to()->isHint())
			hint_edge = hint_edge->to()->firstFrom();

		    if (hint_edge)
		    {
			// Move non-hint node at first hint position
			hint_edge->to()->moveTo(hintPos);
		    }

		    // Remove edge to hint such that we are not visited again
		    *graph -= edge;
		}
	    }
	}
    }

    remove_all_hints(graph);
}    

static void _Layout(Widget w, XEvent *event, String *params,
    Cardinal *num_params)
{
    const GraphEditWidget _w   = GraphEditWidget(w);
    Graph* graph               = _w->graphEdit.graph;
    const GraphGC& graphGC     = _w->graphEdit.graphGC;
    Cardinal& rotation         = _w->graphEdit.rotation;
    LayoutMode mode            = _w->graphEdit.layoutMode;
    Boolean& autoLayout        = _w->graphEdit.autoLayout;

    static char graph_name[] = "graph";

    if (num_params && *num_params > 0 && params[0][0] != '\0')
    {
	LayoutMode mode_param;
	XrmValue v1, v2;
	v1.addr = XPointer(params[0]);
	v1.size = sizeof(String);
	v2.addr = XPointer(&mode_param);
	v2.size = sizeof(LayoutMode);

	Boolean ok = 
	    XtConvertAndStore(w, XtRString, &v1, XtRLayoutMode, &v2);
	if (ok)
	    mode = mode_param;
    }

    Cardinal new_num_params = 
	(num_params && *num_params > 0 ? *num_params - 1 : 0);
    int new_rotation = 
	get_new_rotation(w, params - 1, &new_num_params, 
			 "layout", "+0", "MODE, ");
    if (new_rotation < 0)
	return;

    // Don't get called again while setting values from hooks
    Boolean old_autoLayout = autoLayout;
    autoLayout = False;

    // Call hooks before layouting
    GraphEditLayoutInfo info;
    info.graph    = graph;
    info.mode     = mode;
    info.rotation = new_rotation;
    XtCallCallbacks(w, XtNpreLayoutCallback, XtPointer(&info));

    // Remove all hint nodes
    remove_all_hints(graph);

    // Send graph to layouter
    Layout::add_graph(graph_name);

    for (GraphNode *node = graph->firstVisibleNode(); 
	 node != 0;
	 node = graph->nextVisibleNode(node))
    {
	BoxRegion r = node->region(graphGC);
	int width  = r.space(X);
	int height = r.space(Y);

	if ((new_rotation % 180) / 90 > 0)
	{
	    int tmp = width;
	    width = height;
	    height = tmp;
	}

	string name = node_name(node);
	Layout::add_node(graph_name, name);
	Layout::set_node_width(graph_name, name, width);
	Layout::set_node_height(graph_name, name, height);
	Layout::set_node_position(graph_name, name, -1, -1);
    }

    for (GraphEdge *edge = graph->firstVisibleEdge(); 
	 edge != 0;
	 edge = graph->nextVisibleEdge(edge))
    {
	Layout::add_edge(graph_name, 
			 node_name(edge->from()), node_name(edge->to()));
    }

    // Layout the graph
    layout_widget = w;
    layout_graph  = graph;
    Layout::node_callback    = LayoutNodeCB;
    Layout::hint_callback    = LayoutHintCB;
    Layout::compare_callback = LayoutCompareCB;
    Layout::layout(graph_name);

    // Post-process graph for compact representation
    if (mode == CompactLayoutMode)
	compact_layouted_graph(graph);

    // Clear the graph...
    Layout::remove_graph(graph_name);

    // ... and re-rotate it.
    ostrstream os;
    os << new_rotation;

    string rotation_s = os;
    Cardinal rotate_num_params = 1;
    String rotate_params[1];
    rotate_params[0] = rotation_s;

    rotation = 0;
    _Rotate(w, event, rotate_params, &rotate_num_params);

    // Layout is done
    XtCallCallbacks(w, XtNpostLayoutCallback, XtPointer(&info));

    autoLayout = old_autoLayout;
}

// DoLayout() should be named Layout(), but this conflicts with the
// `Layout' class on some pre-ARM C++ compilers :-(
static void DoLayout(Widget w, XEvent *event, String *params,
    Cardinal *num_params)
{
    _Layout(w, event, params, num_params);
    graphEditRedraw(w);
}


// Normalize graph
static void _Normalize(Widget w, XEvent *, String *, Cardinal *)
{
    const GraphEditWidget _w   = GraphEditWidget(w);
    const Graph* graph         = _w->graphEdit.graph;
    const GraphGC& graphGC     = _w->graphEdit.graphGC;
    const Dimension gridHeight = _w->graphEdit.gridHeight;
    const Dimension gridWidth  = _w->graphEdit.gridWidth;

    BoxRegion r = graph->region(graphGC);

    for (GraphNode *node = graph->firstVisibleNode(); 
	 node != 0;
	 node = graph->nextVisibleNode(node))
    {
	BoxPoint pos = node->pos() - r.origin() 
	    + BoxPoint(gridHeight, gridWidth);

	if (pos != node->pos())
	{
            // set new node position
	    moveTo(w, node, pos, graph->nextVisibleNode(node) == 0);
	}
    }
}

static void Normalize(Widget w, XEvent *event, String *params,
    Cardinal *num_params)
{
    _Normalize(w, event, params, num_params);
    graphEditRedraw(w);
}


// Show and hide edges

static void considerEdges(Widget w, XEvent *, String *params,
			  Cardinal *num_params, Boolean shallBeHidden)
{
    const GraphEditWidget _w = GraphEditWidget(w);
    const Graph* graph       = _w->graphEdit.graph;

    // get the mode
    enum { Nope = 0, Both = 1, From = 2, To = 3, Any = 4 } themode = Nope;
    Boolean changedSomething = False;

    string p = "any";
    if (*num_params >= 1)
	p = params[0];

    if (p == "from")
	themode = From;
    else if (p == "to")
	themode = To;
    else if (p == "any")
	themode = Any;
    else if (p == "both")
	themode = Both;
    else
	cerr << "show-edges: bad mode " << '`' << p << "'" << "\n";

    for (GraphEdge *edge = graph->firstEdge(); edge != 0;
	edge = graph->nextEdge(edge))
    {
	Boolean set = False;

	switch (themode)
	{
	    // There should be a better way of coding this, but I don't know...

	    case From:
		set = edge->from()->selected();
		break;

	    case To:
		set = edge->to()->selected();
		break;

	    case Any:
		set = edge->to()->selected() || edge->from()->selected();
		break;

	    case Both:
		set = edge->to()->selected() && edge->from()->selected();
		break;

	    case Nope:
		set = False;
		break;
	}

	if (set)
	{
	    if (edge->hidden() != shallBeHidden)
	    {
		changedSomething = True;
		edge->hidden() = shallBeHidden;
	    }
	}
    }

    if (changedSomething)
	graphEditRedraw(w);
}

static void ShowEdges(Widget w, XEvent *event, String *params,
    Cardinal *num_params)
{
    considerEdges(w, event, params, num_params, False);
}

static void HideEdges(Widget w, XEvent *event, String *params,
    Cardinal *num_params)
{
    considerEdges(w, event, params, num_params, True);
}
