Graphviz 12.0.1~dev.20240715.2254
Loading...
Searching...
No Matches
gvevent.c
Go to the documentation of this file.
1/*************************************************************************
2 * Copyright (c) 2011 AT&T Intellectual Property
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * https://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors: Details at https://graphviz.org
9 *************************************************************************/
10
11#include "config.h"
12
13#include <string.h>
14#include <stdbool.h>
15#include <stdlib.h>
16#include <math.h>
17
18#include <gvc/gvplugin_layout.h>
19#include <gvc/gvcint.h>
20#include <gvc/gvcproc.h>
21#include <common/utils.h>
22
23extern void emit_graph(GVJ_t * job, graph_t * g);
24extern int gvLayout(GVC_t *gvc, graph_t *g, const char *engine);
25extern int gvRenderFilename(GVC_t *gvc, graph_t *g, const char *format, const char *filename);
26extern void graph_cleanup(graph_t *g);
27
28#define PANFACTOR 10
29#define ZOOMFACTOR 1.1
30#define EPSILON .0001
31
32static char *s_tooltip = "tooltip";
33static char *s_href = "href";
34static char *s_URL = "URL";
35
36static void gv_graph_state(GVJ_t *job, graph_t *g)
37{
39 if (!a)
40 a = agfindgraphattr(g, s_URL);
41 if (a)
43}
44
45static void gv_node_state(GVJ_t *job, node_t *n)
46{
48 if (!a)
50 if (a)
52}
53
54static void gv_edge_state(GVJ_t *job, edge_t *e)
55{
57 if (!a)
59 if (a)
61}
62
63static void gvevent_refresh(GVJ_t * job)
64{
65 graph_t *g = job->gvc->g;
66
67 if (!job->selected_obj) {
68 job->selected_obj = g;
70 gv_graph_state(job, g);
71 }
72 emit_graph(job, g);
73 job->has_been_rendered = true;
74}
75
76/* recursively find innermost cluster containing the point */
78{
79 int i;
80 graph_t *sg;
81 boxf bb;
82
83 for (i = 1; i <= GD_n_cluster(g); i++) {
84 sg = gvevent_find_cluster(GD_clust(g)[i], b);
85 if (sg)
86 return sg;
87 }
88 B2BF(GD_bb(g), bb);
89 if (OVERLAP(b, bb))
90 return g;
91 return NULL;
92}
93
94static void * gvevent_find_obj(graph_t *g, boxf b)
95{
96 graph_t *sg;
97 node_t *n;
98 edge_t *e;
99
100 /* edges might overlap nodes, so search them first */
101 for (n = agfstnode(g); n; n = agnxtnode(g, n))
102 for (e = agfstout(g, n); e; e = agnxtout(g, e))
103 if (overlap_edge(e, b))
104 return e;
105 /* search graph backwards to get topmost node, in case of overlap */
106 for (n = aglstnode(g); n; n = agprvnode(g, n))
107 if (overlap_node(n, b))
108 return n;
109 /* search for innermost cluster */
110 sg = gvevent_find_cluster(g, b);
111 if (sg)
112 return sg;
113
114 /* otherwise - we're always in the graph */
115 return g;
116}
117
118static void gvevent_leave_obj(GVJ_t * job)
119{
120 void *obj = job->current_obj;
121
122 if (obj) {
123 switch (agobjkind(obj)) {
124 case AGRAPH:
125 GD_gui_state(obj) &= (unsigned char)~GUI_STATE_ACTIVE;
126 break;
127 case AGNODE:
128 ND_gui_state(obj) &= (unsigned char)~GUI_STATE_ACTIVE;
129 break;
130 case AGEDGE:
131 ED_gui_state(obj) &= (unsigned char)~GUI_STATE_ACTIVE;
132 break;
133 }
134 }
135 job->active_tooltip = NULL;
136}
137
138static void gvevent_enter_obj(GVJ_t * job)
139{
140 void *obj;
141 graph_t *g;
142 edge_t *e;
143 node_t *n;
144 Agsym_t *a;
145
146 free(job->active_tooltip);
147 job->active_tooltip = NULL;
148 obj = job->current_obj;
149 if (obj) {
150 switch (agobjkind(obj)) {
151 case AGRAPH:
152 g = obj;
155 if (a)
156 job->active_tooltip = strdup_and_subst_obj(agxget(g, a), obj);
157 break;
158 case AGNODE:
159 n = obj;
162 if (a)
163 job->active_tooltip = strdup_and_subst_obj(agxget(n, a), obj);
164 break;
165 case AGEDGE:
166 e = obj;
169 if (a)
170 job->active_tooltip = strdup_and_subst_obj(agxget(e, a), obj);
171 break;
172 }
173 }
174}
175
176static pointf pointer2graph (GVJ_t *job, pointf pointer)
177{
178 pointf p;
179
180 /* transform position in device units to position in graph units */
181 if (job->rotation) {
182 p.x = pointer.y / (job->zoom * job->devscale.y) - job->translation.x;
183 p.y = -pointer.x / (job->zoom * job->devscale.x) - job->translation.y;
184 }
185 else {
186 p.x = pointer.x / (job->zoom * job->devscale.x) - job->translation.x;
187 p.y = pointer.y / (job->zoom * job->devscale.y) - job->translation.y;
188 }
189 return p;
190}
191
192/* CLOSEENOUGH is in 1/72 - probably should be a feature... */
193#define CLOSEENOUGH 1
194
195static void gvevent_find_current_obj(GVJ_t * job, pointf pointer)
196{
197 void *obj;
198 boxf b;
199 double closeenough;
200 pointf p;
201
202 p = pointer2graph (job, pointer);
203
204 /* convert window point to graph coordinates */
205 closeenough = CLOSEENOUGH / job->zoom;
206
207 b.UR.x = p.x + closeenough;
208 b.UR.y = p.y + closeenough;
209 b.LL.x = p.x - closeenough;
210 b.LL.y = p.y - closeenough;
211
212 obj = gvevent_find_obj(job->gvc->g, b);
213 if (obj != job->current_obj) {
215 job->current_obj = obj;
217 job->needs_refresh = true;
218 }
219}
220
222{
223 void *obj;
224
225 obj = job->selected_obj;
226 if (obj) {
227 switch (agobjkind(obj)) {
228 case AGRAPH:
230 GD_gui_state(obj) &= (unsigned char)~GUI_STATE_SELECTED;
231 break;
232 case AGNODE:
234 ND_gui_state(obj) &= (unsigned char)~GUI_STATE_SELECTED;
235 break;
236 case AGEDGE:
238 ED_gui_state(obj) &= (unsigned char)~GUI_STATE_SELECTED;
239 break;
240 }
241 }
242
243 free(job->selected_href);
244 job->selected_href = NULL;
245
246 obj = job->selected_obj = job->current_obj;
247 if (obj) {
248 switch (agobjkind(obj)) {
249 case AGRAPH:
251 gv_graph_state(job, obj);
252 break;
253 case AGNODE:
255 gv_node_state(job, obj);
256 break;
257 case AGEDGE:
259 gv_edge_state(job, obj);
260 break;
261 }
262 }
263}
264
265static void gvevent_button_press(GVJ_t * job, int button, pointf pointer)
266{
267 switch (button) {
268 case 1: /* select / create in edit mode */
269 gvevent_find_current_obj(job, pointer);
271 job->click = true;
272 job->button = (unsigned char)button;
273 job->needs_refresh = true;
274 break;
275 case 2: /* pan */
276 job->click = true;
277 job->button = (unsigned char)button;
278 job->needs_refresh = true;
279 break;
280 case 3: /* insert node or edge */
281 gvevent_find_current_obj(job, pointer);
282 job->click = true;
283 job->button = (unsigned char)button;
284 job->needs_refresh = true;
285 break;
286 case 4:
287 /* scrollwheel zoom in at current mouse x,y */
288/* FIXME - should code window 0,0 point as feature with Y_GOES_DOWN */
289 job->fit_mode = false;
290 if (job->rotation) {
291 job->focus.x -= (pointer.y - job->height / 2.)
292 * (ZOOMFACTOR - 1.) / (job->zoom * job->devscale.y);
293 job->focus.y += (pointer.x - job->width / 2.)
294 * (ZOOMFACTOR - 1.) / (job->zoom * job->devscale.x);
295 }
296 else {
297 job->focus.x += (pointer.x - job->width / 2.)
298 * (ZOOMFACTOR - 1.) / (job->zoom * job->devscale.x);
299 job->focus.y += (pointer.y - job->height / 2.)
300 * (ZOOMFACTOR - 1.) / (job->zoom * job->devscale.y);
301 }
302 job->zoom *= ZOOMFACTOR;
303 job->needs_refresh = true;
304 break;
305 case 5: /* scrollwheel zoom out at current mouse x,y */
306 job->fit_mode = false;
307 job->zoom /= ZOOMFACTOR;
308 if (job->rotation) {
309 job->focus.x += (pointer.y - job->height / 2.)
310 * (ZOOMFACTOR - 1.) / (job->zoom * job->devscale.y);
311 job->focus.y -= (pointer.x - job->width / 2.)
312 * (ZOOMFACTOR - 1.) / (job->zoom * job->devscale.x);
313 }
314 else {
315 job->focus.x -= (pointer.x - job->width / 2.)
316 * (ZOOMFACTOR - 1.) / (job->zoom * job->devscale.x);
317 job->focus.y -= (pointer.y - job->height / 2.)
318 * (ZOOMFACTOR - 1.) / (job->zoom * job->devscale.y);
319 }
320 job->needs_refresh = true;
321 break;
322 }
323 job->oldpointer = pointer;
324}
325
326static void gvevent_button_release(GVJ_t *job, int button, pointf pointer)
327{
328 (void)button;
329 (void)pointer;
330
331 job->click = false;
332 job->button = false;
333}
334
335static void gvevent_motion(GVJ_t * job, pointf pointer)
336{
337 /* dx,dy change in position, in device independent points */
338 double dx = (pointer.x - job->oldpointer.x) / job->devscale.x;
339 double dy = (pointer.y - job->oldpointer.y) / job->devscale.y;
340
341 if (fabs(dx) < EPSILON && fabs(dy) < EPSILON) /* ignore motion events with no motion */
342 return;
343
344 switch (job->button) {
345 case 0: /* drag with no button - */
346 gvevent_find_current_obj(job, pointer);
347 break;
348 case 1: /* drag with button 1 - drag object */
349 /* FIXME - to be implemented */
350 break;
351 case 2: /* drag with button 2 - pan graph */
352 if (job->rotation) {
353 job->focus.x -= dy / job->zoom;
354 job->focus.y += dx / job->zoom;
355 }
356 else {
357 job->focus.x -= dx / job->zoom;
358 job->focus.y -= dy / job->zoom;
359 }
360 job->needs_refresh = true;
361 break;
362 case 3: /* drag with button 3 - drag inserted node or uncompleted edge */
363 break;
364 }
365 job->oldpointer = pointer;
366}
367
368static int quit_cb(GVJ_t * job)
369{
370 (void)job;
371
372 return 1;
373}
374
375static int left_cb(GVJ_t * job)
376{
377 job->fit_mode = false;
378 job->focus.x += PANFACTOR / job->zoom;
379 job->needs_refresh = true;
380 return 0;
381}
382
383static int right_cb(GVJ_t * job)
384{
385 job->fit_mode = false;
386 job->focus.x -= PANFACTOR / job->zoom;
387 job->needs_refresh = true;
388 return 0;
389}
390
391static int up_cb(GVJ_t * job)
392{
393 job->fit_mode = false;
394 job->focus.y += -(PANFACTOR / job->zoom);
395 job->needs_refresh = true;
396 return 0;
397}
398
399static int down_cb(GVJ_t * job)
400{
401 job->fit_mode = false;
402 job->focus.y -= -(PANFACTOR / job->zoom);
403 job->needs_refresh = true;
404 return 0;
405}
406
407static int zoom_in_cb(GVJ_t * job)
408{
409 job->fit_mode = false;
410 job->zoom *= ZOOMFACTOR;
411 job->needs_refresh = true;
412 return 0;
413}
414
415static int zoom_out_cb(GVJ_t * job)
416{
417 job->fit_mode = false;
418 job->zoom /= ZOOMFACTOR;
419 job->needs_refresh = true;
420 return 0;
421}
422
423static int toggle_fit_cb(GVJ_t * job)
424{
425/*FIXME - should allow for margins */
426/* - similar zoom_to_fit code exists in: */
427/* plugin/xlib/gvdevice_xlib.c */
428/* lib/gvc/gvevent.c */
429
430 job->fit_mode = !job->fit_mode;
431 if (job->fit_mode) {
432 /* FIXME - this code looks wrong */
433 int dflt_width, dflt_height;
434 dflt_width = job->width;
435 dflt_height = job->height;
436 job->zoom =
437 MIN((double) job->width / (double) dflt_width,
438 (double) job->height / (double) dflt_height);
439 job->focus.x = 0.0;
440 job->focus.y = 0.0;
441 job->needs_refresh = true;
442 }
443 return 0;
444}
445
446static void gvevent_read (GVJ_t * job, const char *filename, const char *layout)
447{
448 FILE *f;
449 GVC_t *gvc;
450 Agraph_t *g = NULL;
451 gvlayout_engine_t *gvle;
452
453 gvc = job->gvc;
454 if (!filename) {
455 g = agread(stdin,NULL); // continue processing stdin
456 }
457 else {
458 f = fopen(filename, "r");
459 if (!f)
460 return; /* FIXME - need some error handling */
461 g = agread(f,NULL);
462 fclose(f);
463 }
464
465 if (!g)
466 return; /* FIXME - need some error handling */
467
468 if (gvc->g) {
469 gvle = gvc->layout.engine;
470 if (gvle && gvle->cleanup)
471 gvle->cleanup(gvc->g);
473 agclose(gvc->g);
474 }
475
476 aginit (g, AGRAPH, "Agraphinfo_t", sizeof(Agraphinfo_t), true);
477 aginit (g, AGNODE, "Agnodeinfo_t", sizeof(Agnodeinfo_t), true);
478 aginit (g, AGEDGE, "Agedgeinfo_t", sizeof(Agedgeinfo_t), true);
479 gvc->g = g;
480 GD_gvc(g) = gvc;
481 if (gvLayout(gvc, g, layout) == -1)
482 return; /* FIXME - need some error handling */
483 job->selected_obj = NULL;
484 job->current_obj = NULL;
485 job->needs_refresh = true;
486}
487
488static void gvevent_layout (GVJ_t * job, const char *layout)
489{
490 gvLayout(job->gvc, job->gvc->g, layout);
491}
492
493static void gvevent_render (GVJ_t * job, const char *format, const char *filename)
494{
495/* If gvc->jobs is set, a new job for doing the rendering won't be created.
496 * If gvc->active_jobs is set, this will be used in a call to gv_end_job.
497 * If we assume this function is called by an interactive front-end which
498 * actually wants to write a file, the above possibilities can cause problems,
499 * with either gvc->job being NULL or the creation of a new window. To avoid
500 * this, we null out these values for rendering the file, and restore them
501 * afterwards. John may have a better way around this.
502 */
503 GVJ_t* save_jobs;
504 GVJ_t* save_active;
505 if (job->gvc->jobs && (job->gvc->job == NULL)) {
506 save_jobs = job->gvc->jobs;
507 save_active = job->gvc->active_jobs;
508 job->gvc->active_jobs = job->gvc->jobs = NULL;
509 }
510 else
511 save_jobs = NULL;
512 gvRenderFilename(job->gvc, job->gvc->g, format, filename);
513 if (save_jobs) {
514 job->gvc->jobs = save_jobs;
515 job->gvc->active_jobs = save_active;
516 }
517}
518
519
521 {"Q", quit_cb},
522 {"Left", left_cb},
523 {"KP_Left", left_cb},
524 {"Right", right_cb},
525 {"KP_Right", right_cb},
526 {"Up", up_cb},
527 {"KP_Up", up_cb},
528 {"Down", down_cb},
529 {"KP_Down", down_cb},
530 {"plus", zoom_in_cb},
531 {"KP_Add", zoom_in_cb},
532 {"minus", zoom_out_cb},
533 {"KP_Subtract", zoom_out_cb},
534 {"F", toggle_fit_cb},
535};
536
538 sizeof(gvevent_key_binding) / sizeof(gvevent_key_binding[0]);
539
#define MIN(a, b)
Definition arith.h:28
bool overlap_node(node_t *n, boxf b)
Definition utils.c:1329
bool overlap_edge(edge_t *e, boxf b)
Definition utils.c:1378
static float dy
Definition draw.c:38
static float dx
Definition draw.c:37
#define B2BF(b, bf)
Definition geom.h:75
#define OVERLAP(b0, b1)
Definition geom.h:48
void free(void *)
node NULL
Definition grammar.y:149
char * agxget(void *obj, Agsym_t *sym)
Definition attr.c:458
#define agfindedgeattr(g, a)
Definition types.h:617
Agedge_t * agfstout(Agraph_t *g, Agnode_t *n)
Definition edge.c:23
#define ED_gui_state(e)
Definition types.h:586
#define aghead(e)
Definition cgraph.h:890
Agedge_t * agnxtout(Agraph_t *g, Agedge_t *e)
Definition edge.c:38
#define agfindgraphattr(g, a)
Definition types.h:613
#define GD_clust(g)
Definition types.h:360
int agclose(Agraph_t *g)
deletes a graph, freeing its associated storage
Definition graph.c:96
#define GD_bb(g)
Definition types.h:354
#define GD_n_cluster(g)
Definition types.h:389
#define GD_gvc(g)
Definition types.h:355
Agraph_t * agread(void *chan, Agdisc_t *disc)
constructs a new graph
Definition grammar.c:2274
#define GD_gui_state(g)
Definition types.h:366
#define ND_gui_state(n)
Definition types.h:494
Agnode_t * agnxtnode(Agraph_t *g, Agnode_t *n)
Definition node.c:47
Agnode_t * agprvnode(Agraph_t *g, Agnode_t *n)
Definition node.c:62
Agnode_t * agfstnode(Agraph_t *g)
Definition node.c:40
Agnode_t * aglstnode(Agraph_t *g)
Definition node.c:55
#define agfindnodeattr(g, a)
Definition types.h:615
Agraph_t * agraphof(void *obj)
Definition obj.c:184
int agobjkind(void *obj)
Definition obj.c:253
@ AGEDGE
Definition cgraph.h:207
@ AGNODE
Definition cgraph.h:207
@ AGRAPH
Definition cgraph.h:207
void aginit(Agraph_t *g, int kind, const char *rec_name, int rec_size, int move_to_front)
attach new records to objects of specified kind
Definition rec.c:169
int gvLayout(GVC_t *gvc, graph_t *g, const char *engine)
Definition gvc.c:52
int gvRenderFilename(GVC_t *gvc, graph_t *g, const char *format, const char *filename)
Definition gvc.c:114
#define EPSILON
Definition gvevent.c:30
static int quit_cb(GVJ_t *job)
Definition gvevent.c:368
static char * s_href
Definition gvevent.c:33
#define CLOSEENOUGH
Definition gvevent.c:193
static void gvevent_render(GVJ_t *job, const char *format, const char *filename)
Definition gvevent.c:493
#define ZOOMFACTOR
Definition gvevent.c:29
#define PANFACTOR
Definition gvevent.c:28
static char * s_URL
Definition gvevent.c:34
static void gvevent_read(GVJ_t *job, const char *filename, const char *layout)
Definition gvevent.c:446
static void gvevent_select_current_obj(GVJ_t *job)
Definition gvevent.c:221
static void gv_node_state(GVJ_t *job, node_t *n)
Definition gvevent.c:45
static int up_cb(GVJ_t *job)
Definition gvevent.c:391
static int toggle_fit_cb(GVJ_t *job)
Definition gvevent.c:423
static void gv_graph_state(GVJ_t *job, graph_t *g)
Definition gvevent.c:36
static void gvevent_enter_obj(GVJ_t *job)
Definition gvevent.c:138
static void gvevent_button_press(GVJ_t *job, int button, pointf pointer)
Definition gvevent.c:265
static void gvevent_motion(GVJ_t *job, pointf pointer)
Definition gvevent.c:335
static int right_cb(GVJ_t *job)
Definition gvevent.c:383
static pointf pointer2graph(GVJ_t *job, pointf pointer)
Definition gvevent.c:176
static void gvevent_refresh(GVJ_t *job)
Definition gvevent.c:63
static void gvevent_find_current_obj(GVJ_t *job, pointf pointer)
Definition gvevent.c:195
static char * s_tooltip
Definition gvevent.c:32
static int zoom_out_cb(GVJ_t *job)
Definition gvevent.c:415
gvevent_key_binding_t gvevent_key_binding[]
Definition gvevent.c:520
static graph_t * gvevent_find_cluster(graph_t *g, boxf b)
Definition gvevent.c:77
void graph_cleanup(graph_t *g)
Definition input.c:797
static int down_cb(GVJ_t *job)
Definition gvevent.c:399
static void gv_edge_state(GVJ_t *job, edge_t *e)
Definition gvevent.c:54
static void gvevent_layout(GVJ_t *job, const char *layout)
Definition gvevent.c:488
gvdevice_callbacks_t gvdevice_callbacks
Definition gvevent.c:540
static int left_cb(GVJ_t *job)
Definition gvevent.c:375
const size_t gvevent_key_binding_size
Definition gvevent.c:537
static void gvevent_leave_obj(GVJ_t *job)
Definition gvevent.c:118
static int zoom_in_cb(GVJ_t *job)
Definition gvevent.c:407
static void * gvevent_find_obj(graph_t *g, boxf b)
Definition gvevent.c:94
void emit_graph(GVJ_t *job, graph_t *g)
Definition emit.c:3390
static void gvevent_button_release(GVJ_t *job, int button, pointf pointer)
Definition gvevent.c:326
GVIO_API const char * format
Definition gvio.h:51
static gvloadimage_engine_t engine
GVC_t * gvc
Definition htmlparse.c:99
char * strdup_and_subst_obj(char *str, void *obj)
Definition labels.c:385
static int layout(graph_t *g, layout_info *infop)
Definition layout.c:809
graph or subgraph
Definition cgraph.h:425
string attribute descriptor symbol in Agattr_s.dict
Definition cgraph.h:639
Definition gvcint.h:80
GVJ_t * active_jobs
Definition gvcint.h:123
GVJ_t * jobs
Definition gvcint.h:114
gvplugin_active_layout_t layout
Definition gvcint.h:120
GVJ_t * job
Definition gvcint.h:115
graph_t * g
Definition gvcint.h:117
int rotation
Definition gvcjob.h:319
bool click
Definition gvcjob.h:338
void * current_obj
Definition gvcjob.h:346
bool fit_mode
Definition gvcjob.h:336
unsigned char button
Definition gvcjob.h:342
char * active_tooltip
Definition gvcjob.h:350
char * selected_href
Definition gvcjob.h:351
bool needs_refresh
Definition gvcjob.h:337
bool has_been_rendered
Definition gvcjob.h:340
pointf devscale
Definition gvcjob.h:334
void * selected_obj
Definition gvcjob.h:348
pointf focus
Definition gvcjob.h:316
double zoom
Definition gvcjob.h:318
GVC_t * gvc
Definition gvcjob.h:263
unsigned int width
Definition gvcjob.h:327
pointf oldpointer
Definition gvcjob.h:344
pointf translation
Definition gvcjob.h:333
unsigned int height
Definition gvcjob.h:328
Definition geom.h:41
pointf UR
Definition geom.h:41
pointf LL
Definition geom.h:41
void(* cleanup)(graph_t *g)
gvlayout_engine_t * engine
Definition gvcint.h:39
double x
Definition geom.h:29
double y
Definition geom.h:29
#define GUI_STATE_ACTIVE
Definition types.h:256
#define GUI_STATE_SELECTED
Definition types.h:257
#define GUI_STATE_VISITED
Definition types.h:258