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