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

Generated on Tue Feb 22 13:32:32 2011 for Fawkes API by  doxygen 1.4.7