1 | /* $Id$ $Revision$ */ |
2 | /* vim:set shiftwidth=4 ts=8: */ |
3 | |
4 | /************************************************************************* |
5 | * Copyright (c) 2011 AT&T Intellectual Property |
6 | * All rights reserved. This program and the accompanying materials |
7 | * are made available under the terms of the Eclipse Public License v1.0 |
8 | * which accompanies this distribution, and is available at |
9 | * http://www.eclipse.org/legal/epl-v10.html |
10 | * |
11 | * Contributors: See CVS logs. Details at http://www.graphviz.org/ |
12 | *************************************************************************/ |
13 | |
14 | #include "config.h" |
15 | |
16 | #include <string.h> |
17 | #include <stdlib.h> |
18 | #include <math.h> |
19 | |
20 | #include "gvplugin_layout.h" |
21 | #include "gvcint.h" |
22 | #include "gvcproc.h" |
23 | |
24 | extern char *strdup_and_subst_obj(char *str, void * n); |
25 | extern void emit_graph(GVJ_t * job, graph_t * g); |
26 | extern boolean overlap_edge(edge_t *e, boxf b); |
27 | extern boolean overlap_node(node_t *n, boxf b); |
28 | extern int gvLayout(GVC_t *gvc, graph_t *g, const char *engine); |
29 | extern int gvRenderFilename(GVC_t *gvc, graph_t *g, const char *format, const char *filename); |
30 | extern void graph_cleanup(graph_t *g); |
31 | |
32 | #define PANFACTOR 10 |
33 | #define ZOOMFACTOR 1.1 |
34 | #define EPSILON .0001 |
35 | |
36 | static char *s_digraph = "digraph" ; |
37 | static char *s_graph = "graph" ; |
38 | static char *s_subgraph = "subgraph" ; |
39 | static char *s_node = "node" ; |
40 | static char *s_edge = "edge" ; |
41 | static char *s_tooltip = "tooltip" ; |
42 | static char *s_href = "href" ; |
43 | static char *s_URL = "URL" ; |
44 | static char *s_tailport = "tailport" ; |
45 | static char *s_headport = "headport" ; |
46 | static char *s_key = "key" ; |
47 | |
48 | static void gv_graph_state(GVJ_t *job, graph_t *g) |
49 | { |
50 | int j; |
51 | Agsym_t *a; |
52 | gv_argvlist_t *list; |
53 | |
54 | list = &(job->selected_obj_type_name); |
55 | j = 0; |
56 | if (g == agroot(g)) { |
57 | if (agisdirected(g)) |
58 | gv_argvlist_set_item(list, j++, s_digraph); |
59 | else |
60 | gv_argvlist_set_item(list, j++, s_graph); |
61 | } |
62 | else { |
63 | gv_argvlist_set_item(list, j++, s_subgraph); |
64 | } |
65 | gv_argvlist_set_item(list, j++, agnameof(g)); |
66 | list->argc = j; |
67 | |
68 | list = &(job->selected_obj_attributes); |
69 | a = NULL; |
70 | while ((a = agnxtattr(g, AGRAPH, a))) { |
71 | gv_argvlist_set_item(list, j++, a->name); |
72 | gv_argvlist_set_item(list, j++, agxget(g, a)); |
73 | gv_argvlist_set_item(list, j++, (char*)GVATTR_STRING); |
74 | } |
75 | list->argc = j; |
76 | |
77 | a = agfindgraphattr(g, s_href); |
78 | if (!a) |
79 | a = agfindgraphattr(g, s_URL); |
80 | if (a) |
81 | job->selected_href = strdup_and_subst_obj(agxget(g, a), (void*)g); |
82 | } |
83 | |
84 | static void gv_node_state(GVJ_t *job, node_t *n) |
85 | { |
86 | int j; |
87 | Agsym_t *a; |
88 | Agraph_t *g; |
89 | gv_argvlist_t *list; |
90 | |
91 | list = &(job->selected_obj_type_name); |
92 | j = 0; |
93 | gv_argvlist_set_item(list, j++, s_node); |
94 | gv_argvlist_set_item(list, j++, agnameof(n)); |
95 | list->argc = j; |
96 | |
97 | list = &(job->selected_obj_attributes); |
98 | g = agroot(agraphof(n)); |
99 | a = NULL; |
100 | while ((a = agnxtattr(g, AGNODE, a))) { |
101 | gv_argvlist_set_item(list, j++, a->name); |
102 | gv_argvlist_set_item(list, j++, agxget(n, a)); |
103 | } |
104 | list->argc = j; |
105 | |
106 | a = agfindnodeattr(agraphof(n), s_href); |
107 | if (!a) |
108 | a = agfindnodeattr(agraphof(n), s_URL); |
109 | if (a) |
110 | job->selected_href = strdup_and_subst_obj(agxget(n, a), (void*)n); |
111 | } |
112 | |
113 | static void gv_edge_state(GVJ_t *job, edge_t *e) |
114 | { |
115 | int j; |
116 | Agsym_t *a; |
117 | Agraph_t *g; |
118 | gv_argvlist_t *nlist, *alist; |
119 | |
120 | nlist = &(job->selected_obj_type_name); |
121 | |
122 | /* only tail, head, and key are strictly identifying properties, |
123 | * but we commonly also use edge kind (e.g. "->") and tailport,headport |
124 | * in edge names */ |
125 | j = 0; |
126 | gv_argvlist_set_item(nlist, j++, s_edge); |
127 | gv_argvlist_set_item(nlist, j++, agnameof(agtail(e))); |
128 | j++; /* skip tailport slot for now */ |
129 | gv_argvlist_set_item(nlist, j++, agisdirected(agraphof(agtail(e)))?"->" :"--" ); |
130 | gv_argvlist_set_item(nlist, j++, agnameof(aghead(e))); |
131 | j++; /* skip headport slot for now */ |
132 | j++; /* skip key slot for now */ |
133 | nlist->argc = j; |
134 | |
135 | alist = &(job->selected_obj_attributes); |
136 | g = agroot(agraphof(aghead(e))); |
137 | a = NULL; |
138 | while ((a = agnxtattr(g, AGEDGE, a))) { |
139 | |
140 | /* tailport and headport can be shown as part of the name, but they |
141 | * are not identifying properties of the edge so we |
142 | * also list them as modifyable attributes. */ |
143 | if (strcmp(a->name,s_tailport) == 0) |
144 | gv_argvlist_set_item(nlist, 2, agxget(e, a)); |
145 | else if (strcmp(a->name,s_headport) == 0) |
146 | gv_argvlist_set_item(nlist, 5, agxget(e, a)); |
147 | |
148 | /* key is strictly an identifying property to distinguish multiple |
149 | * edges between the same node pair. Its non-writable, so |
150 | * no need to list it as an attribute as well. */ |
151 | else if (strcmp(a->name,s_key) == 0) { |
152 | gv_argvlist_set_item(nlist, 6, agxget(e, a)); |
153 | continue; |
154 | } |
155 | |
156 | gv_argvlist_set_item(alist, j++, a->name); |
157 | gv_argvlist_set_item(alist, j++, agxget(e, a)); |
158 | } |
159 | alist->argc = j; |
160 | |
161 | a = agfindedgeattr(agraphof(aghead(e)), s_href); |
162 | if (!a) |
163 | a = agfindedgeattr(agraphof(aghead(e)), s_URL); |
164 | if (a) |
165 | job->selected_href = strdup_and_subst_obj(agxget(e, a), (void*)e); |
166 | } |
167 | |
168 | static void gvevent_refresh(GVJ_t * job) |
169 | { |
170 | graph_t *g = job->gvc->g; |
171 | |
172 | if (!job->selected_obj) { |
173 | job->selected_obj = g; |
174 | GD_gui_state(g) |= GUI_STATE_SELECTED; |
175 | gv_graph_state(job, g); |
176 | } |
177 | emit_graph(job, g); |
178 | job->has_been_rendered = TRUE; |
179 | } |
180 | |
181 | /* recursively find innermost cluster containing the point */ |
182 | static graph_t *gvevent_find_cluster(graph_t *g, boxf b) |
183 | { |
184 | int i; |
185 | graph_t *sg; |
186 | boxf bb; |
187 | |
188 | for (i = 1; i <= GD_n_cluster(g); i++) { |
189 | sg = gvevent_find_cluster(GD_clust(g)[i], b); |
190 | if (sg) |
191 | return(sg); |
192 | } |
193 | B2BF(GD_bb(g), bb); |
194 | if (OVERLAP(b, bb)) |
195 | return g; |
196 | return NULL; |
197 | } |
198 | |
199 | static void * gvevent_find_obj(graph_t *g, boxf b) |
200 | { |
201 | graph_t *sg; |
202 | node_t *n; |
203 | edge_t *e; |
204 | |
205 | /* edges might overlap nodes, so search them first */ |
206 | for (n = agfstnode(g); n; n = agnxtnode(g, n)) |
207 | for (e = agfstout(g, n); e; e = agnxtout(g, e)) |
208 | if (overlap_edge(e, b)) |
209 | return (void *)e; |
210 | /* search graph backwards to get topmost node, in case of overlap */ |
211 | for (n = aglstnode(g); n; n = agprvnode(g, n)) |
212 | if (overlap_node(n, b)) |
213 | return (void *)n; |
214 | /* search for innermost cluster */ |
215 | sg = gvevent_find_cluster(g, b); |
216 | if (sg) |
217 | return (void *)sg; |
218 | |
219 | /* otherwise - we're always in the graph */ |
220 | return (void *)g; |
221 | } |
222 | |
223 | static void gvevent_leave_obj(GVJ_t * job) |
224 | { |
225 | void *obj = job->current_obj; |
226 | |
227 | if (obj) { |
228 | switch (agobjkind(obj)) { |
229 | case AGRAPH: |
230 | GD_gui_state((graph_t*)obj) &= ~GUI_STATE_ACTIVE; |
231 | break; |
232 | case AGNODE: |
233 | ND_gui_state((node_t*)obj) &= ~GUI_STATE_ACTIVE; |
234 | break; |
235 | case AGEDGE: |
236 | ED_gui_state((edge_t*)obj) &= ~GUI_STATE_ACTIVE; |
237 | break; |
238 | } |
239 | } |
240 | job->active_tooltip = NULL; |
241 | } |
242 | |
243 | static void gvevent_enter_obj(GVJ_t * job) |
244 | { |
245 | void *obj; |
246 | graph_t *g; |
247 | edge_t *e; |
248 | node_t *n; |
249 | Agsym_t *a; |
250 | |
251 | if (job->active_tooltip) { |
252 | free(job->active_tooltip); |
253 | job->active_tooltip = NULL; |
254 | } |
255 | obj = job->current_obj; |
256 | if (obj) { |
257 | switch (agobjkind(obj)) { |
258 | case AGRAPH: |
259 | g = (graph_t*)obj; |
260 | GD_gui_state(g) |= GUI_STATE_ACTIVE; |
261 | a = agfindgraphattr(g, s_tooltip); |
262 | if (a) |
263 | job->active_tooltip = strdup_and_subst_obj(agxget(g, a), obj); |
264 | break; |
265 | case AGNODE: |
266 | n = (node_t*)obj; |
267 | ND_gui_state(n) |= GUI_STATE_ACTIVE; |
268 | a = agfindnodeattr(agraphof(n), s_tooltip); |
269 | if (a) |
270 | job->active_tooltip = strdup_and_subst_obj(agxget(n, a), obj); |
271 | break; |
272 | case AGEDGE: |
273 | e = (edge_t*)obj; |
274 | ED_gui_state(e) |= GUI_STATE_ACTIVE; |
275 | a = agfindedgeattr(agraphof(aghead(e)), s_tooltip); |
276 | if (a) |
277 | job->active_tooltip = strdup_and_subst_obj(agxget(e, a), obj); |
278 | break; |
279 | } |
280 | } |
281 | } |
282 | |
283 | static pointf pointer2graph (GVJ_t *job, pointf pointer) |
284 | { |
285 | pointf p; |
286 | |
287 | /* transform position in device units to position in graph units */ |
288 | if (job->rotation) { |
289 | p.x = pointer.y / (job->zoom * job->devscale.y) - job->translation.x; |
290 | p.y = -pointer.x / (job->zoom * job->devscale.x) - job->translation.y; |
291 | } |
292 | else { |
293 | p.x = pointer.x / (job->zoom * job->devscale.x) - job->translation.x; |
294 | p.y = pointer.y / (job->zoom * job->devscale.y) - job->translation.y; |
295 | } |
296 | return p; |
297 | } |
298 | |
299 | /* CLOSEENOUGH is in 1/72 - probably should be a feature... */ |
300 | #define CLOSEENOUGH 1 |
301 | |
302 | static void gvevent_find_current_obj(GVJ_t * job, pointf pointer) |
303 | { |
304 | void *obj; |
305 | boxf b; |
306 | double closeenough; |
307 | pointf p; |
308 | |
309 | p = pointer2graph (job, pointer); |
310 | |
311 | /* convert window point to graph coordinates */ |
312 | closeenough = CLOSEENOUGH / job->zoom; |
313 | |
314 | b.UR.x = p.x + closeenough; |
315 | b.UR.y = p.y + closeenough; |
316 | b.LL.x = p.x - closeenough; |
317 | b.LL.y = p.y - closeenough; |
318 | |
319 | obj = gvevent_find_obj(job->gvc->g, b); |
320 | if (obj != job->current_obj) { |
321 | gvevent_leave_obj(job); |
322 | job->current_obj = obj; |
323 | gvevent_enter_obj(job); |
324 | job->needs_refresh = 1; |
325 | } |
326 | } |
327 | |
328 | static void gvevent_select_current_obj(GVJ_t * job) |
329 | { |
330 | void *obj; |
331 | |
332 | obj = job->selected_obj; |
333 | if (obj) { |
334 | switch (agobjkind(obj)) { |
335 | case AGRAPH: |
336 | GD_gui_state((graph_t*)obj) |= GUI_STATE_VISITED; |
337 | GD_gui_state((graph_t*)obj) &= ~GUI_STATE_SELECTED; |
338 | break; |
339 | case AGNODE: |
340 | ND_gui_state((node_t*)obj) |= GUI_STATE_VISITED; |
341 | ND_gui_state((node_t*)obj) &= ~GUI_STATE_SELECTED; |
342 | break; |
343 | case AGEDGE: |
344 | ED_gui_state((edge_t*)obj) |= GUI_STATE_VISITED; |
345 | ED_gui_state((edge_t*)obj) &= ~GUI_STATE_SELECTED; |
346 | break; |
347 | } |
348 | } |
349 | |
350 | if (job->selected_href) { |
351 | free(job->selected_href); |
352 | job->selected_href = NULL; |
353 | } |
354 | |
355 | obj = job->selected_obj = job->current_obj; |
356 | if (obj) { |
357 | switch (agobjkind(obj)) { |
358 | case AGRAPH: |
359 | GD_gui_state((graph_t*)obj) |= GUI_STATE_SELECTED; |
360 | gv_graph_state(job, (graph_t*)obj); |
361 | break; |
362 | case AGNODE: |
363 | ND_gui_state((node_t*)obj) |= GUI_STATE_SELECTED; |
364 | gv_node_state(job, (node_t*)obj); |
365 | break; |
366 | case AGEDGE: |
367 | ED_gui_state((edge_t*)obj) |= GUI_STATE_SELECTED; |
368 | gv_edge_state(job, (edge_t*)obj); |
369 | break; |
370 | } |
371 | } |
372 | |
373 | #if 0 |
374 | for (i = 0; i < job->selected_obj_type_name.argc; i++) |
375 | fprintf(stderr,"%s%s" , job->selected_obj_type_name.argv[i], |
376 | (i==(job->selected_obj_type_name.argc - 1))?"\n" :" " ); |
377 | for (i = 0; i < job->selected_obj_attributes.argc; i++) |
378 | fprintf(stderr,"%s%s" , job->selected_obj_attributes.argv[i], (i%2)?"\n" :" = " ); |
379 | fprintf(stderr,"\n" ); |
380 | #endif |
381 | } |
382 | |
383 | static void gvevent_button_press(GVJ_t * job, int button, pointf pointer) |
384 | { |
385 | switch (button) { |
386 | case 1: /* select / create in edit mode */ |
387 | gvevent_find_current_obj(job, pointer); |
388 | gvevent_select_current_obj(job); |
389 | job->click = 1; |
390 | job->button = button; |
391 | job->needs_refresh = 1; |
392 | break; |
393 | case 2: /* pan */ |
394 | job->click = 1; |
395 | job->button = button; |
396 | job->needs_refresh = 1; |
397 | break; |
398 | case 3: /* insert node or edge */ |
399 | gvevent_find_current_obj(job, pointer); |
400 | job->click = 1; |
401 | job->button = button; |
402 | job->needs_refresh = 1; |
403 | break; |
404 | case 4: |
405 | /* scrollwheel zoom in at current mouse x,y */ |
406 | /* FIXME - should code window 0,0 point as feature with Y_GOES_DOWN */ |
407 | job->fit_mode = 0; |
408 | if (job->rotation) { |
409 | job->focus.x -= (pointer.y - job->height / 2.) |
410 | * (ZOOMFACTOR - 1.) / (job->zoom * job->devscale.y); |
411 | job->focus.y += (pointer.x - job->width / 2.) |
412 | * (ZOOMFACTOR - 1.) / (job->zoom * job->devscale.x); |
413 | } |
414 | else { |
415 | job->focus.x += (pointer.x - job->width / 2.) |
416 | * (ZOOMFACTOR - 1.) / (job->zoom * job->devscale.x); |
417 | job->focus.y += (pointer.y - job->height / 2.) |
418 | * (ZOOMFACTOR - 1.) / (job->zoom * job->devscale.y); |
419 | } |
420 | job->zoom *= ZOOMFACTOR; |
421 | job->needs_refresh = 1; |
422 | break; |
423 | case 5: /* scrollwheel zoom out at current mouse x,y */ |
424 | job->fit_mode = 0; |
425 | job->zoom /= ZOOMFACTOR; |
426 | if (job->rotation) { |
427 | job->focus.x += (pointer.y - job->height / 2.) |
428 | * (ZOOMFACTOR - 1.) / (job->zoom * job->devscale.y); |
429 | job->focus.y -= (pointer.x - job->width / 2.) |
430 | * (ZOOMFACTOR - 1.) / (job->zoom * job->devscale.x); |
431 | } |
432 | else { |
433 | job->focus.x -= (pointer.x - job->width / 2.) |
434 | * (ZOOMFACTOR - 1.) / (job->zoom * job->devscale.x); |
435 | job->focus.y -= (pointer.y - job->height / 2.) |
436 | * (ZOOMFACTOR - 1.) / (job->zoom * job->devscale.y); |
437 | } |
438 | job->needs_refresh = 1; |
439 | break; |
440 | } |
441 | job->oldpointer = pointer; |
442 | } |
443 | |
444 | static void gvevent_button_release(GVJ_t *job, int button, pointf pointer) |
445 | { |
446 | job->click = 0; |
447 | job->button = 0; |
448 | } |
449 | |
450 | static void gvevent_motion(GVJ_t * job, pointf pointer) |
451 | { |
452 | /* dx,dy change in position, in device independent points */ |
453 | double dx = (pointer.x - job->oldpointer.x) / job->devscale.x; |
454 | double dy = (pointer.y - job->oldpointer.y) / job->devscale.y; |
455 | |
456 | if (fabs(dx) < EPSILON && fabs(dy) < EPSILON) /* ignore motion events with no motion */ |
457 | return; |
458 | |
459 | switch (job->button) { |
460 | case 0: /* drag with no button - */ |
461 | gvevent_find_current_obj(job, pointer); |
462 | break; |
463 | case 1: /* drag with button 1 - drag object */ |
464 | /* FIXME - to be implemented */ |
465 | break; |
466 | case 2: /* drag with button 2 - pan graph */ |
467 | if (job->rotation) { |
468 | job->focus.x -= dy / job->zoom; |
469 | job->focus.y += dx / job->zoom; |
470 | } |
471 | else { |
472 | job->focus.x -= dx / job->zoom; |
473 | job->focus.y -= dy / job->zoom; |
474 | } |
475 | job->needs_refresh = 1; |
476 | break; |
477 | case 3: /* drag with button 3 - drag inserted node or uncompleted edge */ |
478 | break; |
479 | } |
480 | job->oldpointer = pointer; |
481 | } |
482 | |
483 | static int quit_cb(GVJ_t * job) |
484 | { |
485 | return 1; |
486 | } |
487 | |
488 | static int left_cb(GVJ_t * job) |
489 | { |
490 | job->fit_mode = 0; |
491 | job->focus.x += PANFACTOR / job->zoom; |
492 | job->needs_refresh = 1; |
493 | return 0; |
494 | } |
495 | |
496 | static int right_cb(GVJ_t * job) |
497 | { |
498 | job->fit_mode = 0; |
499 | job->focus.x -= PANFACTOR / job->zoom; |
500 | job->needs_refresh = 1; |
501 | return 0; |
502 | } |
503 | |
504 | static int up_cb(GVJ_t * job) |
505 | { |
506 | job->fit_mode = 0; |
507 | job->focus.y += -(PANFACTOR / job->zoom); |
508 | job->needs_refresh = 1; |
509 | return 0; |
510 | } |
511 | |
512 | static int down_cb(GVJ_t * job) |
513 | { |
514 | job->fit_mode = 0; |
515 | job->focus.y -= -(PANFACTOR / job->zoom); |
516 | job->needs_refresh = 1; |
517 | return 0; |
518 | } |
519 | |
520 | static int zoom_in_cb(GVJ_t * job) |
521 | { |
522 | job->fit_mode = 0; |
523 | job->zoom *= ZOOMFACTOR; |
524 | job->needs_refresh = 1; |
525 | return 0; |
526 | } |
527 | |
528 | static int zoom_out_cb(GVJ_t * job) |
529 | { |
530 | job->fit_mode = 0; |
531 | job->zoom /= ZOOMFACTOR; |
532 | job->needs_refresh = 1; |
533 | return 0; |
534 | } |
535 | |
536 | static int toggle_fit_cb(GVJ_t * job) |
537 | { |
538 | /*FIXME - should allow for margins */ |
539 | /* - similar zoom_to_fit code exists in: */ |
540 | /* plugin/gtk/callbacks.c */ |
541 | /* plugin/xlib/gvdevice_xlib.c */ |
542 | /* lib/gvc/gvevent.c */ |
543 | |
544 | job->fit_mode = !job->fit_mode; |
545 | if (job->fit_mode) { |
546 | /* FIXME - this code looks wrong */ |
547 | int dflt_width, dflt_height; |
548 | dflt_width = job->width; |
549 | dflt_height = job->height; |
550 | job->zoom = |
551 | MIN((double) job->width / (double) dflt_width, |
552 | (double) job->height / (double) dflt_height); |
553 | job->focus.x = 0.0; |
554 | job->focus.y = 0.0; |
555 | job->needs_refresh = 1; |
556 | } |
557 | return 0; |
558 | } |
559 | |
560 | static void gvevent_modify (GVJ_t * job, const char *name, const char *value) |
561 | { |
562 | /* FIXME */ |
563 | } |
564 | |
565 | static void gvevent_delete (GVJ_t * job) |
566 | { |
567 | /* FIXME */ |
568 | } |
569 | |
570 | static void gvevent_read (GVJ_t * job, const char *filename, const char *layout) |
571 | { |
572 | FILE *f; |
573 | GVC_t *gvc; |
574 | Agraph_t *g = NULL; |
575 | gvlayout_engine_t *gvle; |
576 | |
577 | gvc = job->gvc; |
578 | if (!filename) { |
579 | g = agread(stdin,NIL(Agdisc_t *)); // continue processing stdin |
580 | } |
581 | else { |
582 | f = fopen(filename, "r" ); |
583 | if (!f) |
584 | return; /* FIXME - need some error handling */ |
585 | g = agread(f,NIL(Agdisc_t *)); |
586 | fclose(f); |
587 | } |
588 | |
589 | if (!g) |
590 | return; /* FIXME - need some error handling */ |
591 | |
592 | if (gvc->g) { |
593 | gvle = gvc->layout.engine; |
594 | if (gvle && gvle->cleanup) |
595 | gvle->cleanup(gvc->g); |
596 | graph_cleanup(gvc->g); |
597 | agclose(gvc->g); |
598 | } |
599 | |
600 | aginit (g, AGRAPH, "Agraphinfo_t" , sizeof(Agraphinfo_t), TRUE); |
601 | aginit (g, AGNODE, "Agnodeinfo_t" , sizeof(Agnodeinfo_t), TRUE); |
602 | aginit (g, AGEDGE, "Agedgeinfo_t" , sizeof(Agedgeinfo_t), TRUE); |
603 | gvc->g = g; |
604 | GD_gvc(g) = gvc; |
605 | if (gvLayout(gvc, g, layout) == -1) |
606 | return; /* FIXME - need some error handling */ |
607 | job->selected_obj = NULL; |
608 | job->current_obj = NULL; |
609 | job->needs_refresh = 1; |
610 | } |
611 | |
612 | static void gvevent_layout (GVJ_t * job, const char *layout) |
613 | { |
614 | gvLayout(job->gvc, job->gvc->g, layout); |
615 | } |
616 | |
617 | static void gvevent_render (GVJ_t * job, const char *format, const char *filename) |
618 | { |
619 | /* If gvc->jobs is set, a new job for doing the rendering won't be created. |
620 | * If gvc->active_jobs is set, this will be used in a call to gv_end_job. |
621 | * If we assume this function is called by an interactive front-end which |
622 | * actually wants to write a file, the above possibilities can cause problems, |
623 | * with either gvc->job being NULL or the creation of a new window. To avoid |
624 | * this, we null out these values for rendering the file, and restore them |
625 | * afterwards. John may have a better way around this. |
626 | */ |
627 | GVJ_t* save_jobs; |
628 | GVJ_t* save_active; |
629 | if (job->gvc->jobs && (job->gvc->job == NULL)) { |
630 | save_jobs = job->gvc->jobs; |
631 | save_active = job->gvc->active_jobs; |
632 | job->gvc->active_jobs = job->gvc->jobs = NULL; |
633 | } |
634 | else |
635 | save_jobs = NULL; |
636 | gvRenderFilename(job->gvc, job->gvc->g, format, filename); |
637 | if (save_jobs) { |
638 | job->gvc->jobs = save_jobs; |
639 | job->gvc->active_jobs = save_active; |
640 | } |
641 | } |
642 | |
643 | |
644 | gvevent_key_binding_t gvevent_key_binding[] = { |
645 | {"Q" , quit_cb}, |
646 | {"Left" , left_cb}, |
647 | {"KP_Left" , left_cb}, |
648 | {"Right" , right_cb}, |
649 | {"KP_Right" , right_cb}, |
650 | {"Up" , up_cb}, |
651 | {"KP_Up" , up_cb}, |
652 | {"Down" , down_cb}, |
653 | {"KP_Down" , down_cb}, |
654 | {"plus" , zoom_in_cb}, |
655 | {"KP_Add" , zoom_in_cb}, |
656 | {"minus" , zoom_out_cb}, |
657 | {"KP_Subtract" , zoom_out_cb}, |
658 | {"F" , toggle_fit_cb}, |
659 | }; |
660 | |
661 | int gvevent_key_binding_size = ARRAY_SIZE(gvevent_key_binding); |
662 | |
663 | gvdevice_callbacks_t gvdevice_callbacks = { |
664 | gvevent_refresh, |
665 | gvevent_button_press, |
666 | gvevent_button_release, |
667 | gvevent_motion, |
668 | gvevent_modify, |
669 | gvevent_delete, |
670 | gvevent_read, |
671 | gvevent_layout, |
672 | gvevent_render, |
673 | }; |
674 | |