00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017 #ifdef HAVE_CONFIG_H
00018 #include "config.h"
00019 #endif
00020
00021 #include <string.h>
00022
00023 #include "gvplugin_layout.h"
00024 #include "graph.h"
00025 #include "gvcint.h"
00026 #include "gvcproc.h"
00027
00028 extern char *strdup_and_subst_obj(char *str, void * n);
00029 extern void emit_graph(GVJ_t * job, graph_t * g);
00030 extern boolean overlap_edge(edge_t *e, boxf b);
00031 extern boolean overlap_node(node_t *n, boxf b);
00032 extern int gvLayout(GVC_t *gvc, graph_t *g, char *engine);
00033 extern int gvRenderFilename(GVC_t *gvc, graph_t *g, char *format, char *filename);
00034 extern void graph_cleanup(graph_t *g);
00035
00036 #define PANFACTOR 10
00037 #define ZOOMFACTOR 1.1
00038 #define EPSILON .0001
00039
00040 static char *s_digraph = "digraph";
00041 static char *s_graph = "graph";
00042 static char *s_subgraph = "subgraph";
00043 static char *s_node = "node";
00044 static char *s_edge = "edge";
00045 static char *s_tooltip = "tooltip";
00046 static char *s_href = "href";
00047 static char *s_URL = "URL";
00048 static char *s_tailport = "tailport";
00049 static char *s_headport = "headport";
00050 static char *s_key = "key";
00051
00052 static void gv_graph_state(GVJ_t *job, graph_t *g)
00053 {
00054 int i, j;
00055 Agsym_t *a;
00056 gv_argvlist_t *list;
00057
00058 list = &(job->selected_obj_type_name);
00059 j = 0;
00060 if (g == g->root) {
00061 if (g->kind && AGFLAG_DIRECTED)
00062 gv_argvlist_set_item(list, j++, s_digraph);
00063 else
00064 gv_argvlist_set_item(list, j++, s_graph);
00065 }
00066 else {
00067 gv_argvlist_set_item(list, j++, s_subgraph);
00068 }
00069 gv_argvlist_set_item(list, j++, g->name);
00070 list->argc = j;
00071
00072 list = &(job->selected_obj_attributes);
00073 for (i = 0, j = 0; i < dtsize(g->univ->globattr->dict); i++) {
00074 a = g->univ->globattr->list[i];
00075 gv_argvlist_set_item(list, j++, a->name);
00076 gv_argvlist_set_item(list, j++, agxget(g, a->index));
00077 gv_argvlist_set_item(list, j++, (char*)GVATTR_STRING);
00078 }
00079 list->argc = j;
00080
00081 a = agfindattr(g->root, s_href);
00082 if (!a)
00083 a = agfindattr(g->root, s_URL);
00084 if (a)
00085 job->selected_href = strdup_and_subst_obj(agxget(g, a->index), (void*)g);
00086 }
00087
00088 static void gv_node_state(GVJ_t *job, node_t *n)
00089 {
00090 int i, j;
00091 Agsym_t *a;
00092 Agraph_t *g;
00093 gv_argvlist_t *list;
00094
00095 list = &(job->selected_obj_type_name);
00096 j = 0;
00097 gv_argvlist_set_item(list, j++, s_node);
00098 gv_argvlist_set_item(list, j++, n->name);
00099 list->argc = j;
00100
00101 list = &(job->selected_obj_attributes);
00102 g = n -> graph -> root;
00103 for (i = 0, j = 0; i < dtsize(g->univ->nodeattr->dict); i++) {
00104 a = g->univ->nodeattr->list[i];
00105 gv_argvlist_set_item(list, j++, a->name);
00106 gv_argvlist_set_item(list, j++, agxget(n, a->index));
00107 }
00108 list->argc = j;
00109
00110 a = agfindattr(n->graph->proto->n, s_href);
00111 if (!a)
00112 a = agfindattr(n->graph->proto->n, s_URL);
00113 if (a)
00114 job->selected_href = strdup_and_subst_obj(agxget(n, a->index), (void*)n);
00115 }
00116
00117 static void gv_edge_state(GVJ_t *job, edge_t *e)
00118 {
00119 int i, j;
00120 Agsym_t *a;
00121 Agraph_t *g;
00122 gv_argvlist_t *nlist, *alist;
00123
00124 nlist = &(job->selected_obj_type_name);
00125
00126
00127
00128
00129 j = 0;
00130 gv_argvlist_set_item(nlist, j++, s_edge);
00131 gv_argvlist_set_item(nlist, j++, e->tail->name);
00132 j++;
00133 gv_argvlist_set_item(nlist, j++, (e->tail->graph->kind && AGFLAG_DIRECTED)?"->":"--");
00134 gv_argvlist_set_item(nlist, j++, e->head->name);
00135 j++;
00136 j++;
00137 nlist->argc = j;
00138
00139 alist = &(job->selected_obj_attributes);
00140 g = e -> head -> graph -> root;
00141 for (i = 0, j = 0; i < dtsize(g->univ->edgeattr->dict); i++) {
00142 a = g->univ->edgeattr->list[i];
00143
00144
00145
00146
00147 if (strcmp(a->name,s_tailport) == 0)
00148 gv_argvlist_set_item(nlist, 2, agxget(e, a->index));
00149 else if (strcmp(a->name,s_headport) == 0)
00150 gv_argvlist_set_item(nlist, 5, agxget(e, a->index));
00151
00152
00153
00154
00155 else if (strcmp(a->name,s_key) == 0) {
00156 gv_argvlist_set_item(nlist, 6, agxget(e, a->index));
00157 continue;
00158 }
00159
00160 gv_argvlist_set_item(alist, j++, a->name);
00161 gv_argvlist_set_item(alist, j++, agxget(e, a->index));
00162 }
00163 alist->argc = j;
00164
00165 a = agfindattr(e->head->graph->proto->e, s_href);
00166 if (!a)
00167 a = agfindattr(e->head->graph->proto->e, s_URL);
00168 if (a)
00169 job->selected_href = strdup_and_subst_obj(agxget(e, a->index), (void*)e);
00170 }
00171
00172 static void gvevent_refresh(GVJ_t * job)
00173 {
00174 graph_t *g = job->gvc->g;
00175
00176 if (!job->selected_obj) {
00177 job->selected_obj = g;
00178 GD_gui_state(g) |= GUI_STATE_SELECTED;
00179 gv_graph_state(job, g);
00180 }
00181 emit_graph(job, g);
00182 job->has_been_rendered = TRUE;
00183 }
00184
00185
00186 static graph_t *gvevent_find_cluster(graph_t *g, boxf b)
00187 {
00188 int i;
00189 graph_t *sg;
00190 boxf bb;
00191
00192 for (i = 1; i <= GD_n_cluster(g); i++) {
00193 sg = gvevent_find_cluster(GD_clust(g)[i], b);
00194 if (sg)
00195 return(sg);
00196 }
00197 B2BF(GD_bb(g), bb);
00198 if (OVERLAP(b, bb))
00199 return g;
00200 return NULL;
00201 }
00202
00203 static void * gvevent_find_obj(graph_t *g, boxf b)
00204 {
00205 graph_t *sg;
00206 node_t *n;
00207 edge_t *e;
00208
00209
00210 for (n = agfstnode(g); n; n = agnxtnode(g, n))
00211 for (e = agfstout(g, n); e; e = agnxtout(g, e))
00212 if (overlap_edge(e, b))
00213 return (void *)e;
00214
00215 for (n = aglstnode(g); n; n = agprvnode(g, n))
00216 if (overlap_node(n, b))
00217 return (void *)n;
00218
00219 sg = gvevent_find_cluster(g, b);
00220 if (sg)
00221 return (void *)sg;
00222
00223
00224 return (void *)g;
00225 }
00226
00227 static void gvevent_leave_obj(GVJ_t * job)
00228 {
00229 void *obj = job->current_obj;
00230
00231 if (obj) {
00232 switch (agobjkind(obj)) {
00233 case AGGRAPH:
00234 GD_gui_state((graph_t*)obj) &= ~GUI_STATE_ACTIVE;
00235 break;
00236 case AGNODE:
00237 ND_gui_state((node_t*)obj) &= ~GUI_STATE_ACTIVE;
00238 break;
00239 case AGEDGE:
00240 ED_gui_state((edge_t*)obj) &= ~GUI_STATE_ACTIVE;
00241 break;
00242 }
00243 }
00244 job->active_tooltip = NULL;
00245 }
00246
00247 static void gvevent_enter_obj(GVJ_t * job)
00248 {
00249 void *obj;
00250 graph_t *g;
00251 edge_t *e;
00252 node_t *n;
00253 Agsym_t *a;
00254
00255 if (job->active_tooltip) {
00256 free(job->active_tooltip);
00257 job->active_tooltip = NULL;
00258 }
00259 obj = job->current_obj;
00260 if (obj) {
00261 switch (agobjkind(obj)) {
00262 case AGGRAPH:
00263 g = (graph_t*)obj;
00264 GD_gui_state(g) |= GUI_STATE_ACTIVE;
00265 a = agfindattr(g->root, s_tooltip);
00266 if (a)
00267 job->active_tooltip = strdup_and_subst_obj(agxget(g, a->index), obj);
00268 break;
00269 case AGNODE:
00270 n = (node_t*)obj;
00271 ND_gui_state(n) |= GUI_STATE_ACTIVE;
00272 a = agfindattr(n->graph->proto->n, s_tooltip);
00273 if (a)
00274 job->active_tooltip = strdup_and_subst_obj(agxget(n, a->index), obj);
00275 break;
00276 case AGEDGE:
00277 e = (edge_t*)obj;
00278 ED_gui_state(e) |= GUI_STATE_ACTIVE;
00279 a = agfindattr(e->head->graph->proto->e, s_tooltip);
00280 if (a)
00281 job->active_tooltip = strdup_and_subst_obj(agxget(e, a->index), obj);
00282 break;
00283 }
00284 }
00285 }
00286
00287 static pointf pointer2graph (GVJ_t *job, pointf pointer)
00288 {
00289 pointf p;
00290
00291
00292 if (job->rotation) {
00293 p.x = pointer.y / (job->zoom * job->devscale.y) - job->translation.x;
00294 p.y = -pointer.x / (job->zoom * job->devscale.x) - job->translation.y;
00295 }
00296 else {
00297 p.x = pointer.x / (job->zoom * job->devscale.x) - job->translation.x;
00298 p.y = pointer.y / (job->zoom * job->devscale.y) - job->translation.y;
00299 }
00300 return p;
00301 }
00302
00303
00304 #define CLOSEENOUGH 1
00305
00306 static void gvevent_find_current_obj(GVJ_t * job, pointf pointer)
00307 {
00308 void *obj;
00309 boxf b;
00310 double closeenough;
00311 pointf p;
00312
00313 p = pointer2graph (job, pointer);
00314
00315
00316 closeenough = CLOSEENOUGH / job->zoom;
00317
00318 b.UR.x = p.x + closeenough;
00319 b.UR.y = p.y + closeenough;
00320 b.LL.x = p.x - closeenough;
00321 b.LL.y = p.y - closeenough;
00322
00323 obj = gvevent_find_obj(job->gvc->g, b);
00324 if (obj != job->current_obj) {
00325 gvevent_leave_obj(job);
00326 job->current_obj = obj;
00327 gvevent_enter_obj(job);
00328 job->needs_refresh = 1;
00329 }
00330 }
00331
00332 static void gvevent_select_current_obj(GVJ_t * job)
00333 {
00334 void *obj;
00335
00336 obj = job->selected_obj;
00337 if (obj) {
00338 switch (agobjkind(obj)) {
00339 case AGGRAPH:
00340 GD_gui_state((graph_t*)obj) |= GUI_STATE_VISITED;
00341 GD_gui_state((graph_t*)obj) &= ~GUI_STATE_SELECTED;
00342 break;
00343 case AGNODE:
00344 ND_gui_state((node_t*)obj) |= GUI_STATE_VISITED;
00345 ND_gui_state((node_t*)obj) &= ~GUI_STATE_SELECTED;
00346 break;
00347 case AGEDGE:
00348 ED_gui_state((edge_t*)obj) |= GUI_STATE_VISITED;
00349 ED_gui_state((edge_t*)obj) &= ~GUI_STATE_SELECTED;
00350 break;
00351 }
00352 }
00353
00354 if (job->selected_href) {
00355 free(job->selected_href);
00356 job->selected_href = NULL;
00357 }
00358
00359 obj = job->selected_obj = job->current_obj;
00360 if (obj) {
00361 switch (agobjkind(obj)) {
00362 case AGGRAPH:
00363 GD_gui_state((graph_t*)obj) |= GUI_STATE_SELECTED;
00364 gv_graph_state(job, (graph_t*)obj);
00365 break;
00366 case AGNODE:
00367 ND_gui_state((node_t*)obj) |= GUI_STATE_SELECTED;
00368 gv_node_state(job, (node_t*)obj);
00369 break;
00370 case AGEDGE:
00371 ED_gui_state((edge_t*)obj) |= GUI_STATE_SELECTED;
00372 gv_edge_state(job, (edge_t*)obj);
00373 break;
00374 }
00375 }
00376
00377 #if 0
00378 for (i = 0; i < job->selected_obj_type_name.argc; i++)
00379 fprintf(stderr,"%s%s", job->selected_obj_type_name.argv[i],
00380 (i==(job->selected_obj_type_name.argc - 1))?"\n":" ");
00381 for (i = 0; i < job->selected_obj_attributes.argc; i++)
00382 fprintf(stderr,"%s%s", job->selected_obj_attributes.argv[i], (i%2)?"\n":" = ");
00383 fprintf(stderr,"\n");
00384 #endif
00385 }
00386
00387 static void gvevent_button_press(GVJ_t * job, int button, pointf pointer)
00388 {
00389 switch (button) {
00390 case 1:
00391 gvevent_find_current_obj(job, pointer);
00392 gvevent_select_current_obj(job);
00393 job->click = 1;
00394 job->button = button;
00395 job->needs_refresh = 1;
00396 break;
00397 case 2:
00398 job->click = 1;
00399 job->button = button;
00400 job->needs_refresh = 1;
00401 break;
00402 case 3:
00403 gvevent_find_current_obj(job, pointer);
00404 job->click = 1;
00405 job->button = button;
00406 job->needs_refresh = 1;
00407 break;
00408 case 4:
00409
00410
00411 job->fit_mode = 0;
00412 if (job->rotation) {
00413 job->focus.x -= (pointer.y - job->height)
00414 * (ZOOMFACTOR - 1.) / (job->zoom * job->devscale.y);
00415 job->focus.y += (pointer.x)
00416 * (ZOOMFACTOR - 1.) / (job->zoom * job->devscale.x);
00417 }
00418 else {
00419 job->focus.x += (pointer.x)
00420 * (ZOOMFACTOR - 1.) / (job->zoom * job->devscale.x);
00421 job->focus.y += (pointer.y - job->height)
00422 * (ZOOMFACTOR - 1.) / (job->zoom * job->devscale.y);
00423 }
00424 job->zoom *= ZOOMFACTOR;
00425 job->needs_refresh = 1;
00426 break;
00427 case 5:
00428 job->fit_mode = 0;
00429 job->zoom /= ZOOMFACTOR;
00430 if (job->rotation) {
00431 job->focus.x += (pointer.y - job->height)
00432 * (ZOOMFACTOR - 1.) / (job->zoom * job->devscale.y);
00433 job->focus.y -= (pointer.x)
00434 * (ZOOMFACTOR - 1.) / (job->zoom * job->devscale.x);
00435 }
00436 else {
00437 job->focus.x -= (pointer.x)
00438 * (ZOOMFACTOR - 1.) / (job->zoom * job->devscale.x);
00439 job->focus.y -= (pointer.y - job->height)
00440 * (ZOOMFACTOR - 1.) / (job->zoom * job->devscale.y);
00441 }
00442 job->needs_refresh = 1;
00443 break;
00444 }
00445 job->oldpointer = pointer;
00446 }
00447
00448 static void gvevent_button_release(GVJ_t *job, int button, pointf pointer)
00449 {
00450 job->click = 0;
00451 job->button = 0;
00452 }
00453
00454 static void gvevent_motion(GVJ_t * job, pointf pointer)
00455 {
00456
00457 double dx = (pointer.x - job->oldpointer.x) / job->devscale.x;
00458 double dy = (pointer.y - job->oldpointer.y) / job->devscale.y;
00459
00460 if (abs(dx) < EPSILON && abs(dy) < EPSILON)
00461 return;
00462
00463 switch (job->button) {
00464 case 0:
00465 gvevent_find_current_obj(job, pointer);
00466 break;
00467 case 1:
00468
00469 break;
00470 case 2:
00471 if (job->rotation) {
00472 job->focus.x -= dy / job->zoom;
00473 job->focus.y += dx / job->zoom;
00474 }
00475 else {
00476 job->focus.x -= dx / job->zoom;
00477 job->focus.y -= dy / job->zoom;
00478 }
00479 job->needs_refresh = 1;
00480 break;
00481 case 3:
00482 break;
00483 }
00484 job->oldpointer = pointer;
00485 }
00486
00487 static int quit_cb(GVJ_t * job)
00488 {
00489 return 1;
00490 }
00491
00492 static int left_cb(GVJ_t * job)
00493 {
00494 job->fit_mode = 0;
00495 job->focus.x += PANFACTOR / job->zoom;
00496 job->needs_refresh = 1;
00497 return 0;
00498 }
00499
00500 static int right_cb(GVJ_t * job)
00501 {
00502 job->fit_mode = 0;
00503 job->focus.x -= PANFACTOR / job->zoom;
00504 job->needs_refresh = 1;
00505 return 0;
00506 }
00507
00508 static int up_cb(GVJ_t * job)
00509 {
00510 job->fit_mode = 0;
00511 job->focus.y += -(PANFACTOR / job->zoom);
00512 job->needs_refresh = 1;
00513 return 0;
00514 }
00515
00516 static int down_cb(GVJ_t * job)
00517 {
00518 job->fit_mode = 0;
00519 job->focus.y -= -(PANFACTOR / job->zoom);
00520 job->needs_refresh = 1;
00521 return 0;
00522 }
00523
00524 static int zoom_in_cb(GVJ_t * job)
00525 {
00526 job->fit_mode = 0;
00527 job->zoom *= ZOOMFACTOR;
00528 job->needs_refresh = 1;
00529 return 0;
00530 }
00531
00532 static int zoom_out_cb(GVJ_t * job)
00533 {
00534 job->fit_mode = 0;
00535 job->zoom /= ZOOMFACTOR;
00536 job->needs_refresh = 1;
00537 return 0;
00538 }
00539
00540 static int toggle_fit_cb(GVJ_t * job)
00541 {
00542
00543
00544
00545
00546
00547
00548 job->fit_mode = !job->fit_mode;
00549 if (job->fit_mode) {
00550
00551 int dflt_width, dflt_height;
00552 dflt_width = job->width;
00553 dflt_height = job->height;
00554 job->zoom =
00555 MIN((double) job->width / (double) dflt_width,
00556 (double) job->height / (double) dflt_height);
00557 job->focus.x = 0.0;
00558 job->focus.y = 0.0;
00559 job->needs_refresh = 1;
00560 }
00561 return 0;
00562 }
00563
00564 static void gvevent_modify (GVJ_t * job, char *name, char *value)
00565 {
00566
00567 }
00568
00569 static void gvevent_delete (GVJ_t * job)
00570 {
00571
00572 }
00573
00574 static void gvevent_read (GVJ_t * job, char *filename, char *layout)
00575 {
00576 FILE *f;
00577 GVC_t *gvc;
00578 Agraph_t *g = NULL;
00579 gvlayout_engine_t *gvle;
00580
00581 gvc = job->gvc;
00582 if (!filename) {
00583 g = agopen("G", AGDIGRAPH);
00584 job->output_filename = "new.dot";
00585 }
00586 else {
00587 f = fopen(filename, "r");
00588 if (!f)
00589 return;
00590 g = agread(f);
00591 fclose(f);
00592 }
00593 if (!g)
00594 return;
00595 if (gvc->g) {
00596 gvle = gvc->layout.engine;
00597 if (gvle && gvle->cleanup)
00598 gvle->cleanup(gvc->g);
00599 graph_cleanup(gvc->g);
00600 agclose(gvc->g);
00601 }
00602 gvc->g = g;
00603 GD_gvc(g) = gvc;
00604 gvLayout(gvc, g, layout);
00605 job->selected_obj = NULL;
00606 job->current_obj = NULL;
00607 job->needs_refresh = 1;
00608 }
00609
00610 static void gvevent_layout (GVJ_t * job, char *layout)
00611 {
00612 gvLayout(job->gvc, job->gvc->g, layout);
00613 }
00614
00615 static void gvevent_render (GVJ_t * job, char *format, char *filename)
00616 {
00617 gvRenderFilename(job->gvc, job->gvc->g, format, filename);
00618 }
00619
00620
00621 gvevent_key_binding_t gvevent_key_binding[] = {
00622 {"Q", quit_cb},
00623 {"Left", left_cb},
00624 {"KP_Left", left_cb},
00625 {"Right", right_cb},
00626 {"KP_Right", right_cb},
00627 {"Up", up_cb},
00628 {"KP_Up", up_cb},
00629 {"Down", down_cb},
00630 {"KP_Down", down_cb},
00631 {"plus", zoom_in_cb},
00632 {"KP_Add", zoom_in_cb},
00633 {"minus", zoom_out_cb},
00634 {"KP_Subtract", zoom_out_cb},
00635 {"F", toggle_fit_cb},
00636 };
00637
00638 int gvevent_key_binding_size = ARRAY_SIZE(gvevent_key_binding);
00639
00640 gvdevice_callbacks_t gvdevice_callbacks = {
00641 gvevent_refresh,
00642 gvevent_button_press,
00643 gvevent_button_release,
00644 gvevent_motion,
00645 gvevent_modify,
00646 gvevent_delete,
00647 gvevent_read,
00648 gvevent_layout,
00649 gvevent_render,
00650 };