/*
* Copyright (c) 2007 Hypertriton, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include
#include "graph.h"
#include "primitive.h"
#include "menu.h"
#include "gui_math.h"
#include
#include
AG_Graph *
AG_GraphNew(void *parent, Uint flags)
{
AG_Graph *gf;
gf = Malloc(sizeof(AG_Graph));
AG_ObjectInit(gf, &agGraphClass);
gf->flags |= flags;
if (flags & AG_GRAPH_HFILL) { AG_ExpandHoriz(gf); }
if (flags & AG_GRAPH_VFILL) { AG_ExpandVert(gf); }
AG_ObjectAttach(parent, gf);
return (gf);
}
static void
KeyDown(AG_Event *event)
{
AG_Graph *gf = AG_SELF();
int keysym = AG_INT(1);
const int scrollIncr = 10;
switch (keysym) {
case SDLK_LEFT:
gf->xOffs -= scrollIncr;
break;
case SDLK_RIGHT:
gf->xOffs += scrollIncr;
break;
case SDLK_UP:
gf->yOffs -= scrollIncr;
break;
case SDLK_DOWN:
gf->yOffs += scrollIncr;
break;
case SDLK_0:
gf->xOffs = 0;
gf->yOffs = 0;
break;
}
}
static __inline__ int
MouseOverVertex(AG_GraphVertex *vtx, int x, int y)
{
return (abs(x - vtx->x + vtx->graph->xOffs) <= vtx->w/2 &&
abs(y - vtx->y + vtx->graph->yOffs) <= vtx->h/2);
}
static __inline__ void
GetEdgeLabelCoords(AG_GraphEdge *edge, int *x, int *y)
{
*x = (edge->v1->x + edge->v2->x)/2;
*y = (edge->v1->y + edge->v2->y)/2;
}
static __inline__ int
MouseOverEdge(AG_GraphEdge *edge, int x, int y)
{
int lx, ly;
AG_Surface *lbl;
if (edge->labelSu == -1) {
return (0);
}
GetEdgeLabelCoords(edge, &lx, &ly);
lbl = WSURFACE(edge->graph,edge->labelSu);
return (abs(x - lx + edge->graph->xOffs) <= lbl->w/2 &&
abs(y - ly + edge->graph->yOffs) <= lbl->h/2);
}
static void
MouseMotion(AG_Event *event)
{
AG_Graph *gf = AG_SELF();
int x = AG_INT(1);
int y = AG_INT(2);
int dx = AG_INT(3);
int dy = AG_INT(4);
AG_GraphVertex *vtx;
AG_GraphEdge *edge;
if (gf->flags & AG_GRAPH_PANNING) {
gf->xOffs -= dx;
gf->yOffs -= dy;
return;
}
if (gf->flags & AG_GRAPH_DRAGGING) {
TAILQ_FOREACH(vtx, &gf->vertices, vertices) {
if (vtx->flags & AG_GRAPH_SELECTED) {
AG_GraphVertexPosition(vtx,
vtx->x + dx,
vtx->y + dy);
}
}
} else {
TAILQ_FOREACH(vtx, &gf->vertices, vertices) {
AG_SETFLAGS(vtx->flags, AG_GRAPH_MOUSEOVER,
MouseOverVertex(vtx,x,y));
}
TAILQ_FOREACH(edge, &gf->edges, edges) {
AG_SETFLAGS(vtx->flags, AG_GRAPH_MOUSEOVER,
MouseOverEdge(edge,x,y));
}
}
}
static void
MouseButtonUp(AG_Event *event)
{
AG_Graph *gf = AG_SELF();
int button = AG_INT(1);
switch (button) {
case SDL_BUTTON_LEFT:
gf->flags &= ~(AG_GRAPH_DRAGGING);
break;
case SDL_BUTTON_MIDDLE:
gf->flags &= ~(AG_GRAPH_PANNING);
break;
}
}
/* Widget must be locked */
AG_GraphEdge *
AG_GraphEdgeFind(AG_Graph *gf, void *userPtr)
{
AG_GraphEdge *edge;
TAILQ_FOREACH(edge, &gf->edges, edges) {
if (edge->userPtr == userPtr)
return (edge);
}
return (NULL);
}
AG_GraphEdge *
AG_GraphEdgeNew(AG_Graph *gf, AG_GraphVertex *v1, AG_GraphVertex *v2,
void *userPtr)
{
AG_GraphEdge *edge;
AG_ObjectLock(gf);
TAILQ_FOREACH(edge, &gf->edges, edges) {
if (edge->v1 == v1 && edge->v2 == v2) {
AG_SetError(_("Existing edge"));
AG_ObjectUnlock(gf);
return (NULL);
}
}
edge = Malloc(sizeof(AG_GraphEdge));
edge->labelTxt[0] = '\0';
edge->labelSu = -1;
edge->edgeColor = AG_MapRGB(agSurfaceFmt, 0,0,0);
edge->labelColor = AG_MapRGB(agSurfaceFmt, 0,0,0);
edge->flags = 0;
edge->v1 = v1;
edge->v2 = v2;
edge->userPtr = userPtr;
edge->graph = gf;
edge->popupMenu = NULL;
TAILQ_INSERT_TAIL(&gf->edges, edge, edges);
gf->nedges++;
edge->v1->edges = Realloc(edge->v1->edges, (edge->v1->nedges + 1) *
sizeof(AG_GraphEdge *));
edge->v2->edges = Realloc(edge->v2->edges, (edge->v2->nedges + 1) *
sizeof(AG_GraphEdge *));
edge->v1->edges[edge->v1->nedges++] = edge;
edge->v2->edges[edge->v2->nedges++] = edge;
AG_ObjectUnlock(gf);
return (edge);
}
void
AG_GraphEdgeFree(AG_GraphEdge *edge)
{
if (edge->labelSu != -1) {
AG_WidgetUnmapSurface(edge->graph, edge->labelSu);
}
Free(edge);
}
void
AG_GraphEdgeLabel(AG_GraphEdge *ge, const char *fmt, ...)
{
va_list ap;
AG_ObjectLock(ge->graph);
va_start(ap, fmt);
Vsnprintf(ge->labelTxt, sizeof(ge->labelTxt), fmt, ap);
va_end(ap);
if (ge->labelSu >= 0) {
AG_WidgetUnmapSurface(ge->graph, ge->labelSu);
}
AG_TextColor32(ge->labelColor);
ge->labelSu = AG_WidgetMapSurface(ge->graph,
AG_TextRender(ge->labelTxt));
AG_ObjectUnlock(ge->graph);
}
void
AG_GraphEdgeColorLabel(AG_GraphEdge *edge, Uint8 r, Uint8 g, Uint8 b)
{
AG_ObjectLock(edge->graph);
edge->labelColor = AG_MapRGB(agSurfaceFmt, r,g,b);
AG_ObjectUnlock(edge->graph);
}
void
AG_GraphEdgeColor(AG_GraphEdge *edge, Uint8 r, Uint8 g, Uint8 b)
{
AG_ObjectLock(edge->graph);
edge->edgeColor = AG_MapRGB(agSurfaceFmt, r,g,b);
AG_ObjectUnlock(edge->graph);
}
void
AG_GraphEdgePopupMenu(AG_GraphEdge *edge, struct ag_popup_menu *pm)
{
AG_ObjectLock(edge->graph);
edge->popupMenu = pm;
AG_ObjectUnlock(edge->graph);
}
static void
SetVertexStyle(AG_Event *event)
{
AG_GraphVertex *vtx = AG_PTR(1);
int style = AG_INT(2);
AG_GraphVertexStyle(vtx, (enum ag_graph_vertex_style)style);
}
static void
UnselectEdge(AG_Graph *gf, AG_GraphEdge *edge)
{
edge->flags &= ~(AG_GRAPH_SELECTED);
AG_PostEvent(NULL, gf, "graph-edge-unselected", "%p", edge);
}
static void
SelectEdge(AG_Graph *gf, AG_GraphEdge *edge)
{
edge->flags |= AG_GRAPH_SELECTED;
AG_PostEvent(NULL, gf, "graph-edge-selected", "%p", edge);
}
static void
UnselectVertex(AG_Graph *gf, AG_GraphVertex *vtx)
{
vtx->flags &= ~(AG_GRAPH_SELECTED);
AG_PostEvent(NULL, gf, "graph-vertex-unselected", "%p", vtx);
}
static void
SelectVertex(AG_Graph *gf, AG_GraphVertex *vtx)
{
vtx->flags |= AG_GRAPH_SELECTED;
AG_PostEvent(NULL, gf, "graph-vertex-selected", "%p", vtx);
}
static void
MouseButtonDown(AG_Event *event)
{
AG_Graph *gf = AG_SELF();
int button = AG_INT(1);
int x = AG_INT(2);
int y = AG_INT(3);
SDLMod mod = SDL_GetModState();
AG_GraphVertex *vtx, *vtx2;
AG_GraphEdge *edge, *edge2;
AG_PopupMenu *pm;
switch (button) {
case SDL_BUTTON_MIDDLE:
gf->flags |= AG_GRAPH_PANNING;
break;
case SDL_BUTTON_LEFT:
AG_WidgetFocus(gf);
if (gf->flags & AG_GRAPH_NO_SELECT) {
break;
}
if (mod & (KMOD_CTRL|KMOD_SHIFT)) {
TAILQ_FOREACH(edge, &gf->edges, edges) {
if (!MouseOverEdge(edge, x, y)) {
continue;
}
if (edge->flags & AG_GRAPH_SELECTED) {
UnselectEdge(gf, edge);
} else {
SelectEdge(gf, edge);
}
}
TAILQ_FOREACH(vtx, &gf->vertices, vertices) {
if (!MouseOverVertex(vtx, x, y)) {
continue;
}
if (vtx->flags & AG_GRAPH_SELECTED) {
UnselectVertex(gf, vtx);
} else {
SelectVertex(gf, vtx);
}
}
} else {
TAILQ_FOREACH(edge, &gf->edges, edges) {
if (MouseOverEdge(edge, x, y))
break;
}
if (edge != NULL) {
TAILQ_FOREACH(edge2, &gf->edges, edges) {
UnselectEdge(gf, edge2);
}
SelectEdge(gf, edge);
}
TAILQ_FOREACH(vtx, &gf->vertices, vertices) {
if (MouseOverVertex(vtx, x, y))
break;
}
if (vtx != NULL) {
TAILQ_FOREACH(vtx2, &gf->vertices, vertices) {
UnselectVertex(gf, vtx2);
}
SelectVertex(gf, vtx);
}
}
if (!(gf->flags & AG_GRAPH_NO_MOVE)) {
gf->flags |= AG_GRAPH_DRAGGING;
}
break;
case SDL_BUTTON_RIGHT:
if (gf->flags & AG_GRAPH_NO_MENUS) {
break;
}
AG_WidgetFocus(gf);
TAILQ_FOREACH(vtx, &gf->vertices, vertices) {
if (!MouseOverVertex(vtx, x, y)) {
continue;
}
if (vtx->popupMenu != NULL) {
AG_PopupShow(vtx->popupMenu);
} else {
pm = AG_PopupNew(gf);
AG_MenuUintFlags(pm->item, _("Hide vertex"),
NULL,
&vtx->flags, AG_GRAPH_HIDDEN, 1);
AG_MenuSeparator(pm->item);
AG_MenuAction(pm->item, _("Rectangular"), NULL,
SetVertexStyle, "%p,%i", vtx,
AG_GRAPH_RECTANGLE);
AG_MenuAction(pm->item, _("Circular"), NULL,
SetVertexStyle, "%p,%i", vtx,
AG_GRAPH_CIRCLE);
AG_PopupShow(pm);
}
break;
}
TAILQ_FOREACH(edge, &gf->edges, edges) {
if (!MouseOverEdge(edge, x, y)) {
continue;
}
if (edge->popupMenu != NULL) {
AG_PopupShow(edge->popupMenu);
} else {
pm = AG_PopupNew(gf);
AG_MenuUintFlags(pm->item, _("Hide edge"), NULL,
&edge->flags, AG_GRAPH_HIDDEN, 1);
AG_PopupShow(pm);
}
break;
}
default:
break;
}
}
static void
Init(void *obj)
{
AG_Graph *gf = obj;
WIDGET(gf)->flags |= AG_WIDGET_FOCUSABLE;
gf->flags = 0;
gf->wPre = 128;
gf->hPre = 128;
gf->xOffs = 0;
gf->yOffs = 0;
gf->xMin = 0;
gf->yMin = 0;
gf->xMax = 0;
gf->yMax = 0;
TAILQ_INIT(&gf->vertices);
TAILQ_INIT(&gf->edges);
gf->nvertices = 0;
gf->nedges = 0;
gf->pxMin = 0;
gf->pxMax = 0;
gf->pyMin = 0;
gf->pyMax = 0;
gf->r = AG_RECT(0,0,0,0);
#if 0
gf->hbar = AG_ScrollbarNew(gf, AG_SCROLLBAR_HORIZ, 0);
gf->vbar = AG_ScrollbarNew(gf, AG_SCROLLBAR_VERT, 0);
AG_BindInt(gf->hbar, "value", &gf->xOffs);
AG_BindInt(gf->hbar, "min", &gf->xMin);
AG_BindInt(gf->hbar, "max", &gf->xMax);
AG_BindInt(gf->hbar, "visible", &WIDGET(gf)->w);
AG_BindInt(gf->vbar, "value", &gf->yOffs);
AG_BindInt(gf->vbar, "min", &gf->yMin);
AG_BindInt(gf->vbar, "max", &gf->yMax);
AG_BindInt(gf->vbar, "visible", &WIDGET(gf)->h);
#endif
AG_SetEvent(gf, "window-keydown", KeyDown, NULL);
AG_SetEvent(gf, "window-mousebuttondown", MouseButtonDown, NULL);
AG_SetEvent(gf, "window-mousebuttonup", MouseButtonUp, NULL);
AG_SetEvent(gf, "window-mousemotion", MouseMotion, NULL);
}
void
AG_GraphFreeVertices(AG_Graph *gf)
{
AG_GraphVertex *vtx, *vtxNext;
AG_GraphEdge *edge, *edgeNext;
AG_ObjectLock(gf);
for (vtx = TAILQ_FIRST(&gf->vertices);
vtx != TAILQ_END(&gf->vertices);
vtx = vtxNext) {
vtxNext = TAILQ_NEXT(vtx, vertices);
AG_GraphVertexFree(vtx);
}
for (edge = TAILQ_FIRST(&gf->edges);
edge != TAILQ_END(&gf->edges);
edge = edgeNext) {
edgeNext = TAILQ_NEXT(edge, edges);
AG_GraphEdgeFree(edge);
}
TAILQ_INIT(&gf->vertices);
TAILQ_INIT(&gf->edges);
gf->nvertices = 0;
gf->nedges = 0;
gf->xMin = 0;
gf->xMax = 0;
gf->yMin = 0;
gf->yMax = 0;
gf->flags &= ~(AG_GRAPH_DRAGGING);
AG_ObjectUnlock(gf);
}
static void
Destroy(void *p)
{
AG_GraphFreeVertices((AG_Graph *)p);
}
void
AG_GraphSizeHint(AG_Graph *gf, Uint w, Uint h)
{
AG_ObjectLock(gf);
gf->wPre = w;
gf->hPre = h;
AG_ObjectUnlock(gf);
}
static void
SizeRequest(void *obj, AG_SizeReq *r)
{
AG_Graph *gf = obj;
r->w = gf->wPre;
r->h = gf->hPre;
}
static int
SizeAllocate(void *obj, const AG_SizeAlloc *a)
{
AG_Graph *gf = obj;
if (a->w < 1 || a->h < 1)
return (-1);
gf->xOffs = -(a->w/2);
gf->yOffs = -(a->h/2);
gf->r = AG_RECT(0, 0, a->w, a->h);
return (0);
}
static void
Draw(void *obj)
{
AG_Graph *gf = obj;
AG_GraphVertex *vtx;
AG_GraphEdge *edge;
Uint8 bg[4];
AG_PushClipRect(gf, gf->r);
/* Draw the bounding box */
AG_DrawRectOutline(gf,
AG_RECT(gf->pxMin - gf->xOffs, gf->pyMin - gf->yOffs,
gf->pxMax - gf->pxMin, gf->pyMax - gf->pyMin),
AG_MapRGB(agVideoFmt, 128,128,128));
/* Draw the edges */
TAILQ_FOREACH(edge, &gf->edges, edges) {
if (edge->flags & AG_GRAPH_HIDDEN) {
continue;
}
AG_DrawLine(gf,
edge->v1->x - gf->xOffs,
edge->v1->y - gf->yOffs,
edge->v2->x - gf->xOffs,
edge->v2->y - gf->yOffs,
edge->edgeColor);
if (edge->labelSu >= 0) {
AG_Surface *su = WSURFACE(gf,edge->labelSu);
int lblX, lblY;
GetEdgeLabelCoords(edge, &lblX, &lblY);
lblX -= gf->xOffs + su->w/2;
lblY -= gf->yOffs + su->h/2;
if (edge->flags & AG_GRAPH_SELECTED) {
AG_DrawRectOutline(gf,
AG_RECT(lblX-1, lblY-1,
su->w+2, su->h+2),
AG_VideoPixel(edge->labelColor));
}
if (edge->flags & AG_GRAPH_MOUSEOVER) {
AG_DrawRectOutline(gf,
AG_RECT(lblX-2, lblY-2,
su->w+4, su->h+4),
AG_COLOR(TEXT_COLOR));
}
bg[0] = 128;
bg[1] = 128;
bg[2] = 128;
bg[3] = 128;
AG_DrawRectBlended(gf,
AG_RECT(lblX, lblY, su->w, su->h),
bg, AG_ALPHA_SRC);
AG_WidgetBlitSurface(gf, edge->labelSu, lblX, lblY);
}
}
/* Draw the vertices. */
TAILQ_FOREACH(vtx, &gf->vertices, vertices) {
AG_Surface *lbl = WSURFACE(gf,vtx->labelSu);
if (vtx->flags & AG_GRAPH_HIDDEN) {
continue;
}
AG_GetRGBA(vtx->bgColor, agSurfaceFmt,
&bg[0],&bg[1],&bg[2],&bg[3]);
switch (vtx->style) {
case AG_GRAPH_RECTANGLE:
AG_DrawRectBlended(gf,
AG_RECT(vtx->x - vtx->w/2 - gf->xOffs,
vtx->y - vtx->h/2 - gf->yOffs,
vtx->w,
vtx->h),
bg, AG_ALPHA_SRC);
if (vtx->flags & AG_GRAPH_SELECTED) {
AG_DrawRectOutline(gf,
AG_RECT(vtx->x - vtx->w/2 - gf->xOffs - 1,
vtx->y - vtx->h/2 - gf->yOffs - 1,
vtx->w + 2,
vtx->h + 2),
AG_MapRGB(agVideoFmt, 0,0,255));
}
if (vtx->flags & AG_GRAPH_MOUSEOVER) {
AG_DrawRectOutline(gf,
AG_RECT(vtx->x - vtx->w/2 - gf->xOffs - 2,
vtx->y - vtx->h/2 - gf->yOffs - 2,
vtx->w + 4,
vtx->h + 4),
AG_MapRGB(agVideoFmt, 255,0,0));
}
break;
case AG_GRAPH_CIRCLE:
AG_DrawCircle(gf,
vtx->x - gf->xOffs,
vtx->y - gf->yOffs,
MAX(vtx->w,vtx->h)/2,
vtx->bgColor);
break;
}
if (vtx->labelSu >= 0) {
AG_WidgetBlitSurface(gf, vtx->labelSu,
vtx->x - lbl->w/2 - gf->xOffs,
vtx->y - lbl->h/2 - gf->yOffs);
}
}
AG_PopClipRect();
}
/* Graph must be locked. */
AG_GraphVertex *
AG_GraphVertexFind(AG_Graph *gf, void *userPtr)
{
AG_GraphVertex *vtx;
TAILQ_FOREACH(vtx, &gf->vertices, vertices) {
if (vtx->userPtr == userPtr)
return (vtx);
}
return (NULL);
}
AG_GraphVertex *
AG_GraphVertexNew(AG_Graph *gf, void *userPtr)
{
AG_GraphVertex *vtx;
vtx = Malloc(sizeof(AG_GraphVertex));
vtx->labelTxt[0] = '\0';
vtx->labelSu = -1;
vtx->labelColor = AG_MapRGB(agSurfaceFmt, 0,0,0);
vtx->bgColor = AG_MapRGBA(agSurfaceFmt, 255,255,255,128);
vtx->style = AG_GRAPH_RECTANGLE;
vtx->flags = 0;
vtx->x = 0;
vtx->y = 0;
vtx->w = 32;
vtx->h = 32;
vtx->graph = gf;
vtx->userPtr = userPtr;
vtx->edges = NULL;
vtx->nedges = 0;
vtx->popupMenu = NULL;
AG_ObjectLock(gf);
TAILQ_INSERT_TAIL(&gf->vertices, vtx, vertices);
gf->nvertices++;
AG_ObjectUnlock(gf);
return (vtx);
}
void
AG_GraphVertexFree(AG_GraphVertex *vtx)
{
if (vtx->labelSu != -1) {
AG_WidgetUnmapSurface(vtx->graph, vtx->labelSu);
}
Free(vtx->edges);
Free(vtx);
}
void
AG_GraphVertexColorLabel(AG_GraphVertex *vtx, Uint8 r, Uint8 g, Uint8 b)
{
AG_ObjectLock(vtx->graph);
vtx->labelColor = AG_MapRGB(agSurfaceFmt, r,g,b);
AG_ObjectUnlock(vtx->graph);
}
void
AG_GraphVertexColorBG(AG_GraphVertex *vtx, Uint8 r, Uint8 g, Uint8 b)
{
AG_ObjectLock(vtx->graph);
vtx->bgColor = AG_MapRGB(agSurfaceFmt, r,g,b);
AG_ObjectUnlock(vtx->graph);
}
void
AG_GraphVertexLabel(AG_GraphVertex *vtx, const char *fmt, ...)
{
va_list ap;
AG_ObjectLock(vtx->graph);
va_start(ap, fmt);
Vsnprintf(vtx->labelTxt, sizeof(vtx->labelTxt), fmt, ap);
va_end(ap);
if (vtx->labelSu >= 0) {
AG_WidgetUnmapSurface(vtx->graph, vtx->labelSu);
}
AG_TextColor32(vtx->labelColor);
vtx->labelSu = AG_WidgetMapSurface(vtx->graph,
AG_TextRender(vtx->labelTxt));
AG_ObjectUnlock(vtx->graph);
}
void
AG_GraphVertexPosition(AG_GraphVertex *vtx, int x, int y)
{
AG_Graph *gf = vtx->graph;
AG_ObjectLock(gf);
vtx->x = x;
vtx->y = y;
if (x < gf->xMin) { gf->xMin = x; }
if (y < gf->yMin) { gf->yMin = y; }
if (x > gf->xMax) { gf->xMax = x; }
if (y > gf->yMax) { gf->yMax = y; }
AG_ObjectUnlock(gf);
}
void
AG_GraphVertexSize(AG_GraphVertex *vtx, Uint w, Uint h)
{
AG_ObjectLock(vtx->graph);
vtx->w = w;
vtx->h = h;
AG_ObjectUnlock(vtx->graph);
}
void
AG_GraphVertexStyle(AG_GraphVertex *vtx, enum ag_graph_vertex_style style)
{
AG_ObjectLock(vtx->graph);
vtx->style = style;
AG_ObjectUnlock(vtx->graph);
}
void
AG_GraphVertexPopupMenu(AG_GraphVertex *vtx, struct ag_popup_menu *pm)
{
AG_ObjectLock(vtx->graph);
vtx->popupMenu = pm;
AG_ObjectUnlock(vtx->graph);
}
static int
CompareVertices(const void *p1, const void *p2)
{
const AG_GraphVertex *v1 = *(const void **)p1;
const AG_GraphVertex *v2 = *(const void **)p2;
return (v2->nedges - v1->nedges);
}
static AG_GraphVertex *
VertexAtCoords(AG_Graph *gf, int x, int y)
{
AG_GraphVertex *vtx;
TAILQ_FOREACH(vtx, &gf->vertices, vertices) {
if (vtx->x == x && vtx->y == y)
return (vtx);
}
return (NULL);
}
static void
PlaceVertex(AG_Graph *gf, AG_GraphVertex *vtx, AG_GraphVertex **vSorted,
int x, int y)
{
int ox, oy;
int i;
vtx->x = x;
vtx->y = y;
vtx->flags |= AG_GRAPH_AUTOPLACED;
if (x < gf->pxMin) { gf->pxMin = x; }
if (x > gf->pxMax) { gf->pxMax = x; }
if (y < gf->pyMin) { gf->pyMin = y; }
if (y > gf->pyMax) { gf->pyMax = y; }
for (i = 0; i < vtx->nedges; i++) {
AG_GraphEdge *edge = vtx->edges[i];
AG_GraphVertex *oVtx;
float r = 128.0;
float theta = 0.0;
if (edge->v1 == vtx) { oVtx = edge->v2; }
else { oVtx = edge->v1; }
if (oVtx->flags & AG_GRAPH_AUTOPLACED) {
continue;
}
for (;;) {
ox = (int)(r*Cos(theta));
oy = (int)(r*Sin(theta));
if (VertexAtCoords(gf, ox, oy) == NULL) {
PlaceVertex(gf, oVtx, vSorted, ox, oy);
break;
}
theta += (float)(AG_PI*2.0)/6;
if (theta >= (AG_PI*2.0)) {
r += 64.0;
}
}
}
}
/*
* Try to position the vertices such that they are distributed evenly
* throughout a given bounding box, with a minimal number of edges
* crossing each other.
*/
void
AG_GraphAutoPlace(AG_Graph *gf, Uint w, Uint h)
{
AG_GraphVertex **vSorted, *vtx;
int nSorted = 0, i;
int tx, ty;
AG_ObjectLock(gf);
if (gf->nvertices == 0 || gf->nedges == 0) {
AG_ObjectUnlock(gf);
return;
}
/* Sort the vertices based on their number of connected edges. */
vSorted = Malloc(gf->nvertices*sizeof(AG_GraphVertex *));
TAILQ_FOREACH(vtx, &gf->vertices, vertices) {
vtx->flags &= ~(AG_GRAPH_AUTOPLACED);
vSorted[nSorted++] = vtx;
}
qsort(vSorted, (size_t)nSorted, sizeof(AG_GraphVertex *),
CompareVertices);
gf->pxMin = 0;
gf->pxMax = 0;
gf->pyMin = 0;
gf->pyMax = 0;
for (i = 0; i < nSorted; i++) {
if (vSorted[i]->flags & AG_GRAPH_AUTOPLACED) {
continue;
}
for (tx = gf->pxMax+128, ty = 0;
ty <= gf->pyMax;
ty += 64) {
if (VertexAtCoords(gf, tx, ty) == NULL) {
PlaceVertex(gf, vSorted[i], vSorted, tx, ty);
break;
}
}
if (ty <= gf->pyMax) {
continue;
}
for (tx = gf->pxMin-128, ty = 0;
ty <= gf->pyMax;
ty += 64) {
if (VertexAtCoords(gf, tx, ty) == NULL) {
PlaceVertex(gf, vSorted[i], vSorted, tx, ty);
break;
}
}
if (ty <= gf->pyMax) {
continue;
}
}
AG_ObjectUnlock(gf);
Free(vSorted);
}
AG_WidgetClass agGraphClass = {
{
"Agar(Widget:Graph)",
sizeof(AG_Graph),
{ 0,0 },
Init,
NULL, /* free */
Destroy,
NULL, /* load */
NULL, /* save */
NULL /* edit */
},
Draw,
SizeRequest,
SizeAllocate
};