home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PC World Komputer 1999 mARCH
/
PCWK3A99.iso
/
Linux
/
DDD331
/
DDD-3_1_.000
/
DDD-3_1_
/
ddd-3.1.1
/
ddd
/
GraphEdit.C
< prev
next >
Wrap
C/C++ Source or Header
|
1998-11-30
|
86KB
|
3,031 lines
// $Id: GraphEdit.C,v 1.65 1998/11/30 08:59:11 zeller Exp $
// GraphEdit Widget
// Copyright (C) 1995-1998 Technische Universitaet Braunschweig, Germany.
// Written by Andreas Zeller <zeller@ips.cs.tu-bs.de>.
//
// 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.cs.tu-bs.de/softech/ddd/',
// or send a mail to the DDD developers <ddd@ips.cs.tu-bs.de>.
char GraphEdit_rcsid[] =
"$Id: GraphEdit.C,v 1.65 1998/11/30 08:59:11 zeller Exp $";
#ifdef __GNUG__
#pragma implementation
#endif
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <iostream.h>
#include <strstream.h>
#include <iomanip.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 = caddr_t(&_w->primitive.foreground);
}
// Resource list
static XtResource resources[] = {
#define offset(field) XtOffsetOf(GraphEditRec, graphEdit.field)
// {name, class, type, size, offset, default_type, default_addr}
{ XtNgraph, XtCGraph, XtRPointer, sizeof(Graph *),
offset(graph), XtRImmediate, XtPointer(NULL) },
{ XtNmoveDelta, XtCMoveDelta, XtRDimension, sizeof(Dimension),
offset(moveDelta), XtRImmediate, XtPointer(4) },
{ XtNrubberEdges, XtCRubberEdges, XtRBoolean, sizeof(Boolean),
offset(rubberEdges), XtRImmediate, XtPointer(True) },
{ XtNrubberArrows, XtCRubberEdges, XtRBoolean, sizeof(Boolean),
offset(rubberArrows), XtRImmediate, XtPointer(False) },
{ XtNrubberAnnotations, XtCRubberAnnotations, XtRBoolean, sizeof(Boolean),
offset(rubberAnnotations), XtRImmediate, XtPointer(False) },
{ XtNopaqueMove, XtCOpaqueMove, XtRBoolean, sizeof(Boolean),
offset(opaqueMove), XtRImmediate, XtPointer(False) },
{ XtNautoRaise, XtCAutoRaise, XtRBoolean, sizeof(Boolean),
offset(autoRaise), XtRImmediate, XtPointer(True) },
{ XtNshowHints, XtCShowHints, XtRBoolean, sizeof(Boolean),
offset(showHints), XtRImmediate, XtPointer(False) },
{ XtNhintSize, XtCHintSize, XtRDimension, sizeof(Dimension),
offset(hintSize), XtRImmediate, XtPointer(6) },
{ XtNshowAnnotations, XtCShowAnnotations, XtRBoolean, sizeof(Boolean),
offset(showAnnotations), XtRImmediate, XtPointer(True) },
{ XtNgridWidth, XtCGridSize, XtRDimension, sizeof(Dimension),
offset(gridWidth), XtRImmediate, XtPointer(16) },
{ XtNgridHeight, XtCGridSize, XtRDimension, sizeof(Dimension),
offset(gridHeight), XtRImmediate, XtPointer(16) },
{ XtNshowGrid, XtCShowGrid, XtRBoolean, sizeof(Boolean),
offset(showGrid), XtRImmediate, XtPointer(False) },
{ XtNsnapToGrid, XtCSnapToGrid, XtRBoolean, sizeof(Boolean),
offset(snapToGrid), XtRImmediate, XtPointer(False) },
{ XtNautoLayout, XtCAutoLayout, XtRBoolean, sizeof(Boolean),
offset(autoLayout), XtRImmediate, XtPointer(False) },
{ XtNrotation, XtCRotation, XtRCardinal, sizeof(Cardinal),
offset(rotation), XtRImmediate, XtPointer(0)},
{ XtNedgeWidth, XtCEdgeWidth, XtRDimension, sizeof(Dimension),
offset(edgeWidth), XtRImmediate, XtPointer(0) },
{ XtNarrowAngle, XtCArrowAngle, XtRDimension, sizeof(Dimension),
offset(arrowAngle), XtRImmediate, XtPointer(30) },
{ XtNarrowLength, XtCArrowLength, XtRDimension, sizeof(Dimension),
offset(arrowLength), XtRImmediate, XtPointer(10) },
{ XtNselfEdgeDiameter, XtCSelfEdgeDiameter, XtRDimension,
sizeof(Dimension), offset(selfEdgeDiameter),
XtRImmediate, XtPointer(32) },
{ XtNextraWidth, XtCExtraSize, XtRDimension, sizeof(Dimension),
offset(extraWidth), XtRImmediate, XtPointer(0) },
{ XtNextraHeight, XtCExtraSize, XtRDimension, sizeof(Dimension),
offset(extraHeight), XtRImmediate, XtPointer(0) },
{ XtNrequestedWidth, XtCRequestedSize, XtRDimension, sizeof(Dimension),
offset(requestedWidth), XtRImmediate, XtPointer(0) },
{ XtNrequestedHeight, XtCRequestedSize, XtRDimension, sizeof(Dimension),
offset(requestedHeight), XtRImmediate, XtPointer(0) },
{ XtNselectTile, XtCBitmap, XtRBitmap, sizeof(Pixmap),
offset(selectTile), XtRImmediate, XtPointer(0)},
{ XtNedgeAttachMode, XtCEdgeAttachMode, XtREdgeAttachMode,
sizeof(EdgeAttachMode), offset(edgeAttachMode),
XtRImmediate, XtPointer(Straight) },
{ XtNlayoutMode, XtCLayoutMode, XtRLayoutMode,
sizeof(LayoutMode), offset(layoutMode),
XtRImmediate, XtPointer(RegularLayoutMode) },
{ XtNselfEdgePosition, XtCSelfEdgePosition, XtRSelfEdgePosition,
sizeof(SelfEdgePosition), offset(selfEdgePosition),
XtRImmediate, XtPointer(NorthEast) },
{ XtNselfEdgeDirection, XtCSelfEdgeDirection, XtRSelfEdgeDirection,
sizeof(SelfEdgeDirection), offset(selfEdgeDirection),
XtRImmediate, XtPointer(Counterclockwise) },
{ XtNdashedLines, XtCDashedLines, XtRBoolean,
sizeof(Boolean), offset(dashedLines),
XtRImmediate, XtPointer(False) },
{ XtNdefaultCursor, XtCCursor, XtRCursor, sizeof(Cursor),
offset(defaultCursor), XtRImmediate, XtPointer(0)},
{ XtNmoveCursor, XtCCursor, XtRCursor, sizeof(Cursor),
offset(moveCursor), XtRImmediate, XtPointer(0)},
{ XtNselectCursor, XtCCursor, XtRCursor, sizeof(Cursor),
offset(selectCursor), XtRImmediate, XtPointer(0)},
{ XtNselectBottomLeftCursor, XtCCursor, XtRCursor, sizeof(Cursor),
offset(selectBottomLeftCursor), XtRImmediate, XtPointer(0)},
{ XtNselectBottomRightCursor, XtCCursor, XtRCursor, sizeof(Cursor),
offset(selectBottomRightCursor), XtRImmediate, XtPointer(0)},
{ XtNselectTopLeftCursor, XtCCursor, XtRCursor, sizeof(Cursor),
offset(selectTopLeftCursor), XtRImmediate, XtPointer(0)},
{ XtNselectTopRightCursor, XtCCursor, XtRCursor, sizeof(Cursor),
offset(selectTopRightCursor), XtRImmediate, XtPointer(0)},
{ XtNnodeColor, XtCColor, XtRPixel, sizeof(Pixel),
offset(nodeColor), XtRCallProc, XtPointer(defaultForeground) },
{ XtNedgeColor, XtCColor, XtRPixel, sizeof(Pixel),
offset(edgeColor), XtRCallProc, XtPointer(defaultForeground) },
{ XtNframeColor, XtCColor, XtRPixel, sizeof(Pixel),
offset(frameColor), XtRCallProc, XtPointer(defaultForeground) },
{ XtNoutlineColor, XtCColor, XtRPixel, sizeof(Pixel),
offset(outlineColor), XtRCallProc, XtPointer(defaultForeground) },
{ XtNgridColor, XtCColor, XtRPixel, sizeof(Pixel),
offset(gridColor), XtRCallProc, XtPointer(defaultForeground) },
{ XtNselectColor, XtCColor, XtRPixel, sizeof(Pixel),
offset(selectColor), XtRCallProc, XtPointer(defaultForeground) },
{ XtNnodePrintColor, XtCColor, XtRString, sizeof(String),
offset(nodePrintColor), XtRImmediate, 0 },
{ XtNedgePrintColor, XtCColor, XtRString, sizeof(String),
offset(edgePrintColor), XtRImmediate, 0 },
{ XtNpositionChangedCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
offset(positionChangedProc), XtRCallback, XtPointer(0) },
{ XtNselectionChangedCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
offset(selectionChangedProc), XtRCallback, XtPointer(0) },
{ XtNsizeChangedCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
offset(sizeChangedProc), XtRCallback, XtPointer(0) },
{ XtNcompareNodesCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
offset(compareNodesProc), XtRCallback, XtPointer(0) },
{ XtNpreLayoutCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
offset(preLayoutProc), XtRCallback, XtPointer(0) },
{ XtNpostLayoutCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
offset(postLayoutProc), XtRCallback, XtPointer(0) },
{ XtNpreSelectionCallback, 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[] = {
{ "select", Select }, // select()
{ "extend", Extend }, // extend()
{ "toggle", Toggle }, // toggle()
{ "select-or-move", SelectOrMove }, // select-or-move()
{ "extend-or-move", ExtendOrMove }, // extend-or-move()
{ "toggle-or-move", ToggleOrMove }, // toggle-or-move()
{ "move-selected", MoveSelected }, // move-selected(X, Y)
{ "follow", Follow }, // follow()
{ "end", End }, // end()
{ "show-edges", ShowEdges }, // show-edges([any|both|from|to])
{ "hide-edges", HideEdges }, // hide-edges([any|both|from|to])
{ "select-all", SelectAll }, // select-all()
{ "select-first", SelectFirst }, // select-first()
{ "select-next", SelectNext }, // select-next()
{ "select-prev", SelectPrev }, // select-prev()
{ "unselect-all", UnselectAll }, // unselect-all()
{ "snap-to-grid", SnapToGrid }, // snap-to-grid()
{ "_snap-to-grid", _SnapToGrid },
{ "rotate", Rotate }, // rotate([[+|-]DEGREES])
{ "_rotate", _Rotate },
{ "layout", DoLayout }, // layout([regular|compact],
{ "_layout", _Layout }, // [[+|-]DEGREES]])
{ "normalize", Normalize }, // normalize()
{ "_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 */ "GraphEdit",
/* widget_size */ sizeof(GraphEditRec),
/* class_initialize */ ClassInitialize,
/* class_part_initialize */ NULL,
/* class_inited */ False,
/* initialize */ Initialize,
/* initialize_hook */ NULL,
/* 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 */ NULL,
/* set_values_almost */ XtInheritSetValuesAlmost,
/* get_values_hook */ NULL,
/* accept_focus */ NULL,
/* version */ XtVersion,
/* callback_private */ NULL,
/* tm_table */ defaultTranslations,
/* query_geometry */ XtInheritQueryGeometry,
/* display_accelerator */ XtInheritDisplayAccelerator,
/* extension */ NULL,
},
{ /* Primitive fields */
/* border_highlight */ XmInheritBorderHighlight,
/* border_unhighlight */ XmInheritBorderUnhighlight,
/* translations */ XtInheritTranslations,
/* arm_and_activate */ NULL, // XmInheritArmAndActivate?
/* syn_resources */ NULL,
/* num_syn_resources */ 0,
/* extension */ NULL
#if defined(__sgi)
// Paul Sydney <sydney@ulua.mhpcc.af.mil> reports that 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 */ NULL
},
};
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 != NULL) { \
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 = (caddr_t)&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 *)NULL, (Cardinal *)NULL);
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 *)NULL, (Cardinal *)NULL);
EdgeAttachMode mode = *((EdgeAttachMode *)fromVal->addr);
String 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(String, 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 *)NULL, (Cardinal *)NULL);
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 *)NULL, (Cardinal *)NULL);
LayoutMode mode = *((LayoutMode *)fromVal->addr);
String s = "unknown";
switch (mode)
{
case RegularLayoutMode:
s = "regular";
break;
case CompactLayoutMode:
s = "compact";
break;
default:
XtDisplayStringConversionWarning(display, s, XtRString);
break;
}
done(String, 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 *)NULL, (Cardinal *)NULL);
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 *)NULL, (Cardinal *)NULL);
SelfEdgePosition pos = *((SelfEdgePosition *)fromVal->addr);
String 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(String, 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 *)NULL, (Cardinal *)NULL);
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 *)NULL, (Cardinal *)NULL);
SelfEdgeDirection pos = *((SelfEdgeDirection *)fromVal->addr);
String s = "unknown";
switch (pos)
{
case Clockwise:
s = "clockwise";
break;
case Counterclockwise:
s = "counterclockwise";
break;
default:
XtDisplayStringConversionWarning(display, s, XtRString);
break;
}
done(String, 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 *)NULL, (Cardinal *)NULL);
Boolean mode = *((Boolean *)fromVal->addr);
String s = "unknown";
switch (mode)
{
case True:
s = "on";
break;
case False:
s = "off";
break;
default:
XtDisplayStringConversionWarning(display, s, XtRString);
break;
}
done(String, 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 *)NULL, (Cardinal *)NULL);
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 *)NULL, (Cardinal *)NULL);
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, caddr_t(&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, caddr_t(&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, caddr_t(&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())
{
// Both corners must be inside frame
BoxPoint nw = node->region(graphGC).origin();
BoxPoint se = nw + node->region(graphGC).space() - 1;
if (nw <= selected && se <= selected)
{
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())
{
// Both corners must be inside frame
BoxPoint nw = node->region(graphGC).origin();
BoxPoint se = nw + node->region(graphGC).space() - 1;
if (nw <= selected && se <= selected)
{
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,
String name, String default_param,
String 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, caddr_t(&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 = caddr_t(params[0]);
v1.size = sizeof(String);
v2.addr = caddr_t(&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, caddr_t(&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, caddr_t(&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);
}