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 #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