Fawkes API  Fawkes Development Version
graph_drawing_area.cpp
1 
2 /***************************************************************************
3  * graph_drawing_area.cpp - Graph drawing area derived from Gtk::DrawingArea
4  *
5  * Created: Wed Mar 18 10:40:00 2009
6  * Copyright 2008-2009 Tim Niemueller [www.niemueller.de]
7  *
8  ****************************************************************************/
9 
10 /* This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU Library General Public License for more details.
19  *
20  * Read the full text in the LICENSE.GPL file in the doc directory.
21  */
22 
23 #include "graph_drawing_area.h"
24 #include "gvplugin_skillgui_cairo.h"
25 
26 #include <cmath>
27 #include <libgen.h>
28 #include <sys/time.h>
29 
30 /** @class SkillGuiGraphDrawingArea "graph_drawing_area.h"
31  * Graph drawing area.
32  * Derived version of Gtk::DrawingArea that renders a graph via Graphviz.
33  * @author Tim Niemueller
34  */
35 
36 /** Constructor. */
38 {
39  add_events(Gdk::SCROLL_MASK | Gdk::BUTTON_MOTION_MASK);
40 
41  __gvc = gvContext();
42 
43  __graph_fsm = "";
44  __graph = "";
45 
46  __bbw = __bbh = __pad_x = __pad_y = 0.0;
47  __translation_x = __translation_y = 0.0;
48  __scale = 1.0;
49  __scale_override = false;
50  __update_graph = true;
51  __recording = false;
52 
53  gvplugin_skillgui_cairo_setup(__gvc, this);
54 
55  __fcd_save = new Gtk::FileChooserDialog("Save Graph",
56  Gtk::FILE_CHOOSER_ACTION_SAVE);
57  __fcd_open = new Gtk::FileChooserDialog("Load Graph",
58  Gtk::FILE_CHOOSER_ACTION_OPEN);
59  __fcd_recording = new Gtk::FileChooserDialog("Recording Directory",
60  Gtk::FILE_CHOOSER_ACTION_CREATE_FOLDER);
61 
62  //Add response buttons the the dialog:
63  __fcd_save->add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
64  __fcd_save->add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_OK);
65  __fcd_open->add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
66  __fcd_open->add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_OK);
67  __fcd_recording->add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
68  __fcd_recording->add_button(Gtk::Stock::OK, Gtk::RESPONSE_OK);
69 
70 #if GTK_VERSION_GE(3,0)
71  __filter_pdf = Gtk::FileFilter::create();
72  __filter_svg = Gtk::FileFilter::create();
73  __filter_png = Gtk::FileFilter::create();
74  __filter_dot = Gtk::FileFilter::create();
75 #else
76  __filter_pdf = new Gtk::FileFilter();
77  __filter_svg = new Gtk::FileFilter();
78  __filter_png = new Gtk::FileFilter();
79  __filter_dot = new Gtk::FileFilter();
80 #endif
81  __filter_pdf->set_name("Portable Document Format (PDF)");
82  __filter_pdf->add_pattern("*.pdf");
83  __filter_svg->set_name("Scalable Vector Graphic (SVG)");
84  __filter_svg->add_pattern("*.svg");
85  __filter_png->set_name("Portable Network Graphic (PNG)");
86  __filter_png->add_pattern("*.png");
87  __filter_dot->set_name("DOT Graph");
88  __filter_dot->add_pattern("*.dot");
89 #if GTK_VERSION_GE(3,0)
90  __fcd_save->add_filter(__filter_pdf);
91  __fcd_save->add_filter(__filter_svg);
92  __fcd_save->add_filter(__filter_png);
93  __fcd_save->add_filter(__filter_dot);
94  __fcd_save->set_filter(__filter_pdf);
95 
96  __fcd_open->add_filter(__filter_dot);
97  __fcd_open->set_filter(__filter_dot);
98 #else
99  __fcd_save->add_filter(*__filter_pdf);
100  __fcd_save->add_filter(*__filter_svg);
101  __fcd_save->add_filter(*__filter_png);
102  __fcd_save->add_filter(*__filter_dot);
103  __fcd_save->set_filter(*__filter_pdf);
104 
105  __fcd_open->add_filter(*__filter_dot);
106  __fcd_open->set_filter(*__filter_dot);
107 #endif
108 
109  add_events(Gdk::SCROLL_MASK | Gdk::BUTTON_MOTION_MASK |
110  Gdk::BUTTON_PRESS_MASK );
111 
112 #if GTK_VERSION_LT(3,0)
113  signal_expose_event().connect(sigc::mem_fun(*this, &SkillGuiGraphDrawingArea::on_expose_event));
114 #endif
115  signal_button_press_event().connect(sigc::mem_fun(*this, &SkillGuiGraphDrawingArea::on_button_press_event));
116  signal_motion_notify_event().connect(sigc::mem_fun(*this, &SkillGuiGraphDrawingArea::on_motion_notify_event));
117 }
118 
119 SkillGuiGraphDrawingArea::~SkillGuiGraphDrawingArea()
120 {
121  gvFreeContext(__gvc);
122  //delete __fcd;
123  delete __fcd_save;
124  delete __fcd_open;
125  delete __fcd_recording;
126 #if GTK_VERSION_GE(3,0)
127  __filter_pdf.reset();
128  __filter_svg.reset();
129  __filter_png.reset();
130  __filter_dot.reset();
131 #else
132  delete __filter_pdf;
133  delete __filter_svg;
134  delete __filter_png;
135  delete __filter_dot;
136 #endif
137 }
138 
139 
140 /** Get "update disabled" signal.
141  * @return "update disabled" signal
142  */
143 sigc::signal<void>
145 {
146  return __signal_update_disabled;
147 }
148 
149 
150 /** Set graph's FSM name.
151  * @param fsm_name name of FSM the graph belongs to
152  */
153 void
155 {
156  if ( __update_graph ) {
157  if ( __graph_fsm != fsm_name ) {
158  __scale_override = false;
159  }
160  __graph_fsm = fsm_name;
161  } else {
162  __nonupd_graph_fsm = fsm_name;
163  }
164 }
165 
166 
167 /** Set graph.
168  * @param graph string representation of the current graph in the dot language.
169  */
170 void
172 {
173  if ( __update_graph ) {
174  __graph = graph;
175  queue_draw();
176  } else {
177  __nonupd_graph = graph;
178  }
179 
180  if ( __recording ) {
181  char *tmp;
182 #if defined(__MACH__) && defined(__APPLE__)
183  struct timeval t;
184  if (gettimeofday(&t, NULL) == 0) {
185  long int nsec = t.tv_usec * 1000;
186 #else
187  timespec t;
188  if (clock_gettime(CLOCK_REALTIME, &t) == 0) {
189  long int nsec = t.tv_nsec;
190 #endif
191  struct tm tms;
192  localtime_r(&t.tv_sec, &tms);
193 
194  if ( asprintf(&tmp, "%s/%s_%04i%02i%02i-%02i%02i%02i.%09li.dot",
195  __record_directory.c_str(), __graph_fsm.c_str(),
196  tms.tm_year + 1900, tms.tm_mon + 1, tms.tm_mday,
197  tms.tm_hour, tms.tm_min, tms.tm_sec, nsec) != -1)
198  {
199 
200  //printf("Would record to filename %s\n", tmp);
201  save_dotfile(tmp);
202  free(tmp);
203  } else {
204  printf("Warning: Could not create file name for recording, skipping graph\n");
205  }
206  } else {
207  printf("Warning: Could not time recording, skipping graph\n");
208  }
209  }
210 }
211 
212 /** Set bounding box.
213  * To be called only by the Graphviz plugin.
214  * @param bbw bounding box width
215  * @param bbh bounding box height
216  */
217 void
218 SkillGuiGraphDrawingArea::set_bb(double bbw, double bbh)
219 {
220  __bbw = bbw;
221  __bbh = bbh;
222 }
223 
224 
225 /** Set padding.
226  * To be called only by the Graphviz plugin.
227  * @param pad_x padding in x
228  * @param pad_y padding in y
229  */
230 void
231 SkillGuiGraphDrawingArea::set_pad(double pad_x, double pad_y)
232 {
233  __pad_x = pad_x;
234  __pad_y = pad_y;
235 }
236 
237 
238 /** Get padding.
239  * To be called only by the Graphviz plugin.
240  * @param pad_x upon return contains padding in x
241  * @param pad_y upon return contains padding in y
242  */
243 void
244 SkillGuiGraphDrawingArea::get_pad(double &pad_x, double &pad_y)
245 {
246  if (__scale_override) {
247  pad_x = pad_y = 0;
248  } else {
249  pad_x = __pad_x;
250  pad_y = __pad_y;
251  }
252 }
253 
254 
255 /** Set translation.
256  * To be called only by the Graphviz plugin.
257  * @param tx translation in x
258  * @param ty translation in y
259  */
260 void
262 {
263  __translation_x = tx;
264  __translation_y = ty;
265 }
266 
267 
268 /** Set scale.
269  * To be called only by the Graphviz plugin.
270  * @param scale scale value
271  */
272 void
274 {
275  __scale = scale;
276 }
277 
278 /** Get scale.
279  * To be called only by the Graphviz plugin.
280  * @return scale value
281  */
282 double
284 {
285  return __scale;
286 }
287 
288 /** Get translation.
289  * @param tx upon return contains translation value
290  * @param ty upon return contains translation value
291  */
292 void
294 {
295  tx = __translation_x;
296  ty = __translation_y;
297 }
298 
299 
300 /** Get dimensions
301  * @param width upon return contains width
302  * @param height upon return contains height
303  */
304 void
305 SkillGuiGraphDrawingArea::get_dimensions(double &width, double &height)
306 {
307  Gtk::Allocation alloc = get_allocation();
308  width = alloc.get_width();
309  height = alloc.get_height();
310 }
311 
312 
313 /** Zoom in.
314  * Increases zoom factor by 20, no upper limit.
315  */
316 void
318 {
319  Gtk::Allocation alloc = get_allocation();
320  __scale += 0.1;
321  __scale_override = true;
322  __translation_x = (alloc.get_width() - __bbw * __scale) / 2.0;
323  __translation_y = (alloc.get_height() - __bbh * __scale) / 2.0 + __bbh * __scale;
324  queue_draw();
325 }
326 
327 /** Zoom out.
328  * Decreases zoom factor by 20 with a minimum of 1.
329  */
330 void
332 {
333  __scale_override = true;
334  if ( __scale > 0.1 ) {
335  Gtk::Allocation alloc = get_allocation();
336  __scale -= 0.1;
337  __translation_x = (alloc.get_width() - __bbw * __scale) / 2.0;
338  __translation_y = (alloc.get_height() - __bbh * __scale) / 2.0 + __bbh * __scale;
339  queue_draw();
340  }
341 }
342 
343 
344 /** Zoom to fit.
345  * Disables scale override and draws with values suggested by Graphviz plugin.
346  */
347 void
349 {
350  __scale_override = false;
351  queue_draw();
352 }
353 
354 
355 /** Zoom reset.
356  * Reset zoom to 1. Enables scale override.
357  */
358 void
360 {
361  Gtk::Allocation alloc = get_allocation();
362  __scale = 1.0;
363  __scale_override = true;
364  __translation_x = (alloc.get_width() - __bbw) / 2.0 + __pad_x;
365  __translation_y = (alloc.get_height() - __bbh) / 2.0 + __bbh - __pad_y;
366  queue_draw();
367 }
368 
369 
370 /** Check if scale override is enabled.
371  * @return true if scale override is enabled, false otherwise
372  */
373 bool
375 {
376  return __scale_override;
377 }
378 
379 
380 /** Get Cairo context.
381  * This is only valid during the expose event and is only meant for the
382  * Graphviz plugin.
383  * @return Cairo context
384  */
385 Cairo::RefPtr<Cairo::Context>
387 {
388  return __cairo;
389 }
390 
391 
392 
393 /** Check if graph is being updated.
394  * @return true if the graph will be update if new data is received, false otherwise
395  */
396 bool
398 {
399  return __update_graph;
400 }
401 
402 
403 /** Set if the graph should be updated on new data.
404  * @param update true to update on new data, false to disable update
405  */
406 void
408 {
409  if (update && ! __update_graph) {
410  if ( __graph_fsm != __nonupd_graph_fsm ) {
411  __scale_override = false;
412  }
413  __graph = __nonupd_graph;
414  __graph_fsm = __nonupd_graph_fsm;
415  queue_draw();
416  }
417  __update_graph = update;
418 }
419 
420 
421 void
422 SkillGuiGraphDrawingArea::save_dotfile(const char *filename)
423 {
424  FILE *f = fopen(filename, "w");
425  if (f) {
426  if (fwrite(__graph.c_str(), __graph.length(), 1, f) != 1) {
427  // bang, ignored
428  printf("Failed to write dot file '%s'\n", filename);
429  }
430  fclose(f);
431  }
432 }
433 
434 
435 /** Enable/disable recording.
436  * @param recording true to enable recording, false otherwise
437  * @return true if recording is enabled now, false if it is disabled.
438  * Enabling the recording may fail for example if the user chose to abort
439  * the directory creation process.
440  */
441 bool
443 {
444  if (recording) {
445  Gtk::Window *w = dynamic_cast<Gtk::Window *>(get_toplevel());
446  __fcd_recording->set_transient_for(*w);
447  int result = __fcd_recording->run();
448  if (result == Gtk::RESPONSE_OK) {
449  __record_directory = __fcd_recording->get_filename();
450  __recording = true;
451  }
452  __fcd_recording->hide();
453  } else {
454  __recording = false;
455  }
456  return __recording;
457 }
458 
459 
460 /** save current graph. */
461 void
463 {
464  Gtk::Window *w = dynamic_cast<Gtk::Window *>(get_toplevel());
465  __fcd_save->set_transient_for(*w);
466 
467  int result = __fcd_save->run();
468  if (result == Gtk::RESPONSE_OK) {
469 
470 #if GTK_VERSION_GE(3,0)
471  Glib::RefPtr<Gtk::FileFilter> f = __fcd_save->get_filter();
472 #else
473  Gtk::FileFilter *f = __fcd_save->get_filter();
474 #endif
475  std::string filename = __fcd_save->get_filename();
476  if (filename != "") {
477  if (f == __filter_dot) {
478  save_dotfile(filename.c_str());
479  } else {
480  Cairo::RefPtr<Cairo::Surface> surface;
481 
482  bool write_to_png = false;
483  if (f == __filter_pdf) {
484  surface = Cairo::PdfSurface::create(filename, __bbw, __bbh);
485  } else if (f == __filter_svg) {
486  surface = Cairo::SvgSurface::create(filename, __bbw, __bbh);
487  } else if (f == __filter_png) {
488  surface = Cairo::ImageSurface::create(Cairo::FORMAT_ARGB32,
489  (int)ceilf(__bbw),
490  (int)ceilf(__bbh));
491  write_to_png = true;
492  }
493 
494  if (surface) {
495  __cairo = Cairo::Context::create(surface);
496 
497  bool old_scale_override = __scale_override;
498  double old_tx = __translation_x;
499  double old_ty = __translation_y;
500  double old_scale = __scale;
501  __translation_x = __pad_x;
502  __translation_y = __bbh - __pad_y;
503  __scale = 1.0;
504  __scale_override = true;
505 
506  Agraph_t *g = agmemread((char *)__graph.c_str());
507  if (g) {
508  gvLayout(__gvc, g, (char *)"dot");
509  gvRender(__gvc, g, (char *)"skillguicairo", NULL);
510  gvFreeLayout(__gvc, g);
511  agclose(g);
512  }
513 
514  if (write_to_png) {
515  surface->write_to_png(filename);
516  }
517 
518  __cairo.clear();
519 
520  __translation_x = old_tx;
521  __translation_y = old_ty;
522  __scale = old_scale;
523  __scale_override = old_scale_override;
524  }
525  }
526 
527  } else {
528  Gtk::MessageDialog md(*w, "Invalid filename",
529  /* markup */ false, Gtk::MESSAGE_ERROR,
530  Gtk::BUTTONS_OK, /* modal */ true);
531  md.set_title("Invalid File Name");
532  md.run();
533  }
534  }
535 
536  __fcd_save->hide();
537 }
538 
539 
540 /** Open a dot graph and display it. */
541 void
543 {
544  Gtk::Window *w = dynamic_cast<Gtk::Window *>(get_toplevel());
545  __fcd_open->set_transient_for(*w);
546 
547  int result = __fcd_open->run();
548  if (result == Gtk::RESPONSE_OK) {
549  __update_graph = false;
550  __graph = "";
551  char *basec = strdup(__fcd_open->get_filename().c_str());
552  char *basen = basename(basec);
553  __graph_fsm = basen;
554  free(basec);
555 
556  FILE *f = fopen(__fcd_open->get_filename().c_str(), "r");
557  while (! feof(f)) {
558  char tmp[4096];
559  size_t s;
560  if ((s = fread(tmp, 1, 4096, f)) > 0) {
561  __graph.append(tmp, s);
562  }
563  }
564  fclose(f);
565  __signal_update_disabled.emit();
566  queue_draw();
567  }
568 
569  __fcd_open->hide();
570 }
571 
572 
573 #if GTK_VERSION_GE(3,0)
574 /** Draw event handler.
575  * @param cr cairo context
576  * @return true
577  */
578 bool
579 SkillGuiGraphDrawingArea::on_draw(const Cairo::RefPtr<Cairo::Context> &cr)
580 #else
581 /** Expose event handler.
582  * @param event event info structure.
583  * @return signal return value
584  */
585 bool
587 #endif
588 {
589  // This is where we draw on the window
590  Glib::RefPtr<Gdk::Window> window = get_window();
591  if(window) {
592  //Gtk::Allocation allocation = get_allocation();
593  //const int width = allocation.get_width();
594  //const int height = allocation.get_height();
595 
596  // coordinates for the center of the window
597  //int xc, yc;
598  //xc = width / 2;
599  //yc = height / 2;
600 #if GTK_VERSION_LT(3,0)
601  __cairo = window->create_cairo_context();
602 #else
603  __cairo = cr;
604 #endif
605  __cairo->set_source_rgb(1, 1, 1);
606  __cairo->paint();
607 
608  Agraph_t *g = agmemread((char *)__graph.c_str());
609  if (g) {
610  gvLayout(__gvc, g, (char *)"dot");
611  gvRender(__gvc, g, (char *)"skillguicairo", NULL);
612  gvFreeLayout(__gvc, g);
613  agclose(g);
614  }
615 
616  __cairo.clear();
617  }
618 
619  return true;
620 }
621 
622 /** Scroll event handler.
623  * @param event event structure
624  * @return signal return value
625  */
626 bool
628 {
629  if (event->direction == GDK_SCROLL_UP) {
630  zoom_in();
631  } else if (event->direction == GDK_SCROLL_DOWN) {
632  zoom_out();
633  }
634  return true;
635 }
636 
637 
638 /** Button press event handler.
639  * @param event event data
640  * @return true
641  */
642 bool
644 {
645  __last_mouse_x = event->x;
646  __last_mouse_y = event->y;
647  return true;
648 }
649 
650 
651 /** Mouse motion notify event handler.
652  * @param event event data
653  * @return true
654  */
655 bool
657 {
658  __scale_override = true;
659  __translation_x -= __last_mouse_x - event->x;
660  __translation_y -= __last_mouse_y - event->y;
661  __last_mouse_x = event->x;
662  __last_mouse_y = event->y;
663  queue_draw();
664  return true;
665 }
666 
void set_graph(std::string graph)
Set graph.
virtual bool on_scroll_event(GdkEventScroll *event)
Scroll event handler.
bool set_recording(bool recording)
Enable/disable recording.
sigc::signal< void > signal_update_disabled()
Get "update disabled" signal.
SkillGuiGraphDrawingArea()
Constructor.
double get_scale()
Get scale.
bool get_update_graph()
Check if graph is being updated.
void set_graph_fsm(std::string fsm_name)
Set graph&#39;s FSM name.
virtual bool on_expose_event(GdkEventExpose *event)
Expose event handler.
Cairo::RefPtr< Cairo::Context > get_cairo()
Get Cairo context.
virtual bool on_motion_notify_event(GdkEventMotion *event)
Mouse motion notify event handler.
void zoom_fit()
Zoom to fit.
bool scale_override()
Check if scale override is enabled.
void save()
save current graph.
void get_translation(double &tx, double &ty)
Get translation.
void set_update_graph(bool update)
Set if the graph should be updated on new data.
void get_dimensions(double &width, double &height)
Get dimensions.
void set_pad(double pad_x, double pad_y)
Set padding.
void get_pad(double &pad_x, double &pad_y)
Get padding.
void set_translation(double tx, double ty)
Set translation.
void set_bb(double bbw, double bbh)
Set bounding box.
void open()
Open a dot graph and display it.
virtual bool on_button_press_event(GdkEventButton *event)
Button press event handler.
void set_scale(double scale)
Set scale.