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