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