Fawkes API Fawkes Development Version
|
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