Fawkes API  Fawkes Development Version
graph_drawing_area.cpp
00001 
00002 /***************************************************************************
00003  *  graph_drawing_area.cpp - Graph drawing area derived from Gtk::DrawingArea
00004  *
00005  *  Created: Wed Mar 18 10:40:00 2009
00006  *  Copyright  2008-2009  Tim Niemueller [www.niemueller.de]
00007  *
00008  ****************************************************************************/
00009 
00010 /*  This program is free software; you can redistribute it and/or modify
00011  *  it under the terms of the GNU General Public License as published by
00012  *  the Free Software Foundation; either version 2 of the License, or
00013  *  (at your option) any later version.
00014  *
00015  *  This program is distributed in the hope that it will be useful,
00016  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00017  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00018  *  GNU Library General Public License for more details.
00019  *
00020  *  Read the full text in the LICENSE.GPL file in the doc directory.
00021  */
00022 
00023 #include "graph_drawing_area.h"
00024 #include "gvplugin_skillgui_cairo.h"
00025 
00026 #include <cmath>
00027 #include <libgen.h>
00028 #include <sys/time.h>
00029 
00030 /** @class SkillGuiGraphDrawingArea "graph_drawing_area.h"
00031  * Graph drawing area.
00032  * Derived version of Gtk::DrawingArea that renders a graph via Graphviz.
00033  * @author Tim Niemueller
00034  */
00035 
00036 /** Constructor. */
00037 SkillGuiGraphDrawingArea::SkillGuiGraphDrawingArea()
00038 {
00039   add_events(Gdk::SCROLL_MASK | Gdk::BUTTON_MOTION_MASK);
00040 
00041   __gvc = gvContext();
00042 
00043   __graph_fsm = "";
00044   __graph = "";
00045 
00046   __bbw = __bbh = __pad_x = __pad_y = 0.0;
00047   __translation_x = __translation_y = 0.0;
00048   __scale = 1.0;
00049   __scale_override = false;
00050   __update_graph = true;
00051   __recording = false;
00052 
00053   gvplugin_skillgui_cairo_setup(__gvc, this);
00054 
00055   __fcd_save = new Gtk::FileChooserDialog("Save Graph",
00056                                           Gtk::FILE_CHOOSER_ACTION_SAVE);
00057   __fcd_open = new Gtk::FileChooserDialog("Load Graph",
00058                                           Gtk::FILE_CHOOSER_ACTION_OPEN);
00059   __fcd_recording = new Gtk::FileChooserDialog("Recording Directory",
00060                                                  Gtk::FILE_CHOOSER_ACTION_CREATE_FOLDER);
00061 
00062   //Add response buttons the the dialog:
00063   __fcd_save->add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
00064   __fcd_save->add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_OK);
00065   __fcd_open->add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
00066   __fcd_open->add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_OK);
00067   __fcd_recording->add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
00068   __fcd_recording->add_button(Gtk::Stock::OK, Gtk::RESPONSE_OK);
00069 
00070 #if GTK_VERSION_GE(3,0)
00071   __filter_pdf = Gtk::FileFilter::create();
00072   __filter_svg = Gtk::FileFilter::create();
00073   __filter_png = Gtk::FileFilter::create();
00074   __filter_dot = Gtk::FileFilter::create();
00075 #else
00076   __filter_pdf = new Gtk::FileFilter();
00077   __filter_svg = new Gtk::FileFilter();
00078   __filter_png = new Gtk::FileFilter();
00079   __filter_dot = new Gtk::FileFilter();
00080 #endif
00081   __filter_pdf->set_name("Portable Document Format (PDF)");
00082   __filter_pdf->add_pattern("*.pdf");
00083   __filter_svg->set_name("Scalable Vector Graphic (SVG)");
00084   __filter_svg->add_pattern("*.svg");
00085   __filter_png->set_name("Portable Network Graphic (PNG)");
00086   __filter_png->add_pattern("*.png");
00087   __filter_dot->set_name("DOT Graph");
00088   __filter_dot->add_pattern("*.dot");
00089 #if GTK_VERSION_GE(3,0)
00090   __fcd_save->add_filter(__filter_pdf);
00091   __fcd_save->add_filter(__filter_svg);
00092   __fcd_save->add_filter(__filter_png);
00093   __fcd_save->add_filter(__filter_dot);
00094   __fcd_save->set_filter(__filter_pdf);
00095 
00096   __fcd_open->add_filter(__filter_dot);
00097   __fcd_open->set_filter(__filter_dot);
00098 #else
00099   __fcd_save->add_filter(*__filter_pdf);
00100   __fcd_save->add_filter(*__filter_svg);
00101   __fcd_save->add_filter(*__filter_png);
00102   __fcd_save->add_filter(*__filter_dot);
00103   __fcd_save->set_filter(*__filter_pdf);
00104 
00105   __fcd_open->add_filter(*__filter_dot);
00106   __fcd_open->set_filter(*__filter_dot);
00107 #endif
00108 
00109   add_events(Gdk::SCROLL_MASK | Gdk::BUTTON_MOTION_MASK |
00110              Gdk::BUTTON_PRESS_MASK );
00111 
00112 #if GTK_VERSION_LT(3,0)
00113   signal_expose_event().connect(sigc::mem_fun(*this, &SkillGuiGraphDrawingArea::on_expose_event));
00114 #endif
00115   signal_button_press_event().connect(sigc::mem_fun(*this, &SkillGuiGraphDrawingArea::on_button_press_event));
00116   signal_motion_notify_event().connect(sigc::mem_fun(*this, &SkillGuiGraphDrawingArea::on_motion_notify_event));
00117 }
00118 
00119 SkillGuiGraphDrawingArea::~SkillGuiGraphDrawingArea()
00120 {
00121   gvFreeContext(__gvc);
00122   //delete __fcd;
00123   delete __fcd_save;
00124   delete __fcd_open;
00125   delete __fcd_recording;
00126 #if GTK_VERSION_GE(3,0)
00127   __filter_pdf.reset();
00128   __filter_svg.reset();
00129   __filter_png.reset();
00130   __filter_dot.reset();
00131 #else
00132   delete __filter_pdf;
00133   delete __filter_svg;
00134   delete __filter_png;
00135   delete __filter_dot;
00136 #endif
00137 }
00138 
00139 
00140 /** Get "update disabled" signal.
00141  * @return "update disabled" signal
00142  */
00143 sigc::signal<void>
00144 SkillGuiGraphDrawingArea::signal_update_disabled()
00145 {
00146   return __signal_update_disabled;
00147 }
00148 
00149 
00150 /** Set graph's FSM name.
00151  * @param fsm_name name of FSM the graph belongs to
00152  */
00153 void
00154 SkillGuiGraphDrawingArea::set_graph_fsm(std::string fsm_name)
00155 {
00156   if ( __update_graph ) {
00157     if ( __graph_fsm != fsm_name ) {
00158       __scale_override = false;
00159     }
00160     __graph_fsm = fsm_name;
00161   } else {
00162     __nonupd_graph_fsm = fsm_name;
00163   }
00164 }
00165 
00166 
00167 /** Set graph.
00168  * @param graph string representation of the current graph in the dot language.
00169  */
00170 void
00171 SkillGuiGraphDrawingArea::set_graph(std::string graph)
00172 {
00173   if ( __update_graph ) {
00174     __graph = graph;
00175     queue_draw();
00176   } else {
00177     __nonupd_graph = graph;
00178   }
00179 
00180   if ( __recording ) {
00181     char *tmp;
00182 #if defined(__MACH__) && defined(__APPLE__)
00183     struct timeval t;
00184     if (gettimeofday(&t, NULL) == 0) {
00185       long int nsec = t.tv_usec * 1000;
00186 #else
00187     timespec t;
00188     if (clock_gettime(CLOCK_REALTIME, &t) == 0) {
00189       long int nsec = t.tv_nsec;
00190 #endif
00191       struct tm tms;
00192       localtime_r(&t.tv_sec, &tms);
00193 
00194       if ( asprintf(&tmp, "%s/%s_%04i%02i%02i-%02i%02i%02i.%09li.dot",
00195                     __record_directory.c_str(), __graph_fsm.c_str(),
00196                     tms.tm_year + 1900, tms.tm_mon + 1, tms.tm_mday,
00197                     tms.tm_hour, tms.tm_min, tms.tm_sec, nsec) != -1)
00198       {
00199 
00200         //printf("Would record to filename %s\n", tmp);
00201         save_dotfile(tmp);
00202         free(tmp);
00203       } else {
00204         printf("Warning: Could not create file name for recording, skipping graph\n");
00205       }
00206     } else {
00207       printf("Warning: Could not time recording, skipping graph\n");
00208     }
00209   }
00210 }
00211 
00212 /** Set bounding box.
00213  * To be called only by the Graphviz plugin.
00214  * @param bbw bounding box width
00215  * @param bbh bounding box height
00216  */
00217 void
00218 SkillGuiGraphDrawingArea::set_bb(double bbw, double bbh)
00219 {
00220   __bbw = bbw;
00221   __bbh = bbh;
00222 }
00223 
00224 
00225 /** Set padding.
00226  * To be called only by the Graphviz plugin.
00227  * @param pad_x padding in x
00228  * @param pad_y padding in y
00229  */
00230 void
00231 SkillGuiGraphDrawingArea::set_pad(double pad_x, double pad_y)
00232 {
00233   __pad_x = pad_x;
00234   __pad_y = pad_y;
00235 }
00236 
00237 
00238 /** Get padding.
00239  * To be called only by the Graphviz plugin.
00240  * @param pad_x upon return contains padding in x
00241  * @param pad_y upon return contains padding in y
00242  */
00243 void
00244 SkillGuiGraphDrawingArea::get_pad(double &pad_x, double &pad_y)
00245 {
00246   if (__scale_override) {
00247     pad_x = pad_y = 0;
00248   } else {
00249     pad_x = __pad_x;
00250     pad_y = __pad_y;
00251   }
00252 }
00253 
00254 
00255 /** Set translation.
00256  * To be called only by the Graphviz plugin.
00257  * @param tx translation in x
00258  * @param ty translation in y
00259  */
00260 void
00261 SkillGuiGraphDrawingArea::set_translation(double tx, double ty)
00262 {
00263   __translation_x = tx;
00264   __translation_y = ty;
00265 }
00266 
00267 
00268 /** Set scale.
00269  * To be called only by the Graphviz plugin.
00270  * @param scale scale value
00271  */
00272 void
00273 SkillGuiGraphDrawingArea::set_scale(double scale)
00274 {
00275   __scale = scale;
00276 }
00277 
00278 /** Get scale.
00279  * To be called only by the Graphviz plugin.
00280  * @return scale value
00281  */
00282 double
00283 SkillGuiGraphDrawingArea::get_scale()
00284 {
00285   return __scale;
00286 }
00287 
00288 /** Get translation.
00289  * @param tx upon return contains translation value
00290  * @param ty upon return contains translation value
00291  */
00292 void
00293 SkillGuiGraphDrawingArea::get_translation(double &tx, double &ty)
00294 {
00295   tx = __translation_x;
00296   ty = __translation_y;
00297 }
00298 
00299 
00300 /** Get dimensions
00301  * @param width upon return contains width
00302  * @param height upon return contains height
00303  */
00304 void
00305 SkillGuiGraphDrawingArea::get_dimensions(double &width, double &height)
00306 {
00307   Gtk::Allocation alloc = get_allocation();
00308   width  = alloc.get_width();
00309   height = alloc.get_height();
00310 }
00311 
00312 
00313 /** Zoom in.
00314  * Increases zoom factor by 20, no upper limit.
00315  */
00316 void
00317 SkillGuiGraphDrawingArea::zoom_in()
00318 {
00319   Gtk::Allocation alloc = get_allocation();
00320   __scale += 0.1;
00321   __scale_override = true;
00322   __translation_x = (alloc.get_width()  - __bbw * __scale) / 2.0;
00323   __translation_y = (alloc.get_height() - __bbh * __scale) / 2.0 + __bbh * __scale;
00324   queue_draw();
00325 }
00326 
00327 /** Zoom out.
00328  * Decreases zoom factor by 20 with a minimum of 1.
00329  */
00330 void
00331 SkillGuiGraphDrawingArea::zoom_out()
00332 {
00333   __scale_override = true;
00334   if ( __scale > 0.1 ) {
00335     Gtk::Allocation alloc = get_allocation();
00336     __scale -= 0.1;
00337     __translation_x = (alloc.get_width()  - __bbw * __scale) / 2.0;
00338     __translation_y = (alloc.get_height() - __bbh * __scale) / 2.0 + __bbh * __scale;
00339     queue_draw();
00340   }
00341 }
00342 
00343 
00344 /** Zoom to fit.
00345  * Disables scale override and draws with values suggested by Graphviz plugin.
00346  */
00347 void
00348 SkillGuiGraphDrawingArea::zoom_fit()
00349 {
00350   __scale_override = false;
00351   queue_draw();
00352 }
00353 
00354 
00355 /** Zoom reset.
00356  * Reset zoom to 1. Enables scale override.
00357  */
00358 void
00359 SkillGuiGraphDrawingArea::zoom_reset()
00360 {
00361   Gtk::Allocation alloc = get_allocation();
00362   __scale = 1.0;
00363   __scale_override = true;
00364   __translation_x = (alloc.get_width()  - __bbw) / 2.0 + __pad_x;
00365   __translation_y = (alloc.get_height() - __bbh) / 2.0 + __bbh - __pad_y;
00366   queue_draw();
00367 }
00368 
00369 
00370 /** Check if scale override is enabled.
00371  * @return true if scale override is enabled, false otherwise
00372  */
00373 bool
00374 SkillGuiGraphDrawingArea::scale_override()
00375 {
00376   return __scale_override;
00377 }
00378 
00379 
00380 /** Get Cairo context.
00381  * This is only valid during the expose event and is only meant for the
00382  * Graphviz plugin.
00383  * @return Cairo context
00384  */
00385 Cairo::RefPtr<Cairo::Context>
00386 SkillGuiGraphDrawingArea::get_cairo()
00387 {
00388   return __cairo;
00389 }
00390 
00391 
00392 
00393 /** Check if graph is being updated.
00394  * @return true if the graph will be update if new data is received, false otherwise
00395  */
00396 bool
00397 SkillGuiGraphDrawingArea::get_update_graph()
00398 {
00399   return __update_graph;
00400 }
00401 
00402 
00403 /** Set if the graph should be updated on new data.
00404  * @param update true to update on new data, false to disable update
00405  */
00406 void
00407 SkillGuiGraphDrawingArea::set_update_graph(bool update)
00408 {
00409   if (update && ! __update_graph) {
00410     if ( __graph_fsm != __nonupd_graph_fsm ) {
00411       __scale_override = false;
00412     }
00413     __graph     = __nonupd_graph;
00414     __graph_fsm = __nonupd_graph_fsm;
00415     queue_draw();
00416   }
00417   __update_graph = update;
00418 }
00419 
00420 
00421 void
00422 SkillGuiGraphDrawingArea::save_dotfile(const char *filename)
00423 {
00424   FILE *f = fopen(filename, "w");
00425   if (f) {
00426     if (fwrite(__graph.c_str(), __graph.length(), 1, f) != 1) {
00427       // bang, ignored
00428       printf("Failed to write dot file '%s'\n", filename);
00429     }
00430     fclose(f);
00431   }
00432 }
00433 
00434 
00435 /** Enable/disable recording.
00436  * @param recording true to enable recording, false otherwise
00437  * @return true if recording is enabled now, false if it is disabled.
00438  * Enabling the recording may fail for example if the user chose to abort
00439  * the directory creation process.
00440  */
00441 bool
00442 SkillGuiGraphDrawingArea::set_recording(bool recording)
00443 {
00444   if (recording) {
00445     Gtk::Window *w = dynamic_cast<Gtk::Window *>(get_toplevel());
00446     __fcd_recording->set_transient_for(*w);
00447     int result = __fcd_recording->run();
00448     if (result == Gtk::RESPONSE_OK) {
00449       __record_directory = __fcd_recording->get_filename();
00450       __recording = true;
00451     }
00452     __fcd_recording->hide();
00453   } else {
00454     __recording = false;
00455   }
00456   return __recording;
00457 }
00458 
00459 
00460 /** save current graph. */
00461 void
00462 SkillGuiGraphDrawingArea::save()
00463 {
00464   Gtk::Window *w = dynamic_cast<Gtk::Window *>(get_toplevel());
00465   __fcd_save->set_transient_for(*w);
00466 
00467   int result = __fcd_save->run();
00468   if (result == Gtk::RESPONSE_OK) {
00469 
00470 #if GTK_VERSION_GE(3,0)
00471     Glib::RefPtr<Gtk::FileFilter> f = __fcd_save->get_filter();
00472 #else
00473     Gtk::FileFilter *f = __fcd_save->get_filter();
00474 #endif
00475     std::string filename = __fcd_save->get_filename();
00476     if (filename != "") {
00477       if (f == __filter_dot) {
00478         save_dotfile(filename.c_str());
00479       } else {
00480         Cairo::RefPtr<Cairo::Surface> surface;
00481 
00482         bool write_to_png = false;
00483         if (f == __filter_pdf) {
00484           surface = Cairo::PdfSurface::create(filename, __bbw, __bbh);
00485         } else if (f == __filter_svg) {
00486           surface = Cairo::SvgSurface::create(filename, __bbw, __bbh);
00487         } else if (f == __filter_png) {
00488           surface = Cairo::ImageSurface::create(Cairo::FORMAT_ARGB32,
00489                                                 (int)ceilf(__bbw),
00490                                                 (int)ceilf(__bbh));
00491           write_to_png = true;
00492         }
00493 
00494         if (surface) {
00495           __cairo = Cairo::Context::create(surface);
00496           
00497           bool old_scale_override = __scale_override;
00498           double old_tx = __translation_x;
00499           double old_ty = __translation_y;
00500           double old_scale = __scale;
00501           __translation_x = __pad_x;
00502           __translation_y = __bbh - __pad_y;
00503           __scale = 1.0;
00504           __scale_override = true;
00505 
00506           Agraph_t *g = agmemread((char *)__graph.c_str());
00507           if (g) {
00508             gvLayout(__gvc, g, (char *)"dot");
00509             gvRender(__gvc, g, (char *)"skillguicairo", NULL);
00510             gvFreeLayout(__gvc, g);
00511             agclose(g);
00512           }
00513 
00514           if (write_to_png) {
00515             surface->write_to_png(filename);
00516           }
00517 
00518           __cairo.clear();
00519 
00520           __translation_x = old_tx;
00521           __translation_y = old_ty;
00522           __scale = old_scale;
00523           __scale_override = old_scale_override;
00524         }
00525       }
00526 
00527     } else {
00528       Gtk::MessageDialog md(*w, "Invalid filename",
00529                             /* markup */ false, Gtk::MESSAGE_ERROR,
00530                             Gtk::BUTTONS_OK, /* modal */ true);
00531       md.set_title("Invalid File Name");
00532       md.run();
00533     }
00534   }
00535 
00536   __fcd_save->hide();
00537 }
00538 
00539 
00540 /** Open a dot graph and display it. */
00541 void
00542 SkillGuiGraphDrawingArea::open()
00543 {
00544   Gtk::Window *w = dynamic_cast<Gtk::Window *>(get_toplevel());
00545   __fcd_open->set_transient_for(*w);
00546 
00547   int result = __fcd_open->run();
00548   if (result == Gtk::RESPONSE_OK) {
00549     __update_graph = false;
00550     __graph = "";
00551     char *basec = strdup(__fcd_open->get_filename().c_str());
00552     char *basen = basename(basec);
00553     __graph_fsm = basen;
00554     free(basec);
00555 
00556     FILE *f = fopen(__fcd_open->get_filename().c_str(), "r");
00557     while (! feof(f)) {
00558       char tmp[4096];
00559       size_t s;
00560       if ((s = fread(tmp, 1, 4096, f)) > 0) {
00561         __graph.append(tmp, s);
00562       }
00563     }
00564     fclose(f);
00565     __signal_update_disabled.emit();
00566     queue_draw();
00567   }
00568 
00569   __fcd_open->hide();
00570 }
00571 
00572 
00573 #if GTK_VERSION_GE(3,0)
00574 /** Draw event handler.
00575  * @param cr cairo context
00576  * @return true
00577  */
00578 bool
00579 SkillGuiGraphDrawingArea::on_draw(const Cairo::RefPtr<Cairo::Context> &cr)
00580 #else
00581 /** Expose event handler.
00582  * @param event event info structure.
00583  * @return signal return value
00584  */
00585 bool
00586 SkillGuiGraphDrawingArea::on_expose_event(GdkEventExpose* event)
00587 #endif
00588 {
00589   // This is where we draw on the window
00590   Glib::RefPtr<Gdk::Window> window = get_window();
00591   if(window) {
00592     //Gtk::Allocation allocation = get_allocation();
00593     //const int width = allocation.get_width();
00594     //const int height = allocation.get_height();
00595     
00596     // coordinates for the center of the window
00597     //int xc, yc;
00598     //xc = width / 2;
00599     //yc = height / 2;
00600 #if GTK_VERSION_LT(3,0)
00601     __cairo = window->create_cairo_context();
00602 #else
00603     __cairo = cr;
00604 #endif
00605     __cairo->set_source_rgb(1, 1, 1);
00606     __cairo->paint();
00607 
00608     Agraph_t *g = agmemread((char *)__graph.c_str());
00609     if (g) {
00610       gvLayout(__gvc, g, (char *)"dot");
00611       gvRender(__gvc, g, (char *)"skillguicairo", NULL);
00612       gvFreeLayout(__gvc, g);
00613       agclose(g);
00614     }
00615 
00616     __cairo.clear();
00617   }    
00618 
00619   return true;
00620 }
00621 
00622 /** Scroll event handler.
00623  * @param event event structure
00624  * @return signal return value
00625  */
00626 bool
00627 SkillGuiGraphDrawingArea::on_scroll_event(GdkEventScroll *event)
00628 {
00629   if (event->direction == GDK_SCROLL_UP) {
00630     zoom_in();
00631   } else if (event->direction == GDK_SCROLL_DOWN) {
00632     zoom_out();
00633   }
00634   return true;
00635 }
00636 
00637 
00638 /** Button press event handler.
00639  * @param event event data
00640  * @return true
00641  */
00642 bool
00643 SkillGuiGraphDrawingArea::on_button_press_event(GdkEventButton *event)
00644 {
00645   __last_mouse_x = event->x;
00646   __last_mouse_y = event->y;
00647   return true;
00648 }
00649 
00650 
00651 /** Mouse motion notify event handler.
00652  * @param event event data
00653  * @return true
00654  */
00655 bool
00656 SkillGuiGraphDrawingArea::on_motion_notify_event(GdkEventMotion *event)
00657 {
00658   __scale_override = true;
00659   __translation_x -= __last_mouse_x - event->x;
00660   __translation_y -= __last_mouse_y - event->y;
00661   __last_mouse_x = event->x;
00662   __last_mouse_y = event->y;
00663   queue_draw();
00664   return true;
00665 }
00666