/misc/src/release/graphviz-2.18-1/src/graphviz-2.18/lib/gvc/gvevent.c

Go to the documentation of this file.
00001 /* $Id: gvevent.c,v 1.56 2007/11/30 18:57:44 ellson Exp $ $Revision: 1.56 $ */
00002 /* vim:set shiftwidth=4 ts=8: */
00003 
00004 /**********************************************************
00005 *      This software is part of the graphviz package      *
00006 *                http://www.graphviz.org/                 *
00007 *                                                         *
00008 *            Copyright (c) 1994-2004 AT&T Corp.           *
00009 *                and is licensed under the                *
00010 *            Common Public License, Version 1.0           *
00011 *                      by AT&T Corp.                      *
00012 *                                                         *
00013 *        Information and Software Systems Research        *
00014 *              AT&T Research, Florham Park NJ             *
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     /* only tail, head, and key are strictly identifying properties,
00127      * but we commonly alse use edge kind (e.g. "->") and tailport,headport
00128      * in edge names */
00129     j = 0;
00130     gv_argvlist_set_item(nlist, j++, s_edge);
00131     gv_argvlist_set_item(nlist, j++, e->tail->name);
00132     j++; /* skip tailport slot for now */
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++; /* skip headport slot for now */
00136     j++; /* skip key slot for now */
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         /* tailport and headport can be shown as part of the name, but they
00145          * are not identifying properties of the edge so we 
00146          * also list them as modifyable attributes. */
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         /* key is strictly an identifying property to distinguish multiple
00153          * edges between the same node pair.   Its non-writable, so
00154          * no need to list it as an attribute as well. */
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 /* recursively find innermost cluster containing the point */
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     /* edges might overlap nodes, so search them first */
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     /* search graph backwards to get topmost node, in case of overlap */
00215     for (n = aglstnode(g); n; n = agprvnode(g, n))
00216         if (overlap_node(n, b))
00217             return (void *)n;
00218     /* search for innermost cluster */
00219     sg = gvevent_find_cluster(g, b);
00220     if (sg)
00221         return (void *)sg;
00222 
00223     /* otherwise - we're always in the graph */
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     /* transform position in device units to position in graph units */
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 /* CLOSEENOUGH is in 1/72 - probably should be a feature... */
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     /* convert window point to graph coordinates */
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: /* select / create in edit mode */
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: /* pan */
00398         job->click = 1;
00399         job->button = button;
00400         job->needs_refresh = 1;
00401         break;
00402     case 3: /* insert node or edge */
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         /* scrollwheel zoom in at current mouse x,y */
00410 /* FIXME - should code window 0,0 point as feature with Y_GOES_DOWN */
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: /* scrollwheel zoom out at current mouse x,y */
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     /* dx,dy change in position, in device independent points */
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)  /* ignore motion events with no motion */
00461         return;
00462 
00463     switch (job->button) {
00464     case 0: /* drag with no button - */
00465         gvevent_find_current_obj(job, pointer);
00466         break;
00467     case 1: /* drag with button 1 - drag object */
00468         /* FIXME - to be implemented */
00469         break;
00470     case 2: /* drag with button 2 - pan graph */
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: /* drag with button 3 - drag inserted node or uncompleted edge */
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 /*FIXME - should allow for margins */
00543 /*      - similar zoom_to_fit code exists in: */
00544 /*      plugin/gtk/callbacks.c */
00545 /*      plugin/xlib/gvdevice_xlib.c */
00546 /*      lib/gvc/gvevent.c */
00547 
00548     job->fit_mode = !job->fit_mode;
00549     if (job->fit_mode) {
00550         /* FIXME - this code looks wrong */
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     /* FIXME */
00567 }
00568 
00569 static void gvevent_delete (GVJ_t * job)
00570 {
00571     /* FIXME */
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;   /* FIXME - need some error handling */
00590         g = agread(f);
00591         fclose(f);
00592     }
00593     if (!g)
00594         return;   /* FIXME - need some error handling */
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 };

Generated on Mon Mar 31 19:03:26 2008 for Graphviz by  doxygen 1.5.1