Graphviz  2.31.20130522.0446
lib/gvc/gvevent.c
Go to the documentation of this file.
00001 /* $Id$ $Revision$ */
00002 /* vim:set shiftwidth=4 ts=8: */
00003 
00004 /*************************************************************************
00005  * Copyright (c) 2011 AT&T Intellectual Property 
00006  * All rights reserved. This program and the accompanying materials
00007  * are made available under the terms of the Eclipse Public License v1.0
00008  * which accompanies this distribution, and is available at
00009  * http://www.eclipse.org/legal/epl-v10.html
00010  *
00011  * Contributors: See CVS logs. Details at http://www.graphviz.org/
00012  *************************************************************************/
00013 
00014 #ifdef HAVE_CONFIG_H
00015 #include "config.h"
00016 #endif
00017 
00018 #include <string.h>
00019 #include <stdlib.h>
00020 #include <math.h>
00021 
00022 #include "gvplugin_layout.h"
00023 #include "gvcint.h"
00024 #include "gvcproc.h"
00025 
00026 extern char *strdup_and_subst_obj(char *str, void * n);
00027 extern void emit_graph(GVJ_t * job, graph_t * g);
00028 extern boolean overlap_edge(edge_t *e, boxf b);
00029 extern boolean overlap_node(node_t *n, boxf b);
00030 extern int gvLayout(GVC_t *gvc, graph_t *g, const char *engine);
00031 extern int gvRenderFilename(GVC_t *gvc, graph_t *g, const char *format, const char *filename);
00032 extern void graph_cleanup(graph_t *g);
00033 
00034 #define PANFACTOR 10
00035 #define ZOOMFACTOR 1.1
00036 #define EPSILON .0001
00037 
00038 static char *s_digraph = "digraph";
00039 static char *s_graph = "graph";
00040 static char *s_subgraph = "subgraph";
00041 static char *s_node = "node";
00042 static char *s_edge = "edge";
00043 static char *s_tooltip = "tooltip";
00044 static char *s_href = "href";
00045 static char *s_URL = "URL";
00046 static char *s_tailport = "tailport";
00047 static char *s_headport = "headport";
00048 static char *s_key = "key";
00049 
00050 static void gv_graph_state(GVJ_t *job, graph_t *g)
00051 {
00052 #ifndef WITH_CGRAPH
00053     int i;
00054 #endif
00055     int j;
00056     Agsym_t *a;
00057     gv_argvlist_t *list;
00058 
00059     list = &(job->selected_obj_type_name);
00060     j = 0;
00061     if (g == agroot(g)) {
00062         if (agisdirected(g))
00063             gv_argvlist_set_item(list, j++, s_digraph);
00064         else
00065             gv_argvlist_set_item(list, j++, s_graph);
00066     }
00067     else {
00068         gv_argvlist_set_item(list, j++, s_subgraph);
00069     }
00070     gv_argvlist_set_item(list, j++, agnameof(g));
00071     list->argc = j;
00072 
00073     list = &(job->selected_obj_attributes);
00074 #ifndef WITH_CGRAPH
00075     for (i = 0, j = 0; i < dtsize(g->univ->globattr->dict); i++) {
00076         a = g->univ->globattr->list[i];
00077 #else
00078     a = NULL;
00079     while ((a = agnxtattr(g, AGRAPH, a))) {
00080 #endif
00081         gv_argvlist_set_item(list, j++, a->name);
00082 #ifndef WITH_CGRAPH
00083         gv_argvlist_set_item(list, j++, agxget(g, a->index));
00084 #else
00085         gv_argvlist_set_item(list, j++, agxget(g, a));
00086 #endif
00087         gv_argvlist_set_item(list, j++, (char*)GVATTR_STRING);
00088     }
00089     list->argc = j;
00090 
00091     a = agfindgraphattr(g, s_href);
00092     if (!a)
00093         a = agfindgraphattr(g, s_URL);
00094     if (a)
00095 #ifndef WITH_CGRAPH
00096         job->selected_href = strdup_and_subst_obj(agxget(g, a->index), (void*)g);
00097 #else
00098         job->selected_href = strdup_and_subst_obj(agxget(g, a), (void*)g);
00099 #endif
00100 }
00101 
00102 static void gv_node_state(GVJ_t *job, node_t *n)
00103 {
00104 #ifndef WITH_CGRAPH
00105     int i;
00106 #endif
00107     int j;
00108     Agsym_t *a;
00109     Agraph_t *g;
00110     gv_argvlist_t *list;
00111 
00112     list = &(job->selected_obj_type_name);
00113     j = 0;
00114     gv_argvlist_set_item(list, j++, s_node);
00115     gv_argvlist_set_item(list, j++, agnameof(n));
00116     list->argc = j;
00117 
00118     list = &(job->selected_obj_attributes);
00119     g = agroot(agraphof(n));
00120 #ifndef WITH_CGRAPH
00121     for (i = 0, j = 0; i < dtsize(g->univ->nodeattr->dict); i++) {
00122         a = g->univ->nodeattr->list[i];
00123 #else
00124     a = NULL;
00125     while ((a = agnxtattr(g, AGNODE, a))) {
00126 #endif
00127         gv_argvlist_set_item(list, j++, a->name);
00128 #ifndef WITH_CGRAPH
00129         gv_argvlist_set_item(list, j++, agxget(n, a->index));
00130 #else
00131         gv_argvlist_set_item(list, j++, agxget(n, a));
00132 #endif
00133     }
00134     list->argc = j;
00135 
00136     a = agfindnodeattr(agraphof(n), s_href);
00137     if (!a)
00138         a = agfindnodeattr(agraphof(n), s_URL);
00139     if (a)
00140 #ifndef WITH_CGRAPH
00141         job->selected_href = strdup_and_subst_obj(agxget(n, a->index), (void*)n);
00142 #else
00143         job->selected_href = strdup_and_subst_obj(agxget(n, a), (void*)n);
00144 #endif
00145 }
00146 
00147 static void gv_edge_state(GVJ_t *job, edge_t *e)
00148 {
00149 #ifndef WITH_CGRAPH
00150     int i;
00151 #endif
00152     int j;
00153     Agsym_t *a;
00154     Agraph_t *g;
00155     gv_argvlist_t *nlist, *alist;
00156 
00157     nlist = &(job->selected_obj_type_name);
00158 
00159     /* only tail, head, and key are strictly identifying properties,
00160      * but we commonly alse use edge kind (e.g. "->") and tailport,headport
00161      * in edge names */
00162     j = 0;
00163     gv_argvlist_set_item(nlist, j++, s_edge);
00164     gv_argvlist_set_item(nlist, j++, agnameof(agtail(e)));
00165     j++; /* skip tailport slot for now */
00166     gv_argvlist_set_item(nlist, j++, agisdirected(agraphof(agtail(e)))?"->":"--");
00167     gv_argvlist_set_item(nlist, j++, agnameof(aghead(e)));
00168     j++; /* skip headport slot for now */
00169     j++; /* skip key slot for now */
00170     nlist->argc = j;
00171 
00172     alist = &(job->selected_obj_attributes);
00173     g = agroot(agraphof(aghead(e)));
00174 #ifndef WITH_CGRAPH
00175     for (i = 0, j = 0; i < dtsize(g->univ->edgeattr->dict); i++) {
00176         a = g->univ->edgeattr->list[i];
00177 #else
00178     a = NULL;
00179     while ((a = agnxtattr(g, AGEDGE, a))) {
00180 #endif
00181 
00182         /* tailport and headport can be shown as part of the name, but they
00183          * are not identifying properties of the edge so we 
00184          * also list them as modifyable attributes. */
00185         if (strcmp(a->name,s_tailport) == 0)
00186 #ifndef WITH_CGRAPH
00187             gv_argvlist_set_item(nlist, 2, agxget(e, a->index));
00188 #else
00189             gv_argvlist_set_item(nlist, 2, agxget(e, a));
00190 #endif
00191         else if (strcmp(a->name,s_headport) == 0)
00192 #ifndef WITH_CGRAPH
00193             gv_argvlist_set_item(nlist, 5, agxget(e, a->index));
00194 #else
00195             gv_argvlist_set_item(nlist, 5, agxget(e, a));
00196 #endif
00197 
00198         /* key is strictly an identifying property to distinguish multiple
00199          * edges between the same node pair.   Its non-writable, so
00200          * no need to list it as an attribute as well. */
00201         else if (strcmp(a->name,s_key) == 0) {
00202 #ifndef WITH_CGRAPH
00203             gv_argvlist_set_item(nlist, 6, agxget(e, a->index));
00204 #else
00205             gv_argvlist_set_item(nlist, 6, agxget(e, a));
00206 #endif
00207             continue;
00208         }
00209 
00210         gv_argvlist_set_item(alist, j++, a->name);
00211 #ifndef WITH_CGRAPH
00212         gv_argvlist_set_item(alist, j++, agxget(e, a->index));
00213 #else
00214         gv_argvlist_set_item(alist, j++, agxget(e, a));
00215 #endif
00216     }
00217     alist->argc = j;
00218 
00219     a = agfindedgeattr(agraphof(aghead(e)), s_href);
00220     if (!a)
00221         a = agfindedgeattr(agraphof(aghead(e)), s_URL);
00222     if (a)
00223 #ifndef WITH_CGRAPH
00224         job->selected_href = strdup_and_subst_obj(agxget(e, a->index), (void*)e);
00225 #else
00226         job->selected_href = strdup_and_subst_obj(agxget(e, a), (void*)e);
00227 #endif
00228 }
00229 
00230 static void gvevent_refresh(GVJ_t * job)
00231 {
00232     graph_t *g = job->gvc->g;
00233 
00234     if (!job->selected_obj) {
00235         job->selected_obj = g;
00236         GD_gui_state(g) |= GUI_STATE_SELECTED;
00237         gv_graph_state(job, g);
00238     }
00239     emit_graph(job, g);
00240     job->has_been_rendered = TRUE;
00241 }
00242 
00243 /* recursively find innermost cluster containing the point */
00244 static graph_t *gvevent_find_cluster(graph_t *g, boxf b)
00245 {
00246     int i;
00247     graph_t *sg;
00248     boxf bb;
00249 
00250     for (i = 1; i <= GD_n_cluster(g); i++) {
00251         sg = gvevent_find_cluster(GD_clust(g)[i], b);
00252         if (sg)
00253             return(sg);
00254     }
00255     B2BF(GD_bb(g), bb);
00256     if (OVERLAP(b, bb))
00257         return g;
00258     return NULL;
00259 }
00260 
00261 static void * gvevent_find_obj(graph_t *g, boxf b)
00262 {
00263     graph_t *sg;
00264     node_t *n;
00265     edge_t *e;
00266 
00267     /* edges might overlap nodes, so search them first */
00268     for (n = agfstnode(g); n; n = agnxtnode(g, n))
00269         for (e = agfstout(g, n); e; e = agnxtout(g, e))
00270             if (overlap_edge(e, b))
00271                 return (void *)e;
00272     /* search graph backwards to get topmost node, in case of overlap */
00273     for (n = aglstnode(g); n; n = agprvnode(g, n))
00274         if (overlap_node(n, b))
00275             return (void *)n;
00276     /* search for innermost cluster */
00277     sg = gvevent_find_cluster(g, b);
00278     if (sg)
00279         return (void *)sg;
00280 
00281     /* otherwise - we're always in the graph */
00282     return (void *)g;
00283 }
00284 
00285 static void gvevent_leave_obj(GVJ_t * job)
00286 {
00287     void *obj = job->current_obj;
00288 
00289     if (obj) {
00290         switch (agobjkind(obj)) {
00291 #ifndef WITH_CGRAPH
00292         case AGGRAPH:
00293 #else /* WITH_CGRAPH */
00294         case AGRAPH:
00295 #endif /* WITH_CGRAPH */
00296             GD_gui_state((graph_t*)obj) &= ~GUI_STATE_ACTIVE;
00297             break;
00298         case AGNODE:
00299             ND_gui_state((node_t*)obj) &= ~GUI_STATE_ACTIVE;
00300             break;
00301         case AGEDGE:
00302             ED_gui_state((edge_t*)obj) &= ~GUI_STATE_ACTIVE;
00303             break;
00304         }
00305     }
00306     job->active_tooltip = NULL;
00307 }
00308 
00309 static void gvevent_enter_obj(GVJ_t * job)
00310 {
00311     void *obj;
00312     graph_t *g;
00313     edge_t *e;
00314     node_t *n;
00315     Agsym_t *a;
00316 
00317     if (job->active_tooltip) {
00318         free(job->active_tooltip);
00319         job->active_tooltip = NULL;
00320     }
00321     obj = job->current_obj;
00322     if (obj) {
00323         switch (agobjkind(obj)) {
00324 #ifndef WITH_CGRAPH
00325         case AGGRAPH:
00326 #else /* WITH_CGRAPH */
00327         case AGRAPH:
00328 #endif /* WITH_CGRAPH */
00329             g = (graph_t*)obj;
00330             GD_gui_state(g) |= GUI_STATE_ACTIVE;
00331             a = agfindgraphattr(g, s_tooltip);
00332             if (a)
00333 #ifndef WITH_CGRAPH
00334                 job->active_tooltip = strdup_and_subst_obj(agxget(g, a->index), obj);
00335 #else /* WITH_CGRAPH */
00336                 job->active_tooltip = strdup_and_subst_obj(agxget(g, a), obj);
00337 #endif /* WITH_CGRAPH */
00338             break;
00339         case AGNODE:
00340             n = (node_t*)obj;
00341             ND_gui_state(n) |= GUI_STATE_ACTIVE;
00342             a = agfindnodeattr(agraphof(n), s_tooltip);
00343             if (a)
00344 #ifndef WITH_CGRAPH
00345                 job->active_tooltip = strdup_and_subst_obj(agxget(n, a->index), obj);
00346 #else /* WITH_CGRAPH */
00347                 job->active_tooltip = strdup_and_subst_obj(agxget(n, a), obj);
00348 #endif /* WITH_CGRAPH */
00349             break;
00350         case AGEDGE:
00351             e = (edge_t*)obj;
00352             ED_gui_state(e) |= GUI_STATE_ACTIVE;
00353             a = agfindedgeattr(agraphof(aghead(e)), s_tooltip);
00354             if (a)
00355 #ifndef WITH_CGRAPH
00356                 job->active_tooltip = strdup_and_subst_obj(agxget(e, a->index), obj);
00357 #else /* WITH_CGRAPH */
00358                 job->active_tooltip = strdup_and_subst_obj(agxget(e, a), obj);
00359 #endif /* WITH_CGRAPH */
00360             break;
00361         }
00362     }
00363 }
00364 
00365 static pointf pointer2graph (GVJ_t *job, pointf pointer)
00366 {
00367     pointf p;
00368 
00369     /* transform position in device units to position in graph units */
00370     if (job->rotation) {
00371         p.x = pointer.y / (job->zoom * job->devscale.y) - job->translation.x;
00372         p.y = -pointer.x / (job->zoom * job->devscale.x) - job->translation.y;
00373     }
00374     else {
00375         p.x = pointer.x / (job->zoom * job->devscale.x) - job->translation.x;
00376         p.y = pointer.y / (job->zoom * job->devscale.y) - job->translation.y;
00377     }
00378     return p;
00379 }
00380 
00381 /* CLOSEENOUGH is in 1/72 - probably should be a feature... */
00382 #define CLOSEENOUGH 1
00383 
00384 static void gvevent_find_current_obj(GVJ_t * job, pointf pointer)
00385 {
00386     void *obj;
00387     boxf b;
00388     double closeenough;
00389     pointf p;
00390 
00391     p =  pointer2graph (job, pointer);
00392 
00393     /* convert window point to graph coordinates */
00394     closeenough = CLOSEENOUGH / job->zoom;
00395 
00396     b.UR.x = p.x + closeenough;
00397     b.UR.y = p.y + closeenough;
00398     b.LL.x = p.x - closeenough;
00399     b.LL.y = p.y - closeenough;
00400 
00401     obj = gvevent_find_obj(job->gvc->g, b);
00402     if (obj != job->current_obj) {
00403         gvevent_leave_obj(job);
00404         job->current_obj = obj;
00405         gvevent_enter_obj(job);
00406         job->needs_refresh = 1;
00407     }
00408 }
00409 
00410 static void gvevent_select_current_obj(GVJ_t * job)
00411 {
00412     void *obj;
00413 
00414     obj = job->selected_obj;
00415     if (obj) {
00416         switch (agobjkind(obj)) {
00417 #ifndef WITH_CGRAPH
00418         case AGGRAPH:
00419 #else /* WITH_CGRAPH */
00420         case AGRAPH:
00421 #endif /* WITH_CGRAPH */
00422             GD_gui_state((graph_t*)obj) |= GUI_STATE_VISITED;
00423             GD_gui_state((graph_t*)obj) &= ~GUI_STATE_SELECTED;
00424             break;
00425         case AGNODE:
00426             ND_gui_state((node_t*)obj) |= GUI_STATE_VISITED;
00427             ND_gui_state((node_t*)obj) &= ~GUI_STATE_SELECTED;
00428             break;
00429         case AGEDGE:
00430             ED_gui_state((edge_t*)obj) |= GUI_STATE_VISITED;
00431             ED_gui_state((edge_t*)obj) &= ~GUI_STATE_SELECTED;
00432             break;
00433         }
00434     }
00435 
00436     if (job->selected_href) {
00437         free(job->selected_href);
00438         job->selected_href = NULL;
00439     }
00440 
00441     obj = job->selected_obj = job->current_obj;
00442     if (obj) {
00443         switch (agobjkind(obj)) {
00444 #ifndef WITH_CGRAPH
00445         case AGGRAPH:
00446 #else /* WITH_CGRAPH */
00447         case AGRAPH:
00448 #endif /* WITH_CGRAPH */
00449             GD_gui_state((graph_t*)obj) |= GUI_STATE_SELECTED;
00450             gv_graph_state(job, (graph_t*)obj);
00451             break;
00452         case AGNODE:
00453             ND_gui_state((node_t*)obj) |= GUI_STATE_SELECTED;
00454             gv_node_state(job, (node_t*)obj);
00455             break;
00456         case AGEDGE:
00457             ED_gui_state((edge_t*)obj) |= GUI_STATE_SELECTED;
00458             gv_edge_state(job, (edge_t*)obj);
00459             break;
00460         }
00461     }
00462 
00463 #if 0
00464 for (i = 0; i < job->selected_obj_type_name.argc; i++)
00465     fprintf(stderr,"%s%s", job->selected_obj_type_name.argv[i],
00466         (i==(job->selected_obj_type_name.argc - 1))?"\n":" ");
00467 for (i = 0; i < job->selected_obj_attributes.argc; i++)
00468     fprintf(stderr,"%s%s", job->selected_obj_attributes.argv[i], (i%2)?"\n":" = ");
00469 fprintf(stderr,"\n");
00470 #endif
00471 }
00472 
00473 static void gvevent_button_press(GVJ_t * job, int button, pointf pointer)
00474 {
00475     switch (button) {
00476     case 1: /* select / create in edit mode */
00477         gvevent_find_current_obj(job, pointer);
00478         gvevent_select_current_obj(job);
00479         job->click = 1;
00480         job->button = button;
00481         job->needs_refresh = 1;
00482         break;
00483     case 2: /* pan */
00484         job->click = 1;
00485         job->button = button;
00486         job->needs_refresh = 1;
00487         break;
00488     case 3: /* insert node or edge */
00489         gvevent_find_current_obj(job, pointer);
00490         job->click = 1;
00491         job->button = button;
00492         job->needs_refresh = 1;
00493         break;
00494     case 4:
00495         /* scrollwheel zoom in at current mouse x,y */
00496 /* FIXME - should code window 0,0 point as feature with Y_GOES_DOWN */
00497         job->fit_mode = 0;
00498         if (job->rotation) {
00499             job->focus.x -= (pointer.y - job->height / 2.)
00500                     * (ZOOMFACTOR - 1.) / (job->zoom * job->devscale.y);
00501             job->focus.y += (pointer.x - job->width / 2.)
00502                     * (ZOOMFACTOR - 1.) / (job->zoom * job->devscale.x);
00503         }
00504         else {
00505             job->focus.x += (pointer.x - job->width / 2.)
00506                     * (ZOOMFACTOR - 1.) / (job->zoom * job->devscale.x);
00507             job->focus.y += (pointer.y - job->height / 2.)
00508                     * (ZOOMFACTOR - 1.) / (job->zoom * job->devscale.y);
00509         }
00510         job->zoom *= ZOOMFACTOR;
00511         job->needs_refresh = 1;
00512         break;
00513     case 5: /* scrollwheel zoom out at current mouse x,y */
00514         job->fit_mode = 0;
00515         job->zoom /= ZOOMFACTOR;
00516         if (job->rotation) {
00517             job->focus.x += (pointer.y - job->height / 2.)
00518                     * (ZOOMFACTOR - 1.) / (job->zoom * job->devscale.y);
00519             job->focus.y -= (pointer.x - job->width / 2.)
00520                     * (ZOOMFACTOR - 1.) / (job->zoom * job->devscale.x);
00521         }
00522         else {
00523             job->focus.x -= (pointer.x - job->width / 2.)
00524                     * (ZOOMFACTOR - 1.) / (job->zoom * job->devscale.x);
00525             job->focus.y -= (pointer.y - job->height / 2.)
00526                     * (ZOOMFACTOR - 1.) / (job->zoom * job->devscale.y);
00527         }
00528         job->needs_refresh = 1;
00529         break;
00530     }
00531     job->oldpointer = pointer;
00532 }
00533 
00534 static void gvevent_button_release(GVJ_t *job, int button, pointf pointer)
00535 {
00536     job->click = 0;
00537     job->button = 0;
00538 }
00539 
00540 static void gvevent_motion(GVJ_t * job, pointf pointer)
00541 {
00542     /* dx,dy change in position, in device independent points */
00543     double dx = (pointer.x - job->oldpointer.x) / job->devscale.x;
00544     double dy = (pointer.y - job->oldpointer.y) / job->devscale.y;
00545 
00546     if (abs(dx) < EPSILON && abs(dy) < EPSILON)  /* ignore motion events with no motion */
00547         return;
00548 
00549     switch (job->button) {
00550     case 0: /* drag with no button - */
00551         gvevent_find_current_obj(job, pointer);
00552         break;
00553     case 1: /* drag with button 1 - drag object */
00554         /* FIXME - to be implemented */
00555         break;
00556     case 2: /* drag with button 2 - pan graph */
00557         if (job->rotation) {
00558             job->focus.x -= dy / job->zoom;
00559             job->focus.y += dx / job->zoom;
00560         }
00561         else {
00562             job->focus.x -= dx / job->zoom;
00563             job->focus.y -= dy / job->zoom;
00564         }
00565         job->needs_refresh = 1;
00566         break;
00567     case 3: /* drag with button 3 - drag inserted node or uncompleted edge */
00568         break;
00569     }
00570     job->oldpointer = pointer;
00571 }
00572 
00573 static int quit_cb(GVJ_t * job)
00574 {
00575     return 1;
00576 }
00577 
00578 static int left_cb(GVJ_t * job)
00579 {
00580     job->fit_mode = 0;
00581     job->focus.x += PANFACTOR / job->zoom;
00582     job->needs_refresh = 1;
00583     return 0;
00584 }
00585 
00586 static int right_cb(GVJ_t * job)
00587 {
00588     job->fit_mode = 0;
00589     job->focus.x -= PANFACTOR / job->zoom;
00590     job->needs_refresh = 1;
00591     return 0;
00592 }
00593 
00594 static int up_cb(GVJ_t * job)
00595 {
00596     job->fit_mode = 0;
00597     job->focus.y += -(PANFACTOR / job->zoom);
00598     job->needs_refresh = 1;
00599     return 0;
00600 }
00601 
00602 static int down_cb(GVJ_t * job)
00603 {
00604     job->fit_mode = 0;
00605     job->focus.y -= -(PANFACTOR / job->zoom);
00606     job->needs_refresh = 1;
00607     return 0;
00608 }
00609 
00610 static int zoom_in_cb(GVJ_t * job)
00611 {
00612     job->fit_mode = 0;
00613     job->zoom *= ZOOMFACTOR;
00614     job->needs_refresh = 1;
00615     return 0;
00616 }
00617 
00618 static int zoom_out_cb(GVJ_t * job)
00619 {
00620     job->fit_mode = 0;
00621     job->zoom /= ZOOMFACTOR;
00622     job->needs_refresh = 1;
00623     return 0;
00624 }
00625 
00626 static int toggle_fit_cb(GVJ_t * job)
00627 {
00628 /*FIXME - should allow for margins */
00629 /*      - similar zoom_to_fit code exists in: */
00630 /*      plugin/gtk/callbacks.c */
00631 /*      plugin/xlib/gvdevice_xlib.c */
00632 /*      lib/gvc/gvevent.c */
00633 
00634     job->fit_mode = !job->fit_mode;
00635     if (job->fit_mode) {
00636         /* FIXME - this code looks wrong */
00637         int dflt_width, dflt_height;
00638         dflt_width = job->width;
00639         dflt_height = job->height;
00640         job->zoom =
00641             MIN((double) job->width / (double) dflt_width,
00642                 (double) job->height / (double) dflt_height);
00643         job->focus.x = 0.0;
00644         job->focus.y = 0.0;
00645         job->needs_refresh = 1;
00646     }
00647     return 0;
00648 }
00649 
00650 static void gvevent_modify (GVJ_t * job, const char *name, const char *value)
00651 {
00652     /* FIXME */
00653 }
00654 
00655 static void gvevent_delete (GVJ_t * job)
00656 {
00657     /* FIXME */
00658 }
00659 
00660 static void gvevent_read (GVJ_t * job, const char *filename, const char *layout)
00661 {
00662     FILE *f;
00663     GVC_t *gvc;
00664     Agraph_t *g = NULL;
00665     gvlayout_engine_t *gvle;
00666 
00667     gvc = job->gvc;
00668     if (!filename) {
00669 #ifndef WITH_CGRAPH
00670         g = agopen("G", AGDIGRAPH);
00671 #else /* WITH_CGRAPH */
00672         g = agopen("G", Agdirected, NIL(Agdisc_t *));
00673 #endif /* WITH_CGRAPH */
00674         job->output_filename = "new.gv";
00675     }
00676     else {
00677         f = fopen(filename, "r");
00678         if (!f)
00679            return;   /* FIXME - need some error handling */
00680 #ifndef WITH_CGRAPH
00681         g = agread(f);
00682 #else /* WITH_CGRAPH */
00683         g = agread(f,NIL(Agdisc_t *));
00684 #endif /* WITH_CGRAPH */
00685         fclose(f);
00686     }
00687     if (!g)
00688         return;   /* FIXME - need some error handling */
00689     if (gvc->g) {
00690         gvle = gvc->layout.engine;
00691         if (gvle && gvle->cleanup)
00692             gvle->cleanup(gvc->g);
00693         graph_cleanup(gvc->g);
00694         agclose(gvc->g);
00695     }
00696     gvc->g = g;
00697     GD_gvc(g) = gvc;
00698     gvLayout(gvc, g, layout);
00699     job->selected_obj = NULL;
00700     job->current_obj = NULL;
00701     job->needs_refresh = 1;
00702 }
00703 
00704 static void gvevent_layout (GVJ_t * job, const char *layout)
00705 {
00706     gvLayout(job->gvc, job->gvc->g, layout);
00707 }
00708 
00709 static void gvevent_render (GVJ_t * job, const char *format, const char *filename)
00710 {
00711     gvRenderFilename(job->gvc, job->gvc->g, format, filename);
00712 }
00713 
00714 
00715 gvevent_key_binding_t gvevent_key_binding[] = {
00716     {"Q", quit_cb},
00717     {"Left", left_cb},
00718     {"KP_Left", left_cb},
00719     {"Right", right_cb},
00720     {"KP_Right", right_cb},
00721     {"Up", up_cb},
00722     {"KP_Up", up_cb},
00723     {"Down", down_cb},
00724     {"KP_Down", down_cb},
00725     {"plus", zoom_in_cb},
00726     {"KP_Add", zoom_in_cb},
00727     {"minus", zoom_out_cb},
00728     {"KP_Subtract", zoom_out_cb},
00729     {"F", toggle_fit_cb},
00730 };
00731 
00732 int gvevent_key_binding_size = ARRAY_SIZE(gvevent_key_binding);
00733 
00734 gvdevice_callbacks_t gvdevice_callbacks = {
00735     gvevent_refresh,
00736     gvevent_button_press,
00737     gvevent_button_release,
00738     gvevent_motion,
00739     gvevent_modify,
00740     gvevent_delete,
00741     gvevent_read,
00742     gvevent_layout,
00743     gvevent_render,
00744 };